From f962fc2209d001aee89a6a0bcc4f4ae5918c4bde Mon Sep 17 00:00:00 2001 From: sebhoss Date: Mon, 2 Dec 2024 03:01:38 +0000 Subject: [PATCH] deploy: 94f3783514b20722960e8d50efaa1cdffd0b4e10 --- .nojekyll | 0 .well-known/security.txt | 6 + 404.html | 11 + ...95b6f5935480863e2ed4c92d7538f5797f03bf.txt | 1 + CNAME | 1 + atom.xml | 1330 +++++++++++++++++ categories/cd/atom.xml | 20 + categories/cd/index.html | 11 + categories/cd/index.xml | 18 + categories/configuration/atom.xml | 35 + categories/configuration/index.html | 11 + categories/configuration/index.xml | 2 + categories/decentralized/atom.xml | 27 + categories/decentralized/index.html | 11 + categories/decentralized/index.xml | 20 + categories/devops/atom.xml | 253 ++++ categories/devops/index.html | 11 + categories/devops/index.xml | 240 +++ categories/frontend/atom.xml | 58 + categories/frontend/index.html | 11 + categories/frontend/index.xml | 2 + categories/index.html | 11 + categories/index.xml | 1 + categories/network/atom.xml | 7 + categories/network/index.html | 11 + categories/network/index.xml | 7 + categories/snippet/atom.xml | 1290 ++++++++++++++++ categories/snippet/index.html | 11 + categories/snippet/index.xml | 877 +++++++++++ categories/website/atom.xml | 281 ++++ categories/website/index.html | 11 + categories/website/index.xml | 279 ++++ ...183160ff9aaf84e060180993fe1e010ac6907f.css | 1 + ...49baa5b2923035845e419b20b1cfdb177a0de8.css | 1 + ...9a64ccddc0f3182b6174274a173e9dcd5c7c83.css | 1 + ...c9dca0b3c3dc5d3a5f4c287d7ebd48eef1640a.css | 1 + favicon-128.png | Bin 0 -> 37424 bytes favicon.ico | Bin 0 -> 67646 bytes foaf.rdf | 23 + humans.txt | 5 + images/cc0.png | Bin 0 -> 1066 bytes images/email.svg | 1 + images/git-distributor.svg | 135 ++ images/github.svg | 3 + images/gitlab-mirror-settings.png | Bin 0 -> 39421 bytes images/gitlab-pull-mirror.png | Bin 0 -> 252443 bytes images/gitlab-push-mirror.png | Bin 0 -> 179022 bytes images/history.svg | 1 + images/mastodon.svg | 5 + images/matrix.svg | 63 + images/pencil.svg | 1 + images/rss.svg | 3 + images/sitemap.svg | 3 + index.html | 36 + index.xml | 884 +++++++++++ posts/atom.xml | 1330 +++++++++++++++++ posts/awsenv/index.html | 49 + posts/awsenv/jsonld.json | 1 + posts/chezmoi-age/index.html | 25 + posts/chezmoi-age/jsonld.json | 1 + posts/chezmoi-auto-git/index.html | 21 + posts/chezmoi-auto-git/jsonld.json | 1 + posts/chezmoi-auto-update/index.html | 30 + posts/chezmoi-auto-update/jsonld.json | 1 + posts/chezmoi-gpg/index.html | 23 + posts/chezmoi-gpg/jsonld.json | 1 + posts/chezmoi-maintenance/index.html | 30 + posts/chezmoi-maintenance/jsonld.json | 1 + .../clojure-java-interoperability/index.html | 105 ++ .../clojure-java-interoperability/jsonld.json | 1 + .../index.html | 68 + .../jsonld.json | 1 + posts/emacs-backups/index.html | 44 + posts/emacs-backups/jsonld.json | 1 + posts/emacs-systemd/index.html | 36 + posts/emacs-systemd/jsonld.json | 1 + posts/git-mirror/index.html | 23 + posts/git-mirror/jsonld.json | 1 + posts/git-push-only-mirror/index.html | 30 + posts/git-push-only-mirror/jsonld.json | 1 + .../index.html | 30 + .../jsonld.json | 1 + .../github-actions-create-release/index.html | 42 + .../github-actions-create-release/jsonld.json | 1 + .../index.html | 26 + .../jsonld.json | 1 + .../index.html | 30 + .../jsonld.json | 1 + .../github-actions-schedule-build/index.html | 32 + .../github-actions-schedule-build/jsonld.json | 1 + posts/github-actions-send-email/index.html | 34 + posts/github-actions-send-email/jsonld.json | 1 + posts/github-actions-send-toot/index.html | 29 + posts/github-actions-send-toot/jsonld.json | 1 + .../index.html | 27 + .../jsonld.json | 1 + .../index.html | 27 + .../jsonld.json | 1 + .../index.html | 33 + .../jsonld.json | 1 + posts/github-maven-packages/index.html | 61 + posts/github-maven-packages/jsonld.json | 1 + posts/gitlab-distributor/index.html | 36 + posts/gitlab-distributor/jsonld.json | 1 + posts/home-network-hostnames/index.html | 23 + posts/home-network-hostnames/jsonld.json | 1 + posts/hugo-atom/index.html | 152 ++ posts/hugo-atom/jsonld.json | 1 + posts/hugo-bundles/index.html | 36 + posts/hugo-bundles/jsonld.json | 1 + posts/hugo-foaf/index.html | 54 + posts/hugo-foaf/jsonld.json | 1 + posts/hugo-humans/index.html | 37 + posts/hugo-humans/jsonld.json | 1 + posts/hugo-serviceworkers/index.html | 63 + posts/hugo-serviceworkers/jsonld.json | 1 + posts/hugo-webmanifest/index.html | 40 + posts/hugo-webmanifest/jsonld.json | 1 + posts/index.html | 11 + posts/index.xml | 884 +++++++++++ posts/jspecify/index.html | 36 + posts/jspecify/jsonld.json | 1 + posts/kubectl-multi-cluster/index.html | 21 + posts/kubectl-multi-cluster/jsonld.json | 1 + posts/makefile-help/index.html | 62 + posts/makefile-help/jsonld.json | 1 + posts/makefile-ignore/index.html | 23 + posts/makefile-ignore/jsonld.json | 1 + posts/makefile-readme/index.html | 27 + posts/makefile-readme/jsonld.json | 1 + posts/maven-cd-versioning/index.html | 19 + posts/maven-cd-versioning/jsonld.json | 1 + posts/maven-encoding/index.html | 22 + posts/maven-encoding/jsonld.json | 1 + posts/maven-github-sonarcloud/index.html | 50 + posts/maven-github-sonarcloud/jsonld.json | 1 + posts/maven-google-central/index.html | 40 + posts/maven-google-central/jsonld.json | 1 + posts/maven-reproducible/index.html | 21 + posts/maven-reproducible/jsonld.json | 1 + posts/multiple-git-configs/index.html | 32 + posts/multiple-git-configs/jsonld.json | 1 + posts/nvim-plugin-auto-updates/index.html | 70 + posts/nvim-plugin-auto-updates/jsonld.json | 1 + posts/passage-fuzzy-search/index.html | 57 + posts/passage-fuzzy-search/jsonld.json | 1 + posts/shell-init/index.html | 20 + posts/shell-init/jsonld.json | 1 + posts/short-git-clones/index.html | 56 + posts/short-git-clones/jsonld.json | 1 + posts/sway-screenlock/index.html | 20 + posts/sway-screenlock/jsonld.json | 1 + posts/sway-screenshots/index.html | 23 + posts/sway-screenshots/jsonld.json | 1 + posts/sway-waybar/index.html | 36 + posts/sway-waybar/jsonld.json | 1 + posts/tmux-login-shell/index.html | 33 + posts/tmux-login-shell/jsonld.json | 1 + posts/tmux-peek/index.html | 19 + posts/tmux-peek/jsonld.json | 1 + posts/tmux-status-bar/index.html | 28 + posts/tmux-status-bar/jsonld.json | 1 + posts/tmux-tmuxp/index.html | 25 + posts/tmux-tmuxp/jsonld.json | 1 + posts/xdg-dot-files/index.html | 37 + posts/xdg-dot-files/jsonld.json | 1 + robots.txt | 2 + sitemap.xml | 1 + sitemap/atom.xml | 1 + sitemap/index.html | 14 + sitemap/index.xml | 1 + tags/age/atom.xml | 54 + tags/age/index.html | 11 + tags/age/index.xml | 19 + tags/asset/atom.xml | 21 + tags/asset/index.html | 11 + tags/asset/index.xml | 20 + tags/assets/atom.xml | 20 + tags/assets/index.html | 11 + tags/assets/index.xml | 20 + tags/atom/atom.xml | 134 ++ tags/atom/index.html | 11 + tags/atom/index.xml | 134 ++ tags/automation/atom.xml | 15 + tags/automation/index.html | 11 + tags/automation/index.xml | 14 + tags/aws/atom.xml | 36 + tags/aws/index.html | 11 + tags/aws/index.xml | 1 + tags/azure/atom.xml | 36 + tags/azure/index.html | 11 + tags/azure/index.xml | 1 + tags/backup/atom.xml | 29 + tags/backup/index.html | 11 + tags/backup/index.xml | 28 + tags/bitbucket/atom.xml | 35 + tags/bitbucket/index.html | 11 + tags/bitbucket/index.xml | 2 + tags/breakpoints/atom.xml | 58 + tags/breakpoints/index.html | 11 + tags/breakpoints/index.xml | 2 + tags/bundle/atom.xml | 20 + tags/bundle/index.html | 11 + tags/bundle/index.xml | 20 + tags/cache/atom.xml | 18 + tags/cache/index.html | 11 + tags/cache/index.xml | 17 + tags/chezmoi/atom.xml | 64 + tags/chezmoi/index.html | 11 + tags/chezmoi/index.xml | 41 + tags/chsh/atom.xml | 17 + tags/chsh/index.html | 11 + tags/chsh/index.xml | 17 + tags/clipboard/atom.xml | 47 + tags/clipboard/index.html | 11 + tags/clipboard/index.xml | 13 + tags/clojure/atom.xml | 105 ++ tags/clojure/index.html | 11 + tags/clojure/index.xml | 5 + tags/clone/atom.xml | 41 + tags/clone/index.html | 11 + tags/clone/index.xml | 26 + tags/codeberg/atom.xml | 35 + tags/codeberg/index.html | 11 + tags/codeberg/index.xml | 2 + tags/config/atom.xml | 16 + tags/config/index.html | 11 + tags/config/index.xml | 12 + tags/dotfiles/atom.xml | 85 ++ tags/dotfiles/index.html | 11 + tags/dotfiles/index.xml | 61 + tags/emacs/atom.xml | 49 + tags/emacs/index.html | 11 + tags/emacs/index.xml | 42 + tags/email/atom.xml | 25 + tags/email/index.html | 11 + tags/email/index.xml | 24 + tags/encoding/atom.xml | 10 + tags/encoding/index.html | 11 + tags/encoding/index.xml | 9 + tags/encryption/atom.xml | 13 + tags/encryption/index.html | 11 + tags/encryption/index.xml | 11 + tags/exit-code/atom.xml | 14 + tags/exit-code/index.html | 11 + tags/exit-code/index.xml | 7 + tags/foaf/atom.xml | 36 + tags/foaf/index.html | 11 + tags/foaf/index.xml | 36 + tags/fuzzy/atom.xml | 47 + tags/fuzzy/index.html | 11 + tags/fuzzy/index.xml | 13 + tags/fzf/atom.xml | 36 + tags/fzf/index.html | 11 + tags/fzf/index.xml | 1 + tags/git/atom.xml | 179 +++ tags/git/index.html | 11 + tags/git/index.xml | 88 ++ tags/github-actions/atom.xml | 232 +++ tags/github-actions/index.html | 11 + tags/github-actions/index.xml | 221 +++ tags/github-packages/atom.xml | 46 + tags/github-packages/index.html | 11 + tags/github-packages/index.xml | 45 + tags/github/atom.xml | 311 ++++ tags/github/index.html | 11 + tags/github/index.xml | 266 ++++ tags/gitlab/atom.xml | 35 + tags/gitlab/index.html | 11 + tags/gitlab/index.xml | 2 + tags/google/atom.xml | 25 + tags/google/index.html | 11 + tags/google/index.xml | 24 + tags/gpg/atom.xml | 6 + tags/gpg/index.html | 11 + tags/gpg/index.xml | 5 + tags/grim/atom.xml | 7 + tags/grim/index.html | 11 + tags/grim/index.xml | 7 + tags/help/atom.xml | 49 + tags/help/index.html | 11 + tags/help/index.xml | 18 + tags/hostnames/atom.xml | 7 + tags/hostnames/index.html | 11 + tags/hostnames/index.xml | 7 + tags/hugo/atom.xml | 315 ++++ tags/hugo/index.html | 11 + tags/hugo/index.xml | 311 ++++ tags/humans.txt/atom.xml | 19 + tags/humans.txt/index.html | 11 + tags/humans.txt/index.xml | 19 + tags/index.html | 11 + tags/index.xml | 1 + tags/interoperability/atom.xml | 105 ++ tags/interoperability/index.html | 11 + tags/interoperability/index.xml | 5 + tags/java/atom.xml | 144 ++ tags/java/index.html | 11 + tags/java/index.xml | 23 + tags/jspecify/atom.xml | 25 + tags/jspecify/index.html | 11 + tags/jspecify/index.xml | 5 + tags/kubectl/atom.xml | 6 + tags/kubectl/index.html | 11 + tags/kubectl/index.xml | 5 + tags/kubernetes/atom.xml | 6 + tags/kubernetes/index.html | 11 + tags/kubernetes/index.xml | 5 + tags/linux/atom.xml | 10 + tags/linux/index.html | 11 + tags/linux/index.xml | 9 + tags/login-shell/atom.xml | 17 + tags/login-shell/index.html | 11 + tags/login-shell/index.xml | 17 + tags/mac/atom.xml | 10 + tags/mac/index.html | 11 + tags/mac/index.xml | 9 + tags/make/atom.xml | 82 + tags/make/index.html | 11 + tags/make/index.xml | 24 + tags/makefile/atom.xml | 82 + tags/makefile/index.html | 11 + tags/makefile/index.xml | 24 + tags/manifest/atom.xml | 26 + tags/manifest/index.html | 11 + tags/manifest/index.xml | 25 + tags/mastodon/atom.xml | 18 + tags/mastodon/index.html | 11 + tags/mastodon/index.xml | 17 + tags/maven/atom.xml | 147 ++ tags/maven/index.html | 11 + tags/maven/index.xml | 140 ++ tags/mirror/atom.xml | 27 + tags/mirror/index.html | 11 + tags/mirror/index.xml | 20 + tags/neovim/atom.xml | 59 + tags/neovim/index.html | 11 + tags/neovim/index.xml | 28 + tags/nullness/atom.xml | 25 + tags/nullness/index.html | 11 + tags/nullness/index.xml | 5 + tags/passage/atom.xml | 47 + tags/passage/index.html | 11 + tags/passage/index.xml | 13 + tags/peek/atom.xml | 4 + tags/peek/index.html | 11 + tags/peek/index.xml | 3 + tags/performance/atom.xml | 18 + tags/performance/index.html | 11 + tags/performance/index.xml | 1 + tags/perl/atom.xml | 49 + tags/perl/index.html | 11 + tags/perl/index.xml | 18 + tags/plugins/atom.xml | 59 + tags/plugins/index.html | 11 + tags/plugins/index.xml | 28 + tags/publish/atom.xml | 20 + tags/publish/index.html | 11 + tags/publish/index.xml | 19 + tags/push/atom.xml | 27 + tags/push/index.html | 11 + tags/push/index.xml | 20 + tags/react/atom.xml | 58 + tags/react/index.html | 11 + tags/react/index.xml | 2 + tags/readme/atom.xml | 21 + tags/readme/index.html | 11 + tags/readme/index.xml | 1 + tags/release/atom.xml | 52 + tags/release/index.html | 11 + tags/release/index.xml | 50 + tags/rendering/atom.xml | 58 + tags/rendering/index.html | 11 + tags/rendering/index.xml | 2 + tags/repo.or.cz/atom.xml | 35 + tags/repo.or.cz/index.html | 11 + tags/repo.or.cz/index.xml | 2 + tags/repository/atom.xml | 25 + tags/repository/index.html | 11 + tags/repository/index.xml | 24 + tags/reproducible/atom.xml | 13 + tags/reproducible/index.html | 11 + tags/reproducible/index.xml | 12 + tags/rfc/atom.xml | 7 + tags/rfc/index.html | 11 + tags/rfc/index.xml | 7 + tags/schedule/atom.xml | 22 + tags/schedule/index.html | 11 + tags/schedule/index.xml | 21 + tags/screenlock/atom.xml | 5 + tags/screenlock/index.html | 11 + tags/screenlock/index.xml | 4 + tags/screenshot/atom.xml | 7 + tags/screenshot/index.html | 11 + tags/screenshot/index.xml | 7 + tags/search/atom.xml | 47 + tags/search/index.html | 11 + tags/search/index.xml | 13 + tags/service-worker/atom.xml | 51 + tags/service-worker/index.html | 11 + tags/service-worker/index.xml | 50 + tags/shell/atom.xml | 76 + tags/shell/index.html | 11 + tags/shell/index.xml | 28 + tags/slurp/atom.xml | 7 + tags/slurp/index.html | 11 + tags/slurp/index.xml | 7 + tags/sonarqube/atom.xml | 37 + tags/sonarqube/index.html | 11 + tags/sonarqube/index.xml | 36 + tags/ssh/atom.xml | 41 + tags/ssh/index.html | 11 + tags/ssh/index.xml | 26 + tags/status-bar/atom.xml | 8 + tags/status-bar/index.html | 11 + tags/status-bar/index.xml | 8 + tags/swaywm/atom.xml | 28 + tags/swaywm/index.html | 11 + tags/swaywm/index.xml | 27 + tags/systemd/atom.xml | 79 + tags/systemd/index.html | 11 + tags/systemd/index.xml | 42 + tags/teamwork/atom.xml | 21 + tags/teamwork/index.html | 11 + tags/teamwork/index.xml | 1 + tags/timestamp/atom.xml | 17 + tags/timestamp/index.html | 11 + tags/timestamp/index.xml | 16 + tags/tmux/atom.xml | 36 + tags/tmux/index.html | 11 + tags/tmux/index.xml | 34 + tags/tmuxp/atom.xml | 10 + tags/tmuxp/index.html | 11 + tags/tmuxp/index.xml | 9 + tags/toot/atom.xml | 18 + tags/toot/index.html | 11 + tags/toot/index.xml | 17 + tags/updates/atom.xml | 59 + tags/updates/index.html | 11 + tags/updates/index.xml | 28 + tags/upload/atom.xml | 21 + tags/upload/index.html | 11 + tags/upload/index.xml | 20 + tags/versioning/atom.xml | 4 + tags/versioning/index.html | 11 + tags/versioning/index.xml | 3 + tags/vim/atom.xml | 59 + tags/vim/index.html | 11 + tags/vim/index.xml | 28 + tags/waybar/atom.xml | 18 + tags/waybar/index.html | 11 + tags/waybar/index.xml | 18 + tags/web-app/atom.xml | 26 + tags/web-app/index.html | 11 + tags/web-app/index.xml | 25 + tags/windows/atom.xml | 10 + tags/windows/index.html | 11 + tags/windows/index.xml | 9 + tags/xdg/atom.xml | 22 + tags/xdg/index.html | 11 + tags/xdg/index.xml | 21 + 461 files changed, 18501 insertions(+) create mode 100644 .nojekyll create mode 100644 .well-known/security.txt create mode 100644 404.html create mode 100644 6da084089795b6f5935480863e2ed4c92d7538f5797f03bf.txt create mode 100644 CNAME create mode 100644 atom.xml create mode 100644 categories/cd/atom.xml create mode 100644 categories/cd/index.html create mode 100644 categories/cd/index.xml create mode 100644 categories/configuration/atom.xml create mode 100644 categories/configuration/index.html create mode 100644 categories/configuration/index.xml create mode 100644 categories/decentralized/atom.xml create mode 100644 categories/decentralized/index.html create mode 100644 categories/decentralized/index.xml create mode 100644 categories/devops/atom.xml create mode 100644 categories/devops/index.html create mode 100644 categories/devops/index.xml create mode 100644 categories/frontend/atom.xml create mode 100644 categories/frontend/index.html create mode 100644 categories/frontend/index.xml create mode 100644 categories/index.html create mode 100644 categories/index.xml create mode 100644 categories/network/atom.xml create mode 100644 categories/network/index.html create mode 100644 categories/network/index.xml create mode 100644 categories/snippet/atom.xml create mode 100644 categories/snippet/index.html create mode 100644 categories/snippet/index.xml create mode 100644 categories/website/atom.xml create mode 100644 categories/website/index.html create mode 100644 categories/website/index.xml create mode 100644 css/darkmode.min.2a5ba1ebf79d23ac725522a486af2ffebf831a4a634fc1471a4b0007daa25afb4417f20dc4cf0e2c1eeb8eb704183160ff9aaf84e060180993fe1e010ac6907f.css create mode 100644 css/desktop.min.5d7960c466db691c7fe9df667675aa312241bb90e879e462f75e2502350908820cf7477e12664f246157a8a37449baa5b2923035845e419b20b1cfdb177a0de8.css create mode 100644 css/mobile.min.e14dc54fd7b9d2dea52a0580221a956079e9f0dbb1583eb2c3f19ab416911abcc496c1a81fe403d98380b4105b9a64ccddc0f3182b6174274a173e9dcd5c7c83.css create mode 100644 css/tablet.min.8aba29eb35692e2677e7c26e41408d9ed27ed94e71fe6634ad016f9e91a712f9e1c53317bdaf09aff9d1acc503c9dca0b3c3dc5d3a5f4c287d7ebd48eef1640a.css create mode 100644 favicon-128.png create mode 100644 favicon.ico create mode 100644 foaf.rdf create mode 100644 humans.txt create mode 100644 images/cc0.png create mode 100644 images/email.svg create mode 100644 images/git-distributor.svg create mode 100644 images/github.svg create mode 100644 images/gitlab-mirror-settings.png create mode 100644 images/gitlab-pull-mirror.png create mode 100644 images/gitlab-push-mirror.png create mode 100644 images/history.svg create mode 100644 images/mastodon.svg create mode 100644 images/matrix.svg create mode 100644 images/pencil.svg create mode 100644 images/rss.svg create mode 100644 images/sitemap.svg create mode 100644 index.html create mode 100644 index.xml create mode 100644 posts/atom.xml create mode 100644 posts/awsenv/index.html create mode 100644 posts/awsenv/jsonld.json create mode 100644 posts/chezmoi-age/index.html create mode 100644 posts/chezmoi-age/jsonld.json create mode 100644 posts/chezmoi-auto-git/index.html create mode 100644 posts/chezmoi-auto-git/jsonld.json create mode 100644 posts/chezmoi-auto-update/index.html create mode 100644 posts/chezmoi-auto-update/jsonld.json create mode 100644 posts/chezmoi-gpg/index.html create mode 100644 posts/chezmoi-gpg/jsonld.json create mode 100644 posts/chezmoi-maintenance/index.html create mode 100644 posts/chezmoi-maintenance/jsonld.json create mode 100644 posts/clojure-java-interoperability/index.html create mode 100644 posts/clojure-java-interoperability/jsonld.json create mode 100644 posts/declarative-conditional-rendering-in-react/index.html create mode 100644 posts/declarative-conditional-rendering-in-react/jsonld.json create mode 100644 posts/emacs-backups/index.html create mode 100644 posts/emacs-backups/jsonld.json create mode 100644 posts/emacs-systemd/index.html create mode 100644 posts/emacs-systemd/jsonld.json create mode 100644 posts/git-mirror/index.html create mode 100644 posts/git-mirror/jsonld.json create mode 100644 posts/git-push-only-mirror/index.html create mode 100644 posts/git-push-only-mirror/jsonld.json create mode 100644 posts/github-actions-cache-maven-artifacts/index.html create mode 100644 posts/github-actions-cache-maven-artifacts/jsonld.json create mode 100644 posts/github-actions-create-release/index.html create mode 100644 posts/github-actions-create-release/jsonld.json create mode 100644 posts/github-actions-create-timestamp/index.html create mode 100644 posts/github-actions-create-timestamp/jsonld.json create mode 100644 posts/github-actions-publish-hugo-site/index.html create mode 100644 posts/github-actions-publish-hugo-site/jsonld.json create mode 100644 posts/github-actions-schedule-build/index.html create mode 100644 posts/github-actions-schedule-build/jsonld.json create mode 100644 posts/github-actions-send-email/index.html create mode 100644 posts/github-actions-send-email/jsonld.json create mode 100644 posts/github-actions-send-toot/index.html create mode 100644 posts/github-actions-send-toot/jsonld.json create mode 100644 posts/github-actions-specify-hugo-version/index.html create mode 100644 posts/github-actions-specify-hugo-version/jsonld.json create mode 100644 posts/github-actions-specify-java-version/index.html create mode 100644 posts/github-actions-specify-java-version/jsonld.json create mode 100644 posts/github-actions-upload-release-assets/index.html create mode 100644 posts/github-actions-upload-release-assets/jsonld.json create mode 100644 posts/github-maven-packages/index.html create mode 100644 posts/github-maven-packages/jsonld.json create mode 100644 posts/gitlab-distributor/index.html create mode 100644 posts/gitlab-distributor/jsonld.json create mode 100644 posts/home-network-hostnames/index.html create mode 100644 posts/home-network-hostnames/jsonld.json create mode 100644 posts/hugo-atom/index.html create mode 100644 posts/hugo-atom/jsonld.json create mode 100644 posts/hugo-bundles/index.html create mode 100644 posts/hugo-bundles/jsonld.json create mode 100644 posts/hugo-foaf/index.html create mode 100644 posts/hugo-foaf/jsonld.json create mode 100644 posts/hugo-humans/index.html create mode 100644 posts/hugo-humans/jsonld.json create mode 100644 posts/hugo-serviceworkers/index.html create mode 100644 posts/hugo-serviceworkers/jsonld.json create mode 100644 posts/hugo-webmanifest/index.html create mode 100644 posts/hugo-webmanifest/jsonld.json create mode 100644 posts/index.html create mode 100644 posts/index.xml create mode 100644 posts/jspecify/index.html create mode 100644 posts/jspecify/jsonld.json create mode 100644 posts/kubectl-multi-cluster/index.html create mode 100644 posts/kubectl-multi-cluster/jsonld.json create mode 100644 posts/makefile-help/index.html create mode 100644 posts/makefile-help/jsonld.json create mode 100644 posts/makefile-ignore/index.html create mode 100644 posts/makefile-ignore/jsonld.json create mode 100644 posts/makefile-readme/index.html create mode 100644 posts/makefile-readme/jsonld.json create mode 100644 posts/maven-cd-versioning/index.html create mode 100644 posts/maven-cd-versioning/jsonld.json create mode 100644 posts/maven-encoding/index.html create mode 100644 posts/maven-encoding/jsonld.json create mode 100644 posts/maven-github-sonarcloud/index.html create mode 100644 posts/maven-github-sonarcloud/jsonld.json create mode 100644 posts/maven-google-central/index.html create mode 100644 posts/maven-google-central/jsonld.json create mode 100644 posts/maven-reproducible/index.html create mode 100644 posts/maven-reproducible/jsonld.json create mode 100644 posts/multiple-git-configs/index.html create mode 100644 posts/multiple-git-configs/jsonld.json create mode 100644 posts/nvim-plugin-auto-updates/index.html create mode 100644 posts/nvim-plugin-auto-updates/jsonld.json create mode 100644 posts/passage-fuzzy-search/index.html create mode 100644 posts/passage-fuzzy-search/jsonld.json create mode 100644 posts/shell-init/index.html create mode 100644 posts/shell-init/jsonld.json create mode 100644 posts/short-git-clones/index.html create mode 100644 posts/short-git-clones/jsonld.json create mode 100644 posts/sway-screenlock/index.html create mode 100644 posts/sway-screenlock/jsonld.json create mode 100644 posts/sway-screenshots/index.html create mode 100644 posts/sway-screenshots/jsonld.json create mode 100644 posts/sway-waybar/index.html create mode 100644 posts/sway-waybar/jsonld.json create mode 100644 posts/tmux-login-shell/index.html create mode 100644 posts/tmux-login-shell/jsonld.json create mode 100644 posts/tmux-peek/index.html create mode 100644 posts/tmux-peek/jsonld.json create mode 100644 posts/tmux-status-bar/index.html create mode 100644 posts/tmux-status-bar/jsonld.json create mode 100644 posts/tmux-tmuxp/index.html create mode 100644 posts/tmux-tmuxp/jsonld.json create mode 100644 posts/xdg-dot-files/index.html create mode 100644 posts/xdg-dot-files/jsonld.json create mode 100644 robots.txt create mode 100644 sitemap.xml create mode 100644 sitemap/atom.xml create mode 100644 sitemap/index.html create mode 100644 sitemap/index.xml create mode 100644 tags/age/atom.xml create mode 100644 tags/age/index.html create mode 100644 tags/age/index.xml create mode 100644 tags/asset/atom.xml create mode 100644 tags/asset/index.html create mode 100644 tags/asset/index.xml create mode 100644 tags/assets/atom.xml create mode 100644 tags/assets/index.html create mode 100644 tags/assets/index.xml create mode 100644 tags/atom/atom.xml create mode 100644 tags/atom/index.html create mode 100644 tags/atom/index.xml create mode 100644 tags/automation/atom.xml create mode 100644 tags/automation/index.html create mode 100644 tags/automation/index.xml create mode 100644 tags/aws/atom.xml create mode 100644 tags/aws/index.html create mode 100644 tags/aws/index.xml create mode 100644 tags/azure/atom.xml create mode 100644 tags/azure/index.html create mode 100644 tags/azure/index.xml create mode 100644 tags/backup/atom.xml create mode 100644 tags/backup/index.html create mode 100644 tags/backup/index.xml create mode 100644 tags/bitbucket/atom.xml create mode 100644 tags/bitbucket/index.html create mode 100644 tags/bitbucket/index.xml create mode 100644 tags/breakpoints/atom.xml create mode 100644 tags/breakpoints/index.html create mode 100644 tags/breakpoints/index.xml create mode 100644 tags/bundle/atom.xml create mode 100644 tags/bundle/index.html create mode 100644 tags/bundle/index.xml create mode 100644 tags/cache/atom.xml create mode 100644 tags/cache/index.html create mode 100644 tags/cache/index.xml create mode 100644 tags/chezmoi/atom.xml create mode 100644 tags/chezmoi/index.html create mode 100644 tags/chezmoi/index.xml create mode 100644 tags/chsh/atom.xml create mode 100644 tags/chsh/index.html create mode 100644 tags/chsh/index.xml create mode 100644 tags/clipboard/atom.xml create mode 100644 tags/clipboard/index.html create mode 100644 tags/clipboard/index.xml create mode 100644 tags/clojure/atom.xml create mode 100644 tags/clojure/index.html create mode 100644 tags/clojure/index.xml create mode 100644 tags/clone/atom.xml create mode 100644 tags/clone/index.html create mode 100644 tags/clone/index.xml create mode 100644 tags/codeberg/atom.xml create mode 100644 tags/codeberg/index.html create mode 100644 tags/codeberg/index.xml create mode 100644 tags/config/atom.xml create mode 100644 tags/config/index.html create mode 100644 tags/config/index.xml create mode 100644 tags/dotfiles/atom.xml create mode 100644 tags/dotfiles/index.html create mode 100644 tags/dotfiles/index.xml create mode 100644 tags/emacs/atom.xml create mode 100644 tags/emacs/index.html create mode 100644 tags/emacs/index.xml create mode 100644 tags/email/atom.xml create mode 100644 tags/email/index.html create mode 100644 tags/email/index.xml create mode 100644 tags/encoding/atom.xml create mode 100644 tags/encoding/index.html create mode 100644 tags/encoding/index.xml create mode 100644 tags/encryption/atom.xml create mode 100644 tags/encryption/index.html create mode 100644 tags/encryption/index.xml create mode 100644 tags/exit-code/atom.xml create mode 100644 tags/exit-code/index.html create mode 100644 tags/exit-code/index.xml create mode 100644 tags/foaf/atom.xml create mode 100644 tags/foaf/index.html create mode 100644 tags/foaf/index.xml create mode 100644 tags/fuzzy/atom.xml create mode 100644 tags/fuzzy/index.html create mode 100644 tags/fuzzy/index.xml create mode 100644 tags/fzf/atom.xml create mode 100644 tags/fzf/index.html create mode 100644 tags/fzf/index.xml create mode 100644 tags/git/atom.xml create mode 100644 tags/git/index.html create mode 100644 tags/git/index.xml create mode 100644 tags/github-actions/atom.xml create mode 100644 tags/github-actions/index.html create mode 100644 tags/github-actions/index.xml create mode 100644 tags/github-packages/atom.xml create mode 100644 tags/github-packages/index.html create mode 100644 tags/github-packages/index.xml create mode 100644 tags/github/atom.xml create mode 100644 tags/github/index.html create mode 100644 tags/github/index.xml create mode 100644 tags/gitlab/atom.xml create mode 100644 tags/gitlab/index.html create mode 100644 tags/gitlab/index.xml create mode 100644 tags/google/atom.xml create mode 100644 tags/google/index.html create mode 100644 tags/google/index.xml create mode 100644 tags/gpg/atom.xml create mode 100644 tags/gpg/index.html create mode 100644 tags/gpg/index.xml create mode 100644 tags/grim/atom.xml create mode 100644 tags/grim/index.html create mode 100644 tags/grim/index.xml create mode 100644 tags/help/atom.xml create mode 100644 tags/help/index.html create mode 100644 tags/help/index.xml create mode 100644 tags/hostnames/atom.xml create mode 100644 tags/hostnames/index.html create mode 100644 tags/hostnames/index.xml create mode 100644 tags/hugo/atom.xml create mode 100644 tags/hugo/index.html create mode 100644 tags/hugo/index.xml create mode 100644 tags/humans.txt/atom.xml create mode 100644 tags/humans.txt/index.html create mode 100644 tags/humans.txt/index.xml create mode 100644 tags/index.html create mode 100644 tags/index.xml create mode 100644 tags/interoperability/atom.xml create mode 100644 tags/interoperability/index.html create mode 100644 tags/interoperability/index.xml create mode 100644 tags/java/atom.xml create mode 100644 tags/java/index.html create mode 100644 tags/java/index.xml create mode 100644 tags/jspecify/atom.xml create mode 100644 tags/jspecify/index.html create mode 100644 tags/jspecify/index.xml create mode 100644 tags/kubectl/atom.xml create mode 100644 tags/kubectl/index.html create mode 100644 tags/kubectl/index.xml create mode 100644 tags/kubernetes/atom.xml create mode 100644 tags/kubernetes/index.html create mode 100644 tags/kubernetes/index.xml create mode 100644 tags/linux/atom.xml create mode 100644 tags/linux/index.html create mode 100644 tags/linux/index.xml create mode 100644 tags/login-shell/atom.xml create mode 100644 tags/login-shell/index.html create mode 100644 tags/login-shell/index.xml create mode 100644 tags/mac/atom.xml create mode 100644 tags/mac/index.html create mode 100644 tags/mac/index.xml create mode 100644 tags/make/atom.xml create mode 100644 tags/make/index.html create mode 100644 tags/make/index.xml create mode 100644 tags/makefile/atom.xml create mode 100644 tags/makefile/index.html create mode 100644 tags/makefile/index.xml create mode 100644 tags/manifest/atom.xml create mode 100644 tags/manifest/index.html create mode 100644 tags/manifest/index.xml create mode 100644 tags/mastodon/atom.xml create mode 100644 tags/mastodon/index.html create mode 100644 tags/mastodon/index.xml create mode 100644 tags/maven/atom.xml create mode 100644 tags/maven/index.html create mode 100644 tags/maven/index.xml create mode 100644 tags/mirror/atom.xml create mode 100644 tags/mirror/index.html create mode 100644 tags/mirror/index.xml create mode 100644 tags/neovim/atom.xml create mode 100644 tags/neovim/index.html create mode 100644 tags/neovim/index.xml create mode 100644 tags/nullness/atom.xml create mode 100644 tags/nullness/index.html create mode 100644 tags/nullness/index.xml create mode 100644 tags/passage/atom.xml create mode 100644 tags/passage/index.html create mode 100644 tags/passage/index.xml create mode 100644 tags/peek/atom.xml create mode 100644 tags/peek/index.html create mode 100644 tags/peek/index.xml create mode 100644 tags/performance/atom.xml create mode 100644 tags/performance/index.html create mode 100644 tags/performance/index.xml create mode 100644 tags/perl/atom.xml create mode 100644 tags/perl/index.html create mode 100644 tags/perl/index.xml create mode 100644 tags/plugins/atom.xml create mode 100644 tags/plugins/index.html create mode 100644 tags/plugins/index.xml create mode 100644 tags/publish/atom.xml create mode 100644 tags/publish/index.html create mode 100644 tags/publish/index.xml create mode 100644 tags/push/atom.xml create mode 100644 tags/push/index.html create mode 100644 tags/push/index.xml create mode 100644 tags/react/atom.xml create mode 100644 tags/react/index.html create mode 100644 tags/react/index.xml create mode 100644 tags/readme/atom.xml create mode 100644 tags/readme/index.html create mode 100644 tags/readme/index.xml create mode 100644 tags/release/atom.xml create mode 100644 tags/release/index.html create mode 100644 tags/release/index.xml create mode 100644 tags/rendering/atom.xml create mode 100644 tags/rendering/index.html create mode 100644 tags/rendering/index.xml create mode 100644 tags/repo.or.cz/atom.xml create mode 100644 tags/repo.or.cz/index.html create mode 100644 tags/repo.or.cz/index.xml create mode 100644 tags/repository/atom.xml create mode 100644 tags/repository/index.html create mode 100644 tags/repository/index.xml create mode 100644 tags/reproducible/atom.xml create mode 100644 tags/reproducible/index.html create mode 100644 tags/reproducible/index.xml create mode 100644 tags/rfc/atom.xml create mode 100644 tags/rfc/index.html create mode 100644 tags/rfc/index.xml create mode 100644 tags/schedule/atom.xml create mode 100644 tags/schedule/index.html create mode 100644 tags/schedule/index.xml create mode 100644 tags/screenlock/atom.xml create mode 100644 tags/screenlock/index.html create mode 100644 tags/screenlock/index.xml create mode 100644 tags/screenshot/atom.xml create mode 100644 tags/screenshot/index.html create mode 100644 tags/screenshot/index.xml create mode 100644 tags/search/atom.xml create mode 100644 tags/search/index.html create mode 100644 tags/search/index.xml create mode 100644 tags/service-worker/atom.xml create mode 100644 tags/service-worker/index.html create mode 100644 tags/service-worker/index.xml create mode 100644 tags/shell/atom.xml create mode 100644 tags/shell/index.html create mode 100644 tags/shell/index.xml create mode 100644 tags/slurp/atom.xml create mode 100644 tags/slurp/index.html create mode 100644 tags/slurp/index.xml create mode 100644 tags/sonarqube/atom.xml create mode 100644 tags/sonarqube/index.html create mode 100644 tags/sonarqube/index.xml create mode 100644 tags/ssh/atom.xml create mode 100644 tags/ssh/index.html create mode 100644 tags/ssh/index.xml create mode 100644 tags/status-bar/atom.xml create mode 100644 tags/status-bar/index.html create mode 100644 tags/status-bar/index.xml create mode 100644 tags/swaywm/atom.xml create mode 100644 tags/swaywm/index.html create mode 100644 tags/swaywm/index.xml create mode 100644 tags/systemd/atom.xml create mode 100644 tags/systemd/index.html create mode 100644 tags/systemd/index.xml create mode 100644 tags/teamwork/atom.xml create mode 100644 tags/teamwork/index.html create mode 100644 tags/teamwork/index.xml create mode 100644 tags/timestamp/atom.xml create mode 100644 tags/timestamp/index.html create mode 100644 tags/timestamp/index.xml create mode 100644 tags/tmux/atom.xml create mode 100644 tags/tmux/index.html create mode 100644 tags/tmux/index.xml create mode 100644 tags/tmuxp/atom.xml create mode 100644 tags/tmuxp/index.html create mode 100644 tags/tmuxp/index.xml create mode 100644 tags/toot/atom.xml create mode 100644 tags/toot/index.html create mode 100644 tags/toot/index.xml create mode 100644 tags/updates/atom.xml create mode 100644 tags/updates/index.html create mode 100644 tags/updates/index.xml create mode 100644 tags/upload/atom.xml create mode 100644 tags/upload/index.html create mode 100644 tags/upload/index.xml create mode 100644 tags/versioning/atom.xml create mode 100644 tags/versioning/index.html create mode 100644 tags/versioning/index.xml create mode 100644 tags/vim/atom.xml create mode 100644 tags/vim/index.html create mode 100644 tags/vim/index.xml create mode 100644 tags/waybar/atom.xml create mode 100644 tags/waybar/index.html create mode 100644 tags/waybar/index.xml create mode 100644 tags/web-app/atom.xml create mode 100644 tags/web-app/index.html create mode 100644 tags/web-app/index.xml create mode 100644 tags/windows/atom.xml create mode 100644 tags/windows/index.html create mode 100644 tags/windows/index.xml create mode 100644 tags/xdg/atom.xml create mode 100644 tags/xdg/index.html create mode 100644 tags/xdg/index.xml diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/.well-known/security.txt b/.well-known/security.txt new file mode 100644 index 000000000..4af58a1ab --- /dev/null +++ b/.well-known/security.txt @@ -0,0 +1,6 @@ + +Contact: mailto:security@shoss.de +Contact: mailto:security@xn--ho-hia.de +Expires: 2025-01-02T02:59:16Z +Preferred-Languages: en +Canonical: https://seb.xn--ho-hia.de/.well-known/security.txt diff --git a/404.html b/404.html new file mode 100644 index 000000000..5a3053bae --- /dev/null +++ b/404.html @@ -0,0 +1,11 @@ +Sebastian Hoß – 404 Page not found

404 - Not Found

The page you are looking for does not exist or was moved to another location.

Either go back to the start page or search for something like site:seb.xn--ho-hia.de YOUR_SEARCH_TERM on google.

\ No newline at end of file diff --git a/6da084089795b6f5935480863e2ed4c92d7538f5797f03bf.txt b/6da084089795b6f5935480863e2ed4c92d7538f5797f03bf.txt new file mode 100644 index 000000000..993ece128 --- /dev/null +++ b/6da084089795b6f5935480863e2ed4c92d7538f5797f03bf.txt @@ -0,0 +1 @@ +6da084089795b6f5935480863e2ed4c92d7538f5797f03bf diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..af0e1bfa2 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +seb.xn--ho-hia.de diff --git a/atom.xml b/atom.xml new file mode 100644 index 000000000..b36ab4769 --- /dev/null +++ b/atom.xml @@ -0,0 +1,1330 @@ +HugoSebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/jspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/2023-01-09T00:00:00+00:002023-01-09T07:05:35+01:00Every Java developer has probably encountered a NullPointerException at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:

+
String someName;         // value is 'null'
+
+someName.toUpperCase(); // throws NullPointerException
+

Modern IDEs have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is null and therefore a potential for a NullPointerException is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be null. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.

+

jspecify is the latest approach that tries to establish a standard. It has gained wide community support and recently celebrated their first public release (0.3.0).

+

The following snippet shows the dependency declaration for Maven projects:

+
<dependencies>
+    <dependency>
+        <groupId>org.jspecify</groupId>
+        <artifactId>jspecify</artifactId>
+        <version>0.3.0</version>
+    </dependency>
+</dependencies>
+

In case you want to declare that nothing in your module can ever be null, place the @NullMarked on your module-info.java like this:

+
@org.jspecify.annotations.NullMarked
+module your.module.here {
+
+    requires org.jspecify;
+
+    // ...
+
+}
+

The tooling support is not quiet clear yet, however if you are developing a library there is no harm in adding these annotations now and let your users enjoy their null-free life once tools have caught up.

+]]>
Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/2023-01-08T00:00:00+00:002023-01-08T09:25:44+01:00age is another tool supported by chezmoi to keep data private. Compared to gpg it is much simpler by focusing on the encryption parts only.

+

Add the following snippet to your .chezmoi.toml to configure chezmoi to use age:

+
encryption = "age"
+[age]
+  identity = "path/to/age/private-key"
+  recipient = "age...public...key..."
+

Adding files to your chezmoi source directory remains the same as compared to using gpg - just call chezmoi add --encrypt path/to/file.

+]]>
Multiple Git Configurationshttps://seb.xn--ho-hia.de/posts/multiple-git-configs/2023-01-05T00:00:00+00:002023-01-06T17:32:06+01:00To split yet re-use as much configuration for Git as possible, you can create one root configuration that looks similar to this:

+
[user]
+  name = Your Name Here
+
+[includeIf "gitdir:~/git/personal/"]
+  path = ~/.config/git/personal
+[includeIf "gitdir:~/git/work/"]
+  path = ~/.config/git/work
+

The includeIf directive supports multiple keywords. In my case, work and personal projects have a different root directory, therefore I can filter based on the location using gitdir. The personal Git configuration simply looks like this:

+
[user]
+  email = personal.email@example.com
+

and the work related configuration like this using a different email address:

+
[user]
+  email = first.last@work.example
+

Additional settings that are different for personal/work accounts can be split the same way, for example to use a different signing key for work.

+]]>
passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/2022-12-27T00:00:00+00:002023-01-06T16:40:24+01:00To fuzzy search through passwords managed with passage, I’ve written the following script that is inspired by the upstream version which is using fzf.

+
fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

This version requires fd, skim, xargs, and passage itself of course. The detailed breakdown on how it works is as follows:

+
    +
  1. Use fd to find all files within ${PASSAGE_DIR} that end in .age. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.
  2. +
  3. Using both --base-directory and --exec echo '{.}' ensures that passwords are returned in such form that they can be passed back into passage again. The placeholder '{.}' is a feature provided by fd which strips the file extension from each returned value.
  4. +
  5. All passwords are then passed into sk to allow to fuzzy search across them all. Setting --no-multi ensures that only a single password can be selected.
  6. +
  7. Finally, xargs calls passage and replaces the curly braces with the selected password. Thanks to --clip=1, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.
  8. +
+

To call that script, I’ve saved it as passage-fuzzy-search.sh in my .local/bin folder and added some checks into it to verify that every required software is actually installed.

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This shell script presents passwords saved with passage through skim
+#
+# Call it like this:
+#   passage-fuzzy-search.sh
+#
+# Required software that isn't in GNU coreutils:
+#   - 'passage' to read passwords
+#   - 'fd' to find passwords
+#   - 'sk' to filter passwords
+###############################################################################
+
+if ! (( ${+commands[passage]} )); then
+    echo 'passage not installed. Please install passage.'
+    exit
+fi
+if ! (( ${+commands[sk]} )); then
+    echo 'sk not installed. Please install skim.'
+    exit
+fi
+if ! (( ${+commands[fd]} )); then
+    echo 'fd not installed. Please install fd-find.'
+    exit
+fi
+
+fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

Since typing passage-fuzzy-search.sh is way too long, I have added an alias like this:

+
alias pp='passage-fuzzy-search.sh'
+
]]>
chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/2022-12-26T00:00:00+00:002023-01-06T17:32:06+01:00To automatically synchronize dotfiles across my computers, I’ve written the following systemd unit:

+
[Unit]
+Description=Update chezmoi managed dotfiles
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/chezmoi update --no-tty --force
+RemainAfterExit=false
+
+[Install]
+WantedBy=default.target
+

This unit pulls changes from upstream first and then applies the changes to the current computer after I’m logged in and a network connection is available. The --no-tty flag is required because there is no tty when systemd executes chezmoi. Likewise, the --force flag ensures that no interactive prompt will be displayed which we cannot answer since systemd is executing this unit without us being involved.

+]]>
chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/2022-12-12T00:00:00+00:002023-01-06T16:40:24+01:00Many CLI applications offer initialization scripts to integrate into a shell, for example starship init zsh or zoxide init zsh. The documentation of these tools usually tell you to put something like eval "$(starship init zsh)" into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the init command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.

+

chezmoi provides a template function called output which replaces itself with the output of the command you specified. You can use that function this to integrate various tools into your shell as the following example shows while using zsh:

+
    +
  1. Create a directory that holds all init scripts for every tool you want to use. +
     $ mkdir --parents "${ZDOTDIR}"/tools.d
    +
  2. +
  3. Let your shell load all available scripts in that directory. This snippet should be part of your .zshrc file: +
    for init_script in "${ZDOTDIR}"/tools.d/*.sh; do
    +  source "${init_script}"
    +done
    +
  4. +
  5. Create chezmoi .tmpl files for each tool and place them in the chezmoi source directory that matches the directory you created in step 1: +
    {{ output "starship" "init" "zsh" "--print-full-init" }}
    +
  6. +
  7. Call chezmoi apply to generate the init scripts.
  8. +
+

The only downside here is that you have to re-run chezmoi apply after updating one of the tools because they change their init scripts sometimes. That problem can be solved with chezmoi auto-updates.

+]]>
awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/2022-02-14T00:00:00+00:002023-01-06T16:40:24+01:00To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the AWS_PROFILE environment variable which is used by many tools that interact with the AWS API, like awscli or terraform. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses aws sso login to perform an MFA login into AWS. The AWS profiles must be set up in such a way that aws configure list-profiles can detect them, which is typically done by adding them in ${AWS_CONFIG_FILE:-$HOME/.aws/config}.

+
#!/usr/bin/env sh
+
+###############################################################################
+# This script performs an AWS SSO login for the user-selected AWS profile
+# and sets the AWS_PROFILE environment variable afterwards. To use
+# this, create an alias that sources this script like this:
+#
+#     alias awsenv='source path/to/this/script.sh'
+#
+# Required software that is not in GNU coreutils:
+#   - 'aws' to list profiles & get current caller identity
+#   - 'fzf' to list all available AWS profiles
+###############################################################################
+
+# prompt user to select one AWS profile
+profile=$(aws configure list-profiles | \
+  fzf --cycle --layout=reverse --tiebreak=index)
+
+# user can cancel switching profiles by pressing ESC
+if [ -n "${profile}" ]; then
+  # check is access token exists and is valid for selected profile
+  if ! aws --profile "${profile}" sts get-caller-identity >/dev/null 2>&1; then
+    # perform login into profile in case access token is invalid
+    if ! aws sso login --profile "${profile}"; then
+      # short circuit in case login failed
+      return
+    fi
+  fi
+  # AWS_PROFILE is used by many AWS-related tools
+  echo "Setting AWS_PROFILE to [${profile}]"
+  export AWS_PROFILE="${profile}"
+  # do not expose internal variables
+  unset profile
+fi
+
]]>
Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/2022-01-29T00:00:00+00:002023-01-06T17:32:06+01:00Clojure has several forms and macros to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.

+

Using gen-class

+

Clojure code can be compiled to standard JVM bytecode using gen-class.

+

Adding static modifiers

+

Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class com.example.Computation.

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :methods [#^{:static true} [incrementRange [int] java.util.List]]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [max]
+  (increment-range max))
+

The Java wrapper has to follow the standard rules for method names. Therefore increment-range has to be renamed to incrementRange (or some similar name without the “-” in it). The “-” prefix for the Java wrapper can be configured inside the :gen-class form and will be removed once gen-class runs. The usage from Java looks like this:

+
package com.example
+
+public class ClojureJavaInteropStatic {
+
+    public static void main(String[] args) {
+        List incrementedRange = Computation.incrementRange(10);
+    }
+
+}
+

Adding generics

+

The returned list in the above code is raw because the method definition doesn’t use generics. To solve this problem declare that the generated class :implements a certain interface that exposes the desired method definition(s). You won’t be able to declare your methods as static anymore, but get a generified method for all your Java needs.

+

The Java interface:

+
package com.example
+
+public interface RangeIncrementer {
+  List<Long> incrementRange(int max);
+}
+

The changed Clojure namespace:

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :implements [com.example.RangeIncrementer]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [this max]
+  (increment-range max))
+

Finally, the generified usage from Java:

+
package com.example
+
+public class ClojureJavaInteropGenerics {
+
+    public static void main(String[] args) {
+        RangeIncrementer incrementer = new Computation();
+        List<Long> incrementedRange = incrementer.incrementRange(10);
+    }
+
+}
+

Couple of notes for this as well: First the generated class still only returns the raw type (List instead of List<Integer>). So instead of using the class, use the interface for the variable declaration (RangeIncrementer incrementer = .. instead of Computation comp = ..). The interface will return the non-raw List. Second the function definition for -incrementRange is now slightly different. It needs an additional parameter (this) which exposes the current instance to the generated class/method.

+

Returning an array of something is also possible with the following construct "[Ljava.lang.Object;". Need a 2-dim array? Just use "[[Ljava.lang.Object;" (notice the extra [) and so on. However, be aware that the method return types have to match, for example you can’t specify a return type of array if your Clojure function does not return an array. In the example above the call to map returns LazySeq which itself is a java.util.List. Therefore, the method declaration is valid, and you won’t get any ClassCastException when calling incrementRange from Java.

+

Make your life easier with macros

+

Instead of defining every Clojure function which should be exported twice (the real function + the Java wrapper), it is possible to use a macro to do that extra work automatically.

+
(require '[clojure.string :as string)
+
+(defn camel-case [input]
+  (let [words (string/split input #"[\s_-]+")]
+    (string/join (cons (string/lower-case (first words)) (map string/capitalize (rest words))))))
+
+(defn java-name [clojure-name]
+  (symbol (str "-" (camel-case (str clojure-name)))))
+
+(defmacro defn* [name & declarations]
+  (let [java-name (java-name name)]
+    `(do (defn ~name ~declarations)
+       (defn ~java-name ~declarations))))
+

The macro defn* replaces defn and automatically creates a second function with a valid camel-cased Java method name. The macro is available as a small library at Maven Central. The macro won’t add the extra parameter mentioned above to Java wrapper, so it is only useful for declaring static methods.

+

Using the Clojure Runtime

+

Using gen-class imposes certain limitations on calling Clojure code from Java. One of those are functions which make use of Clojure parameter destructuring. To invoke those functions you have to use the Clojure runtime.

+
// The Clojure 'require' function from the 'clojure.core' namespace.
+Var require = RT.var("clojure.core", "require");
+
+// Your namespace
+Symbol namespace = Symbol.intern("DESIRED.NAMESPACE.HERE");
+
+// Your function
+Var function = RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION");
+
+// The required keyword for the above function
+Keyword keyword = Keyword.intern("REQUIRED-KEYWORD");
+
+// Require/Import your namespace
+require.invoke(namespace);
+
+// Invoke your function with the given keyword and its value
+Object result = function.invoke(keyword, VALUE);
+

The desired namespace has to be on the classpath for this to work. Alternatively it is possible to load an entire Clojure script, as shown in the following example:

+
RT.loadResourceScript("DESIRED/NAMESPACE/HERE.clj");
+RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION").invoke(PARAMETER);
+

On a big project it is properly wise to move Java->Clojure interop code into helper classes/methods. Look here for an example.

+]]>
Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/2022-01-15T00:00:00+00:002023-01-06T16:22:24+01:00One feature that often surprises people while teaching them React is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.

+

In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.

+
const RendersNothing = () => <></>
+

Now consider the following example, that adds some if-then-else logic to the same component:

+
const MightRenderSomething = () => {
+  if (someCondition) {
+    return <span>hello world!</span>
+  }
+  return <></>
+}
+

This component encapsulates the if-then-else logic of conditionally rendering a hello world message. Instead of cluttering your entire app with the same logic, you can now simply re-use that same component that contains this if condition. To see the full power of this technique, consider the following example. At first, we are going to define a hook that reads the current window width, then define components that conditionally render based on the current window width, and finally use those components in an example application.

+
const useWindowWidth = () => {
+  const [width, setWidth] = React.useState(0)
+
+  React.useEffect(() => {
+    const handleResize = () => {
+      setWidth(window.innerWidth)
+    }
+    window.addEventListener("resize", handleResize)
+    return () => {
+      window.removeEventListener("resize", handleResize)
+    }
+  }, [])
+
+  return width
+}
+

The following components use that hook to implement UI breakpoints for small (mobile) and large (desktop) screens. Note that the value 768 is just an example - replace it with whatever your design system tells you to.

+
const ForMobileDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+  
+  if (windowWidth < 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+
+const ForDesktopDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+
+  if (windowWidth >= 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+

Both of these components simply render nothing when the window width does not have an appropriate size. If the window width does have the right size, they render their children. We can use those components in our application like this:

+
const SomeActualComponent = () => (
+    <div>
+      <h1>common headline</h1>
+      <ForMobileDevicesOnly>
+        <span>only visible on mobile devices</span>
+      </ForMobileDevicesOnly>
+      <ForDesktopDevicesOnly>
+        <span>only visible on desktop devices</span>
+      </ForDesktopDevicesOnly>
+    </div>
+)
+

The above code snippet declares that some part of the UI can only be seen by mobile users, while others can only be seen by desktop users. Parts of the UI that are shared amongst all users are not wrapped by any of the components defined above.

+]]>
Proper hostnames in your local networkhttps://seb.xn--ho-hia.de/posts/home-network-hostnames/2022-01-08T00:00:00+00:002023-01-06T16:22:24+01:00Thanks to RFC 8375, we now have a proper domain to use for all our local devices. Simply move everything underneath .home.arpa to join the fun. In case you have hostnamectl available on your system run the following command to change the hostname of a device:

+
# set hostname
+$ hostnamectl hostname some-device.home.arpa
+
+# check hostname
+$ hostnamectl status
+
]]>
Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/2022-01-01T00:00:00+00:002023-01-06T16:22:24+01:00Both Vim and Neovim have a built-in plugin mechanism that loads plugins from ~/.vim/pack/*/{start,opt}/* (Vim) or ~/.local/share/nvim/site/pack/*/{start,opt}/* (Neovim). All you have to do to install new plugins, is to git clone their repository into those directories. To automatically update those clones, create the following script:

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This script git-pulls all installed nvim plugins which are using the built-in
+# nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack
+#
+# Required software that is not in GNU coreutils:
+#   - 'git' to fetch plugin updates from upstream
+###############################################################################
+
+### User specific variables, adjust to your own needs
+
+# folder that contains all nvim plugins
+PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack"
+
+### Script logic
+
+echo "updating all plugins in ${PLUGIN_DIR}"
+# iterate through all directories and git pull em
+for directory in "${PLUGIN_DIR}"/*/{start,opt}/*; do
+    if [ -d "${directory}" ]; then
+        plugin=$(basename "${directory}")
+        echo "updating ${plugin}"
+        git -C "${directory}" pull --quiet
+    fi
+done
+

In case you are using Vim, adjust the PLUGIN_DIR variable to point to your Vim plugin directory instead and save the resulting shell script as a file called update-nvim-plugins.sh in some folder of your choice. Do not forget to set the executable bit with chmod +x /path/to/your/folder/update-nvim-plugins.sh. Since all good developers must be lazy, write the following systemd service to execute the above script automatically:

+
[Unit]
+Description=cron job that triggers an update of all nvim plugins
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/path/to/your/folder/update-nvim-plugins.sh
+RemainAfterExit=false
+

Adjust the ExecStart line to match the location where you saved the above script and place that service definition in a file called nvim-plugins-update.service into your local ~/.config/systemd/user directory. Add another file called nvim-plugins-update.timer next to it that defines a systemd timer with the following content:

+
[Unit]
+Description=Update nvim plugins every week
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+RandomizedDelaySec=5hours
+
+[Install]
+WantedBy=timers.target
+

Adjust how often you want to update the plugins you are using in the OnCalendar line. Enable this service/timer with:

+
$ systemctl --user enable nvim-plugins-update
+

Finally, add the following shell aliases to make it easier to interact with the created systemd units:

+
# trigger an update manually
+alias update-nvim-plugins='systemctl --user start nvim-plugins-update'
+# see status of last auto-update
+alias update-nvim-plugins-status='systemctl --user status nvim-plugins-update'
+# see logs of past auto-updates
+alias update-nvim-plugins-logs='journalctl --user --unit nvim-plugins-update'
+

With this setup in place, all your plugins will be automatically updated once per week or however often you have configured in the timer.

+]]>
Manage tmux sessions with tmuxphttps://seb.xn--ho-hia.de/posts/tmux-tmuxp/2021-12-20T00:00:00+00:002023-01-06T16:22:24+01:00To manage tmux sessions, I like to use tmuxp. It works by having pre-defined sessions in ~/.config/tmuxp which looks like this:

+
session_name: cool-app
+start_directory: ~/projects/cool-app
+windows:
+- window_name: backend
+  start_directory: backend
+- window_name: frontend
+  start_directory: frontend
+

In case the name of the file is cool-app.yaml, you can open the sessions with tmuxp load cool-app --yes.

+]]>
Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/2021-12-06T00:00:00+00:002023-01-06T16:46:32+01:00To automatically version Maven projects, I like to use the m-versions-p like this:

+
$ mvn versions:set -DnewVersion=my.new.version -DgenerateBackupPoms=false
+

This will update the version property of every module in the reactor to prepare them for the next release. In case you are using GitHub Actions, consider using a timestamp.

+]]>
GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/2021-11-22T00:00:00+00:002023-01-06T15:27:21+01:00GitHub Packages can be used to host Maven packages with the following configuration in your ~/.m2/settings.xml:

+
<settings>
+  <profiles>
+    <profile>
+      <id>github</id>
+      <repositories>
+        <repository>
+          <id>maven-build-process</id>
+          <name>GitHub maven-build-process Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/maven-build-process</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+        <repository>
+          <id>hcf4j</id>
+          <name>GitHub hcf4j Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/hcf4j</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+    </profile>
+  </profiles>
+  <servers>
+    <server>
+      <id>maven-build-process</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+    <server>
+      <id>hcf4j</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+  </servers>
+</settings>
+

You will have to add another repository/server for each project you are fetching from GitHub.

+]]>
Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/2021-11-08T00:00:00+00:002023-01-06T15:27:21+01:00Some time ago, Google started hosting a copy of Maven Central. Configure it in your ~/.m2/settings.xml like this:

+
<settings>
+  <mirrors>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (Asia)</name>
+      <url>https://maven-central-asia.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (EU)</name>
+      <url>https://maven-central-eu.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (US)</name>
+      <url>https://maven-central.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+  </mirrors>
+</settings>
+

Pick the mirror nearest to your location to get best speeds.

+]]>
Backups with emacshttps://seb.xn--ho-hia.de/posts/emacs-backups/2021-10-25T00:00:00+00:002023-01-06T15:27:21+01:00emacs will create backups of your files by default. Those backups are located right next to the original file and are called <file>~. Unfortunately, emacs will not clean those up by default, which annoys me from time to time. Therefore, I’m now using the following configuration to keep those backups in a different folder:

+
(setq version-control t     ;; Use version numbers for backups.
+      kept-new-versions 10  ;; Number of newest versions to keep.
+      kept-old-versions 0   ;; Number of oldest versions to keep.
+      delete-old-versions t ;; Don't ask to delete excess backup versions.
+      backup-by-copying t)  ;; Copy all files, don't rename them.
+
+(setq vc-make-backup-files t)
+
+;; Default and per-save backups go here:
+(setq backup-directory-alist '(("" . "~/.emacs.d/backup/per-save")))
+
+(defun force-backup-of-buffer ()
+  ;; Make a special "per session" backup at the first save of each
+  ;; emacs session.
+  (when (not buffer-backed-up)
+    ;; Override the default parameters for per-session backups.
+    (let ((backup-directory-alist '(("" . "~/.emacs.d/backup/per-session")))
+          (kept-new-versions 3))
+      (backup-buffer)))
+  ;; Make a "per save" backup on each save.  The first save results in
+  ;; both a per-session and a per-save backup, to keep the numbering
+  ;; of per-save backups consistent.
+  (let ((buffer-backed-up nil))
+    (backup-buffer)))
+
+(add-hook 'before-save-hook  'force-backup-of-buffer)
+

Thanks to that configuration, backups per-save will be created in ~/.emacs.d/backup/per-save and backups per-session in ~/.emacs.d/backup/per-session.

+]]>
Maintaining dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-maintenance/2021-10-11T00:00:00+00:002023-01-06T17:32:06+01:00To make it easier managing many dotfiles with chezmoi, a shell function similar to the one below can be used:

+
function m-dotfiles-ok {
+    # public
+    chezmoi add ~/.config/zsh --recursive
+    chezmoi add ~/.config/sway --recursive
+    chezmoi add ~/.config/tmux --recursive
+    chezmoi add ....
+
+    # secrets
+    chezmoi add --encrypt ~/.config/npm/npmrc
+    chezmoi add --encrypt ~/.ssh/id_rsa
+    chezmoi add --encrypt ...
+}
+

Whenever you feel happy with your current setup, just call m-dotfiles-ok to push changes into the chezmoi source directory. Files will automatically be encrypted with gpg and committed/pushed into a Git repository if you have done the necessary configuration beforehand.

+

In general, editing your dotfiles directly as explained in the second option of the FAQ seems easier though. Refactoring your dotfiles is especially easy when the exact_ prefix is used for directories. As explained in the documentation, all files that are not managed by chezmoi will be removed, therefore your configuration will always match what is in your source directory.

+]]>
Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/2021-09-20T00:00:00+00:002023-01-08T09:25:44+01:00RECOMMENDATION: Use age instead of gpg.

+

chezmoi can use various external tools to keep data private. gpg is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure gpg with chezmoi, just set yourself as the recipient like this:

+
[gpg]
+  recipient = "your.name@example.com"
+

Calling chezmoi add --encrypt /path/to/secret will now create encrypt the file with your public key which allows you to decrypt them later with your private key.

+]]>
Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/2021-09-06T00:00:00+00:002023-01-06T17:32:06+01:00chezmoi can automatically commit and push changes to your dotfiles into a (remote) Git repository. Enable it with the following snippet in your chezmoi.toml

+
[sourceVCS]
+    autoCommit = true
+    autoPush = true
+

Every time you call chezmoi add /path/to/file will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.

+]]>
Waybar on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-waybar/2021-08-23T00:00:00+00:002023-01-06T16:40:24+01:00Waybar can be used as a status bar for SwayWM. You tell Sway to use it with the following snippet in your Sway configuration:

+
bar {
+    swaybar_command waybar
+}
+

Configure Waybar itself in ~/.config/waybar/config:

+
{
+    "layer": "top",
+    "modules-left": ["sway/workspaces", "sway/mode"],
+    "modules-center": ["sway/window"],
+    "modules-right": ["clock"],
+    "sway/window": {
+        "max-length": 50
+    },
+    "clock": {
+        "format-alt": "{:%a, %d. %b  %H:%M}"
+    }
+}
+
]]>
tmux Status Barhttps://seb.xn--ho-hia.de/posts/tmux-status-bar/2021-08-09T00:00:00+00:002023-01-06T16:22:24+01:00To see the currently active tmux status bar configuration, call:

+
$ tmux show-options -g | grep status
+

Change on of those values with in the current tmux session:

+
$ tmux set-option status-right ""
+

Persist the change in your tmux.conf like this:

+
# disable right side of status bar
+set-option -g status-right ""
+
]]>
emacs and systemdhttps://seb.xn--ho-hia.de/posts/emacs-systemd/2021-07-26T00:00:00+00:002023-01-06T16:40:24+01:00I like to use emacs to edit files in a terminal. It tends to start a little slow, therefore I’ve created a systemd unit to automatically start the emacs daemon and use aliases to connect to the running daemon. The unit looks like this:

+
[Unit]
+Description=Emacs text editor [%I]
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+
+[Service]
+Type=forking
+ExecStart=/usr/bin/emacs --daemon=%i
+ExecStop=/usr/bin/emacsclient --eval "(kill-emacs)"
+Environment=SSH_AUTH_SOCK=%t/keyring/ssh
+Restart=on-failure
+
+[Install]
+WantedBy=default.target
+

Enable it with systemctl --user enable emacs@user and define any number of aliases to make connecting to the emacs daemon easier:

+
alias e='emacsclient --tty --socket-name=user'
+alias vim='emacsclient --tty --socket-name=user'
+alias vi='emacsclient --tty --socket-name=user'
+alias nano='emacsclient --tty --socket-name=user'
+alias ed='emacsclient --tty --socket-name=user'
+
]]>
Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/2021-07-12T00:00:00+00:002023-01-06T17:32:06+01:00In case you want to have push-only mirrors for your Git repository, consider adding a special mirror remote like this:

+
$ git remote add mirrors DISABLED
+$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git
+$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git
+$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git
+

The above will create a new remote called mirrors which has no fetch URL and therefore can only be pushed:

+
$ git remote -v
+mirrors DISABLED (fetch)
+mirrors git@codeberg.org:org/repo.git (push)
+mirrors git@gitlab.com:org/repo.git (push)
+mirrors git@bitbucket.org:org/repo.git (push)
+

Calling git push mirrors main:main will push the local main branch into all defined mirrors.

+ + +]]>
Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/2021-06-28T00:00:00+00:002023-01-06T16:46:32+01:00In case you want to make use of the decentralized nature of Git, consider using multiple push targets like this:

+
$ git remote set-url origin --push --add git@example.com/project.git
+$ git remote set-url origin --push --add git@another.com/project.git
+

Note that the first call to set-url will overwrite an existing remote creating with git clone. Any additional call will actually recognize the --add option and add the new target to an existing remote.

+ + +]]>
Using multiple clusters with kubectlhttps://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/2021-06-14T00:00:00+00:002023-01-06T17:32:06+01:00To connect to multiple Kubernetes clusters with kubectl, I like to define aliases like this:

+
alias rancher="kubectl --kubeconfig ~/.kube/rancher.config"
+alias work="kubectl --kubeconfig ~/.kube/work.config"
+alias customer="kubectl --kubeconfig ~/.kube/customer.config"
+

Those aliases allow me to write things like rancher get pods --namespace some-namespace without worrying the wrong context is active. Using multiple configurations - one for each cluster - seems to be easier to manage since most clusters allow to download a ready-to-use configuration file. Instead of mangling them together manually, I just specify another alias whenever I get to work with another cluster.

+]]>
Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/2021-05-31T00:00:00+00:002023-01-06T17:32:06+01:00Maven projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.

+
<properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+</properties>
+
+ +]]>
Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/2021-05-17T00:00:00+00:002023-01-06T16:22:24+01:00To create reproducible builds with Maven projects, it’s enough to specify the project.build.outputTimestamp property like this:

+
<properties>
+    <project.build.outputTimestamp>2020</project.build.outputTimestamp>
+</properties>
+
+ +]]>
Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/2021-05-03T00:00:00+00:002023-01-06T17:32:06+01:00To analyze Maven projects with SonarCloud using GitHub Actions, first create the following settings.xml file:

+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
+
+    <pluginGroups>
+        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
+    </pluginGroups>
+
+    <activeProfiles>
+        <activeProfile>sonar</activeProfile>
+    </activeProfiles>
+
+    <profiles>
+        <profile>
+            <id>sonar</id>
+            <properties>
+                <sonar.host.url>https://sonarcloud.io</sonar.host.url>
+                <sonar.organization>YOUR_ORG</sonar.organization>
+                <sonar.projectKey>YOUR_PROJECT</sonar.projectKey>
+                <sonar.login>${env.SONAR_TOKEN}</sonar.login>
+            </properties>
+        </profile>
+    </profiles>
+</settings>
+

Finally, add a step to your workflow:

+
- name: Verify Project
+  run: mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar
+  env:
+    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+
+ +]]>
Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/2021-04-19T00:00:00+00:002023-01-06T16:22:24+01:00To use tmux as your login shell, use chsh:

+
# list all available shells
+$ chsh --list-shells
+/bin/sh
+/bin/bash
+/sbin/nologin
+/usr/bin/sh
+/usr/bin/bash
+/usr/sbin/nologin
+/usr/bin/zsh
+/bin/zsh
+/usr/bin/tmux
+/bin/tmux
+
+# select login shell
+$ chsh --shell /usr/bin/tmux
+
]]>
Lock your screen on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenlock/2021-04-05T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM users can use swaylock to lock their screen. Place the following key binding in your Sway configuration:

+
# lock your screen
+bindsym $mod+Ctrl+l exec swaylock --color 000000
+

$mod+Ctrl+l will lock your screen and turn it to black. The --color flag allows any color in the form of rrggbb[aa].

+]]>
Peeking with tmuxhttps://seb.xn--ho-hia.de/posts/tmux-peek/2021-04-05T00:00:00+00:002023-01-06T15:27:21+01:00tmux uses can use the following snippet to peek at files. Place it in your .bashrc or similar file.

+
peek() { tmux split-window -p 33 "$EDITOR" "$@" }
+

Calling peek <file> will open <file> in lower third of tmux window.

+]]>
Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/2021-03-22T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM uses can use a mixture of grim and slurp to take screenshots of their desktop. Place the following key binding in your Sway configuration:

+
# take screenshot of currently focused screen
+bindsym $mod+Print exec /usr/bin/grim -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
+# take screenshot of selection
+bindsym $mod+Shift+p exec /usr/bin/grim -g "$(/usr/bin/slurp)" $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
]]>
Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/2021-03-08T00:00:00+00:002023-01-06T16:40:24+01:00README file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a Makefile in the root of your project which contains the exact same instructions. Thanks to make, all your contributors can now use TAB-completion to run any of the pre-defined make targets.

+

The following example is part of one of my projects, and I certainly don’t want to type (or even copy) that all the time:

+
.PHONY: release-into-local-nexus
+release-into-local-nexus:
+	mvn versions:set \
+	   -DnewVersion=$(TIMESTAMPED_VERSION) \
+	   -DgenerateBackupPoms=false
+	-mvn clean deploy scm:tag \
+	   -DpushChanges=false \
+	   -DskipLocalStaging=true \
+	   -Drelease=local
+	mvn versions:set \
+	   -DnewVersion=9999.99.99-SNAPSHOT \
+	   -DgenerateBackupPoms=false
+

With the above target in place, everyone can now do make release-into-local-nexus instead of typing/copying the commands themselves. Thanks to TAB-completion you just have to do make r<TAB> and confirm with >ENTER> to perform a release.

+ + +]]>
Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/2021-02-22T00:00:00+00:002023-01-06T16:40:24+01:00To delay the execution of an GitHub Action, use a mixture of the on: schedule: ... configuration, and a conditional build step.

+
name: <PIPELINE>
+on:
+  schedule:
+    - cron: '<CRON>'
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Count commits in last week
+        id: commits
+        run: echo "::set-output name=count::$(git rev-list --count HEAD --since='<DATE>')"
+      - name: Build project
+        if: steps.commits.outputs.count > 0
+        run: build-project
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <CRON>: cron expression - use https://crontab.guru/.
  • +
  • <DATE>: Git date expression that matches <CRON>.
  • +
+]]>
Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/2021-02-08T00:00:00+00:002023-01-06T16:40:24+01:00In case you are using a Makefile to define a complex build step - for example start database, run tests, stop database - consider using the - qualifier in front of your actual build step like this:

+
.PHONY: build
+build:
+	start-database
+	-build-software
+	stop-database
+

Thanks to -, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.

+ + +]]>
Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/2021-01-25T00:00:00+00:002023-01-06T16:40:24+01:00To use a serviceworker to cache a Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/javascript"]
+  suffixes = ["js"]
+[outputFormats.ServiceWorker]
+  name = "ServiceWorker"
+  mediaType = "application/javascript"
+  baseName = "serviceworker"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.serviceworker.js with the following content:

+
const CACHE = 'cache-and-update';
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(precache());
+});
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(fromCache(event.request));
+  event.waitUntil(update(event.request));
+});
+
+const precache = async () => {
+    const cache = await caches.open(CACHE);
+    return await cache.addAll([
+        {{ range $i, $e := .Site.RegularPages }}
+        '{{ $.RelPermalink }}'{{ if $i }}, {{ end }}
+        {{ end }}
+    ]);
+}
+
+const fromCache = async (request) => {
+    const cache = await caches.open(CACHE);
+    const match = await cache.match(request);
+    return match || Promise.reject('no-match');
+}
+
+const update = async (request) => {
+    const cache = await caches.open(CACHE);
+    const response = await fetch(request);
+    return await cache.put(request, response);
+}
+
+ +]]>
Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/2021-01-11T00:00:00+00:002023-01-06T16:40:24+01:00To publish a web app manifest document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/manifest+json"]
+  suffixes = ["webmanifest"]
+[outputFormats.Webmanifest]
+  name = "Web App Manifest"
+  mediaType = "application/manifest+json"
+  baseName = "manifest"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.manifest.json with the following content:

+
{
+  "name": "{{ .Site.Title }}",
+  "short_name": "{{ .Site.Title }}",
+  "start_url": ".",
+  "display": "minimal-ui",
+  "background_color": "#fff",
+  "description": "{{ .Site.Params.description }}"
+}
+
+ +]]>
Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/2020-12-28T00:00:00+00:002023-01-06T15:27:21+01:00Hugo allows bundling of assets with several built-in functions:

+
{{ $normalize := resources.Get "/css/normalize.css" }}
+{{ $font := resources.Get "/css/font.css" }}
+{{ $header := resources.Get "/css/header.css" }}
+{{ $footer := resources.Get "/css/footer.css" }}
+{{ $navigation := resources.Get "/css/navigation.css" }}
+{{ $navigation_mobile := resources.Get "/css/navigation-mobile.css" }}
+{{ $layout := resources.Get "/css/layout.css" }}
+{{ $layout_mobile := resources.Get "/css/layout-mobile.css" }}
+{{ $syntax := resources.Get "/css/syntax.css" }}
+{{ $darkmode := resources.Get "/css/darkmode.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat "css/base.css" | resources.Minify | resources.Fingerprint "sha512" }}
+{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat "css/mobile.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+<link href="{{ $base.Permalink }}" integrity="{{ $base.Data.Integrity }}" media="screen" rel="stylesheet">
+<link href="{{ $mobile.Permalink }}" integrity="{{ $mobile.Data.Integrity }}" media="screen and (max-width: 800px)" rel="stylesheet">
+
+<link href="{{ $darkmode.Permalink }}" integrity="{{ $darkmode.Data.Integrity }}" media="screen and (prefers-color-scheme: dark)" rel="stylesheet">
+
]]>
humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/2020-12-14T00:00:00+00:002023-01-06T16:40:24+01:00To publish a humans.txt document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."text/plain"]
+  suffixes = ["txt"]
+[outputFormats.Humans]
+  name = "Humans"
+  mediaType = "text/plain"
+  baseName = "humans"
+  isPlainText = true
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.humans.txt with the following content:

+
/* TEAM */
+{{ range $.Site.Data.contributors }}
+{{ .title }}: {{ .first_name }} {{ .last_name }}
+Site: {{ .website }}
+{{ end }}
+
]]>
FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/2020-11-30T00:00:00+00:002023-01-06T16:40:24+01:00To publish a FOAF document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/rdf+xml"]
+  suffixes = ["rdf"]
+[outputFormats.Foaf]
+  name = "FOAF"
+  mediaType = "application/rdf+xml"
+  baseName = "foaf"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.foaf.rdf with the following content:

+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/">
+  <foaf:PersonalProfileDocument rdf:about="">
+    <foaf:maker rdf:resource="#me" />
+    <foaf:primaryTopic rdf:resource="{{ .Site.Title }}" />
+  </foaf:PersonalProfileDocument>
+
+  <foaf:Project rdf:ID="{{ .Site.Title }}">
+    <foaf:name>{{ .Site.Title }}</foaf:name>
+    <foaf:homepage rdf:resource="{{ .Site.BaseURL }}" />
+  </foaf:Project>
+
+  {{ range $.Site.Data.contributors }}
+  <foaf:Person rdf:ID="{{ .id }}">
+    <foaf:name>{{ .first_name }} {{ .last_name }}</foaf:name>
+    <foaf:title>{{ .title }}</foaf:title>
+    <foaf:givenname>{{ .first_name }}</foaf:givenname>
+    <foaf:family_name>{{ .last_name }}</foaf:family_name>
+    <foaf:mbox rdf:resource="mailto:{{ .email }}" />
+    <foaf:homepage rdf:resource="{{ .website }}" />
+  </foaf:Person>
+  {{ end }}
+</rdf:RDF>
+
]]>
Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/2020-11-16T00:00:00+00:002023-01-06T16:40:24+01:00To publish Atom feeds for your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/atom+xml"]
+  suffixes = ["xml"]
+[outputFormats.Atom]
+  name = "Atom"
+  mediaType = "application/atom+xml"
+  baseName = "atom"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/list.atom.xml with the following content:

+
{{ printf `<?xml version="1.0" encoding="utf-8"?>` | safeHTML }}
+<feed xmlns="http://www.w3.org/2005/Atom"{{ with site.LanguageCode }} xml:lang="{{ . }}"{{ end }}>
+    <generator uri="https://gohugo.io/" version="{{ hugo.Version }}">Hugo</generator>
+    {{- $title := site.Title -}}
+    {{- with .Title -}}
+        {{- if (not (eq . site.Title)) -}}
+            {{- $title = printf `%s %s %s` . (i18n "feed_title_on" | default "on") site.Title -}}
+        {{- end -}}
+    {{- end -}}
+    {{- if .IsTranslated -}}
+        {{ $title = printf "%s (%s)" $title (index site.Data.i18n.languages .Lang) }}
+    {{- end -}}
+    {{ printf `<title type="html"><![CDATA[%s]]></title>` $title | safeHTML }}
+    {{ with (or (.Param "subtitle") (.Param "tagline")) }}
+        {{ printf `<subtitle type="html"><![CDATA[%s]]></subtitle>` . | safeHTML }}
+    {{ end }}
+    {{ $output_formats := .OutputFormats }}
+    {{ range $output_formats -}}
+        {{- $rel := (or (and (eq "atom" (.Name | lower)) "self") "alternate") -}}
+        {{ with $output_formats.Get .Name }}
+            {{ printf `<link href=%q rel=%q type=%q title=%q />` .Permalink $rel .MediaType.Type .Name | safeHTML }}
+        {{- end -}}
+    {{- end }}
+    {{- range .Translations }}
+        {{ $output_formats := .OutputFormats }}
+        {{- $lang := .Lang }}
+        {{- $langstr := index site.Data.i18n.languages .Lang }}
+        {{ range $output_formats -}}
+            {{ with $output_formats.Get .Name }}
+                {{ printf `<link href=%q rel="alternate" type=%q hreflang=%q title="[%s] %s" />` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }}
+            {{- end -}}
+        {{- end }}
+    {{- end }}
+    <updated>{{ now.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+    {{ with site.Copyright }}
+        {{- $copyright := replace . "{year}" now.Year -}} {{/* In case the site.copyright uses a special string "{year}" */}}
+        {{- $copyright = replace $copyright "&copy;" "©" -}}
+        <rights>{{ $copyright | plainify }}</rights>
+    {{- end }}
+    {{ with .Param "feed" }}
+        {{/* For this to work, the $icon file should be present in the assets/ directory */}}
+        {{- $icon := .icon | default "icon.svg" -}}
+        {{- with resources.Get $icon -}}
+            <icon>{{ (. | fingerprint).Permalink }}</icon>
+        {{- end }}
+
+        {{/* For this to work, the $logo file should be present in the assets/ directory */}}
+        {{- $logo := .logo | default "logo.svg" -}}
+        {{- with resources.Get $logo -}}
+            <logo>{{ (. | fingerprint).Permalink }}</logo>
+        {{- end }}
+    {{ end }}
+    {{ with site.Author.name -}}
+        <author>
+            <name>{{ . }}</name>
+            {{ with site.Author.email }}
+                <email>{{ . }}</email>
+            {{ end -}}
+        </author>
+    {{- end }}
+    {{ with site.Params.id }}
+        <id>{{ . | plainify }}</id>
+    {{ else }}
+        <id>{{ .Permalink }}</id>
+    {{ end }}
+    {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }}
+    {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}}
+    {{/* Range through only the pages with a Type in $feed_sections. */}}
+    {{- $pages := where .RegularPages "Type" "in" $feed_sections -}}
+    {{- if (eq .Kind "home") -}}
+        {{- $pages = where site.RegularPages "Type" "in" $feed_sections -}}
+    {{- end -}}
+    {{/* Remove the pages that have the disable_feed parameter set to true. */}}
+    {{- $pages = where $pages ".Params.disable_feed" "!=" true -}}
+    {{- range first $limit $pages }}
+        {{ $page := . }}
+        <entry>
+            {{ printf `<title type="html"><![CDATA[%s]]></title>` .Title | safeHTML }}
+            <link href="{{ .Permalink }}?utm_source=atom_feed" rel="alternate" type="text/html" />
+            {{- range .Translations }}
+                {{- $link := printf "%s?utm_source=atom_feed" .Permalink | safeHTML }}
+                {{- printf `<link href=%q rel="alternate" type="text/html" hreflang=%q />` $link .Lang | safeHTML }}
+            {{- end }}
+            {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}}
+            {{- range first 5 (site.RegularPages.Related .) }}
+                <link href="{{ .Permalink }}?utm_source=atom_feed" rel="related" type="text/html" title="{{ .Title }}" />
+            {{- end }}
+            {{ with .Params.id }}
+                <id>{{ . | plainify }}</id>
+            {{ else }}
+                <id>{{ .Permalink }}</id>
+            {{ end }}
+            {{ with .Params.author -}}
+                {{- range . -}} <!-- Assuming the author front-matter to be a list -->
+                    <author>
+                        <name>{{ . }}</name>
+                    </author>
+                {{- end -}}
+            {{- end }}
+            <published>{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</published>
+            <updated>{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+            {{ $description1 := .Description | default "" }}
+            {{ $description := (cond (eq "" $description1) "" (printf "<blockquote>%s</blockquote>" ($description1 | markdownify))) }}
+            {{ printf `<content type="html"><![CDATA[%s%s]]></content>` $description .Content | safeHTML }}
+            {{ with site.Taxonomies }}
+                {{ range $taxo,$_ := . }} <!-- Defaults taxos: "tags", "categories" -->
+                    {{ with $page.Param $taxo }}
+                        {{ $taxo_list := . }} <!-- $taxo_list will be the tags/categories list -->
+                        {{ with site.GetPage (printf "/%s" $taxo) }}
+                            {{ $taxonomy_page := . }}
+                            {{ range $taxo_list }} <!-- Below, assuming pretty URLs -->
+                                <category scheme="{{ printf "%s%s" $taxonomy_page.Permalink (. | urlize) }}" term="{{ (. | urlize) }}" label="{{ . }}" />
+                            {{ end }}
+                        {{ end }}
+                    {{ end }}
+                {{ end }}
+            {{ end }}
+        </entry>
+    {{ end }}
+</feed>
+
]]>
Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/2020-11-16T00:00:00+00:002023-01-06T15:27:21+01:00The rzr/fediverse-action action allows to send a toot in your GitHub Action.

+
name: <NAME>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Publish Toot
+        uses: rzr/fediverse-action@master
+        with:
+          access-token: ${{ secrets.MASTODON_TOKEN }}
+          message: <MESSAGE>
+          host: ${{ secrets.MASTODON_SERVER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <MESSAGE>: Message for the toot.
  • +
+]]>
Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/2020-11-02T00:00:00+00:002023-01-06T15:27:21+01:00The dawidd6/action-send-mail action allows to send an email in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Send mail
+        uses: dawidd6/action-send-mail@v3
+        with:
+          server_address: ${{ secrets.MAIL_SERVER }}
+          server_port: ${{ secrets.MAIL_PORT }}
+          username: ${{ secrets.MAIL_USERNAME }}
+          password: ${{ secrets.MAIL_PASSWORD }}
+          subject: <SUBJECT>
+          body: <BODY>
+          to: ${{ secrets.MAIL_RECIPIENT }}
+          from: ${{ secrets.MAIL_SENDER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <SUBJECT>: Subject for the email.
  • +
  • <BODY>: Body for the email.
  • +
+

Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define MAIL_RECIPIENT for each project.

+]]>
Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/2020-10-19T00:00:00+00:002023-01-06T15:27:21+01:00The actions/upload-release-asset action allows to upload a release artifact in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Upload Release Asset
+        id: upload_release_asset
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./some/path/to/file.zip
+          asset_name: public-name-for-file.zip
+          asset_content_type: application/zip
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/2020-10-05T00:00:00+00:002023-01-06T17:32:06+01:00The actions/create-release action allows to create a new GitHub releases in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+     - name: Create Release
+       uses: actions/create-release@v1
+       env:
+         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+       with:
+         tag_name: <TAG>
+         release_name: <RELEASE>
+         draft: false
+         prerelease: false
+         body: |
+           Your release text here
+
+           Some code block:
+           ```yaml
+           yaml:
+             inside:
+               of:
+                 another: yaml
+           ```           
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <TAG>: The Git tag to create.
  • +
  • <RELEASE>: The release name to use.
  • +
+]]>
Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/2020-09-21T00:00:00+00:002023-01-06T15:27:21+01:00The peaceiris/actions-gh-pages action allows to publish a Hugo site in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Deploy Website
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: <PUBLISH_DIR>
+          force_orphan: true
+          cname: <CNAME>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <PUBLISH_DIR>: The file system location of the built site.
  • +
  • <CNAME>: The CNAME of your custom domain.
  • +
+]]>
Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/2020-09-07T00:00:00+00:002023-01-06T15:27:21+01:00The actions/cache action allows to cache artifacts in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Cache Maven artifacts
+        uses: actions/cache@v1
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-            
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/2020-08-24T00:00:00+00:002023-01-06T15:27:21+01:00The actions-hugo action allows to use a specific Hugo version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Setup hugo
+        uses: peaceiris/actions-hugo@v2
+        with:
+          hugo-version: <HUGO_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <HUGO_VERSION>: The released versions or use latest to always use the latest version of Hugo.
  • +
+]]>
Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/2020-08-10T00:00:00+00:002023-01-06T15:27:21+01:00The setup-java action allows to use a specific Java version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Set up JDK <JDK_VERSION>
+        uses: actions/setup-java@v1
+        with:
+          java-version: <JDK_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <JDK_VERSION>: The required Java version for your project.
  • +
+]]>
XDG Base Directory Specificationhttps://seb.xn--ho-hia.de/posts/xdg-dot-files/2020-07-27T00:00:00+00:002023-01-06T16:22:24+01:00The XDG Base Directory Specification has been around for a while, yet not every software has adopted it yet. Here is an incomplete list of fixes:

+
# use existing env variables or define new
+[ -z "$XDG_CACHE_HOME"  ] && export XDG_CACHE_HOME="$HOME/.cache"
+[ -z "$XDG_CONFIG_DIRS" ] && export XDG_CONFIG_DIRS="/etc/xdg"
+[ -z "$XDG_CONFIG_HOME" ] && export XDG_CONFIG_HOME="$HOME/.config"
+[ -z "$XDG_DATA_DIRS"   ] && export XDG_DATA_DIRS="/usr/local/share:/usr/share"
+[ -z "$XDG_DATA_HOME"   ] && export XDG_DATA_HOME="$HOME/.local/share"
+
+# gradle
+export GRADLE_USER_HOME="$XDG_DATA_HOME/gradle"
+
+# httpie
+export HTTPIE_CONFIG_DIR="$XDG_CONFIG_HOME/httpie"
+
+# npm
+export NPM_CONFIG_USERCONFIG="$XDG_CONFIG_HOME/npm/npmrc"
+export npm_config_cache="$XDG_CACHE_HOME/npm"
+
+# password-store
+export PASSWORD_STORE_DIR="$XDG_DATA_HOME/password-store"
+

To make your own software XDG-aware, consider using the dirs-dev or configdir libraries.

+]]>
GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/2020-07-13T00:00:00+00:002023-01-06T17:32:06+01:00Git at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. Pro Git rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.

+

While the central server approach is easy to use, it might not work in all scenarios:

+
    +
  1. An enterprise wants to share internal code as part of an open source project. All development is happening internally, and the public mirror gets an occasional update once in a while.
  2. +
  3. To protect against outages of the central server, mirrors should be created and be kept up-to-date.
  4. +
+

In case of the first scenario, tools like copybara, repoSpanner, or distributed-Git-forks offer a wide range of features to cover most details.

+

The second scenario can be solved manually with tools like gitomatic or automatically with GitLab’s mirror feature quite easy. GitLab allows to create a single pull-mirror and multiple push-mirrors. Therefore, it can be used to pull from your central server and push into all mirrors.

+

NOTE: This feature was previously available in the free tier but has now moved to GitLab Ultimate.

+
               +----------------+               
+               |     GitHub     |               
+               +----------------+               
+                        ^                       
+                        |                       
+                        |                       
+               +----------------+               
+         +-----|     GitLab     |------+        
+         |     +----------------+      |        
+         |                             |        
+         |                             |        
+         v                             v        
++----------------+            +----------------+
+|    Codeberg    |            |    BitBucket   |
++----------------+            +----------------+
+

To create such a setup, follow these steps:

+
    +
  1. Go to Settings > Repository and expand Mirroring repositories +Code Flow
  2. +
  3. Enter the URL of your central Git repository as the pull source, for example https://github.com/metio/ilo.git +Code Flow
  4. +
  5. Enter one push target for each mirror. Since pushing usually requires authentication, verify that the URL of the mirror contains a username, for example https://YOUR_USER@codeberg.org/metio.wtf/ilo.git. Add an access token for each mirror and select password as authentication method. +Code Flow
  6. +
+

In case you prefer SSH keys over HTTP access tokens, just select SSH public key as authentication method and verify that your key is both saved in GitLab and all mirrors.

+]]>
Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/2020-06-29T00:00:00+00:002023-01-06T16:22:24+01:00Use the following Perl snippet to automatically generate help output for your Makefile:

+
GREEN  := $(shell tput -Txterm setaf 2)
+WHITE  := $(shell tput -Txterm setaf 7)
+YELLOW := $(shell tput -Txterm setaf 3)
+RESET  := $(shell tput -Txterm sgr0)
+
+HELP_FUN = \
+    %help; \
+    while(<>) { push @{$$help{$$2 // 'targets'}}, [$$1, $$3] if /^([a-zA-Z\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
+    print "usage: make [target]\n\n"; \
+    for (sort keys %help) { \
+    print "${WHITE}$$_:${RESET}\n"; \
+    for (@{$$help{$$_}}) { \
+    $$sep = " " x (32 - length $$_->[0]); \
+    print "  ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
+    }; \
+    print "\n"; }
+

To use HELP_FUN, add the following help target to the same Makefile:

+
.DEFAULT_GOAL := help
+
+.PHONY: help
+help: ##@other Show this help
+	@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
+

Each target in the Makefile is marked as phony to signal that those targets are not actually files that are generated as part of your build process. The optional description of a target can be placed after the ##@ prefix. The first word represents the group of a target and everything that follows is the description of a target. All targets should be formatted just like the help target:

+
.PHONY: compile
+compile: ##@hacking Compile your code
+	<compile some code>
+
+.PHONY: test
+test: ##@hacking Test your code
+	<test some code>
+
+.PHONY: sign-cla
+sign-cla: ##@contrib Sign the contributor license agreement
+	<sign some file>
+

Once in place, you can either use make without any argument to call the help target or use make help to see the generated output:

+
$ make
+usage: make [target]
+
+contrib:
+  sign-cla            Sign the contributor license agreement
+
+hacking:
+  compile             Compile your code
+  test                Test your code
+
+other:
+  help                Show this help
+
]]>
Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/2020-06-15T00:00:00+00:002023-01-06T17:32:06+01:00In case you don’t want to write git clone git@github.com:orga/repo.git all the time, consider using a custom SSH configuration (~/.ssh/config) like this:

+
Host github
+    HostName github.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITHUB>
+
+Host gitlab
+    HostName gitlab.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITLAB>
+
+Host bitbucket
+    HostName bitbucket.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-BITBUCKET>
+
+Host codeberg
+    HostName codeberg.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-CODEBERG>
+

Once configured, you can now write:

+
$ git clone github:orga/repo
+$ git clone gitlab:orga/repo
+$ git clone bitbucket:orga/repo
+$ git clone codeberg:orga/repo
+

In case you are working with many repositories inside a single organization, consider adding the following Git configuration ($XDG_CONFIG_HOME/git/config or ~/.gitconfig):

+
[url "github:orga/"]
+  insteadOf = orga:
+[url "gitlab:orga/"]
+  insteadOf = orgl:
+[url "bitbucket:orga/"]
+  insteadOf = orgb:
+[url "codeberg:orga/"]
+  insteadOf = orgc:
+

Which allows you to just write:

+
$ git clone orga:repo
+$ git clone orgl:repo
+$ git clone orgb:repo
+$ git clone orgc:repo
+

Git will substitute the insteadOf values like orga: with the configured url (for example github:orga/). The actual clone URL is github:orga/repo at this point, which can be used by Git together with the SSH configuration mentioned above to clone repositories.

+]]>
Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/2020-06-11T00:00:00+00:002023-01-06T15:27:21+01:00In case you are into calver or have another reason to create a timestamp with GitHub Actions, do the following:

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Create release version
+        id: <ID>
+        run: echo "::set-output name=<NAME>::$(date +'%Y.%m.%d-%H%M%S')"
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <ID>: The unique ID of the timestamp step.
  • +
  • <NAME>: The name of the created timestamp.
  • +
+

The special syntax ::set-output name=<NAME>:: declares that the output of the command (echo) should be saved in a variable called <NAME>. Together with the <ID> of the pipeline step, this value can be referenced with the expression ${{ steps.<ID>.outputs.<NAME> }} in the following steps of your pipeline.

+]]>
\ No newline at end of file diff --git a/categories/cd/atom.xml b/categories/cd/atom.xml new file mode 100644 index 000000000..4f4d429bb --- /dev/null +++ b/categories/cd/atom.xml @@ -0,0 +1,20 @@ +HugoCd on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/categories/cd/Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/2021-12-06T00:00:00+00:002023-01-06T16:46:32+01:00To automatically version Maven projects, I like to use the m-versions-p like this:

+
$ mvn versions:set -DnewVersion=my.new.version -DgenerateBackupPoms=false
+

This will update the version property of every module in the reactor to prepare them for the next release. In case you are using GitHub Actions, consider using a timestamp.

+]]>
Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/2020-06-11T00:00:00+00:002023-01-06T15:27:21+01:00In case you are into calver or have another reason to create a timestamp with GitHub Actions, do the following:

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Create release version
+        id: <ID>
+        run: echo "::set-output name=<NAME>::$(date +'%Y.%m.%d-%H%M%S')"
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <ID>: The unique ID of the timestamp step.
  • +
  • <NAME>: The name of the created timestamp.
  • +
+

The special syntax ::set-output name=<NAME>:: declares that the output of the command (echo) should be saved in a variable called <NAME>. Together with the <ID> of the pipeline step, this value can be referenced with the expression ${{ steps.<ID>.outputs.<NAME> }} in the following steps of your pipeline.

+]]>
\ No newline at end of file diff --git a/categories/cd/index.html b/categories/cd/index.html new file mode 100644 index 000000000..5666d70ef --- /dev/null +++ b/categories/cd/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Cd

Category: Cd

\ No newline at end of file diff --git a/categories/cd/index.xml b/categories/cd/index.xml new file mode 100644 index 000000000..2ef7908c7 --- /dev/null +++ b/categories/cd/index.xml @@ -0,0 +1,18 @@ +Cd on Sebastian Hoßhttps://seb.xn--ho-hia.de/categories/cd/Recent content in Cd on Sebastian HoßHugoenFri, 06 Jan 2023 16:46:32 +0100Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/Mon, 06 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-cd-versioning/<p>To automatically version <a href="https://maven.apache.org/">Maven</a> projects, I like to use the <a href="https://www.mojohaus.org/versions-maven-plugin/">m-versions-p</a> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ mvn versions:set -DnewVersion<span class="o">=</span>my.new.version -DgenerateBackupPoms<span class="o">=</span><span class="nb">false</span> +</span></span></code></pre></div><p>This will update the <code>version</code> property of every module in the reactor to prepare them for the next release. In case you are using <a href="https://github.com/features/actions">GitHub Actions</a>, consider using a <a href="../github-actions-create-timestamp">timestamp</a>.</p>Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/Thu, 11 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/<p>In case you are into <a href="https://calver.org/">calver</a> or have another reason to create a timestamp with <a href="https://github.com/features/actions">GitHub Actions</a>, do the following:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create release version</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;ID&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=&lt;NAME&gt;::$(date +&#39;%Y.%m.%d-%H%M%S&#39;)&#34;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;ID&gt;</code>: The unique ID of the timestamp step.</li> +<li><code>&lt;NAME&gt;</code>: The name of the created timestamp.</li> +</ul> +<p>The special syntax <code>::set-output name=&lt;NAME&gt;::</code> declares that the output of the command (<code>echo</code>) should be saved in a variable called <code>&lt;NAME&gt;</code>. Together with the <code>&lt;ID&gt;</code> of the pipeline step, this value can be referenced with the expression <code>${{ steps.&lt;ID&gt;.outputs.&lt;NAME&gt; }}</code> in the following steps of your pipeline.</p> \ No newline at end of file diff --git a/categories/configuration/atom.xml b/categories/configuration/atom.xml new file mode 100644 index 000000000..2b2667ae9 --- /dev/null +++ b/categories/configuration/atom.xml @@ -0,0 +1,35 @@ +HugoConfiguration on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/categories/configuration/GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/2020-07-13T00:00:00+00:002023-01-06T17:32:06+01:00Git at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. Pro Git rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.

+

While the central server approach is easy to use, it might not work in all scenarios:

+
    +
  1. An enterprise wants to share internal code as part of an open source project. All development is happening internally, and the public mirror gets an occasional update once in a while.
  2. +
  3. To protect against outages of the central server, mirrors should be created and be kept up-to-date.
  4. +
+

In case of the first scenario, tools like copybara, repoSpanner, or distributed-Git-forks offer a wide range of features to cover most details.

+

The second scenario can be solved manually with tools like gitomatic or automatically with GitLab’s mirror feature quite easy. GitLab allows to create a single pull-mirror and multiple push-mirrors. Therefore, it can be used to pull from your central server and push into all mirrors.

+

NOTE: This feature was previously available in the free tier but has now moved to GitLab Ultimate.

+
               +----------------+               
+               |     GitHub     |               
+               +----------------+               
+                        ^                       
+                        |                       
+                        |                       
+               +----------------+               
+         +-----|     GitLab     |------+        
+         |     +----------------+      |        
+         |                             |        
+         |                             |        
+         v                             v        
++----------------+            +----------------+
+|    Codeberg    |            |    BitBucket   |
++----------------+            +----------------+
+

To create such a setup, follow these steps:

+
    +
  1. Go to Settings > Repository and expand Mirroring repositories +Code Flow
  2. +
  3. Enter the URL of your central Git repository as the pull source, for example https://github.com/metio/ilo.git +Code Flow
  4. +
  5. Enter one push target for each mirror. Since pushing usually requires authentication, verify that the URL of the mirror contains a username, for example https://YOUR_USER@codeberg.org/metio.wtf/ilo.git. Add an access token for each mirror and select password as authentication method. +Code Flow
  6. +
+

In case you prefer SSH keys over HTTP access tokens, just select SSH public key as authentication method and verify that your key is both saved in GitLab and all mirrors.

+]]>
\ No newline at end of file diff --git a/categories/configuration/index.html b/categories/configuration/index.html new file mode 100644 index 000000000..3704415f0 --- /dev/null +++ b/categories/configuration/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Configuration

Category: Configuration

\ No newline at end of file diff --git a/categories/configuration/index.xml b/categories/configuration/index.xml new file mode 100644 index 000000000..02b8fcb98 --- /dev/null +++ b/categories/configuration/index.xml @@ -0,0 +1,2 @@ +Configuration on Sebastian Hoßhttps://seb.xn--ho-hia.de/categories/configuration/Recent content in Configuration on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/gitlab-distributor/<p><a href="https://git-scm.com/">Git</a> at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. <a href="https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows">Pro Git</a> rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.</p> +<p>While the central server approach is easy to use, it might not work in all scenarios:</p> \ No newline at end of file diff --git a/categories/decentralized/atom.xml b/categories/decentralized/atom.xml new file mode 100644 index 000000000..7c6fcb45a --- /dev/null +++ b/categories/decentralized/atom.xml @@ -0,0 +1,27 @@ +HugoDecentralized on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/categories/decentralized/Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/2021-07-12T00:00:00+00:002023-01-06T17:32:06+01:00In case you want to have push-only mirrors for your Git repository, consider adding a special mirror remote like this:

+
$ git remote add mirrors DISABLED
+$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git
+$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git
+$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git
+

The above will create a new remote called mirrors which has no fetch URL and therefore can only be pushed:

+
$ git remote -v
+mirrors DISABLED (fetch)
+mirrors git@codeberg.org:org/repo.git (push)
+mirrors git@gitlab.com:org/repo.git (push)
+mirrors git@bitbucket.org:org/repo.git (push)
+

Calling git push mirrors main:main will push the local main branch into all defined mirrors.

+ + +]]>
Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/2021-06-28T00:00:00+00:002023-01-06T16:46:32+01:00In case you want to make use of the decentralized nature of Git, consider using multiple push targets like this:

+
$ git remote set-url origin --push --add git@example.com/project.git
+$ git remote set-url origin --push --add git@another.com/project.git
+

Note that the first call to set-url will overwrite an existing remote creating with git clone. Any additional call will actually recognize the --add option and add the new target to an existing remote.

+ + +]]>
\ No newline at end of file diff --git a/categories/decentralized/index.html b/categories/decentralized/index.html new file mode 100644 index 000000000..131ec358e --- /dev/null +++ b/categories/decentralized/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Decentralized

Category: Decentralized

\ No newline at end of file diff --git a/categories/decentralized/index.xml b/categories/decentralized/index.xml new file mode 100644 index 000000000..7b63d5f8b --- /dev/null +++ b/categories/decentralized/index.xml @@ -0,0 +1,20 @@ +Decentralized on Sebastian Hoßhttps://seb.xn--ho-hia.de/categories/decentralized/Recent content in Decentralized on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/Mon, 12 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-push-only-mirror/<p>In case you want to have push-only mirrors for your <a href="https://git-scm.com/">Git</a> repository, consider adding a special mirror remote like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote add mirrors DISABLED +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git +</span></span></code></pre></div><p>The above will create a new remote called <code>mirrors</code> which has no <code>fetch</code> URL and therefore can only be pushed:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote -v +</span></span><span class="line"><span class="cl">mirrors DISABLED <span class="o">(</span>fetch<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@codeberg.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@gitlab.com:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@bitbucket.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span></code></pre></div><p>Calling <code>git push mirrors main:main</code> will push the local <code>main</code> branch into all defined mirrors.</p>Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/Mon, 28 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-mirror/<p>In case you want to make use of the decentralized nature of <a href="https://git-scm.com/">Git</a>, consider using multiple push targets like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote set-url origin --push --add git@example.com/project.git +</span></span><span class="line"><span class="cl">$ git remote set-url origin --push --add git@another.com/project.git +</span></span></code></pre></div><p>Note that the first call to <code>set-url</code> will overwrite an existing remote creating with <code>git clone</code>. Any additional call will actually recognize the <code>--add</code> option and add the new target to an existing remote.</p> +<h2 id="links">Links</h2> +<ul> +<li><a href="../git-push-only-mirror">push only mirrors</a></li> +<li><a href="../gitlab-distributor">gitlab-distributor</a></li> +</ul> \ No newline at end of file diff --git a/categories/devops/atom.xml b/categories/devops/atom.xml new file mode 100644 index 000000000..2ee53eb68 --- /dev/null +++ b/categories/devops/atom.xml @@ -0,0 +1,253 @@ +HugoDevops on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/categories/devops/Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/2021-05-31T00:00:00+00:002023-01-06T17:32:06+01:00Maven projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.

+
<properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+</properties>
+
+ +]]>
Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/2021-05-17T00:00:00+00:002023-01-06T16:22:24+01:00To create reproducible builds with Maven projects, it’s enough to specify the project.build.outputTimestamp property like this:

+
<properties>
+    <project.build.outputTimestamp>2020</project.build.outputTimestamp>
+</properties>
+
+ +]]>
Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/2021-05-03T00:00:00+00:002023-01-06T17:32:06+01:00To analyze Maven projects with SonarCloud using GitHub Actions, first create the following settings.xml file:

+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
+
+    <pluginGroups>
+        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
+    </pluginGroups>
+
+    <activeProfiles>
+        <activeProfile>sonar</activeProfile>
+    </activeProfiles>
+
+    <profiles>
+        <profile>
+            <id>sonar</id>
+            <properties>
+                <sonar.host.url>https://sonarcloud.io</sonar.host.url>
+                <sonar.organization>YOUR_ORG</sonar.organization>
+                <sonar.projectKey>YOUR_PROJECT</sonar.projectKey>
+                <sonar.login>${env.SONAR_TOKEN}</sonar.login>
+            </properties>
+        </profile>
+    </profiles>
+</settings>
+

Finally, add a step to your workflow:

+
- name: Verify Project
+  run: mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar
+  env:
+    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+
+ +]]>
Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/2021-02-22T00:00:00+00:002023-01-06T16:40:24+01:00To delay the execution of an GitHub Action, use a mixture of the on: schedule: ... configuration, and a conditional build step.

+
name: <PIPELINE>
+on:
+  schedule:
+    - cron: '<CRON>'
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Count commits in last week
+        id: commits
+        run: echo "::set-output name=count::$(git rev-list --count HEAD --since='<DATE>')"
+      - name: Build project
+        if: steps.commits.outputs.count > 0
+        run: build-project
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <CRON>: cron expression - use https://crontab.guru/.
  • +
  • <DATE>: Git date expression that matches <CRON>.
  • +
+]]>
Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/2020-11-16T00:00:00+00:002023-01-06T15:27:21+01:00The rzr/fediverse-action action allows to send a toot in your GitHub Action.

+
name: <NAME>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Publish Toot
+        uses: rzr/fediverse-action@master
+        with:
+          access-token: ${{ secrets.MASTODON_TOKEN }}
+          message: <MESSAGE>
+          host: ${{ secrets.MASTODON_SERVER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <MESSAGE>: Message for the toot.
  • +
+]]>
Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/2020-11-02T00:00:00+00:002023-01-06T15:27:21+01:00The dawidd6/action-send-mail action allows to send an email in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Send mail
+        uses: dawidd6/action-send-mail@v3
+        with:
+          server_address: ${{ secrets.MAIL_SERVER }}
+          server_port: ${{ secrets.MAIL_PORT }}
+          username: ${{ secrets.MAIL_USERNAME }}
+          password: ${{ secrets.MAIL_PASSWORD }}
+          subject: <SUBJECT>
+          body: <BODY>
+          to: ${{ secrets.MAIL_RECIPIENT }}
+          from: ${{ secrets.MAIL_SENDER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <SUBJECT>: Subject for the email.
  • +
  • <BODY>: Body for the email.
  • +
+

Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define MAIL_RECIPIENT for each project.

+]]>
Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/2020-10-19T00:00:00+00:002023-01-06T15:27:21+01:00The actions/upload-release-asset action allows to upload a release artifact in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Upload Release Asset
+        id: upload_release_asset
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./some/path/to/file.zip
+          asset_name: public-name-for-file.zip
+          asset_content_type: application/zip
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/2020-10-05T00:00:00+00:002023-01-06T17:32:06+01:00The actions/create-release action allows to create a new GitHub releases in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+     - name: Create Release
+       uses: actions/create-release@v1
+       env:
+         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+       with:
+         tag_name: <TAG>
+         release_name: <RELEASE>
+         draft: false
+         prerelease: false
+         body: |
+           Your release text here
+
+           Some code block:
+           ```yaml
+           yaml:
+             inside:
+               of:
+                 another: yaml
+           ```           
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <TAG>: The Git tag to create.
  • +
  • <RELEASE>: The release name to use.
  • +
+]]>
Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/2020-09-21T00:00:00+00:002023-01-06T15:27:21+01:00The peaceiris/actions-gh-pages action allows to publish a Hugo site in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Deploy Website
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: <PUBLISH_DIR>
+          force_orphan: true
+          cname: <CNAME>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <PUBLISH_DIR>: The file system location of the built site.
  • +
  • <CNAME>: The CNAME of your custom domain.
  • +
+]]>
Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/2020-09-07T00:00:00+00:002023-01-06T15:27:21+01:00The actions/cache action allows to cache artifacts in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Cache Maven artifacts
+        uses: actions/cache@v1
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-            
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/2020-08-24T00:00:00+00:002023-01-06T15:27:21+01:00The actions-hugo action allows to use a specific Hugo version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Setup hugo
+        uses: peaceiris/actions-hugo@v2
+        with:
+          hugo-version: <HUGO_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <HUGO_VERSION>: The released versions or use latest to always use the latest version of Hugo.
  • +
+]]>
Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/2020-08-10T00:00:00+00:002023-01-06T15:27:21+01:00The setup-java action allows to use a specific Java version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Set up JDK <JDK_VERSION>
+        uses: actions/setup-java@v1
+        with:
+          java-version: <JDK_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <JDK_VERSION>: The required Java version for your project.
  • +
+]]>
Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/2020-06-11T00:00:00+00:002023-01-06T15:27:21+01:00In case you are into calver or have another reason to create a timestamp with GitHub Actions, do the following:

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Create release version
+        id: <ID>
+        run: echo "::set-output name=<NAME>::$(date +'%Y.%m.%d-%H%M%S')"
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <ID>: The unique ID of the timestamp step.
  • +
  • <NAME>: The name of the created timestamp.
  • +
+

The special syntax ::set-output name=<NAME>:: declares that the output of the command (echo) should be saved in a variable called <NAME>. Together with the <ID> of the pipeline step, this value can be referenced with the expression ${{ steps.<ID>.outputs.<NAME> }} in the following steps of your pipeline.

+]]>
\ No newline at end of file diff --git a/categories/devops/index.html b/categories/devops/index.html new file mode 100644 index 000000000..0458bfdb7 --- /dev/null +++ b/categories/devops/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Devops

Category: Devops

\ No newline at end of file diff --git a/categories/devops/index.xml b/categories/devops/index.xml new file mode 100644 index 000000000..838ec93ce --- /dev/null +++ b/categories/devops/index.xml @@ -0,0 +1,240 @@ +Devops on Sebastian Hoßhttps://seb.xn--ho-hia.de/categories/devops/Recent content in Devops on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-encoding/<p><a href="https://maven.apache.org/">Maven</a> projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.reporting.outputEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.reporting.outputEncoding&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +</ul>Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/Mon, 17 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-reproducible/<p>To create <a href="https://reproducible-builds.org/">reproducible builds</a> with <a href="https://maven.apache.org/">Maven</a> projects, it&rsquo;s enough to specify the <code>project.build.outputTimestamp</code> property like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.outputTimestamp&gt;</span>2020<span class="nt">&lt;/project.build.outputTimestamp&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +<li><a href="https://maven.apache.org/guides/mini/guide-reproducible-builds.html">https://maven.apache.org/guides/mini/guide-reproducible-builds.html</a></li> +<li><a href="https://github.com/rodiontsev/maven-build-info-plugin">https://github.com/rodiontsev/maven-build-info-plugin</a></li> +<li><a href="https://github.com/phax/ph-buildinfo-maven-plugin">https://github.com/phax/ph-buildinfo-maven-plugin</a></li> +<li><a href="https://github.com/Zlika/reproducible-build-maven-plugin">https://github.com/Zlika/reproducible-build-maven-plugin</a></li> +</ul>Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/Mon, 03 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/<p>To analyze <a href="https://maven.apache.org/">Maven</a> projects with <a href="https://sonarcloud.io">SonarCloud</a> using <a href="https://github.com/features/actions">GitHub Actions</a>, first create the following <code>settings.xml</code> file:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings</span> <span class="na">xmlns=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xmlns:xsi=</span><span class="s">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xsi:schemaLocation=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0 +</span></span></span><span class="line"><span class="cl"><span class="s"> http://maven.apache.org/xsd/settings-1.0.0.xsd&#34;</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroup&gt;</span>org.sonarsource.scanner.maven<span class="nt">&lt;/pluginGroup&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfile&gt;</span>sonar<span class="nt">&lt;/activeProfile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>sonar<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.host.url&gt;</span>https://sonarcloud.io<span class="nt">&lt;/sonar.host.url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.organization&gt;</span>YOUR_ORG<span class="nt">&lt;/sonar.organization&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.projectKey&gt;</span>YOUR_PROJECT<span class="nt">&lt;/sonar.projectKey&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.login&gt;</span>${env.SONAR_TOKEN}<span class="nt">&lt;/sonar.login&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Finally, add a step to your workflow:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Verify Project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">SONAR_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.SONAR_TOKEN }}</span><span class="w"> +</span></span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/settings.html">https://maven.apache.org/settings.html</a></li> +<li><a href="https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/">https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/</a></li> +</ul>Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/Mon, 22 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-schedule-build/<p>To delay the execution of an <a href="https://github.com/features/actions">GitHub Action</a>, use a mixture of the <code>on: schedule: ...</code> configuration, and a <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif">conditional build step</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">schedule</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&lt;CRON&gt;&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Count commits in last week</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">commits</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=count::$(git rev-list --count HEAD --since=&#39;&lt;DATE&gt;&#39;)&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">steps.commits.outputs.count &gt; 0</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">build-project</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;CRON&gt;</code>: cron expression - use <a href="https://crontab.guru/">https://crontab.guru/</a>.</li> +<li><code>&lt;DATE&gt;</code>: Git date expression that matches <code>&lt;CRON&gt;</code>.</li> +</ul>Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-toot/<p>The <a href="https://github.com/rzr/fediverse-action">rzr/fediverse-action</a> action allows to send a <a href="https://joinmastodon.org/">toot</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;NAME&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish Toot</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">rzr/fediverse-action@master</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">access-token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;MESSAGE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_SERVER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;MESSAGE&gt;</code>: Message for the toot.</li> +</ul>Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/Mon, 02 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-email/<p>The <a href="https://github.com/dawidd6/action-send-mail">dawidd6/action-send-mail</a> action allows to send an email in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Send mail</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">dawidd6/action-send-mail@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_address</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SERVER }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_port</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PORT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_USERNAME }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PASSWORD }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">subject</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;SUBJECT&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;BODY&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_RECIPIENT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SENDER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;SUBJECT&gt;</code>: Subject for the email.</li> +<li><code>&lt;BODY&gt;</code>: Body for the email.</li> +</ul> +<p>Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define <code>MAIL_RECIPIENT</code> for each project.</p>Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/<p>The <a href="https://github.com/actions/upload-release-asset">actions/upload-release-asset</a> action allows to upload a <a href="https://developer.github.com/v3/repos/releases/#upload-a-release-asset">release artifact</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload Release Asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">upload_release_asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-release-asset@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">upload_url</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.create_release.outputs.upload_url }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_path</span><span class="p">:</span><span class="w"> </span><span class="l">./some/path/to/file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_name</span><span class="p">:</span><span class="w"> </span><span class="l">public-name-for-file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_content_type</span><span class="p">:</span><span class="w"> </span><span class="l">application/zip</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/Mon, 05 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-release/<p>The <a href="https://github.com/actions/create-release">actions/create-release</a> action allows to create a new <a href="https://help.github.com/en/github/administering-a-repository/about-releases">GitHub releases</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create Release</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/create-release@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">tag_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;TAG&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">release_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RELEASE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">draft</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">prerelease</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Your release text here +</span></span></span><span class="line"><span class="cl"><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Some code block: +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> yaml: +</span></span></span><span class="line"><span class="cl"><span class="sd"> inside: +</span></span></span><span class="line"><span class="cl"><span class="sd"> of: +</span></span></span><span class="line"><span class="cl"><span class="sd"> another: yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;TAG&gt;</code>: The Git tag to create.</li> +<li><code>&lt;RELEASE&gt;</code>: The release name to use.</li> +</ul>Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/Mon, 21 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/<p>The <a href="https://github.com/peaceiris/actions-gh-pages">peaceiris/actions-gh-pages</a> action allows to publish a <a href="https://gohugo.io/">Hugo</a> site in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Website</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-gh-pages@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">github_token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">publish_dir</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PUBLISH_DIR&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">force_orphan</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">cname</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;CNAME&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;PUBLISH_DIR&gt;</code>: The file system location of the built site.</li> +<li><code>&lt;CNAME&gt;</code>: The <code>CNAME</code> of your custom domain.</li> +</ul>Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/Mon, 07 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions/cache</a> action allows to cache artifacts in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Maven artifacts</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">~/.m2/repository</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-maven-${{ hashFiles(&#39;**/pom.xml&#39;) }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> ${{ runner.os }}-maven-</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/Mon, 24 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions-hugo</a> action allows to use a specific <a href="https://gohugo.io/">Hugo</a> version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup hugo</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-hugo@v2</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hugo-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;HUGO_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;HUGO_VERSION&gt;</code>: The <a href="https://github.com/gohugoio/hugo/releases">released versions</a> or use <code>latest</code> to always use the latest version of Hugo.</li> +</ul>Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/Mon, 10 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/<p>The <a href="https://github.com/actions/setup-java">setup-java</a> action allows to use a specific Java version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up JDK &lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-java@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">java-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;JDK_VERSION&gt;</code>: The required Java version for your project.</li> +</ul>Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/Thu, 11 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/<p>In case you are into <a href="https://calver.org/">calver</a> or have another reason to create a timestamp with <a href="https://github.com/features/actions">GitHub Actions</a>, do the following:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create release version</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;ID&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=&lt;NAME&gt;::$(date +&#39;%Y.%m.%d-%H%M%S&#39;)&#34;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;ID&gt;</code>: The unique ID of the timestamp step.</li> +<li><code>&lt;NAME&gt;</code>: The name of the created timestamp.</li> +</ul> +<p>The special syntax <code>::set-output name=&lt;NAME&gt;::</code> declares that the output of the command (<code>echo</code>) should be saved in a variable called <code>&lt;NAME&gt;</code>. Together with the <code>&lt;ID&gt;</code> of the pipeline step, this value can be referenced with the expression <code>${{ steps.&lt;ID&gt;.outputs.&lt;NAME&gt; }}</code> in the following steps of your pipeline.</p> \ No newline at end of file diff --git a/categories/frontend/atom.xml b/categories/frontend/atom.xml new file mode 100644 index 000000000..01a608df1 --- /dev/null +++ b/categories/frontend/atom.xml @@ -0,0 +1,58 @@ +HugoFrontend on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/categories/frontend/Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/2022-01-15T00:00:00+00:002023-01-06T16:22:24+01:00One feature that often surprises people while teaching them React is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.

+

In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.

+
const RendersNothing = () => <></>
+

Now consider the following example, that adds some if-then-else logic to the same component:

+
const MightRenderSomething = () => {
+  if (someCondition) {
+    return <span>hello world!</span>
+  }
+  return <></>
+}
+

This component encapsulates the if-then-else logic of conditionally rendering a hello world message. Instead of cluttering your entire app with the same logic, you can now simply re-use that same component that contains this if condition. To see the full power of this technique, consider the following example. At first, we are going to define a hook that reads the current window width, then define components that conditionally render based on the current window width, and finally use those components in an example application.

+
const useWindowWidth = () => {
+  const [width, setWidth] = React.useState(0)
+
+  React.useEffect(() => {
+    const handleResize = () => {
+      setWidth(window.innerWidth)
+    }
+    window.addEventListener("resize", handleResize)
+    return () => {
+      window.removeEventListener("resize", handleResize)
+    }
+  }, [])
+
+  return width
+}
+

The following components use that hook to implement UI breakpoints for small (mobile) and large (desktop) screens. Note that the value 768 is just an example - replace it with whatever your design system tells you to.

+
const ForMobileDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+  
+  if (windowWidth < 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+
+const ForDesktopDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+
+  if (windowWidth >= 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+

Both of these components simply render nothing when the window width does not have an appropriate size. If the window width does have the right size, they render their children. We can use those components in our application like this:

+
const SomeActualComponent = () => (
+    <div>
+      <h1>common headline</h1>
+      <ForMobileDevicesOnly>
+        <span>only visible on mobile devices</span>
+      </ForMobileDevicesOnly>
+      <ForDesktopDevicesOnly>
+        <span>only visible on desktop devices</span>
+      </ForDesktopDevicesOnly>
+    </div>
+)
+

The above code snippet declares that some part of the UI can only be seen by mobile users, while others can only be seen by desktop users. Parts of the UI that are shared amongst all users are not wrapped by any of the components defined above.

+]]>
\ No newline at end of file diff --git a/categories/frontend/index.html b/categories/frontend/index.html new file mode 100644 index 000000000..242785424 --- /dev/null +++ b/categories/frontend/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Frontend

Category: Frontend

\ No newline at end of file diff --git a/categories/frontend/index.xml b/categories/frontend/index.xml new file mode 100644 index 000000000..33219f11d --- /dev/null +++ b/categories/frontend/index.xml @@ -0,0 +1,2 @@ +Frontend on Sebastian Hoßhttps://seb.xn--ho-hia.de/categories/frontend/Recent content in Frontend on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/Sat, 15 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/<p>One feature that often surprises people while teaching them <a href="https://reactjs.org/">React</a> is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.</p> +<p>In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.</p> \ No newline at end of file diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 000000000..bedd48ff5 --- /dev/null +++ b/categories/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Categories

Categories

\ No newline at end of file diff --git a/categories/index.xml b/categories/index.xml new file mode 100644 index 000000000..9ae97bc9c --- /dev/null +++ b/categories/index.xml @@ -0,0 +1 @@ +Categories on Sebastian Hoßhttps://seb.xn--ho-hia.de/categories/Recent content in Categories on Sebastian HoßHugoenMon, 09 Jan 2023 07:05:35 +0100Snippethttps://seb.xn--ho-hia.de/categories/snippet/Mon, 09 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/categories/snippet/Frontendhttps://seb.xn--ho-hia.de/categories/frontend/Sat, 15 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/categories/frontend/Networkhttps://seb.xn--ho-hia.de/categories/network/Sat, 08 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/categories/network/Cdhttps://seb.xn--ho-hia.de/categories/cd/Mon, 06 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/categories/cd/Decentralizedhttps://seb.xn--ho-hia.de/categories/decentralized/Mon, 12 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/categories/decentralized/Devopshttps://seb.xn--ho-hia.de/categories/devops/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/categories/devops/Websitehttps://seb.xn--ho-hia.de/categories/website/Mon, 25 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/categories/website/Configurationhttps://seb.xn--ho-hia.de/categories/configuration/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/categories/configuration/ \ No newline at end of file diff --git a/categories/network/atom.xml b/categories/network/atom.xml new file mode 100644 index 000000000..7c269b035 --- /dev/null +++ b/categories/network/atom.xml @@ -0,0 +1,7 @@ +HugoNetwork on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/categories/network/Proper hostnames in your local networkhttps://seb.xn--ho-hia.de/posts/home-network-hostnames/2022-01-08T00:00:00+00:002023-01-06T16:22:24+01:00Thanks to RFC 8375, we now have a proper domain to use for all our local devices. Simply move everything underneath .home.arpa to join the fun. In case you have hostnamectl available on your system run the following command to change the hostname of a device:

+
# set hostname
+$ hostnamectl hostname some-device.home.arpa
+
+# check hostname
+$ hostnamectl status
+
]]>
\ No newline at end of file diff --git a/categories/network/index.html b/categories/network/index.html new file mode 100644 index 000000000..e0cd019ce --- /dev/null +++ b/categories/network/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Network

Category: Network

\ No newline at end of file diff --git a/categories/network/index.xml b/categories/network/index.xml new file mode 100644 index 000000000..76cb5c7a1 --- /dev/null +++ b/categories/network/index.xml @@ -0,0 +1,7 @@ +Network on Sebastian Hoßhttps://seb.xn--ho-hia.de/categories/network/Recent content in Network on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Proper hostnames in your local networkhttps://seb.xn--ho-hia.de/posts/home-network-hostnames/Sat, 08 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/home-network-hostnames/<p>Thanks to <a href="https://www.rfc-editor.org/rfc/rfc8375.html">RFC 8375</a>, we now have a proper domain to use for all our local devices. Simply move everything underneath <code>.home.arpa</code> to join the fun. In case you have <code>hostnamectl</code> available on your system run the following command to change the hostname of a device:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> <span class="nb">set</span> hostname +</span></span><span class="line"><span class="cl"><span class="gp">$</span> hostnamectl hostname some-device.home.arpa +</span></span><span class="line"><span class="cl"><span class="err"> +</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="gp">#</span> check hostname +</span></span><span class="line"><span class="cl"><span class="gp">$</span> hostnamectl status +</span></span></code></pre></div> \ No newline at end of file diff --git a/categories/snippet/atom.xml b/categories/snippet/atom.xml new file mode 100644 index 000000000..bb84f2f13 --- /dev/null +++ b/categories/snippet/atom.xml @@ -0,0 +1,1290 @@ +HugoSnippet on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/categories/snippet/jspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/2023-01-09T00:00:00+00:002023-01-09T07:05:35+01:00Every Java developer has probably encountered a NullPointerException at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:

+
String someName;         // value is 'null'
+
+someName.toUpperCase(); // throws NullPointerException
+

Modern IDEs have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is null and therefore a potential for a NullPointerException is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be null. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.

+

jspecify is the latest approach that tries to establish a standard. It has gained wide community support and recently celebrated their first public release (0.3.0).

+

The following snippet shows the dependency declaration for Maven projects:

+
<dependencies>
+    <dependency>
+        <groupId>org.jspecify</groupId>
+        <artifactId>jspecify</artifactId>
+        <version>0.3.0</version>
+    </dependency>
+</dependencies>
+

In case you want to declare that nothing in your module can ever be null, place the @NullMarked on your module-info.java like this:

+
@org.jspecify.annotations.NullMarked
+module your.module.here {
+
+    requires org.jspecify;
+
+    // ...
+
+}
+

The tooling support is not quiet clear yet, however if you are developing a library there is no harm in adding these annotations now and let your users enjoy their null-free life once tools have caught up.

+]]>
Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/2023-01-08T00:00:00+00:002023-01-08T09:25:44+01:00age is another tool supported by chezmoi to keep data private. Compared to gpg it is much simpler by focusing on the encryption parts only.

+

Add the following snippet to your .chezmoi.toml to configure chezmoi to use age:

+
encryption = "age"
+[age]
+  identity = "path/to/age/private-key"
+  recipient = "age...public...key..."
+

Adding files to your chezmoi source directory remains the same as compared to using gpg - just call chezmoi add --encrypt path/to/file.

+]]>
Multiple Git Configurationshttps://seb.xn--ho-hia.de/posts/multiple-git-configs/2023-01-05T00:00:00+00:002023-01-06T17:32:06+01:00To split yet re-use as much configuration for Git as possible, you can create one root configuration that looks similar to this:

+
[user]
+  name = Your Name Here
+
+[includeIf "gitdir:~/git/personal/"]
+  path = ~/.config/git/personal
+[includeIf "gitdir:~/git/work/"]
+  path = ~/.config/git/work
+

The includeIf directive supports multiple keywords. In my case, work and personal projects have a different root directory, therefore I can filter based on the location using gitdir. The personal Git configuration simply looks like this:

+
[user]
+  email = personal.email@example.com
+

and the work related configuration like this using a different email address:

+
[user]
+  email = first.last@work.example
+

Additional settings that are different for personal/work accounts can be split the same way, for example to use a different signing key for work.

+]]>
passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/2022-12-27T00:00:00+00:002023-01-06T16:40:24+01:00To fuzzy search through passwords managed with passage, I’ve written the following script that is inspired by the upstream version which is using fzf.

+
fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

This version requires fd, skim, xargs, and passage itself of course. The detailed breakdown on how it works is as follows:

+
    +
  1. Use fd to find all files within ${PASSAGE_DIR} that end in .age. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.
  2. +
  3. Using both --base-directory and --exec echo '{.}' ensures that passwords are returned in such form that they can be passed back into passage again. The placeholder '{.}' is a feature provided by fd which strips the file extension from each returned value.
  4. +
  5. All passwords are then passed into sk to allow to fuzzy search across them all. Setting --no-multi ensures that only a single password can be selected.
  6. +
  7. Finally, xargs calls passage and replaces the curly braces with the selected password. Thanks to --clip=1, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.
  8. +
+

To call that script, I’ve saved it as passage-fuzzy-search.sh in my .local/bin folder and added some checks into it to verify that every required software is actually installed.

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This shell script presents passwords saved with passage through skim
+#
+# Call it like this:
+#   passage-fuzzy-search.sh
+#
+# Required software that isn't in GNU coreutils:
+#   - 'passage' to read passwords
+#   - 'fd' to find passwords
+#   - 'sk' to filter passwords
+###############################################################################
+
+if ! (( ${+commands[passage]} )); then
+    echo 'passage not installed. Please install passage.'
+    exit
+fi
+if ! (( ${+commands[sk]} )); then
+    echo 'sk not installed. Please install skim.'
+    exit
+fi
+if ! (( ${+commands[fd]} )); then
+    echo 'fd not installed. Please install fd-find.'
+    exit
+fi
+
+fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

Since typing passage-fuzzy-search.sh is way too long, I have added an alias like this:

+
alias pp='passage-fuzzy-search.sh'
+
]]>
chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/2022-12-26T00:00:00+00:002023-01-06T17:32:06+01:00To automatically synchronize dotfiles across my computers, I’ve written the following systemd unit:

+
[Unit]
+Description=Update chezmoi managed dotfiles
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/chezmoi update --no-tty --force
+RemainAfterExit=false
+
+[Install]
+WantedBy=default.target
+

This unit pulls changes from upstream first and then applies the changes to the current computer after I’m logged in and a network connection is available. The --no-tty flag is required because there is no tty when systemd executes chezmoi. Likewise, the --force flag ensures that no interactive prompt will be displayed which we cannot answer since systemd is executing this unit without us being involved.

+]]>
chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/2022-12-12T00:00:00+00:002023-01-06T16:40:24+01:00Many CLI applications offer initialization scripts to integrate into a shell, for example starship init zsh or zoxide init zsh. The documentation of these tools usually tell you to put something like eval "$(starship init zsh)" into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the init command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.

+

chezmoi provides a template function called output which replaces itself with the output of the command you specified. You can use that function this to integrate various tools into your shell as the following example shows while using zsh:

+
    +
  1. Create a directory that holds all init scripts for every tool you want to use. +
     $ mkdir --parents "${ZDOTDIR}"/tools.d
    +
  2. +
  3. Let your shell load all available scripts in that directory. This snippet should be part of your .zshrc file: +
    for init_script in "${ZDOTDIR}"/tools.d/*.sh; do
    +  source "${init_script}"
    +done
    +
  4. +
  5. Create chezmoi .tmpl files for each tool and place them in the chezmoi source directory that matches the directory you created in step 1: +
    {{ output "starship" "init" "zsh" "--print-full-init" }}
    +
  6. +
  7. Call chezmoi apply to generate the init scripts.
  8. +
+

The only downside here is that you have to re-run chezmoi apply after updating one of the tools because they change their init scripts sometimes. That problem can be solved with chezmoi auto-updates.

+]]>
awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/2022-02-14T00:00:00+00:002023-01-06T16:40:24+01:00To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the AWS_PROFILE environment variable which is used by many tools that interact with the AWS API, like awscli or terraform. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses aws sso login to perform an MFA login into AWS. The AWS profiles must be set up in such a way that aws configure list-profiles can detect them, which is typically done by adding them in ${AWS_CONFIG_FILE:-$HOME/.aws/config}.

+
#!/usr/bin/env sh
+
+###############################################################################
+# This script performs an AWS SSO login for the user-selected AWS profile
+# and sets the AWS_PROFILE environment variable afterwards. To use
+# this, create an alias that sources this script like this:
+#
+#     alias awsenv='source path/to/this/script.sh'
+#
+# Required software that is not in GNU coreutils:
+#   - 'aws' to list profiles & get current caller identity
+#   - 'fzf' to list all available AWS profiles
+###############################################################################
+
+# prompt user to select one AWS profile
+profile=$(aws configure list-profiles | \
+  fzf --cycle --layout=reverse --tiebreak=index)
+
+# user can cancel switching profiles by pressing ESC
+if [ -n "${profile}" ]; then
+  # check is access token exists and is valid for selected profile
+  if ! aws --profile "${profile}" sts get-caller-identity >/dev/null 2>&1; then
+    # perform login into profile in case access token is invalid
+    if ! aws sso login --profile "${profile}"; then
+      # short circuit in case login failed
+      return
+    fi
+  fi
+  # AWS_PROFILE is used by many AWS-related tools
+  echo "Setting AWS_PROFILE to [${profile}]"
+  export AWS_PROFILE="${profile}"
+  # do not expose internal variables
+  unset profile
+fi
+
]]>
Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/2022-01-29T00:00:00+00:002023-01-06T17:32:06+01:00Clojure has several forms and macros to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.

+

Using gen-class

+

Clojure code can be compiled to standard JVM bytecode using gen-class.

+

Adding static modifiers

+

Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class com.example.Computation.

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :methods [#^{:static true} [incrementRange [int] java.util.List]]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [max]
+  (increment-range max))
+

The Java wrapper has to follow the standard rules for method names. Therefore increment-range has to be renamed to incrementRange (or some similar name without the “-” in it). The “-” prefix for the Java wrapper can be configured inside the :gen-class form and will be removed once gen-class runs. The usage from Java looks like this:

+
package com.example
+
+public class ClojureJavaInteropStatic {
+
+    public static void main(String[] args) {
+        List incrementedRange = Computation.incrementRange(10);
+    }
+
+}
+

Adding generics

+

The returned list in the above code is raw because the method definition doesn’t use generics. To solve this problem declare that the generated class :implements a certain interface that exposes the desired method definition(s). You won’t be able to declare your methods as static anymore, but get a generified method for all your Java needs.

+

The Java interface:

+
package com.example
+
+public interface RangeIncrementer {
+  List<Long> incrementRange(int max);
+}
+

The changed Clojure namespace:

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :implements [com.example.RangeIncrementer]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [this max]
+  (increment-range max))
+

Finally, the generified usage from Java:

+
package com.example
+
+public class ClojureJavaInteropGenerics {
+
+    public static void main(String[] args) {
+        RangeIncrementer incrementer = new Computation();
+        List<Long> incrementedRange = incrementer.incrementRange(10);
+    }
+
+}
+

Couple of notes for this as well: First the generated class still only returns the raw type (List instead of List<Integer>). So instead of using the class, use the interface for the variable declaration (RangeIncrementer incrementer = .. instead of Computation comp = ..). The interface will return the non-raw List. Second the function definition for -incrementRange is now slightly different. It needs an additional parameter (this) which exposes the current instance to the generated class/method.

+

Returning an array of something is also possible with the following construct "[Ljava.lang.Object;". Need a 2-dim array? Just use "[[Ljava.lang.Object;" (notice the extra [) and so on. However, be aware that the method return types have to match, for example you can’t specify a return type of array if your Clojure function does not return an array. In the example above the call to map returns LazySeq which itself is a java.util.List. Therefore, the method declaration is valid, and you won’t get any ClassCastException when calling incrementRange from Java.

+

Make your life easier with macros

+

Instead of defining every Clojure function which should be exported twice (the real function + the Java wrapper), it is possible to use a macro to do that extra work automatically.

+
(require '[clojure.string :as string)
+
+(defn camel-case [input]
+  (let [words (string/split input #"[\s_-]+")]
+    (string/join (cons (string/lower-case (first words)) (map string/capitalize (rest words))))))
+
+(defn java-name [clojure-name]
+  (symbol (str "-" (camel-case (str clojure-name)))))
+
+(defmacro defn* [name & declarations]
+  (let [java-name (java-name name)]
+    `(do (defn ~name ~declarations)
+       (defn ~java-name ~declarations))))
+

The macro defn* replaces defn and automatically creates a second function with a valid camel-cased Java method name. The macro is available as a small library at Maven Central. The macro won’t add the extra parameter mentioned above to Java wrapper, so it is only useful for declaring static methods.

+

Using the Clojure Runtime

+

Using gen-class imposes certain limitations on calling Clojure code from Java. One of those are functions which make use of Clojure parameter destructuring. To invoke those functions you have to use the Clojure runtime.

+
// The Clojure 'require' function from the 'clojure.core' namespace.
+Var require = RT.var("clojure.core", "require");
+
+// Your namespace
+Symbol namespace = Symbol.intern("DESIRED.NAMESPACE.HERE");
+
+// Your function
+Var function = RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION");
+
+// The required keyword for the above function
+Keyword keyword = Keyword.intern("REQUIRED-KEYWORD");
+
+// Require/Import your namespace
+require.invoke(namespace);
+
+// Invoke your function with the given keyword and its value
+Object result = function.invoke(keyword, VALUE);
+

The desired namespace has to be on the classpath for this to work. Alternatively it is possible to load an entire Clojure script, as shown in the following example:

+
RT.loadResourceScript("DESIRED/NAMESPACE/HERE.clj");
+RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION").invoke(PARAMETER);
+

On a big project it is properly wise to move Java->Clojure interop code into helper classes/methods. Look here for an example.

+]]>
Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/2022-01-15T00:00:00+00:002023-01-06T16:22:24+01:00One feature that often surprises people while teaching them React is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.

+

In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.

+
const RendersNothing = () => <></>
+

Now consider the following example, that adds some if-then-else logic to the same component:

+
const MightRenderSomething = () => {
+  if (someCondition) {
+    return <span>hello world!</span>
+  }
+  return <></>
+}
+

This component encapsulates the if-then-else logic of conditionally rendering a hello world message. Instead of cluttering your entire app with the same logic, you can now simply re-use that same component that contains this if condition. To see the full power of this technique, consider the following example. At first, we are going to define a hook that reads the current window width, then define components that conditionally render based on the current window width, and finally use those components in an example application.

+
const useWindowWidth = () => {
+  const [width, setWidth] = React.useState(0)
+
+  React.useEffect(() => {
+    const handleResize = () => {
+      setWidth(window.innerWidth)
+    }
+    window.addEventListener("resize", handleResize)
+    return () => {
+      window.removeEventListener("resize", handleResize)
+    }
+  }, [])
+
+  return width
+}
+

The following components use that hook to implement UI breakpoints for small (mobile) and large (desktop) screens. Note that the value 768 is just an example - replace it with whatever your design system tells you to.

+
const ForMobileDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+  
+  if (windowWidth < 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+
+const ForDesktopDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+
+  if (windowWidth >= 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+

Both of these components simply render nothing when the window width does not have an appropriate size. If the window width does have the right size, they render their children. We can use those components in our application like this:

+
const SomeActualComponent = () => (
+    <div>
+      <h1>common headline</h1>
+      <ForMobileDevicesOnly>
+        <span>only visible on mobile devices</span>
+      </ForMobileDevicesOnly>
+      <ForDesktopDevicesOnly>
+        <span>only visible on desktop devices</span>
+      </ForDesktopDevicesOnly>
+    </div>
+)
+

The above code snippet declares that some part of the UI can only be seen by mobile users, while others can only be seen by desktop users. Parts of the UI that are shared amongst all users are not wrapped by any of the components defined above.

+]]>
Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/2022-01-01T00:00:00+00:002023-01-06T16:22:24+01:00Both Vim and Neovim have a built-in plugin mechanism that loads plugins from ~/.vim/pack/*/{start,opt}/* (Vim) or ~/.local/share/nvim/site/pack/*/{start,opt}/* (Neovim). All you have to do to install new plugins, is to git clone their repository into those directories. To automatically update those clones, create the following script:

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This script git-pulls all installed nvim plugins which are using the built-in
+# nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack
+#
+# Required software that is not in GNU coreutils:
+#   - 'git' to fetch plugin updates from upstream
+###############################################################################
+
+### User specific variables, adjust to your own needs
+
+# folder that contains all nvim plugins
+PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack"
+
+### Script logic
+
+echo "updating all plugins in ${PLUGIN_DIR}"
+# iterate through all directories and git pull em
+for directory in "${PLUGIN_DIR}"/*/{start,opt}/*; do
+    if [ -d "${directory}" ]; then
+        plugin=$(basename "${directory}")
+        echo "updating ${plugin}"
+        git -C "${directory}" pull --quiet
+    fi
+done
+

In case you are using Vim, adjust the PLUGIN_DIR variable to point to your Vim plugin directory instead and save the resulting shell script as a file called update-nvim-plugins.sh in some folder of your choice. Do not forget to set the executable bit with chmod +x /path/to/your/folder/update-nvim-plugins.sh. Since all good developers must be lazy, write the following systemd service to execute the above script automatically:

+
[Unit]
+Description=cron job that triggers an update of all nvim plugins
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/path/to/your/folder/update-nvim-plugins.sh
+RemainAfterExit=false
+

Adjust the ExecStart line to match the location where you saved the above script and place that service definition in a file called nvim-plugins-update.service into your local ~/.config/systemd/user directory. Add another file called nvim-plugins-update.timer next to it that defines a systemd timer with the following content:

+
[Unit]
+Description=Update nvim plugins every week
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+RandomizedDelaySec=5hours
+
+[Install]
+WantedBy=timers.target
+

Adjust how often you want to update the plugins you are using in the OnCalendar line. Enable this service/timer with:

+
$ systemctl --user enable nvim-plugins-update
+

Finally, add the following shell aliases to make it easier to interact with the created systemd units:

+
# trigger an update manually
+alias update-nvim-plugins='systemctl --user start nvim-plugins-update'
+# see status of last auto-update
+alias update-nvim-plugins-status='systemctl --user status nvim-plugins-update'
+# see logs of past auto-updates
+alias update-nvim-plugins-logs='journalctl --user --unit nvim-plugins-update'
+

With this setup in place, all your plugins will be automatically updated once per week or however often you have configured in the timer.

+]]>
Manage tmux sessions with tmuxphttps://seb.xn--ho-hia.de/posts/tmux-tmuxp/2021-12-20T00:00:00+00:002023-01-06T16:22:24+01:00To manage tmux sessions, I like to use tmuxp. It works by having pre-defined sessions in ~/.config/tmuxp which looks like this:

+
session_name: cool-app
+start_directory: ~/projects/cool-app
+windows:
+- window_name: backend
+  start_directory: backend
+- window_name: frontend
+  start_directory: frontend
+

In case the name of the file is cool-app.yaml, you can open the sessions with tmuxp load cool-app --yes.

+]]>
Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/2021-12-06T00:00:00+00:002023-01-06T16:46:32+01:00To automatically version Maven projects, I like to use the m-versions-p like this:

+
$ mvn versions:set -DnewVersion=my.new.version -DgenerateBackupPoms=false
+

This will update the version property of every module in the reactor to prepare them for the next release. In case you are using GitHub Actions, consider using a timestamp.

+]]>
GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/2021-11-22T00:00:00+00:002023-01-06T15:27:21+01:00GitHub Packages can be used to host Maven packages with the following configuration in your ~/.m2/settings.xml:

+
<settings>
+  <profiles>
+    <profile>
+      <id>github</id>
+      <repositories>
+        <repository>
+          <id>maven-build-process</id>
+          <name>GitHub maven-build-process Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/maven-build-process</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+        <repository>
+          <id>hcf4j</id>
+          <name>GitHub hcf4j Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/hcf4j</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+    </profile>
+  </profiles>
+  <servers>
+    <server>
+      <id>maven-build-process</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+    <server>
+      <id>hcf4j</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+  </servers>
+</settings>
+

You will have to add another repository/server for each project you are fetching from GitHub.

+]]>
Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/2021-11-08T00:00:00+00:002023-01-06T15:27:21+01:00Some time ago, Google started hosting a copy of Maven Central. Configure it in your ~/.m2/settings.xml like this:

+
<settings>
+  <mirrors>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (Asia)</name>
+      <url>https://maven-central-asia.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (EU)</name>
+      <url>https://maven-central-eu.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (US)</name>
+      <url>https://maven-central.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+  </mirrors>
+</settings>
+

Pick the mirror nearest to your location to get best speeds.

+]]>
Backups with emacshttps://seb.xn--ho-hia.de/posts/emacs-backups/2021-10-25T00:00:00+00:002023-01-06T15:27:21+01:00emacs will create backups of your files by default. Those backups are located right next to the original file and are called <file>~. Unfortunately, emacs will not clean those up by default, which annoys me from time to time. Therefore, I’m now using the following configuration to keep those backups in a different folder:

+
(setq version-control t     ;; Use version numbers for backups.
+      kept-new-versions 10  ;; Number of newest versions to keep.
+      kept-old-versions 0   ;; Number of oldest versions to keep.
+      delete-old-versions t ;; Don't ask to delete excess backup versions.
+      backup-by-copying t)  ;; Copy all files, don't rename them.
+
+(setq vc-make-backup-files t)
+
+;; Default and per-save backups go here:
+(setq backup-directory-alist '(("" . "~/.emacs.d/backup/per-save")))
+
+(defun force-backup-of-buffer ()
+  ;; Make a special "per session" backup at the first save of each
+  ;; emacs session.
+  (when (not buffer-backed-up)
+    ;; Override the default parameters for per-session backups.
+    (let ((backup-directory-alist '(("" . "~/.emacs.d/backup/per-session")))
+          (kept-new-versions 3))
+      (backup-buffer)))
+  ;; Make a "per save" backup on each save.  The first save results in
+  ;; both a per-session and a per-save backup, to keep the numbering
+  ;; of per-save backups consistent.
+  (let ((buffer-backed-up nil))
+    (backup-buffer)))
+
+(add-hook 'before-save-hook  'force-backup-of-buffer)
+

Thanks to that configuration, backups per-save will be created in ~/.emacs.d/backup/per-save and backups per-session in ~/.emacs.d/backup/per-session.

+]]>
Maintaining dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-maintenance/2021-10-11T00:00:00+00:002023-01-06T17:32:06+01:00To make it easier managing many dotfiles with chezmoi, a shell function similar to the one below can be used:

+
function m-dotfiles-ok {
+    # public
+    chezmoi add ~/.config/zsh --recursive
+    chezmoi add ~/.config/sway --recursive
+    chezmoi add ~/.config/tmux --recursive
+    chezmoi add ....
+
+    # secrets
+    chezmoi add --encrypt ~/.config/npm/npmrc
+    chezmoi add --encrypt ~/.ssh/id_rsa
+    chezmoi add --encrypt ...
+}
+

Whenever you feel happy with your current setup, just call m-dotfiles-ok to push changes into the chezmoi source directory. Files will automatically be encrypted with gpg and committed/pushed into a Git repository if you have done the necessary configuration beforehand.

+

In general, editing your dotfiles directly as explained in the second option of the FAQ seems easier though. Refactoring your dotfiles is especially easy when the exact_ prefix is used for directories. As explained in the documentation, all files that are not managed by chezmoi will be removed, therefore your configuration will always match what is in your source directory.

+]]>
Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/2021-09-20T00:00:00+00:002023-01-08T09:25:44+01:00RECOMMENDATION: Use age instead of gpg.

+

chezmoi can use various external tools to keep data private. gpg is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure gpg with chezmoi, just set yourself as the recipient like this:

+
[gpg]
+  recipient = "your.name@example.com"
+

Calling chezmoi add --encrypt /path/to/secret will now create encrypt the file with your public key which allows you to decrypt them later with your private key.

+]]>
Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/2021-09-06T00:00:00+00:002023-01-06T17:32:06+01:00chezmoi can automatically commit and push changes to your dotfiles into a (remote) Git repository. Enable it with the following snippet in your chezmoi.toml

+
[sourceVCS]
+    autoCommit = true
+    autoPush = true
+

Every time you call chezmoi add /path/to/file will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.

+]]>
Waybar on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-waybar/2021-08-23T00:00:00+00:002023-01-06T16:40:24+01:00Waybar can be used as a status bar for SwayWM. You tell Sway to use it with the following snippet in your Sway configuration:

+
bar {
+    swaybar_command waybar
+}
+

Configure Waybar itself in ~/.config/waybar/config:

+
{
+    "layer": "top",
+    "modules-left": ["sway/workspaces", "sway/mode"],
+    "modules-center": ["sway/window"],
+    "modules-right": ["clock"],
+    "sway/window": {
+        "max-length": 50
+    },
+    "clock": {
+        "format-alt": "{:%a, %d. %b  %H:%M}"
+    }
+}
+
]]>
tmux Status Barhttps://seb.xn--ho-hia.de/posts/tmux-status-bar/2021-08-09T00:00:00+00:002023-01-06T16:22:24+01:00To see the currently active tmux status bar configuration, call:

+
$ tmux show-options -g | grep status
+

Change on of those values with in the current tmux session:

+
$ tmux set-option status-right ""
+

Persist the change in your tmux.conf like this:

+
# disable right side of status bar
+set-option -g status-right ""
+
]]>
emacs and systemdhttps://seb.xn--ho-hia.de/posts/emacs-systemd/2021-07-26T00:00:00+00:002023-01-06T16:40:24+01:00I like to use emacs to edit files in a terminal. It tends to start a little slow, therefore I’ve created a systemd unit to automatically start the emacs daemon and use aliases to connect to the running daemon. The unit looks like this:

+
[Unit]
+Description=Emacs text editor [%I]
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+
+[Service]
+Type=forking
+ExecStart=/usr/bin/emacs --daemon=%i
+ExecStop=/usr/bin/emacsclient --eval "(kill-emacs)"
+Environment=SSH_AUTH_SOCK=%t/keyring/ssh
+Restart=on-failure
+
+[Install]
+WantedBy=default.target
+

Enable it with systemctl --user enable emacs@user and define any number of aliases to make connecting to the emacs daemon easier:

+
alias e='emacsclient --tty --socket-name=user'
+alias vim='emacsclient --tty --socket-name=user'
+alias vi='emacsclient --tty --socket-name=user'
+alias nano='emacsclient --tty --socket-name=user'
+alias ed='emacsclient --tty --socket-name=user'
+
]]>
Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/2021-07-12T00:00:00+00:002023-01-06T17:32:06+01:00In case you want to have push-only mirrors for your Git repository, consider adding a special mirror remote like this:

+
$ git remote add mirrors DISABLED
+$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git
+$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git
+$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git
+

The above will create a new remote called mirrors which has no fetch URL and therefore can only be pushed:

+
$ git remote -v
+mirrors DISABLED (fetch)
+mirrors git@codeberg.org:org/repo.git (push)
+mirrors git@gitlab.com:org/repo.git (push)
+mirrors git@bitbucket.org:org/repo.git (push)
+

Calling git push mirrors main:main will push the local main branch into all defined mirrors.

+ + +]]>
Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/2021-06-28T00:00:00+00:002023-01-06T16:46:32+01:00In case you want to make use of the decentralized nature of Git, consider using multiple push targets like this:

+
$ git remote set-url origin --push --add git@example.com/project.git
+$ git remote set-url origin --push --add git@another.com/project.git
+

Note that the first call to set-url will overwrite an existing remote creating with git clone. Any additional call will actually recognize the --add option and add the new target to an existing remote.

+ + +]]>
Using multiple clusters with kubectlhttps://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/2021-06-14T00:00:00+00:002023-01-06T17:32:06+01:00To connect to multiple Kubernetes clusters with kubectl, I like to define aliases like this:

+
alias rancher="kubectl --kubeconfig ~/.kube/rancher.config"
+alias work="kubectl --kubeconfig ~/.kube/work.config"
+alias customer="kubectl --kubeconfig ~/.kube/customer.config"
+

Those aliases allow me to write things like rancher get pods --namespace some-namespace without worrying the wrong context is active. Using multiple configurations - one for each cluster - seems to be easier to manage since most clusters allow to download a ready-to-use configuration file. Instead of mangling them together manually, I just specify another alias whenever I get to work with another cluster.

+]]>
Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/2021-05-31T00:00:00+00:002023-01-06T17:32:06+01:00Maven projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.

+
<properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+</properties>
+
+ +]]>
Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/2021-05-17T00:00:00+00:002023-01-06T16:22:24+01:00To create reproducible builds with Maven projects, it’s enough to specify the project.build.outputTimestamp property like this:

+
<properties>
+    <project.build.outputTimestamp>2020</project.build.outputTimestamp>
+</properties>
+
+ +]]>
Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/2021-05-03T00:00:00+00:002023-01-06T17:32:06+01:00To analyze Maven projects with SonarCloud using GitHub Actions, first create the following settings.xml file:

+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
+
+    <pluginGroups>
+        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
+    </pluginGroups>
+
+    <activeProfiles>
+        <activeProfile>sonar</activeProfile>
+    </activeProfiles>
+
+    <profiles>
+        <profile>
+            <id>sonar</id>
+            <properties>
+                <sonar.host.url>https://sonarcloud.io</sonar.host.url>
+                <sonar.organization>YOUR_ORG</sonar.organization>
+                <sonar.projectKey>YOUR_PROJECT</sonar.projectKey>
+                <sonar.login>${env.SONAR_TOKEN}</sonar.login>
+            </properties>
+        </profile>
+    </profiles>
+</settings>
+

Finally, add a step to your workflow:

+
- name: Verify Project
+  run: mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar
+  env:
+    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+
+ +]]>
Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/2021-04-19T00:00:00+00:002023-01-06T16:22:24+01:00To use tmux as your login shell, use chsh:

+
# list all available shells
+$ chsh --list-shells
+/bin/sh
+/bin/bash
+/sbin/nologin
+/usr/bin/sh
+/usr/bin/bash
+/usr/sbin/nologin
+/usr/bin/zsh
+/bin/zsh
+/usr/bin/tmux
+/bin/tmux
+
+# select login shell
+$ chsh --shell /usr/bin/tmux
+
]]>
Lock your screen on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenlock/2021-04-05T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM users can use swaylock to lock their screen. Place the following key binding in your Sway configuration:

+
# lock your screen
+bindsym $mod+Ctrl+l exec swaylock --color 000000
+

$mod+Ctrl+l will lock your screen and turn it to black. The --color flag allows any color in the form of rrggbb[aa].

+]]>
Peeking with tmuxhttps://seb.xn--ho-hia.de/posts/tmux-peek/2021-04-05T00:00:00+00:002023-01-06T15:27:21+01:00tmux uses can use the following snippet to peek at files. Place it in your .bashrc or similar file.

+
peek() { tmux split-window -p 33 "$EDITOR" "$@" }
+

Calling peek <file> will open <file> in lower third of tmux window.

+]]>
Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/2021-03-22T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM uses can use a mixture of grim and slurp to take screenshots of their desktop. Place the following key binding in your Sway configuration:

+
# take screenshot of currently focused screen
+bindsym $mod+Print exec /usr/bin/grim -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
+# take screenshot of selection
+bindsym $mod+Shift+p exec /usr/bin/grim -g "$(/usr/bin/slurp)" $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
]]>
Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/2021-03-08T00:00:00+00:002023-01-06T16:40:24+01:00README file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a Makefile in the root of your project which contains the exact same instructions. Thanks to make, all your contributors can now use TAB-completion to run any of the pre-defined make targets.

+

The following example is part of one of my projects, and I certainly don’t want to type (or even copy) that all the time:

+
.PHONY: release-into-local-nexus
+release-into-local-nexus:
+	mvn versions:set \
+	   -DnewVersion=$(TIMESTAMPED_VERSION) \
+	   -DgenerateBackupPoms=false
+	-mvn clean deploy scm:tag \
+	   -DpushChanges=false \
+	   -DskipLocalStaging=true \
+	   -Drelease=local
+	mvn versions:set \
+	   -DnewVersion=9999.99.99-SNAPSHOT \
+	   -DgenerateBackupPoms=false
+

With the above target in place, everyone can now do make release-into-local-nexus instead of typing/copying the commands themselves. Thanks to TAB-completion you just have to do make r<TAB> and confirm with >ENTER> to perform a release.

+ + +]]>
Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/2021-02-22T00:00:00+00:002023-01-06T16:40:24+01:00To delay the execution of an GitHub Action, use a mixture of the on: schedule: ... configuration, and a conditional build step.

+
name: <PIPELINE>
+on:
+  schedule:
+    - cron: '<CRON>'
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Count commits in last week
+        id: commits
+        run: echo "::set-output name=count::$(git rev-list --count HEAD --since='<DATE>')"
+      - name: Build project
+        if: steps.commits.outputs.count > 0
+        run: build-project
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <CRON>: cron expression - use https://crontab.guru/.
  • +
  • <DATE>: Git date expression that matches <CRON>.
  • +
+]]>
Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/2021-02-08T00:00:00+00:002023-01-06T16:40:24+01:00In case you are using a Makefile to define a complex build step - for example start database, run tests, stop database - consider using the - qualifier in front of your actual build step like this:

+
.PHONY: build
+build:
+	start-database
+	-build-software
+	stop-database
+

Thanks to -, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.

+ + +]]>
Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/2021-01-25T00:00:00+00:002023-01-06T16:40:24+01:00To use a serviceworker to cache a Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/javascript"]
+  suffixes = ["js"]
+[outputFormats.ServiceWorker]
+  name = "ServiceWorker"
+  mediaType = "application/javascript"
+  baseName = "serviceworker"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.serviceworker.js with the following content:

+
const CACHE = 'cache-and-update';
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(precache());
+});
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(fromCache(event.request));
+  event.waitUntil(update(event.request));
+});
+
+const precache = async () => {
+    const cache = await caches.open(CACHE);
+    return await cache.addAll([
+        {{ range $i, $e := .Site.RegularPages }}
+        '{{ $.RelPermalink }}'{{ if $i }}, {{ end }}
+        {{ end }}
+    ]);
+}
+
+const fromCache = async (request) => {
+    const cache = await caches.open(CACHE);
+    const match = await cache.match(request);
+    return match || Promise.reject('no-match');
+}
+
+const update = async (request) => {
+    const cache = await caches.open(CACHE);
+    const response = await fetch(request);
+    return await cache.put(request, response);
+}
+
+ +]]>
Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/2021-01-11T00:00:00+00:002023-01-06T16:40:24+01:00To publish a web app manifest document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/manifest+json"]
+  suffixes = ["webmanifest"]
+[outputFormats.Webmanifest]
+  name = "Web App Manifest"
+  mediaType = "application/manifest+json"
+  baseName = "manifest"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.manifest.json with the following content:

+
{
+  "name": "{{ .Site.Title }}",
+  "short_name": "{{ .Site.Title }}",
+  "start_url": ".",
+  "display": "minimal-ui",
+  "background_color": "#fff",
+  "description": "{{ .Site.Params.description }}"
+}
+
+ +]]>
Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/2020-12-28T00:00:00+00:002023-01-06T15:27:21+01:00Hugo allows bundling of assets with several built-in functions:

+
{{ $normalize := resources.Get "/css/normalize.css" }}
+{{ $font := resources.Get "/css/font.css" }}
+{{ $header := resources.Get "/css/header.css" }}
+{{ $footer := resources.Get "/css/footer.css" }}
+{{ $navigation := resources.Get "/css/navigation.css" }}
+{{ $navigation_mobile := resources.Get "/css/navigation-mobile.css" }}
+{{ $layout := resources.Get "/css/layout.css" }}
+{{ $layout_mobile := resources.Get "/css/layout-mobile.css" }}
+{{ $syntax := resources.Get "/css/syntax.css" }}
+{{ $darkmode := resources.Get "/css/darkmode.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat "css/base.css" | resources.Minify | resources.Fingerprint "sha512" }}
+{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat "css/mobile.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+<link href="{{ $base.Permalink }}" integrity="{{ $base.Data.Integrity }}" media="screen" rel="stylesheet">
+<link href="{{ $mobile.Permalink }}" integrity="{{ $mobile.Data.Integrity }}" media="screen and (max-width: 800px)" rel="stylesheet">
+
+<link href="{{ $darkmode.Permalink }}" integrity="{{ $darkmode.Data.Integrity }}" media="screen and (prefers-color-scheme: dark)" rel="stylesheet">
+
]]>
humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/2020-12-14T00:00:00+00:002023-01-06T16:40:24+01:00To publish a humans.txt document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."text/plain"]
+  suffixes = ["txt"]
+[outputFormats.Humans]
+  name = "Humans"
+  mediaType = "text/plain"
+  baseName = "humans"
+  isPlainText = true
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.humans.txt with the following content:

+
/* TEAM */
+{{ range $.Site.Data.contributors }}
+{{ .title }}: {{ .first_name }} {{ .last_name }}
+Site: {{ .website }}
+{{ end }}
+
]]>
FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/2020-11-30T00:00:00+00:002023-01-06T16:40:24+01:00To publish a FOAF document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/rdf+xml"]
+  suffixes = ["rdf"]
+[outputFormats.Foaf]
+  name = "FOAF"
+  mediaType = "application/rdf+xml"
+  baseName = "foaf"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.foaf.rdf with the following content:

+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/">
+  <foaf:PersonalProfileDocument rdf:about="">
+    <foaf:maker rdf:resource="#me" />
+    <foaf:primaryTopic rdf:resource="{{ .Site.Title }}" />
+  </foaf:PersonalProfileDocument>
+
+  <foaf:Project rdf:ID="{{ .Site.Title }}">
+    <foaf:name>{{ .Site.Title }}</foaf:name>
+    <foaf:homepage rdf:resource="{{ .Site.BaseURL }}" />
+  </foaf:Project>
+
+  {{ range $.Site.Data.contributors }}
+  <foaf:Person rdf:ID="{{ .id }}">
+    <foaf:name>{{ .first_name }} {{ .last_name }}</foaf:name>
+    <foaf:title>{{ .title }}</foaf:title>
+    <foaf:givenname>{{ .first_name }}</foaf:givenname>
+    <foaf:family_name>{{ .last_name }}</foaf:family_name>
+    <foaf:mbox rdf:resource="mailto:{{ .email }}" />
+    <foaf:homepage rdf:resource="{{ .website }}" />
+  </foaf:Person>
+  {{ end }}
+</rdf:RDF>
+
]]>
Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/2020-11-16T00:00:00+00:002023-01-06T16:40:24+01:00To publish Atom feeds for your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/atom+xml"]
+  suffixes = ["xml"]
+[outputFormats.Atom]
+  name = "Atom"
+  mediaType = "application/atom+xml"
+  baseName = "atom"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/list.atom.xml with the following content:

+
{{ printf `<?xml version="1.0" encoding="utf-8"?>` | safeHTML }}
+<feed xmlns="http://www.w3.org/2005/Atom"{{ with site.LanguageCode }} xml:lang="{{ . }}"{{ end }}>
+    <generator uri="https://gohugo.io/" version="{{ hugo.Version }}">Hugo</generator>
+    {{- $title := site.Title -}}
+    {{- with .Title -}}
+        {{- if (not (eq . site.Title)) -}}
+            {{- $title = printf `%s %s %s` . (i18n "feed_title_on" | default "on") site.Title -}}
+        {{- end -}}
+    {{- end -}}
+    {{- if .IsTranslated -}}
+        {{ $title = printf "%s (%s)" $title (index site.Data.i18n.languages .Lang) }}
+    {{- end -}}
+    {{ printf `<title type="html"><![CDATA[%s]]></title>` $title | safeHTML }}
+    {{ with (or (.Param "subtitle") (.Param "tagline")) }}
+        {{ printf `<subtitle type="html"><![CDATA[%s]]></subtitle>` . | safeHTML }}
+    {{ end }}
+    {{ $output_formats := .OutputFormats }}
+    {{ range $output_formats -}}
+        {{- $rel := (or (and (eq "atom" (.Name | lower)) "self") "alternate") -}}
+        {{ with $output_formats.Get .Name }}
+            {{ printf `<link href=%q rel=%q type=%q title=%q />` .Permalink $rel .MediaType.Type .Name | safeHTML }}
+        {{- end -}}
+    {{- end }}
+    {{- range .Translations }}
+        {{ $output_formats := .OutputFormats }}
+        {{- $lang := .Lang }}
+        {{- $langstr := index site.Data.i18n.languages .Lang }}
+        {{ range $output_formats -}}
+            {{ with $output_formats.Get .Name }}
+                {{ printf `<link href=%q rel="alternate" type=%q hreflang=%q title="[%s] %s" />` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }}
+            {{- end -}}
+        {{- end }}
+    {{- end }}
+    <updated>{{ now.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+    {{ with site.Copyright }}
+        {{- $copyright := replace . "{year}" now.Year -}} {{/* In case the site.copyright uses a special string "{year}" */}}
+        {{- $copyright = replace $copyright "&copy;" "©" -}}
+        <rights>{{ $copyright | plainify }}</rights>
+    {{- end }}
+    {{ with .Param "feed" }}
+        {{/* For this to work, the $icon file should be present in the assets/ directory */}}
+        {{- $icon := .icon | default "icon.svg" -}}
+        {{- with resources.Get $icon -}}
+            <icon>{{ (. | fingerprint).Permalink }}</icon>
+        {{- end }}
+
+        {{/* For this to work, the $logo file should be present in the assets/ directory */}}
+        {{- $logo := .logo | default "logo.svg" -}}
+        {{- with resources.Get $logo -}}
+            <logo>{{ (. | fingerprint).Permalink }}</logo>
+        {{- end }}
+    {{ end }}
+    {{ with site.Author.name -}}
+        <author>
+            <name>{{ . }}</name>
+            {{ with site.Author.email }}
+                <email>{{ . }}</email>
+            {{ end -}}
+        </author>
+    {{- end }}
+    {{ with site.Params.id }}
+        <id>{{ . | plainify }}</id>
+    {{ else }}
+        <id>{{ .Permalink }}</id>
+    {{ end }}
+    {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }}
+    {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}}
+    {{/* Range through only the pages with a Type in $feed_sections. */}}
+    {{- $pages := where .RegularPages "Type" "in" $feed_sections -}}
+    {{- if (eq .Kind "home") -}}
+        {{- $pages = where site.RegularPages "Type" "in" $feed_sections -}}
+    {{- end -}}
+    {{/* Remove the pages that have the disable_feed parameter set to true. */}}
+    {{- $pages = where $pages ".Params.disable_feed" "!=" true -}}
+    {{- range first $limit $pages }}
+        {{ $page := . }}
+        <entry>
+            {{ printf `<title type="html"><![CDATA[%s]]></title>` .Title | safeHTML }}
+            <link href="{{ .Permalink }}?utm_source=atom_feed" rel="alternate" type="text/html" />
+            {{- range .Translations }}
+                {{- $link := printf "%s?utm_source=atom_feed" .Permalink | safeHTML }}
+                {{- printf `<link href=%q rel="alternate" type="text/html" hreflang=%q />` $link .Lang | safeHTML }}
+            {{- end }}
+            {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}}
+            {{- range first 5 (site.RegularPages.Related .) }}
+                <link href="{{ .Permalink }}?utm_source=atom_feed" rel="related" type="text/html" title="{{ .Title }}" />
+            {{- end }}
+            {{ with .Params.id }}
+                <id>{{ . | plainify }}</id>
+            {{ else }}
+                <id>{{ .Permalink }}</id>
+            {{ end }}
+            {{ with .Params.author -}}
+                {{- range . -}} <!-- Assuming the author front-matter to be a list -->
+                    <author>
+                        <name>{{ . }}</name>
+                    </author>
+                {{- end -}}
+            {{- end }}
+            <published>{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</published>
+            <updated>{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+            {{ $description1 := .Description | default "" }}
+            {{ $description := (cond (eq "" $description1) "" (printf "<blockquote>%s</blockquote>" ($description1 | markdownify))) }}
+            {{ printf `<content type="html"><![CDATA[%s%s]]></content>` $description .Content | safeHTML }}
+            {{ with site.Taxonomies }}
+                {{ range $taxo,$_ := . }} <!-- Defaults taxos: "tags", "categories" -->
+                    {{ with $page.Param $taxo }}
+                        {{ $taxo_list := . }} <!-- $taxo_list will be the tags/categories list -->
+                        {{ with site.GetPage (printf "/%s" $taxo) }}
+                            {{ $taxonomy_page := . }}
+                            {{ range $taxo_list }} <!-- Below, assuming pretty URLs -->
+                                <category scheme="{{ printf "%s%s" $taxonomy_page.Permalink (. | urlize) }}" term="{{ (. | urlize) }}" label="{{ . }}" />
+                            {{ end }}
+                        {{ end }}
+                    {{ end }}
+                {{ end }}
+            {{ end }}
+        </entry>
+    {{ end }}
+</feed>
+
]]>
Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/2020-11-16T00:00:00+00:002023-01-06T15:27:21+01:00The rzr/fediverse-action action allows to send a toot in your GitHub Action.

+
name: <NAME>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Publish Toot
+        uses: rzr/fediverse-action@master
+        with:
+          access-token: ${{ secrets.MASTODON_TOKEN }}
+          message: <MESSAGE>
+          host: ${{ secrets.MASTODON_SERVER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <MESSAGE>: Message for the toot.
  • +
+]]>
Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/2020-11-02T00:00:00+00:002023-01-06T15:27:21+01:00The dawidd6/action-send-mail action allows to send an email in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Send mail
+        uses: dawidd6/action-send-mail@v3
+        with:
+          server_address: ${{ secrets.MAIL_SERVER }}
+          server_port: ${{ secrets.MAIL_PORT }}
+          username: ${{ secrets.MAIL_USERNAME }}
+          password: ${{ secrets.MAIL_PASSWORD }}
+          subject: <SUBJECT>
+          body: <BODY>
+          to: ${{ secrets.MAIL_RECIPIENT }}
+          from: ${{ secrets.MAIL_SENDER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <SUBJECT>: Subject for the email.
  • +
  • <BODY>: Body for the email.
  • +
+

Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define MAIL_RECIPIENT for each project.

+]]>
Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/2020-10-19T00:00:00+00:002023-01-06T15:27:21+01:00The actions/upload-release-asset action allows to upload a release artifact in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Upload Release Asset
+        id: upload_release_asset
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./some/path/to/file.zip
+          asset_name: public-name-for-file.zip
+          asset_content_type: application/zip
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/2020-10-05T00:00:00+00:002023-01-06T17:32:06+01:00The actions/create-release action allows to create a new GitHub releases in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+     - name: Create Release
+       uses: actions/create-release@v1
+       env:
+         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+       with:
+         tag_name: <TAG>
+         release_name: <RELEASE>
+         draft: false
+         prerelease: false
+         body: |
+           Your release text here
+
+           Some code block:
+           ```yaml
+           yaml:
+             inside:
+               of:
+                 another: yaml
+           ```           
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <TAG>: The Git tag to create.
  • +
  • <RELEASE>: The release name to use.
  • +
+]]>
Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/2020-09-21T00:00:00+00:002023-01-06T15:27:21+01:00The peaceiris/actions-gh-pages action allows to publish a Hugo site in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Deploy Website
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: <PUBLISH_DIR>
+          force_orphan: true
+          cname: <CNAME>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <PUBLISH_DIR>: The file system location of the built site.
  • +
  • <CNAME>: The CNAME of your custom domain.
  • +
+]]>
Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/2020-09-07T00:00:00+00:002023-01-06T15:27:21+01:00The actions/cache action allows to cache artifacts in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Cache Maven artifacts
+        uses: actions/cache@v1
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-            
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/2020-08-24T00:00:00+00:002023-01-06T15:27:21+01:00The actions-hugo action allows to use a specific Hugo version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Setup hugo
+        uses: peaceiris/actions-hugo@v2
+        with:
+          hugo-version: <HUGO_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <HUGO_VERSION>: The released versions or use latest to always use the latest version of Hugo.
  • +
+]]>
Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/2020-08-10T00:00:00+00:002023-01-06T15:27:21+01:00The setup-java action allows to use a specific Java version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Set up JDK <JDK_VERSION>
+        uses: actions/setup-java@v1
+        with:
+          java-version: <JDK_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <JDK_VERSION>: The required Java version for your project.
  • +
+]]>
XDG Base Directory Specificationhttps://seb.xn--ho-hia.de/posts/xdg-dot-files/2020-07-27T00:00:00+00:002023-01-06T16:22:24+01:00The XDG Base Directory Specification has been around for a while, yet not every software has adopted it yet. Here is an incomplete list of fixes:

+
# use existing env variables or define new
+[ -z "$XDG_CACHE_HOME"  ] && export XDG_CACHE_HOME="$HOME/.cache"
+[ -z "$XDG_CONFIG_DIRS" ] && export XDG_CONFIG_DIRS="/etc/xdg"
+[ -z "$XDG_CONFIG_HOME" ] && export XDG_CONFIG_HOME="$HOME/.config"
+[ -z "$XDG_DATA_DIRS"   ] && export XDG_DATA_DIRS="/usr/local/share:/usr/share"
+[ -z "$XDG_DATA_HOME"   ] && export XDG_DATA_HOME="$HOME/.local/share"
+
+# gradle
+export GRADLE_USER_HOME="$XDG_DATA_HOME/gradle"
+
+# httpie
+export HTTPIE_CONFIG_DIR="$XDG_CONFIG_HOME/httpie"
+
+# npm
+export NPM_CONFIG_USERCONFIG="$XDG_CONFIG_HOME/npm/npmrc"
+export npm_config_cache="$XDG_CACHE_HOME/npm"
+
+# password-store
+export PASSWORD_STORE_DIR="$XDG_DATA_HOME/password-store"
+

To make your own software XDG-aware, consider using the dirs-dev or configdir libraries.

+]]>
Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/2020-06-29T00:00:00+00:002023-01-06T16:22:24+01:00Use the following Perl snippet to automatically generate help output for your Makefile:

+
GREEN  := $(shell tput -Txterm setaf 2)
+WHITE  := $(shell tput -Txterm setaf 7)
+YELLOW := $(shell tput -Txterm setaf 3)
+RESET  := $(shell tput -Txterm sgr0)
+
+HELP_FUN = \
+    %help; \
+    while(<>) { push @{$$help{$$2 // 'targets'}}, [$$1, $$3] if /^([a-zA-Z\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
+    print "usage: make [target]\n\n"; \
+    for (sort keys %help) { \
+    print "${WHITE}$$_:${RESET}\n"; \
+    for (@{$$help{$$_}}) { \
+    $$sep = " " x (32 - length $$_->[0]); \
+    print "  ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
+    }; \
+    print "\n"; }
+

To use HELP_FUN, add the following help target to the same Makefile:

+
.DEFAULT_GOAL := help
+
+.PHONY: help
+help: ##@other Show this help
+	@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
+

Each target in the Makefile is marked as phony to signal that those targets are not actually files that are generated as part of your build process. The optional description of a target can be placed after the ##@ prefix. The first word represents the group of a target and everything that follows is the description of a target. All targets should be formatted just like the help target:

+
.PHONY: compile
+compile: ##@hacking Compile your code
+	<compile some code>
+
+.PHONY: test
+test: ##@hacking Test your code
+	<test some code>
+
+.PHONY: sign-cla
+sign-cla: ##@contrib Sign the contributor license agreement
+	<sign some file>
+

Once in place, you can either use make without any argument to call the help target or use make help to see the generated output:

+
$ make
+usage: make [target]
+
+contrib:
+  sign-cla            Sign the contributor license agreement
+
+hacking:
+  compile             Compile your code
+  test                Test your code
+
+other:
+  help                Show this help
+
]]>
Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/2020-06-15T00:00:00+00:002023-01-06T17:32:06+01:00In case you don’t want to write git clone git@github.com:orga/repo.git all the time, consider using a custom SSH configuration (~/.ssh/config) like this:

+
Host github
+    HostName github.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITHUB>
+
+Host gitlab
+    HostName gitlab.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITLAB>
+
+Host bitbucket
+    HostName bitbucket.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-BITBUCKET>
+
+Host codeberg
+    HostName codeberg.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-CODEBERG>
+

Once configured, you can now write:

+
$ git clone github:orga/repo
+$ git clone gitlab:orga/repo
+$ git clone bitbucket:orga/repo
+$ git clone codeberg:orga/repo
+

In case you are working with many repositories inside a single organization, consider adding the following Git configuration ($XDG_CONFIG_HOME/git/config or ~/.gitconfig):

+
[url "github:orga/"]
+  insteadOf = orga:
+[url "gitlab:orga/"]
+  insteadOf = orgl:
+[url "bitbucket:orga/"]
+  insteadOf = orgb:
+[url "codeberg:orga/"]
+  insteadOf = orgc:
+

Which allows you to just write:

+
$ git clone orga:repo
+$ git clone orgl:repo
+$ git clone orgb:repo
+$ git clone orgc:repo
+

Git will substitute the insteadOf values like orga: with the configured url (for example github:orga/). The actual clone URL is github:orga/repo at this point, which can be used by Git together with the SSH configuration mentioned above to clone repositories.

+]]>
Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/2020-06-11T00:00:00+00:002023-01-06T15:27:21+01:00In case you are into calver or have another reason to create a timestamp with GitHub Actions, do the following:

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Create release version
+        id: <ID>
+        run: echo "::set-output name=<NAME>::$(date +'%Y.%m.%d-%H%M%S')"
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <ID>: The unique ID of the timestamp step.
  • +
  • <NAME>: The name of the created timestamp.
  • +
+

The special syntax ::set-output name=<NAME>:: declares that the output of the command (echo) should be saved in a variable called <NAME>. Together with the <ID> of the pipeline step, this value can be referenced with the expression ${{ steps.<ID>.outputs.<NAME> }} in the following steps of your pipeline.

+]]>
\ No newline at end of file diff --git a/categories/snippet/index.html b/categories/snippet/index.html new file mode 100644 index 000000000..446e1b222 --- /dev/null +++ b/categories/snippet/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Snippet

Category: Snippet

\ No newline at end of file diff --git a/categories/snippet/index.xml b/categories/snippet/index.xml new file mode 100644 index 000000000..7bb00a94b --- /dev/null +++ b/categories/snippet/index.xml @@ -0,0 +1,877 @@ +Snippet on Sebastian Hoßhttps://seb.xn--ho-hia.de/categories/snippet/Recent content in Snippet on Sebastian HoßHugoenMon, 09 Jan 2023 07:05:35 +0100jspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/Mon, 09 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/jspecify/<p>Every <a href="https://www.java.com/">Java</a> developer has probably encountered a <code>NullPointerException</code> at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="n">someName</span><span class="p">;</span><span class="w"> </span><span class="c1">// value is &#39;null&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">someName</span><span class="p">.</span><span class="na">toUpperCase</span><span class="p">();</span><span class="w"> </span><span class="c1">// throws NullPointerException</span><span class="w"> +</span></span></span></code></pre></div><p>Modern <a href="https://en.wikipedia.org/wiki/Integrated_development_environment">IDEs</a> have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is <code>null</code> and therefore a potential for a <code>NullPointerException</code> is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be <code>null</code>. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.</p>Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/Sun, 08 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-age/<p><a href="https://age-encryption.org/">age</a> is another tool supported by <a href="https://www.chezmoi.io/">chezmoi</a> to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. Compared to <code>gpg</code> it is much simpler by focusing on the encryption parts only.</p> +<p>Add the following snippet to your <code>.chezmoi.toml</code> to configure <code>chezmoi</code> to use <code>age</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">encryption</span> <span class="p">=</span> <span class="s2">&#34;age&#34;</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">age</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">identity</span> <span class="p">=</span> <span class="s2">&#34;path/to/age/private-key&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;age...public...key...&#34;</span> +</span></span></code></pre></div><p>Adding files to your <code>chezmoi</code> source directory remains the same as compared to using <code>gpg</code> - just call <code>chezmoi add --encrypt path/to/file</code>.</p>Multiple Git Configurationshttps://seb.xn--ho-hia.de/posts/multiple-git-configs/Thu, 05 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/multiple-git-configs/<p>To split yet re-use as much configuration for Git as possible, you can create one root configuration that looks similar to this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span> +</span></span><span class="line"><span class="cl"> <span class="na">name</span> <span class="o">=</span> <span class="s">Your Name Here</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[includeIf &#34;gitdir:~/git/personal/&#34;]</span> +</span></span><span class="line"><span class="cl"> <span class="na">path</span> <span class="o">=</span> <span class="s">~/.config/git/personal</span> +</span></span><span class="line"><span class="cl"><span class="k">[includeIf &#34;gitdir:~/git/work/&#34;]</span> +</span></span><span class="line"><span class="cl"> <span class="na">path</span> <span class="o">=</span> <span class="s">~/.config/git/work</span> +</span></span></code></pre></div><p>The <a href="https://git-scm.com/docs/git-config#_includes">includeIf</a> directive supports multiple keywords. In my case, work and personal projects have a different root directory, therefore I can filter based on the location using <code>gitdir</code>. The personal Git configuration simply looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span> +</span></span><span class="line"><span class="cl"> <span class="na">email</span> <span class="o">=</span> <span class="s">personal.email@example.com</span> +</span></span></code></pre></div><p>and the work related configuration like this using a different email address:</p>passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/passage-fuzzy-search/<p>To fuzzy search through passwords managed with <a href="https://github.com/FiloSottile/passage">passage</a>, I&rsquo;ve written the following script that is inspired by the upstream version which is using <code>fzf</code>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">fd --type<span class="o">=</span>file --base-directory<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PASSAGE_DIR</span><span class="k">:-</span><span class="si">${</span><span class="nv">HOME</span><span class="si">}</span><span class="p">/.passage/store</span><span class="si">}</span><span class="s2">&#34;</span> .age --exec <span class="nb">echo</span> <span class="s1">&#39;{.}&#39;</span> <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> sk --cycle --layout<span class="o">=</span>reverse --tiebreak<span class="o">=</span>score --no-multi <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> xargs --replace --max-args<span class="o">=</span><span class="m">1</span> --no-run-if-empty <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> passage show --clip<span class="o">=</span><span class="m">1</span> <span class="o">{}</span> +</span></span></code></pre></div><p>This version requires <a href="https://github.com/sharkdp/fd/">fd</a>, <a href="https://github.com/lotabout/skim">skim</a>, <a href="https://www.gnu.org/software/findutils/manual/html_node/find_html/Invoking-xargs.html">xargs</a>, and <a href="https://github.com/FiloSottile/passage">passage</a> itself of course. The detailed breakdown on how it works is as follows:</p> +<ol> +<li>Use <code>fd</code> to find all files within <code>${PASSAGE_DIR}</code> that end in <code>.age</code>. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.</li> +<li>Using both <code>--base-directory</code> and <code>--exec echo '{.}'</code> ensures that passwords are returned in such form that they can be passed back into <code>passage</code> again. The placeholder <code>'{.}'</code> is a feature provided by <code>fd</code> which strips the file extension from each returned value.</li> +<li>All passwords are then passed into <code>sk</code> to allow to fuzzy search across them all. Setting <code>--no-multi</code> ensures that only a single password can be selected.</li> +<li>Finally, <code>xargs</code> calls <code>passage</code> and replaces the curly braces with the selected password. Thanks to <code>--clip=1</code>, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.</li> +</ol> +<p>To call that script, I&rsquo;ve saved it as <code>passage-fuzzy-search.sh</code> in my <code>.local/bin</code> folder and added some checks into it to verify that every required software is actually installed.</p>chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/Mon, 26 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-update/<p>To automatically synchronize dotfiles across my computers, I&rsquo;ve written the following <code>systemd</code> unit:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-systemd" data-lang="systemd"><span class="line"><span class="cl"><span class="k">[Unit]</span> +</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Update chezmoi managed dotfiles</span> +</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"><span class="na">Wants</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Service]</span> +</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/chezmoi update --no-tty --force</span> +</span></span><span class="line"><span class="cl"><span class="na">RemainAfterExit</span><span class="o">=</span><span class="s">false</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Install]</span> +</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span> +</span></span></code></pre></div><p>This unit pulls changes from upstream first and then applies the changes to the current computer after I&rsquo;m logged in and a network connection is available. The <code>--no-tty</code> flag is required because there is no tty when systemd executes <code>chezmoi</code>. Likewise, the <code>--force</code> flag ensures that no interactive prompt will be displayed which we cannot answer since <code>systemd</code> is executing this unit without us being involved.</p>chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/Mon, 12 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/shell-init/<p>Many CLI applications offer initialization scripts to integrate into a shell, for example <code>starship init zsh</code> or <code>zoxide init zsh</code>. The documentation of these tools usually tell you to put something like <code>eval &quot;$(starship init zsh)&quot;</code> into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the <code>init</code> command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.</p>awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/Mon, 14 Feb 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/awsenv/<p>To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the <code>AWS_PROFILE</code> environment variable which is used by many tools that interact with the AWS API, like <code>awscli</code> or <code>terraform</code>. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses <code>aws sso login</code> to perform an MFA login into AWS. The AWS profiles must be set up in such a way that <code>aws configure list-profiles</code> can detect them, which is typically done by adding them in <code>${AWS_CONFIG_FILE:-$HOME/.aws/config}</code>.</p>Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/Sat, 29 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/clojure-java-interoperability/<p>Clojure has several <a href="https://clojure.org/java_interop">forms and macros</a> to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.</p> +<h2 id="using-gen-class">Using <code>gen-class</code></h2> +<p>Clojure code can be <a href="https://clojure.org/compilation">compiled</a> to standard JVM bytecode using <a href="https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/gen-class">gen-class</a>.</p> +<h3 id="adding-static-modifiers">Adding static modifiers</h3> +<p>Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class <code>com.example.Computation</code>.</p>Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/Sat, 15 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/<p>One feature that often surprises people while teaching them <a href="https://reactjs.org/">React</a> is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.</p> +<p>In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.</p>Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/<p>Both <a href="https://www.vim.org/">Vim</a> and <a href="https://neovim.io/">Neovim</a> have a <a href="https://vimhelp.org/repeat.txt.html#packages">built-in</a> <a href="https://neovim.io/doc/user/usr_05.html#plugin">plugin mechanism</a> that loads plugins from <code>~/.vim/pack/*/{start,opt}/*</code> (Vim) or <code>~/.local/share/nvim/site/pack/*/{start,opt}/*</code> (Neovim). All you have to do to install new plugins, is to <code>git clone</code> their repository into those directories. To automatically update those clones, create the following script:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env zsh +</span></span></span><span class="line"><span class="cl"><span class="cp"></span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"><span class="c1"># This script git-pulls all installed nvim plugins which are using the built-in</span> +</span></span><span class="line"><span class="cl"><span class="c1"># nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack</span> +</span></span><span class="line"><span class="cl"><span class="c1">#</span> +</span></span><span class="line"><span class="cl"><span class="c1"># Required software that is not in GNU coreutils:</span> +</span></span><span class="line"><span class="cl"><span class="c1"># - &#39;git&#39; to fetch plugin updates from upstream</span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### User specific variables, adjust to your own needs</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># folder that contains all nvim plugins</span> +</span></span><span class="line"><span class="cl"><span class="nv">PLUGIN_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_DATA_HOME</span><span class="k">:-</span><span class="nv">$HOME</span><span class="p">/.local/share</span><span class="si">}</span><span class="s2">/nvim/site/pack&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### Script logic</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;updating all plugins in </span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"><span class="c1"># iterate through all directories and git pull em</span> +</span></span><span class="line"><span class="cl"><span class="k">for</span> directory in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span>/*/<span class="o">{</span>start,opt<span class="o">}</span>/*<span class="p">;</span> <span class="k">do</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> +</span></span><span class="line"><span class="cl"> <span class="nv">plugin</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;updating </span><span class="si">${</span><span class="nv">plugin</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"> git -C <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> pull --quiet +</span></span><span class="line"><span class="cl"> <span class="k">fi</span> +</span></span><span class="line"><span class="cl"><span class="k">done</span> +</span></span></code></pre></div><p>In case you are using Vim, adjust the <code>PLUGIN_DIR</code> variable to point to your Vim plugin directory instead and save the resulting shell script as a file called <code>update-nvim-plugins.sh</code> in some folder of your choice. Do not forget to set the executable bit with <code>chmod +x /path/to/your/folder/update-nvim-plugins.sh</code>. Since all good developers must be lazy, write the following <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html">systemd service</a> to execute the above script automatically:</p>Manage tmux sessions with tmuxphttps://seb.xn--ho-hia.de/posts/tmux-tmuxp/Mon, 20 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-tmuxp/<p>To manage <a href="https://github.com/tmux/tmux">tmux</a> sessions, I like to use <a href="https://github.com/tmux-python/tmuxp">tmuxp</a>. It works by having pre-defined sessions in <code>~/.config/tmuxp</code> which looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">session_name</span><span class="p">:</span><span class="w"> </span><span class="l">cool-app</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">~/projects/cool-app</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">windows</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">window_name</span><span class="p">:</span><span class="w"> </span><span class="l">backend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">backend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">window_name</span><span class="p">:</span><span class="w"> </span><span class="l">frontend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">frontend</span><span class="w"> +</span></span></span></code></pre></div><p>In case the name of the file is <code>cool-app.yaml</code>, you can open the sessions with <code>tmuxp load cool-app --yes</code>.</p>Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/Mon, 06 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-cd-versioning/<p>To automatically version <a href="https://maven.apache.org/">Maven</a> projects, I like to use the <a href="https://www.mojohaus.org/versions-maven-plugin/">m-versions-p</a> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ mvn versions:set -DnewVersion<span class="o">=</span>my.new.version -DgenerateBackupPoms<span class="o">=</span><span class="nb">false</span> +</span></span></code></pre></div><p>This will update the <code>version</code> property of every module in the reactor to prepare them for the next release. In case you are using <a href="https://github.com/features/actions">GitHub Actions</a>, consider using a <a href="../github-actions-create-timestamp">timestamp</a>.</p>GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/Mon, 22 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-maven-packages/<p><a href="https://github.com/features/packages">GitHub Packages</a> can be used to host <a href="https://maven.apache.org/">Maven</a> packages with the following configuration in your <code>~/.m2/settings.xml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>github<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub maven-build-process Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/maven-build-process<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub hcf4j Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/hcf4j<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;servers&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/servers&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>You will have to add another repository/server for each project you are fetching from GitHub.</p>Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/Mon, 08 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-google-central/<p>Some time ago, <a href="https://www.google.com/">Google</a> started hosting a <a href="https://storage-download.googleapis.com/maven-central/index.html">copy</a> of <a href="https://search.maven.org/">Maven Central</a>. Configure it in your <code>~/.m2/settings.xml</code> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrors&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (Asia)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-asia.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (EU)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-eu.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (US)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirrors&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Pick the mirror nearest to your location to get best speeds.</p>Backups with emacshttps://seb.xn--ho-hia.de/posts/emacs-backups/Mon, 25 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/emacs-backups/<p><a href="https://www.gnu.org/software/emacs/">emacs</a> will create backups of your files by default. Those backups are located right next to the original file and are called <code>&lt;file&gt;~</code>. Unfortunately, emacs will not clean those up by default, which annoys me from time to time. Therefore, I&rsquo;m now using the following configuration to keep those backups in a different folder:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-el" data-lang="el"><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">version-control</span> <span class="no">t</span> <span class="c1">;; Use version numbers for backups.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kept-new-versions</span> <span class="mi">10</span> <span class="c1">;; Number of newest versions to keep.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kept-old-versions</span> <span class="mi">0</span> <span class="c1">;; Number of oldest versions to keep.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">delete-old-versions</span> <span class="no">t</span> <span class="c1">;; Don&#39;t ask to delete excess backup versions.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">backup-by-copying</span> <span class="no">t</span><span class="p">)</span> <span class="c1">;; Copy all files, don&#39;t rename them.</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">vc-make-backup-files</span> <span class="no">t</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">;; Default and per-save backups go here:</span> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">backup-directory-alist</span> <span class="o">&#39;</span><span class="p">((</span><span class="s">&#34;&#34;</span> <span class="o">.</span> <span class="s">&#34;~/.emacs.d/backup/per-save&#34;</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">defun</span> <span class="nv">force-backup-of-buffer</span> <span class="p">()</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Make a special &#34;per session&#34; backup at the first save of each</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; emacs session.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">when</span> <span class="p">(</span><span class="nv">not</span> <span class="nv">buffer-backed-up</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Override the default parameters for per-session backups.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">backup-directory-alist</span> <span class="o">&#39;</span><span class="p">((</span><span class="s">&#34;&#34;</span> <span class="o">.</span> <span class="s">&#34;~/.emacs.d/backup/per-session&#34;</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">kept-new-versions</span> <span class="mi">3</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">backup-buffer</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Make a &#34;per save&#34; backup on each save. The first save results in</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; both a per-session and a per-save backup, to keep the numbering</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; of per-save backups consistent.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">buffer-backed-up</span> <span class="no">nil</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">backup-buffer</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">add-hook</span> <span class="ss">&#39;before-save-hook</span> <span class="ss">&#39;force-backup-of-buffer</span><span class="p">)</span> +</span></span></code></pre></div><p>Thanks to that configuration, backups per-save will be created in <code>~/.emacs.d/backup/per-save</code> and backups per-session in <code>~/.emacs.d/backup/per-session</code>.</p>Maintaining dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-maintenance/Mon, 11 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-maintenance/<p>To make it easier managing many dotfiles with <a href="https://www.chezmoi.io/">chezmoi</a>, a shell function similar to the one below can be used:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="k">function</span> m-dotfiles-ok <span class="o">{</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># public</span> +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/zsh --recursive +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/sway --recursive +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/tmux --recursive +</span></span><span class="line"><span class="cl"> chezmoi add .... +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># secrets</span> +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ~/.config/npm/npmrc +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ~/.ssh/id_rsa +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ... +</span></span><span class="line"><span class="cl"><span class="o">}</span> +</span></span></code></pre></div><p>Whenever you feel happy with your current setup, just call <code>m-dotfiles-ok</code> to push changes into the chezmoi source directory. Files will automatically be <a href="../chezmoi-gpg">encrypted</a> with <a href="https://www.gnupg.org/">gpg</a> and <a href="../chezmoi-auto-git">committed/pushed</a> into a <a href="https://git-scm.com/">Git</a> repository if you have done the necessary configuration beforehand.</p>Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/Mon, 20 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-gpg/<p><strong>RECOMMENDATION</strong>: Use <a href="../chezmoi-age">age</a> instead of <code>gpg</code>.</p> +<p><a href="https://www.chezmoi.io/">chezmoi</a> can use various external tools to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. <a href="https://www.gnupg.org/">gpg</a> is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure <code>gpg</code> with <code>chezmoi</code>, just set yourself as the recipient like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">gpg</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;your.name@example.com&#34;</span> +</span></span></code></pre></div><p>Calling <code>chezmoi add --encrypt /path/to/secret</code> will now create encrypt the file with your public key which allows you to decrypt them later with your private key.</p>Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/Mon, 06 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-git/<p><a href="https://www.chezmoi.io/">chezmoi</a> can automatically commit and push changes to your <a href="https://en.wikipedia.org/wiki/dotfile">dotfiles</a> into a (remote) <a href="https://git-scm.com/">Git</a> repository. Enable it with the following snippet in your <code>chezmoi.toml</code></p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">sourceVCS</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoCommit</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoPush</span> <span class="p">=</span> <span class="kc">true</span> +</span></span></code></pre></div><p>Every time you call <code>chezmoi add /path/to/file</code> will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.</p>Waybar on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-waybar/Mon, 23 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-waybar/<p><a href="https://github.com/Alexays/Waybar/">Waybar</a> can be used as a status bar for <a href="https://swaywm.org/">SwayWM</a>. You tell Sway to use it with the following snippet in your Sway configuration:</p> +<pre tabindex="0"><code>bar { + swaybar_command waybar +} +</code></pre><p>Configure Waybar itself in <code>~/.config/waybar/config</code>:</p> +<pre tabindex="0"><code>{ + &#34;layer&#34;: &#34;top&#34;, + &#34;modules-left&#34;: [&#34;sway/workspaces&#34;, &#34;sway/mode&#34;], + &#34;modules-center&#34;: [&#34;sway/window&#34;], + &#34;modules-right&#34;: [&#34;clock&#34;], + &#34;sway/window&#34;: { + &#34;max-length&#34;: 50 + }, + &#34;clock&#34;: { + &#34;format-alt&#34;: &#34;{:%a, %d. %b %H:%M}&#34; + } +} +</code></pre>tmux Status Barhttps://seb.xn--ho-hia.de/posts/tmux-status-bar/Mon, 09 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-status-bar/<p>To see the currently active <a href="https://github.com/tmux/tmux">tmux</a> status bar configuration, call:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ tmux show-options -g <span class="p">|</span> grep status +</span></span></code></pre></div><p>Change on of those values with in the current tmux session:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ tmux set-option status-right <span class="s2">&#34;&#34;</span> +</span></span></code></pre></div><p>Persist the change in your <code>tmux.conf</code> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># disable right side of status bar</span> +</span></span><span class="line"><span class="cl">set-option -g status-right <span class="s2">&#34;&#34;</span> +</span></span></code></pre></div>emacs and systemdhttps://seb.xn--ho-hia.de/posts/emacs-systemd/Mon, 26 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/emacs-systemd/<p>I like to use <a href="https://www.gnu.org/software/emacs/">emacs</a> to edit files in a terminal. It tends to start a little slow, therefore I&rsquo;ve created a <a href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</a> unit to automatically start the emacs daemon and use aliases to connect to the running daemon. The unit looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span> +</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Emacs text editor [%I]</span> +</span></span><span class="line"><span class="cl"><span class="na">Documentation</span><span class="o">=</span><span class="s">info:emacs man:emacs(1) https://gnu.org/software/emacs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Service]</span> +</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">forking</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/emacs --daemon=%i</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStop</span><span class="o">=</span><span class="s">/usr/bin/emacsclient --eval &#34;(kill-emacs)&#34;</span> +</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">SSH_AUTH_SOCK=%t/keyring/ssh</span> +</span></span><span class="line"><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">on-failure</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Install]</span> +</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span> +</span></span></code></pre></div><p>Enable it with <code>systemctl --user enable emacs@user</code> and define any number of aliases to make connecting to the emacs daemon easier:</p>Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/Mon, 12 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-push-only-mirror/<p>In case you want to have push-only mirrors for your <a href="https://git-scm.com/">Git</a> repository, consider adding a special mirror remote like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote add mirrors DISABLED +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git +</span></span></code></pre></div><p>The above will create a new remote called <code>mirrors</code> which has no <code>fetch</code> URL and therefore can only be pushed:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote -v +</span></span><span class="line"><span class="cl">mirrors DISABLED <span class="o">(</span>fetch<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@codeberg.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@gitlab.com:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@bitbucket.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span></code></pre></div><p>Calling <code>git push mirrors main:main</code> will push the local <code>main</code> branch into all defined mirrors.</p>Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/Mon, 28 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-mirror/<p>In case you want to make use of the decentralized nature of <a href="https://git-scm.com/">Git</a>, consider using multiple push targets like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote set-url origin --push --add git@example.com/project.git +</span></span><span class="line"><span class="cl">$ git remote set-url origin --push --add git@another.com/project.git +</span></span></code></pre></div><p>Note that the first call to <code>set-url</code> will overwrite an existing remote creating with <code>git clone</code>. Any additional call will actually recognize the <code>--add</code> option and add the new target to an existing remote.</p> +<h2 id="links">Links</h2> +<ul> +<li><a href="../git-push-only-mirror">push only mirrors</a></li> +<li><a href="../gitlab-distributor">gitlab-distributor</a></li> +</ul>Using multiple clusters with kubectlhttps://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/Mon, 14 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/<p>To connect to multiple <a href="https://kubernetes.io/">Kubernetes</a> clusters with <a href="https://kubernetes.io/docs/reference/kubectl/overview/">kubectl</a>, I like to define aliases like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">rancher</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/rancher.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">work</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/work.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">customer</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/customer.config&#34;</span> +</span></span></code></pre></div><p>Those aliases allow me to write things like <code>rancher get pods --namespace some-namespace</code> without worrying the wrong context is active. Using multiple configurations - one for each cluster - seems to be easier to manage since most clusters allow to download a ready-to-use configuration file. Instead of mangling them together manually, I just specify another alias whenever I get to work with another cluster.</p>Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-encoding/<p><a href="https://maven.apache.org/">Maven</a> projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.reporting.outputEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.reporting.outputEncoding&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +</ul>Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/Mon, 17 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-reproducible/<p>To create <a href="https://reproducible-builds.org/">reproducible builds</a> with <a href="https://maven.apache.org/">Maven</a> projects, it&rsquo;s enough to specify the <code>project.build.outputTimestamp</code> property like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.outputTimestamp&gt;</span>2020<span class="nt">&lt;/project.build.outputTimestamp&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +<li><a href="https://maven.apache.org/guides/mini/guide-reproducible-builds.html">https://maven.apache.org/guides/mini/guide-reproducible-builds.html</a></li> +<li><a href="https://github.com/rodiontsev/maven-build-info-plugin">https://github.com/rodiontsev/maven-build-info-plugin</a></li> +<li><a href="https://github.com/phax/ph-buildinfo-maven-plugin">https://github.com/phax/ph-buildinfo-maven-plugin</a></li> +<li><a href="https://github.com/Zlika/reproducible-build-maven-plugin">https://github.com/Zlika/reproducible-build-maven-plugin</a></li> +</ul>Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/Mon, 03 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/<p>To analyze <a href="https://maven.apache.org/">Maven</a> projects with <a href="https://sonarcloud.io">SonarCloud</a> using <a href="https://github.com/features/actions">GitHub Actions</a>, first create the following <code>settings.xml</code> file:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings</span> <span class="na">xmlns=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xmlns:xsi=</span><span class="s">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xsi:schemaLocation=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0 +</span></span></span><span class="line"><span class="cl"><span class="s"> http://maven.apache.org/xsd/settings-1.0.0.xsd&#34;</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroup&gt;</span>org.sonarsource.scanner.maven<span class="nt">&lt;/pluginGroup&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfile&gt;</span>sonar<span class="nt">&lt;/activeProfile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>sonar<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.host.url&gt;</span>https://sonarcloud.io<span class="nt">&lt;/sonar.host.url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.organization&gt;</span>YOUR_ORG<span class="nt">&lt;/sonar.organization&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.projectKey&gt;</span>YOUR_PROJECT<span class="nt">&lt;/sonar.projectKey&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.login&gt;</span>${env.SONAR_TOKEN}<span class="nt">&lt;/sonar.login&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Finally, add a step to your workflow:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Verify Project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">SONAR_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.SONAR_TOKEN }}</span><span class="w"> +</span></span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/settings.html">https://maven.apache.org/settings.html</a></li> +<li><a href="https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/">https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/</a></li> +</ul>Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/Mon, 19 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-login-shell/<p>To use <a href="https://github.com/tmux/tmux">tmux</a> as your login shell, use <code>chsh</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># list all available shells</span> +</span></span><span class="line"><span class="cl">$ chsh --list-shells +</span></span><span class="line"><span class="cl">/bin/sh +</span></span><span class="line"><span class="cl">/bin/bash +</span></span><span class="line"><span class="cl">/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/sh +</span></span><span class="line"><span class="cl">/usr/bin/bash +</span></span><span class="line"><span class="cl">/usr/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/zsh +</span></span><span class="line"><span class="cl">/bin/zsh +</span></span><span class="line"><span class="cl">/usr/bin/tmux +</span></span><span class="line"><span class="cl">/bin/tmux +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># select login shell</span> +</span></span><span class="line"><span class="cl">$ chsh --shell /usr/bin/tmux +</span></span></code></pre></div>Lock your screen on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenlock/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenlock/<p><a href="https://swaywm.org/">SwayWM</a> users can use <a href="https://github.com/swaywm/swaylock">swaylock</a> to lock their screen. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># lock your screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Ctrl+l <span class="nb">exec</span> swaylock --color <span class="m">000000</span> +</span></span></code></pre></div><p><code>$mod+Ctrl+l</code> will lock your screen and turn it to black. The <code>--color</code> flag allows any color in the form of <code>rrggbb[aa]</code>.</p>Peeking with tmuxhttps://seb.xn--ho-hia.de/posts/tmux-peek/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-peek/<p><a href="https://github.com/tmux/tmux">tmux</a> uses can use the following snippet to peek at files. Place it in your <code>.bashrc</code> or similar file.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">peek<span class="o">()</span> <span class="o">{</span> tmux split-window -p <span class="m">33</span> <span class="s2">&#34;</span><span class="nv">$EDITOR</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span> <span class="o">}</span> +</span></span></code></pre></div><p>Calling <code>peek &lt;file&gt;</code> will open <code>&lt;file&gt;</code> in lower third of tmux window.</p>Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/Mon, 22 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenshots/<p><a href="https://swaywm.org/">SwayWM</a> uses can use a mixture of <a href="https://github.com/emersion/grim">grim</a> and <a href="https://github.com/emersion/slurp">slurp</a> to take screenshots of their desktop. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># take screenshot of currently focused screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Print <span class="nb">exec</span> /usr/bin/grim -o <span class="k">$(</span>swaymsg -t get_outputs <span class="p">|</span> jq -r <span class="s1">&#39;.[] | select(.focused) | .name&#39;</span><span class="k">)</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># take screenshot of selection</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Shift+p <span class="nb">exec</span> /usr/bin/grim -g <span class="s2">&#34;</span><span class="k">$(</span>/usr/bin/slurp<span class="k">)</span><span class="s2">&#34;</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span></code></pre></div>Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/Mon, 08 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-readme/<p><code>README</code> file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a <code>Makefile</code> in the root of your project which contains the exact same instructions. Thanks to <code>make</code>, all your contributors can now use TAB-completion to run any of the pre-defined <code>make</code> targets.</p>Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/Mon, 22 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-schedule-build/<p>To delay the execution of an <a href="https://github.com/features/actions">GitHub Action</a>, use a mixture of the <code>on: schedule: ...</code> configuration, and a <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif">conditional build step</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">schedule</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&lt;CRON&gt;&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Count commits in last week</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">commits</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=count::$(git rev-list --count HEAD --since=&#39;&lt;DATE&gt;&#39;)&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">steps.commits.outputs.count &gt; 0</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">build-project</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;CRON&gt;</code>: cron expression - use <a href="https://crontab.guru/">https://crontab.guru/</a>.</li> +<li><code>&lt;DATE&gt;</code>: Git date expression that matches <code>&lt;CRON&gt;</code>.</li> +</ul>Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/Mon, 08 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-ignore/<p>In case you are using a <code>Makefile</code> to define a complex build step - for example start database, run tests, stop database - consider using the <code>-</code> qualifier in front of your actual build step like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">build</span> +</span></span><span class="line"><span class="cl"><span class="nf">build</span><span class="o">:</span> +</span></span><span class="line"><span class="cl"> start-database +</span></span><span class="line"><span class="cl"> -build-software +</span></span><span class="line"><span class="cl"> stop-database +</span></span></code></pre></div><p>Thanks to <code>-</code>, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.</p>Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/Mon, 25 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-serviceworkers/<p>To use a <a href="https://serviceworke.rs/">serviceworker</a> to cache a <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/javascript&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;js&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">ServiceWorker</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;ServiceWorker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/javascript&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;serviceworker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.serviceworker.js</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">const CACHE = &#39;cache-and-update&#39;; + +self.addEventListener(&#39;install&#39;, (event) =&gt; { + event.waitUntil(precache()); +}); + +self.addEventListener(&#39;fetch&#39;, (event) =&gt; { + event.respondWith(fromCache(event.request)); + event.waitUntil(update(event.request)); +}); + +const precache = async () =&gt; { + const cache = await caches.open(CACHE); + return await cache.addAll([ + {{ range $i, $e := .Site.RegularPages }} + &#39;{{ $.RelPermalink }}&#39;{{ if $i }}, {{ end }} + {{ end }} + ]); +} + +const fromCache = async (request) =&gt; { + const cache = await caches.open(CACHE); + const match = await cache.match(request); + return match || Promise.reject(&#39;no-match&#39;); +} + +const update = async (request) =&gt; { + const cache = await caches.open(CACHE); + const response = await fetch(request); + return await cache.put(request, response); +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://github.com/gohugoio/hugo/issues/5495">https://github.com/gohugoio/hugo/issues/5495</a></li> +<li><a href="https://github.com/wildhaber/offline-first-sw">https://github.com/wildhaber/offline-first-sw</a></li> +<li><a href="https://gohugohq.com/howto/go-offline-with-service-worker/">https://gohugohq.com/howto/go-offline-with-service-worker/</a></li> +</ul>Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/Mon, 11 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-webmanifest/<p>To publish a <a href="https://developer.mozilla.org/en-US/docs/Web/Manifest">web app manifest</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/manifest+json&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;webmanifest&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Webmanifest</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Web App Manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/manifest+json&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.manifest.json</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{ + &#34;name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;short_name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;start_url&#34;: &#34;.&#34;, + &#34;display&#34;: &#34;minimal-ui&#34;, + &#34;background_color&#34;: &#34;#fff&#34;, + &#34;description&#34;: &#34;{{ .Site.Params.description }}&#34; +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://web.dev/add-manifest/">https://web.dev/add-manifest/</a></li> +</ul>Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/Mon, 28 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-bundles/<p><a href="https://gohugo.io/">Hugo</a> allows <a href="https://gohugo.io/hugo-pipes/bundling/">bundling</a> of assets with several built-in functions:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ $normalize := resources.Get &#34;/css/normalize.css&#34; }} +{{ $font := resources.Get &#34;/css/font.css&#34; }} +{{ $header := resources.Get &#34;/css/header.css&#34; }} +{{ $footer := resources.Get &#34;/css/footer.css&#34; }} +{{ $navigation := resources.Get &#34;/css/navigation.css&#34; }} +{{ $navigation_mobile := resources.Get &#34;/css/navigation-mobile.css&#34; }} +{{ $layout := resources.Get &#34;/css/layout.css&#34; }} +{{ $layout_mobile := resources.Get &#34;/css/layout-mobile.css&#34; }} +{{ $syntax := resources.Get &#34;/css/syntax.css&#34; }} +{{ $darkmode := resources.Get &#34;/css/darkmode.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat &#34;css/base.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} +{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat &#34;css/mobile.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +&lt;link href=&#34;{{ $base.Permalink }}&#34; integrity=&#34;{{ $base.Data.Integrity }}&#34; media=&#34;screen&#34; rel=&#34;stylesheet&#34;&gt; +&lt;link href=&#34;{{ $mobile.Permalink }}&#34; integrity=&#34;{{ $mobile.Data.Integrity }}&#34; media=&#34;screen and (max-width: 800px)&#34; rel=&#34;stylesheet&#34;&gt; + +&lt;link href=&#34;{{ $darkmode.Permalink }}&#34; integrity=&#34;{{ $darkmode.Data.Integrity }}&#34; media=&#34;screen and (prefers-color-scheme: dark)&#34; rel=&#34;stylesheet&#34;&gt; +</code></pre>humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/Mon, 14 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-humans/<p>To publish a <a href="http://humanstxt.org/">humans.txt</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;text/plain&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;txt&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Humans</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;text/plain&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.humans.txt</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">/* TEAM */ +{{ range $.Site.Data.contributors }} +{{ .title }}: {{ .first_name }} {{ .last_name }} +Site: {{ .website }} +{{ end }} +</code></pre>FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/Mon, 30 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-foaf/<p>To publish a <a href="http://www.foaf-project.org/">FOAF</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/rdf+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;rdf&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Foaf</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;FOAF&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/rdf+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;foaf&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.foaf.rdf</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">&lt;rdf:RDF xmlns:rdf=&#34;http://www.w3.org/1999/02/22-rdf-syntax-ns#&#34; xmlns:rdfs=&#34;http://www.w3.org/2000/01/rdf-schema#&#34; xmlns:foaf=&#34;http://xmlns.com/foaf/0.1/&#34;&gt; + &lt;foaf:PersonalProfileDocument rdf:about=&#34;&#34;&gt; + &lt;foaf:maker rdf:resource=&#34;#me&#34; /&gt; + &lt;foaf:primaryTopic rdf:resource=&#34;{{ .Site.Title }}&#34; /&gt; + &lt;/foaf:PersonalProfileDocument&gt; + + &lt;foaf:Project rdf:ID=&#34;{{ .Site.Title }}&#34;&gt; + &lt;foaf:name&gt;{{ .Site.Title }}&lt;/foaf:name&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .Site.BaseURL }}&#34; /&gt; + &lt;/foaf:Project&gt; + + {{ range $.Site.Data.contributors }} + &lt;foaf:Person rdf:ID=&#34;{{ .id }}&#34;&gt; + &lt;foaf:name&gt;{{ .first_name }} {{ .last_name }}&lt;/foaf:name&gt; + &lt;foaf:title&gt;{{ .title }}&lt;/foaf:title&gt; + &lt;foaf:givenname&gt;{{ .first_name }}&lt;/foaf:givenname&gt; + &lt;foaf:family_name&gt;{{ .last_name }}&lt;/foaf:family_name&gt; + &lt;foaf:mbox rdf:resource=&#34;mailto:{{ .email }}&#34; /&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .website }}&#34; /&gt; + &lt;/foaf:Person&gt; + {{ end }} +&lt;/rdf:RDF&gt; +</code></pre>Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-atom/<p>To publish Atom feeds for your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/atom+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Atom</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/atom+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/list.atom.xml</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ printf `&lt;?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&gt;` | safeHTML }} +&lt;feed xmlns=&#34;http://www.w3.org/2005/Atom&#34;{{ with site.LanguageCode }} xml:lang=&#34;{{ . }}&#34;{{ end }}&gt; + &lt;generator uri=&#34;https://gohugo.io/&#34; version=&#34;{{ hugo.Version }}&#34;&gt;Hugo&lt;/generator&gt; + {{- $title := site.Title -}} + {{- with .Title -}} + {{- if (not (eq . site.Title)) -}} + {{- $title = printf `%s %s %s` . (i18n &#34;feed_title_on&#34; | default &#34;on&#34;) site.Title -}} + {{- end -}} + {{- end -}} + {{- if .IsTranslated -}} + {{ $title = printf &#34;%s (%s)&#34; $title (index site.Data.i18n.languages .Lang) }} + {{- end -}} + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` $title | safeHTML }} + {{ with (or (.Param &#34;subtitle&#34;) (.Param &#34;tagline&#34;)) }} + {{ printf `&lt;subtitle type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/subtitle&gt;` . | safeHTML }} + {{ end }} + {{ $output_formats := .OutputFormats }} + {{ range $output_formats -}} + {{- $rel := (or (and (eq &#34;atom&#34; (.Name | lower)) &#34;self&#34;) &#34;alternate&#34;) -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=%q type=%q title=%q /&gt;` .Permalink $rel .MediaType.Type .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- range .Translations }} + {{ $output_formats := .OutputFormats }} + {{- $lang := .Lang }} + {{- $langstr := index site.Data.i18n.languages .Lang }} + {{ range $output_formats -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=&#34;alternate&#34; type=%q hreflang=%q title=&#34;[%s] %s&#34; /&gt;` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- end }} + &lt;updated&gt;{{ now.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ with site.Copyright }} + {{- $copyright := replace . &#34;{year}&#34; now.Year -}} {{/* In case the site.copyright uses a special string &#34;{year}&#34; */}} + {{- $copyright = replace $copyright &#34;&amp;copy;&#34; &#34;©&#34; -}} + &lt;rights&gt;{{ $copyright | plainify }}&lt;/rights&gt; + {{- end }} + {{ with .Param &#34;feed&#34; }} + {{/* For this to work, the $icon file should be present in the assets/ directory */}} + {{- $icon := .icon | default &#34;icon.svg&#34; -}} + {{- with resources.Get $icon -}} + &lt;icon&gt;{{ (. | fingerprint).Permalink }}&lt;/icon&gt; + {{- end }} + + {{/* For this to work, the $logo file should be present in the assets/ directory */}} + {{- $logo := .logo | default &#34;logo.svg&#34; -}} + {{- with resources.Get $logo -}} + &lt;logo&gt;{{ (. | fingerprint).Permalink }}&lt;/logo&gt; + {{- end }} + {{ end }} + {{ with site.Author.name -}} + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + {{ with site.Author.email }} + &lt;email&gt;{{ . }}&lt;/email&gt; + {{ end -}} + &lt;/author&gt; + {{- end }} + {{ with site.Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }} + {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}} + {{/* Range through only the pages with a Type in $feed_sections. */}} + {{- $pages := where .RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- if (eq .Kind &#34;home&#34;) -}} + {{- $pages = where site.RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- end -}} + {{/* Remove the pages that have the disable_feed parameter set to true. */}} + {{- $pages = where $pages &#34;.Params.disable_feed&#34; &#34;!=&#34; true -}} + {{- range first $limit $pages }} + {{ $page := . }} + &lt;entry&gt; + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` .Title | safeHTML }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;alternate&#34; type=&#34;text/html&#34; /&gt; + {{- range .Translations }} + {{- $link := printf &#34;%s?utm_source=atom_feed&#34; .Permalink | safeHTML }} + {{- printf `&lt;link href=%q rel=&#34;alternate&#34; type=&#34;text/html&#34; hreflang=%q /&gt;` $link .Lang | safeHTML }} + {{- end }} + {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}} + {{- range first 5 (site.RegularPages.Related .) }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;related&#34; type=&#34;text/html&#34; title=&#34;{{ .Title }}&#34; /&gt; + {{- end }} + {{ with .Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{ with .Params.author -}} + {{- range . -}} &lt;!-- Assuming the author front-matter to be a list --&gt; + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + &lt;/author&gt; + {{- end -}} + {{- end }} + &lt;published&gt;{{ .Date.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/published&gt; + &lt;updated&gt;{{ .Lastmod.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ $description1 := .Description | default &#34;&#34; }} + {{ $description := (cond (eq &#34;&#34; $description1) &#34;&#34; (printf &#34;&lt;blockquote&gt;%s&lt;/blockquote&gt;&#34; ($description1 | markdownify))) }} + {{ printf `&lt;content type=&#34;html&#34;&gt;&lt;![CDATA[%s%s]]&gt;&lt;/content&gt;` $description .Content | safeHTML }} + {{ with site.Taxonomies }} + {{ range $taxo,$_ := . }} &lt;!-- Defaults taxos: &#34;tags&#34;, &#34;categories&#34; --&gt; + {{ with $page.Param $taxo }} + {{ $taxo_list := . }} &lt;!-- $taxo_list will be the tags/categories list --&gt; + {{ with site.GetPage (printf &#34;/%s&#34; $taxo) }} + {{ $taxonomy_page := . }} + {{ range $taxo_list }} &lt;!-- Below, assuming pretty URLs --&gt; + &lt;category scheme=&#34;{{ printf &#34;%s%s&#34; $taxonomy_page.Permalink (. | urlize) }}&#34; term=&#34;{{ (. | urlize) }}&#34; label=&#34;{{ . }}&#34; /&gt; + {{ end }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + &lt;/entry&gt; + {{ end }} +&lt;/feed&gt; +</code></pre>Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-toot/<p>The <a href="https://github.com/rzr/fediverse-action">rzr/fediverse-action</a> action allows to send a <a href="https://joinmastodon.org/">toot</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;NAME&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish Toot</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">rzr/fediverse-action@master</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">access-token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;MESSAGE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_SERVER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;MESSAGE&gt;</code>: Message for the toot.</li> +</ul>Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/Mon, 02 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-email/<p>The <a href="https://github.com/dawidd6/action-send-mail">dawidd6/action-send-mail</a> action allows to send an email in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Send mail</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">dawidd6/action-send-mail@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_address</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SERVER }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_port</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PORT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_USERNAME }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PASSWORD }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">subject</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;SUBJECT&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;BODY&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_RECIPIENT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SENDER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;SUBJECT&gt;</code>: Subject for the email.</li> +<li><code>&lt;BODY&gt;</code>: Body for the email.</li> +</ul> +<p>Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define <code>MAIL_RECIPIENT</code> for each project.</p>Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/<p>The <a href="https://github.com/actions/upload-release-asset">actions/upload-release-asset</a> action allows to upload a <a href="https://developer.github.com/v3/repos/releases/#upload-a-release-asset">release artifact</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload Release Asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">upload_release_asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-release-asset@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">upload_url</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.create_release.outputs.upload_url }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_path</span><span class="p">:</span><span class="w"> </span><span class="l">./some/path/to/file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_name</span><span class="p">:</span><span class="w"> </span><span class="l">public-name-for-file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_content_type</span><span class="p">:</span><span class="w"> </span><span class="l">application/zip</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/Mon, 05 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-release/<p>The <a href="https://github.com/actions/create-release">actions/create-release</a> action allows to create a new <a href="https://help.github.com/en/github/administering-a-repository/about-releases">GitHub releases</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create Release</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/create-release@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">tag_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;TAG&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">release_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RELEASE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">draft</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">prerelease</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Your release text here +</span></span></span><span class="line"><span class="cl"><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Some code block: +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> yaml: +</span></span></span><span class="line"><span class="cl"><span class="sd"> inside: +</span></span></span><span class="line"><span class="cl"><span class="sd"> of: +</span></span></span><span class="line"><span class="cl"><span class="sd"> another: yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;TAG&gt;</code>: The Git tag to create.</li> +<li><code>&lt;RELEASE&gt;</code>: The release name to use.</li> +</ul>Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/Mon, 21 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/<p>The <a href="https://github.com/peaceiris/actions-gh-pages">peaceiris/actions-gh-pages</a> action allows to publish a <a href="https://gohugo.io/">Hugo</a> site in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Website</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-gh-pages@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">github_token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">publish_dir</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PUBLISH_DIR&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">force_orphan</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">cname</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;CNAME&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;PUBLISH_DIR&gt;</code>: The file system location of the built site.</li> +<li><code>&lt;CNAME&gt;</code>: The <code>CNAME</code> of your custom domain.</li> +</ul>Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/Mon, 07 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions/cache</a> action allows to cache artifacts in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Maven artifacts</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">~/.m2/repository</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-maven-${{ hashFiles(&#39;**/pom.xml&#39;) }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> ${{ runner.os }}-maven-</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/Mon, 24 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions-hugo</a> action allows to use a specific <a href="https://gohugo.io/">Hugo</a> version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup hugo</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-hugo@v2</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hugo-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;HUGO_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;HUGO_VERSION&gt;</code>: The <a href="https://github.com/gohugoio/hugo/releases">released versions</a> or use <code>latest</code> to always use the latest version of Hugo.</li> +</ul>Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/Mon, 10 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/<p>The <a href="https://github.com/actions/setup-java">setup-java</a> action allows to use a specific Java version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up JDK &lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-java@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">java-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;JDK_VERSION&gt;</code>: The required Java version for your project.</li> +</ul>XDG Base Directory Specificationhttps://seb.xn--ho-hia.de/posts/xdg-dot-files/Mon, 27 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/xdg-dot-files/<p>The <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html">XDG Base Directory Specification</a> has been around for a while, yet not every software has adopted it yet. Here is an incomplete list of fixes:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># use existing env variables or define new</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CACHE_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CACHE_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.cache&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_DIRS</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CONFIG_DIRS</span><span class="o">=</span><span class="s2">&#34;/etc/xdg&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CONFIG_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_DATA_DIRS</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_DATA_DIRS</span><span class="o">=</span><span class="s2">&#34;/usr/local/share:/usr/share&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_DATA_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/share&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># gradle</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">GRADLE_USER_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">/gradle&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># httpie</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">HTTPIE_CONFIG_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">/httpie&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># npm</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">NPM_CONFIG_USERCONFIG</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">/npm/npmrc&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">npm_config_cache</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CACHE_HOME</span><span class="s2">/npm&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># password-store</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PASSWORD_STORE_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">/password-store&#34;</span> +</span></span></code></pre></div><p>To make your own software XDG-aware, consider using the <a href="https://github.com/dirs-dev">dirs-dev</a> or <a href="https://github.com/shibukawa/configdir">configdir</a> libraries.</p>Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/Mon, 29 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-help/<p>Use the following <a href="https://www.perl.org/">Perl</a> snippet to automatically generate help output for your <code>Makefile</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nv">GREEN</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 2<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">WHITE</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 7<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">YELLOW</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 3<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">RESET</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm sgr0<span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nv">HELP_FUN</span> <span class="o">=</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> %help<span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">while</span><span class="o">(</span>&lt;&gt;<span class="o">)</span> <span class="o">{</span> push @<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span><span class="m">2</span> // <span class="s1">&#39;targets&#39;</span><span class="o">}}</span>, <span class="o">[</span><span class="nv">$$</span>1, <span class="nv">$$</span>3<span class="o">]</span> <span class="k">if</span> /^<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">)</span><span class="se">\s</span>*:.*<span class="se">\#\#</span><span class="o">(</span>?:@<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">))</span>?<span class="se">\s</span><span class="o">(</span>.*<span class="o">)</span><span class="nv">$$</span>/ <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;usage: make [target]\n\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>sort keys %help<span class="o">)</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WHITE</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_:</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>@<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span>_<span class="o">}})</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="nv">$$sep</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span> x <span class="o">(</span><span class="m">32</span> - length <span class="nv">$$</span>_-&gt;<span class="o">[</span>0<span class="o">])</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34; </span><span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[0]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="nv">$$</span><span class="s2">sep</span><span class="si">${</span><span class="nv">GREEN</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[1]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;\n&#34;</span><span class="p">;</span> <span class="o">}</span> +</span></span></code></pre></div><p>To use <code>HELP_FUN</code>, add the following <code>help</code> target to the same <code>Makefile</code>:</p>Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/Mon, 15 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/short-git-clones/<p>In case you don&rsquo;t want to write <code>git clone git@github.com:orga/repo.git</code> all the time, consider using a custom SSH configuration (<code>~/.ssh/config</code>) like this:</p> +<pre tabindex="0"><code>Host github + HostName github.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITHUB&gt; + +Host gitlab + HostName gitlab.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITLAB&gt; + +Host bitbucket + HostName bitbucket.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-BITBUCKET&gt; + +Host codeberg + HostName codeberg.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-CODEBERG&gt; +</code></pre><p>Once configured, you can now write:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git clone github:orga/repo +</span></span><span class="line"><span class="cl">$ git clone gitlab:orga/repo +</span></span><span class="line"><span class="cl">$ git clone bitbucket:orga/repo +</span></span><span class="line"><span class="cl">$ git clone codeberg:orga/repo +</span></span></code></pre></div><p>In case you are working with many repositories inside a single organization, consider adding the following Git configuration (<code>$XDG_CONFIG_HOME/git/config</code> or <code>~/.gitconfig</code>):</p>Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/Thu, 11 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/<p>In case you are into <a href="https://calver.org/">calver</a> or have another reason to create a timestamp with <a href="https://github.com/features/actions">GitHub Actions</a>, do the following:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create release version</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;ID&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=&lt;NAME&gt;::$(date +&#39;%Y.%m.%d-%H%M%S&#39;)&#34;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;ID&gt;</code>: The unique ID of the timestamp step.</li> +<li><code>&lt;NAME&gt;</code>: The name of the created timestamp.</li> +</ul> +<p>The special syntax <code>::set-output name=&lt;NAME&gt;::</code> declares that the output of the command (<code>echo</code>) should be saved in a variable called <code>&lt;NAME&gt;</code>. Together with the <code>&lt;ID&gt;</code> of the pipeline step, this value can be referenced with the expression <code>${{ steps.&lt;ID&gt;.outputs.&lt;NAME&gt; }}</code> in the following steps of your pipeline.</p> \ No newline at end of file diff --git a/categories/website/atom.xml b/categories/website/atom.xml new file mode 100644 index 000000000..53a02d0e9 --- /dev/null +++ b/categories/website/atom.xml @@ -0,0 +1,281 @@ +HugoWebsite on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/categories/website/Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/2021-01-25T00:00:00+00:002023-01-06T16:40:24+01:00To use a serviceworker to cache a Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/javascript"]
+  suffixes = ["js"]
+[outputFormats.ServiceWorker]
+  name = "ServiceWorker"
+  mediaType = "application/javascript"
+  baseName = "serviceworker"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.serviceworker.js with the following content:

+
const CACHE = 'cache-and-update';
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(precache());
+});
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(fromCache(event.request));
+  event.waitUntil(update(event.request));
+});
+
+const precache = async () => {
+    const cache = await caches.open(CACHE);
+    return await cache.addAll([
+        {{ range $i, $e := .Site.RegularPages }}
+        '{{ $.RelPermalink }}'{{ if $i }}, {{ end }}
+        {{ end }}
+    ]);
+}
+
+const fromCache = async (request) => {
+    const cache = await caches.open(CACHE);
+    const match = await cache.match(request);
+    return match || Promise.reject('no-match');
+}
+
+const update = async (request) => {
+    const cache = await caches.open(CACHE);
+    const response = await fetch(request);
+    return await cache.put(request, response);
+}
+
+ +]]>
Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/2021-01-11T00:00:00+00:002023-01-06T16:40:24+01:00To publish a web app manifest document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/manifest+json"]
+  suffixes = ["webmanifest"]
+[outputFormats.Webmanifest]
+  name = "Web App Manifest"
+  mediaType = "application/manifest+json"
+  baseName = "manifest"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.manifest.json with the following content:

+
{
+  "name": "{{ .Site.Title }}",
+  "short_name": "{{ .Site.Title }}",
+  "start_url": ".",
+  "display": "minimal-ui",
+  "background_color": "#fff",
+  "description": "{{ .Site.Params.description }}"
+}
+
+ +]]>
Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/2020-12-28T00:00:00+00:002023-01-06T15:27:21+01:00Hugo allows bundling of assets with several built-in functions:

+
{{ $normalize := resources.Get "/css/normalize.css" }}
+{{ $font := resources.Get "/css/font.css" }}
+{{ $header := resources.Get "/css/header.css" }}
+{{ $footer := resources.Get "/css/footer.css" }}
+{{ $navigation := resources.Get "/css/navigation.css" }}
+{{ $navigation_mobile := resources.Get "/css/navigation-mobile.css" }}
+{{ $layout := resources.Get "/css/layout.css" }}
+{{ $layout_mobile := resources.Get "/css/layout-mobile.css" }}
+{{ $syntax := resources.Get "/css/syntax.css" }}
+{{ $darkmode := resources.Get "/css/darkmode.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat "css/base.css" | resources.Minify | resources.Fingerprint "sha512" }}
+{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat "css/mobile.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+<link href="{{ $base.Permalink }}" integrity="{{ $base.Data.Integrity }}" media="screen" rel="stylesheet">
+<link href="{{ $mobile.Permalink }}" integrity="{{ $mobile.Data.Integrity }}" media="screen and (max-width: 800px)" rel="stylesheet">
+
+<link href="{{ $darkmode.Permalink }}" integrity="{{ $darkmode.Data.Integrity }}" media="screen and (prefers-color-scheme: dark)" rel="stylesheet">
+
]]>
humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/2020-12-14T00:00:00+00:002023-01-06T16:40:24+01:00To publish a humans.txt document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."text/plain"]
+  suffixes = ["txt"]
+[outputFormats.Humans]
+  name = "Humans"
+  mediaType = "text/plain"
+  baseName = "humans"
+  isPlainText = true
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.humans.txt with the following content:

+
/* TEAM */
+{{ range $.Site.Data.contributors }}
+{{ .title }}: {{ .first_name }} {{ .last_name }}
+Site: {{ .website }}
+{{ end }}
+
]]>
FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/2020-11-30T00:00:00+00:002023-01-06T16:40:24+01:00To publish a FOAF document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/rdf+xml"]
+  suffixes = ["rdf"]
+[outputFormats.Foaf]
+  name = "FOAF"
+  mediaType = "application/rdf+xml"
+  baseName = "foaf"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.foaf.rdf with the following content:

+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/">
+  <foaf:PersonalProfileDocument rdf:about="">
+    <foaf:maker rdf:resource="#me" />
+    <foaf:primaryTopic rdf:resource="{{ .Site.Title }}" />
+  </foaf:PersonalProfileDocument>
+
+  <foaf:Project rdf:ID="{{ .Site.Title }}">
+    <foaf:name>{{ .Site.Title }}</foaf:name>
+    <foaf:homepage rdf:resource="{{ .Site.BaseURL }}" />
+  </foaf:Project>
+
+  {{ range $.Site.Data.contributors }}
+  <foaf:Person rdf:ID="{{ .id }}">
+    <foaf:name>{{ .first_name }} {{ .last_name }}</foaf:name>
+    <foaf:title>{{ .title }}</foaf:title>
+    <foaf:givenname>{{ .first_name }}</foaf:givenname>
+    <foaf:family_name>{{ .last_name }}</foaf:family_name>
+    <foaf:mbox rdf:resource="mailto:{{ .email }}" />
+    <foaf:homepage rdf:resource="{{ .website }}" />
+  </foaf:Person>
+  {{ end }}
+</rdf:RDF>
+
]]>
Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/2020-11-16T00:00:00+00:002023-01-06T16:40:24+01:00To publish Atom feeds for your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/atom+xml"]
+  suffixes = ["xml"]
+[outputFormats.Atom]
+  name = "Atom"
+  mediaType = "application/atom+xml"
+  baseName = "atom"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/list.atom.xml with the following content:

+
{{ printf `<?xml version="1.0" encoding="utf-8"?>` | safeHTML }}
+<feed xmlns="http://www.w3.org/2005/Atom"{{ with site.LanguageCode }} xml:lang="{{ . }}"{{ end }}>
+    <generator uri="https://gohugo.io/" version="{{ hugo.Version }}">Hugo</generator>
+    {{- $title := site.Title -}}
+    {{- with .Title -}}
+        {{- if (not (eq . site.Title)) -}}
+            {{- $title = printf `%s %s %s` . (i18n "feed_title_on" | default "on") site.Title -}}
+        {{- end -}}
+    {{- end -}}
+    {{- if .IsTranslated -}}
+        {{ $title = printf "%s (%s)" $title (index site.Data.i18n.languages .Lang) }}
+    {{- end -}}
+    {{ printf `<title type="html"><![CDATA[%s]]></title>` $title | safeHTML }}
+    {{ with (or (.Param "subtitle") (.Param "tagline")) }}
+        {{ printf `<subtitle type="html"><![CDATA[%s]]></subtitle>` . | safeHTML }}
+    {{ end }}
+    {{ $output_formats := .OutputFormats }}
+    {{ range $output_formats -}}
+        {{- $rel := (or (and (eq "atom" (.Name | lower)) "self") "alternate") -}}
+        {{ with $output_formats.Get .Name }}
+            {{ printf `<link href=%q rel=%q type=%q title=%q />` .Permalink $rel .MediaType.Type .Name | safeHTML }}
+        {{- end -}}
+    {{- end }}
+    {{- range .Translations }}
+        {{ $output_formats := .OutputFormats }}
+        {{- $lang := .Lang }}
+        {{- $langstr := index site.Data.i18n.languages .Lang }}
+        {{ range $output_formats -}}
+            {{ with $output_formats.Get .Name }}
+                {{ printf `<link href=%q rel="alternate" type=%q hreflang=%q title="[%s] %s" />` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }}
+            {{- end -}}
+        {{- end }}
+    {{- end }}
+    <updated>{{ now.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+    {{ with site.Copyright }}
+        {{- $copyright := replace . "{year}" now.Year -}} {{/* In case the site.copyright uses a special string "{year}" */}}
+        {{- $copyright = replace $copyright "&copy;" "©" -}}
+        <rights>{{ $copyright | plainify }}</rights>
+    {{- end }}
+    {{ with .Param "feed" }}
+        {{/* For this to work, the $icon file should be present in the assets/ directory */}}
+        {{- $icon := .icon | default "icon.svg" -}}
+        {{- with resources.Get $icon -}}
+            <icon>{{ (. | fingerprint).Permalink }}</icon>
+        {{- end }}
+
+        {{/* For this to work, the $logo file should be present in the assets/ directory */}}
+        {{- $logo := .logo | default "logo.svg" -}}
+        {{- with resources.Get $logo -}}
+            <logo>{{ (. | fingerprint).Permalink }}</logo>
+        {{- end }}
+    {{ end }}
+    {{ with site.Author.name -}}
+        <author>
+            <name>{{ . }}</name>
+            {{ with site.Author.email }}
+                <email>{{ . }}</email>
+            {{ end -}}
+        </author>
+    {{- end }}
+    {{ with site.Params.id }}
+        <id>{{ . | plainify }}</id>
+    {{ else }}
+        <id>{{ .Permalink }}</id>
+    {{ end }}
+    {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }}
+    {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}}
+    {{/* Range through only the pages with a Type in $feed_sections. */}}
+    {{- $pages := where .RegularPages "Type" "in" $feed_sections -}}
+    {{- if (eq .Kind "home") -}}
+        {{- $pages = where site.RegularPages "Type" "in" $feed_sections -}}
+    {{- end -}}
+    {{/* Remove the pages that have the disable_feed parameter set to true. */}}
+    {{- $pages = where $pages ".Params.disable_feed" "!=" true -}}
+    {{- range first $limit $pages }}
+        {{ $page := . }}
+        <entry>
+            {{ printf `<title type="html"><![CDATA[%s]]></title>` .Title | safeHTML }}
+            <link href="{{ .Permalink }}?utm_source=atom_feed" rel="alternate" type="text/html" />
+            {{- range .Translations }}
+                {{- $link := printf "%s?utm_source=atom_feed" .Permalink | safeHTML }}
+                {{- printf `<link href=%q rel="alternate" type="text/html" hreflang=%q />` $link .Lang | safeHTML }}
+            {{- end }}
+            {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}}
+            {{- range first 5 (site.RegularPages.Related .) }}
+                <link href="{{ .Permalink }}?utm_source=atom_feed" rel="related" type="text/html" title="{{ .Title }}" />
+            {{- end }}
+            {{ with .Params.id }}
+                <id>{{ . | plainify }}</id>
+            {{ else }}
+                <id>{{ .Permalink }}</id>
+            {{ end }}
+            {{ with .Params.author -}}
+                {{- range . -}} <!-- Assuming the author front-matter to be a list -->
+                    <author>
+                        <name>{{ . }}</name>
+                    </author>
+                {{- end -}}
+            {{- end }}
+            <published>{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</published>
+            <updated>{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+            {{ $description1 := .Description | default "" }}
+            {{ $description := (cond (eq "" $description1) "" (printf "<blockquote>%s</blockquote>" ($description1 | markdownify))) }}
+            {{ printf `<content type="html"><![CDATA[%s%s]]></content>` $description .Content | safeHTML }}
+            {{ with site.Taxonomies }}
+                {{ range $taxo,$_ := . }} <!-- Defaults taxos: "tags", "categories" -->
+                    {{ with $page.Param $taxo }}
+                        {{ $taxo_list := . }} <!-- $taxo_list will be the tags/categories list -->
+                        {{ with site.GetPage (printf "/%s" $taxo) }}
+                            {{ $taxonomy_page := . }}
+                            {{ range $taxo_list }} <!-- Below, assuming pretty URLs -->
+                                <category scheme="{{ printf "%s%s" $taxonomy_page.Permalink (. | urlize) }}" term="{{ (. | urlize) }}" label="{{ . }}" />
+                            {{ end }}
+                        {{ end }}
+                    {{ end }}
+                {{ end }}
+            {{ end }}
+        </entry>
+    {{ end }}
+</feed>
+
]]>
\ No newline at end of file diff --git a/categories/website/index.html b/categories/website/index.html new file mode 100644 index 000000000..2e16ef294 --- /dev/null +++ b/categories/website/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Website

Category: Website

\ No newline at end of file diff --git a/categories/website/index.xml b/categories/website/index.xml new file mode 100644 index 000000000..839de37fe --- /dev/null +++ b/categories/website/index.xml @@ -0,0 +1,279 @@ +Website on Sebastian Hoßhttps://seb.xn--ho-hia.de/categories/website/Recent content in Website on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/Mon, 25 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-serviceworkers/<p>To use a <a href="https://serviceworke.rs/">serviceworker</a> to cache a <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/javascript&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;js&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">ServiceWorker</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;ServiceWorker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/javascript&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;serviceworker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.serviceworker.js</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">const CACHE = &#39;cache-and-update&#39;; + +self.addEventListener(&#39;install&#39;, (event) =&gt; { + event.waitUntil(precache()); +}); + +self.addEventListener(&#39;fetch&#39;, (event) =&gt; { + event.respondWith(fromCache(event.request)); + event.waitUntil(update(event.request)); +}); + +const precache = async () =&gt; { + const cache = await caches.open(CACHE); + return await cache.addAll([ + {{ range $i, $e := .Site.RegularPages }} + &#39;{{ $.RelPermalink }}&#39;{{ if $i }}, {{ end }} + {{ end }} + ]); +} + +const fromCache = async (request) =&gt; { + const cache = await caches.open(CACHE); + const match = await cache.match(request); + return match || Promise.reject(&#39;no-match&#39;); +} + +const update = async (request) =&gt; { + const cache = await caches.open(CACHE); + const response = await fetch(request); + return await cache.put(request, response); +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://github.com/gohugoio/hugo/issues/5495">https://github.com/gohugoio/hugo/issues/5495</a></li> +<li><a href="https://github.com/wildhaber/offline-first-sw">https://github.com/wildhaber/offline-first-sw</a></li> +<li><a href="https://gohugohq.com/howto/go-offline-with-service-worker/">https://gohugohq.com/howto/go-offline-with-service-worker/</a></li> +</ul>Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/Mon, 11 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-webmanifest/<p>To publish a <a href="https://developer.mozilla.org/en-US/docs/Web/Manifest">web app manifest</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/manifest+json&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;webmanifest&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Webmanifest</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Web App Manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/manifest+json&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.manifest.json</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{ + &#34;name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;short_name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;start_url&#34;: &#34;.&#34;, + &#34;display&#34;: &#34;minimal-ui&#34;, + &#34;background_color&#34;: &#34;#fff&#34;, + &#34;description&#34;: &#34;{{ .Site.Params.description }}&#34; +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://web.dev/add-manifest/">https://web.dev/add-manifest/</a></li> +</ul>Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/Mon, 28 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-bundles/<p><a href="https://gohugo.io/">Hugo</a> allows <a href="https://gohugo.io/hugo-pipes/bundling/">bundling</a> of assets with several built-in functions:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ $normalize := resources.Get &#34;/css/normalize.css&#34; }} +{{ $font := resources.Get &#34;/css/font.css&#34; }} +{{ $header := resources.Get &#34;/css/header.css&#34; }} +{{ $footer := resources.Get &#34;/css/footer.css&#34; }} +{{ $navigation := resources.Get &#34;/css/navigation.css&#34; }} +{{ $navigation_mobile := resources.Get &#34;/css/navigation-mobile.css&#34; }} +{{ $layout := resources.Get &#34;/css/layout.css&#34; }} +{{ $layout_mobile := resources.Get &#34;/css/layout-mobile.css&#34; }} +{{ $syntax := resources.Get &#34;/css/syntax.css&#34; }} +{{ $darkmode := resources.Get &#34;/css/darkmode.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat &#34;css/base.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} +{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat &#34;css/mobile.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +&lt;link href=&#34;{{ $base.Permalink }}&#34; integrity=&#34;{{ $base.Data.Integrity }}&#34; media=&#34;screen&#34; rel=&#34;stylesheet&#34;&gt; +&lt;link href=&#34;{{ $mobile.Permalink }}&#34; integrity=&#34;{{ $mobile.Data.Integrity }}&#34; media=&#34;screen and (max-width: 800px)&#34; rel=&#34;stylesheet&#34;&gt; + +&lt;link href=&#34;{{ $darkmode.Permalink }}&#34; integrity=&#34;{{ $darkmode.Data.Integrity }}&#34; media=&#34;screen and (prefers-color-scheme: dark)&#34; rel=&#34;stylesheet&#34;&gt; +</code></pre>humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/Mon, 14 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-humans/<p>To publish a <a href="http://humanstxt.org/">humans.txt</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;text/plain&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;txt&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Humans</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;text/plain&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.humans.txt</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">/* TEAM */ +{{ range $.Site.Data.contributors }} +{{ .title }}: {{ .first_name }} {{ .last_name }} +Site: {{ .website }} +{{ end }} +</code></pre>FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/Mon, 30 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-foaf/<p>To publish a <a href="http://www.foaf-project.org/">FOAF</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/rdf+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;rdf&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Foaf</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;FOAF&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/rdf+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;foaf&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.foaf.rdf</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">&lt;rdf:RDF xmlns:rdf=&#34;http://www.w3.org/1999/02/22-rdf-syntax-ns#&#34; xmlns:rdfs=&#34;http://www.w3.org/2000/01/rdf-schema#&#34; xmlns:foaf=&#34;http://xmlns.com/foaf/0.1/&#34;&gt; + &lt;foaf:PersonalProfileDocument rdf:about=&#34;&#34;&gt; + &lt;foaf:maker rdf:resource=&#34;#me&#34; /&gt; + &lt;foaf:primaryTopic rdf:resource=&#34;{{ .Site.Title }}&#34; /&gt; + &lt;/foaf:PersonalProfileDocument&gt; + + &lt;foaf:Project rdf:ID=&#34;{{ .Site.Title }}&#34;&gt; + &lt;foaf:name&gt;{{ .Site.Title }}&lt;/foaf:name&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .Site.BaseURL }}&#34; /&gt; + &lt;/foaf:Project&gt; + + {{ range $.Site.Data.contributors }} + &lt;foaf:Person rdf:ID=&#34;{{ .id }}&#34;&gt; + &lt;foaf:name&gt;{{ .first_name }} {{ .last_name }}&lt;/foaf:name&gt; + &lt;foaf:title&gt;{{ .title }}&lt;/foaf:title&gt; + &lt;foaf:givenname&gt;{{ .first_name }}&lt;/foaf:givenname&gt; + &lt;foaf:family_name&gt;{{ .last_name }}&lt;/foaf:family_name&gt; + &lt;foaf:mbox rdf:resource=&#34;mailto:{{ .email }}&#34; /&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .website }}&#34; /&gt; + &lt;/foaf:Person&gt; + {{ end }} +&lt;/rdf:RDF&gt; +</code></pre>Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-atom/<p>To publish Atom feeds for your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/atom+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Atom</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/atom+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/list.atom.xml</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ printf `&lt;?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&gt;` | safeHTML }} +&lt;feed xmlns=&#34;http://www.w3.org/2005/Atom&#34;{{ with site.LanguageCode }} xml:lang=&#34;{{ . }}&#34;{{ end }}&gt; + &lt;generator uri=&#34;https://gohugo.io/&#34; version=&#34;{{ hugo.Version }}&#34;&gt;Hugo&lt;/generator&gt; + {{- $title := site.Title -}} + {{- with .Title -}} + {{- if (not (eq . site.Title)) -}} + {{- $title = printf `%s %s %s` . (i18n &#34;feed_title_on&#34; | default &#34;on&#34;) site.Title -}} + {{- end -}} + {{- end -}} + {{- if .IsTranslated -}} + {{ $title = printf &#34;%s (%s)&#34; $title (index site.Data.i18n.languages .Lang) }} + {{- end -}} + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` $title | safeHTML }} + {{ with (or (.Param &#34;subtitle&#34;) (.Param &#34;tagline&#34;)) }} + {{ printf `&lt;subtitle type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/subtitle&gt;` . | safeHTML }} + {{ end }} + {{ $output_formats := .OutputFormats }} + {{ range $output_formats -}} + {{- $rel := (or (and (eq &#34;atom&#34; (.Name | lower)) &#34;self&#34;) &#34;alternate&#34;) -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=%q type=%q title=%q /&gt;` .Permalink $rel .MediaType.Type .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- range .Translations }} + {{ $output_formats := .OutputFormats }} + {{- $lang := .Lang }} + {{- $langstr := index site.Data.i18n.languages .Lang }} + {{ range $output_formats -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=&#34;alternate&#34; type=%q hreflang=%q title=&#34;[%s] %s&#34; /&gt;` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- end }} + &lt;updated&gt;{{ now.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ with site.Copyright }} + {{- $copyright := replace . &#34;{year}&#34; now.Year -}} {{/* In case the site.copyright uses a special string &#34;{year}&#34; */}} + {{- $copyright = replace $copyright &#34;&amp;copy;&#34; &#34;©&#34; -}} + &lt;rights&gt;{{ $copyright | plainify }}&lt;/rights&gt; + {{- end }} + {{ with .Param &#34;feed&#34; }} + {{/* For this to work, the $icon file should be present in the assets/ directory */}} + {{- $icon := .icon | default &#34;icon.svg&#34; -}} + {{- with resources.Get $icon -}} + &lt;icon&gt;{{ (. | fingerprint).Permalink }}&lt;/icon&gt; + {{- end }} + + {{/* For this to work, the $logo file should be present in the assets/ directory */}} + {{- $logo := .logo | default &#34;logo.svg&#34; -}} + {{- with resources.Get $logo -}} + &lt;logo&gt;{{ (. | fingerprint).Permalink }}&lt;/logo&gt; + {{- end }} + {{ end }} + {{ with site.Author.name -}} + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + {{ with site.Author.email }} + &lt;email&gt;{{ . }}&lt;/email&gt; + {{ end -}} + &lt;/author&gt; + {{- end }} + {{ with site.Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }} + {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}} + {{/* Range through only the pages with a Type in $feed_sections. */}} + {{- $pages := where .RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- if (eq .Kind &#34;home&#34;) -}} + {{- $pages = where site.RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- end -}} + {{/* Remove the pages that have the disable_feed parameter set to true. */}} + {{- $pages = where $pages &#34;.Params.disable_feed&#34; &#34;!=&#34; true -}} + {{- range first $limit $pages }} + {{ $page := . }} + &lt;entry&gt; + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` .Title | safeHTML }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;alternate&#34; type=&#34;text/html&#34; /&gt; + {{- range .Translations }} + {{- $link := printf &#34;%s?utm_source=atom_feed&#34; .Permalink | safeHTML }} + {{- printf `&lt;link href=%q rel=&#34;alternate&#34; type=&#34;text/html&#34; hreflang=%q /&gt;` $link .Lang | safeHTML }} + {{- end }} + {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}} + {{- range first 5 (site.RegularPages.Related .) }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;related&#34; type=&#34;text/html&#34; title=&#34;{{ .Title }}&#34; /&gt; + {{- end }} + {{ with .Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{ with .Params.author -}} + {{- range . -}} &lt;!-- Assuming the author front-matter to be a list --&gt; + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + &lt;/author&gt; + {{- end -}} + {{- end }} + &lt;published&gt;{{ .Date.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/published&gt; + &lt;updated&gt;{{ .Lastmod.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ $description1 := .Description | default &#34;&#34; }} + {{ $description := (cond (eq &#34;&#34; $description1) &#34;&#34; (printf &#34;&lt;blockquote&gt;%s&lt;/blockquote&gt;&#34; ($description1 | markdownify))) }} + {{ printf `&lt;content type=&#34;html&#34;&gt;&lt;![CDATA[%s%s]]&gt;&lt;/content&gt;` $description .Content | safeHTML }} + {{ with site.Taxonomies }} + {{ range $taxo,$_ := . }} &lt;!-- Defaults taxos: &#34;tags&#34;, &#34;categories&#34; --&gt; + {{ with $page.Param $taxo }} + {{ $taxo_list := . }} &lt;!-- $taxo_list will be the tags/categories list --&gt; + {{ with site.GetPage (printf &#34;/%s&#34; $taxo) }} + {{ $taxonomy_page := . }} + {{ range $taxo_list }} &lt;!-- Below, assuming pretty URLs --&gt; + &lt;category scheme=&#34;{{ printf &#34;%s%s&#34; $taxonomy_page.Permalink (. | urlize) }}&#34; term=&#34;{{ (. | urlize) }}&#34; label=&#34;{{ . }}&#34; /&gt; + {{ end }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + &lt;/entry&gt; + {{ end }} +&lt;/feed&gt; +</code></pre> \ No newline at end of file diff --git a/css/darkmode.min.2a5ba1ebf79d23ac725522a486af2ffebf831a4a634fc1471a4b0007daa25afb4417f20dc4cf0e2c1eeb8eb704183160ff9aaf84e060180993fe1e010ac6907f.css b/css/darkmode.min.2a5ba1ebf79d23ac725522a486af2ffebf831a4a634fc1471a4b0007daa25afb4417f20dc4cf0e2c1eeb8eb704183160ff9aaf84e060180993fe1e010ac6907f.css new file mode 100644 index 000000000..bef9bbc52 --- /dev/null +++ b/css/darkmode.min.2a5ba1ebf79d23ac725522a486af2ffebf831a4a634fc1471a4b0007daa25afb4417f20dc4cf0e2c1eeb8eb704183160ff9aaf84e060180993fe1e010ac6907f.css @@ -0,0 +1 @@ +body{background-color:#000;color:#fff}#header a{color:#fff}.svgimg{filter:invert(1)}a,a>span{color:#268bd2}li::marker{color:#fff}.post-titel,.post-titel>a,.post-titel>a:visited{color:#fff}.post-tag{color:gray} \ No newline at end of file diff --git a/css/desktop.min.5d7960c466db691c7fe9df667675aa312241bb90e879e462f75e2502350908820cf7477e12664f246157a8a37449baa5b2923035845e419b20b1cfdb177a0de8.css b/css/desktop.min.5d7960c466db691c7fe9df667675aa312241bb90e879e462f75e2502350908820cf7477e12664f246157a8a37449baa5b2923035845e419b20b1cfdb177a0de8.css new file mode 100644 index 000000000..3d41df01c --- /dev/null +++ b/css/desktop.min.5d7960c466db691c7fe9df667675aa312241bb90e879e462f75e2502350908820cf7477e12664f246157a8a37449baa5b2923035845e419b20b1cfdb177a0de8.css @@ -0,0 +1 @@ +/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}body{font-family:dejavu sans,Georgia,sans-serifs,serif;color:#000}.subtitle{font-size:small;color:gray}blockquote{font-style:italic}blockquote>p::before,blockquote>p::after{content:'”'}code{font-weight:700}.post-titel>a{text-decoration:none}.post-titel>h1{color:#000;margin-bottom:0}.post-titel,.post-tag,.post-titel>a{color:#000;text-decoration:none}.post-tag{color:gray}.icon{margin:0}#header{display:flex;align-items:center;align-self:center;border:dotted gray;border-width:0 0 2px;margin-left:15px;margin-right:15px;max-width:100vw}#header a{text-decoration:none;color:#000}#projectname{flex-grow:1;display:flex}#projectname #sitemaplink{margin-top:4px;margin-left:10px;mask:url(/images/sitemap.svg)no-repeat center}#languages{font-size:25px;border:dotted gray;border-width:0 2px 0 0}#languages a{margin-right:15px}#actions a:first-child{margin-left:15px}#actions a:not(:last-child){margin-right:15px}#footer{border:dotted gray;border-width:2px 0 0;font-size:small;margin-right:15px;margin-left:15px;text-align:center}#footer a{text-decoration:none;color:#000}#navigation{border:dotted gray}a.active,li.active{font-weight:700}#site{display:grid}#header{grid-area:header}#navigation{grid-area:navigation}#content{grid-area:content;margin-left:15px;margin-right:15px}#footer{grid-area:footer}#content pre{overflow-x:auto;max-width:90vw;padding-left:5px;padding-top:5px;padding-bottom:10px}#post-content img{width:80vw;margin-top:24px;margin-bottom:24px}#home-divider{border:0 dotted gray;border-top-width:2px;margin-top:24px}#content>.post-titel{margin-bottom:0}.bg{color:#f8f8f2;background-color:#272822}.chroma{color:#f8f8f2;background-color:#272822}.chroma .x{}.chroma .err{color:#960050;background-color:#1e0010}.chroma .cl{}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{background-color:#ffc}.chroma .lnt{white-space:pre;user-select:none;margin-right:.4em;padding:0 .4em;color:#7f7f7f}.chroma .ln{white-space:pre;user-select:none;margin-right:.4em;padding:0 .4em;color:#7f7f7f}.chroma .line{display:flex}.chroma .k{color:#66d9ef}.chroma .kc{color:#66d9ef}.chroma .kd{color:#66d9ef}.chroma .kn{color:#f92672}.chroma .kp{color:#66d9ef}.chroma .kr{color:#66d9ef}.chroma .kt{color:#66d9ef}.chroma .n{}.chroma .na{color:#a6e22e}.chroma .nb{}.chroma .bp{}.chroma .nc{color:#a6e22e}.chroma .no{color:#66d9ef}.chroma .nd{color:#a6e22e}.chroma .ni{}.chroma .ne{color:#a6e22e}.chroma .nf{color:#a6e22e}.chroma .fm{}.chroma .nl{}.chroma .nn{}.chroma .nx{color:#a6e22e}.chroma .py{}.chroma .nt{color:#f92672}.chroma .nv{}.chroma .vc{}.chroma .vg{}.chroma .vi{}.chroma .vm{}.chroma .l{color:#ae81ff}.chroma .ld{color:#e6db74}.chroma .s{color:#e6db74}.chroma .sa{color:#e6db74}.chroma .sb{color:#e6db74}.chroma .sc{color:#e6db74}.chroma .dl{color:#e6db74}.chroma .sd{color:#e6db74}.chroma .s2{color:#e6db74}.chroma .se{color:#ae81ff}.chroma .sh{color:#e6db74}.chroma .si{color:#e6db74}.chroma .sx{color:#e6db74}.chroma .sr{color:#e6db74}.chroma .s1{color:#e6db74}.chroma .ss{color:#e6db74}.chroma .m{color:#ae81ff}.chroma .mb{color:#ae81ff}.chroma .mf{color:#ae81ff}.chroma .mh{color:#ae81ff}.chroma .mi{color:#ae81ff}.chroma .il{color:#ae81ff}.chroma .mo{color:#ae81ff}.chroma .o{color:#f92672}.chroma .ow{color:#f92672}.chroma .p{}.chroma .c{color:#75715e}.chroma .ch{color:#75715e}.chroma .cm{color:#75715e}.chroma .c1{color:#75715e}.chroma .cs{color:#75715e}.chroma .cp{color:#75715e}.chroma .cpf{color:#75715e}.chroma .g{}.chroma .gd{color:#f92672}.chroma .ge{font-style:italic}.chroma .gr{}.chroma .gh{}.chroma .gi{color:#a6e22e}.chroma .go{}.chroma .gp{}.chroma .gs{font-weight:700}.chroma .gu{color:#75715e}.chroma .gt{}.chroma .gl{}.chroma .w{}#navigation{border-width:0 2px 0 0;margin-left:0;margin-right:0;margin-bottom:15px;padding-right:15px}.mobileonly,.mobile-and-tablet{display:none;visibility:hidden}#site{grid-template-rows:100px auto 100px;grid-template-columns:250px 1fr;grid-template-areas:"header header" "navigation content" "navigation footer"}#header{visibility:visible}#content img{max-width:calc(90vw - 250px)}#content pre{max-width:calc(90vw - 250px)}.highlight{width:80ch;margin:auto} \ No newline at end of file diff --git a/css/mobile.min.e14dc54fd7b9d2dea52a0580221a956079e9f0dbb1583eb2c3f19ab416911abcc496c1a81fe403d98380b4105b9a64ccddc0f3182b6174274a173e9dcd5c7c83.css b/css/mobile.min.e14dc54fd7b9d2dea52a0580221a956079e9f0dbb1583eb2c3f19ab416911abcc496c1a81fe403d98380b4105b9a64ccddc0f3182b6174274a173e9dcd5c7c83.css new file mode 100644 index 000000000..606e939bc --- /dev/null +++ b/css/mobile.min.e14dc54fd7b9d2dea52a0580221a956079e9f0dbb1583eb2c3f19ab416911abcc496c1a81fe403d98380b4105b9a64ccddc0f3182b6174274a173e9dcd5c7c83.css @@ -0,0 +1 @@ +/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}body{font-family:dejavu sans,Georgia,sans-serifs,serif;color:#000}.subtitle{font-size:small;color:gray}blockquote{font-style:italic}blockquote>p::before,blockquote>p::after{content:'”'}code{font-weight:700}.post-titel>a{text-decoration:none}.post-titel>h1{color:#000;margin-bottom:0}.post-titel,.post-tag,.post-titel>a{color:#000;text-decoration:none}.post-tag{color:gray}.icon{margin:0}#header{display:flex;align-items:center;align-self:center;border:dotted gray;border-width:0 0 2px;margin-left:15px;margin-right:15px;max-width:100vw}#header a{text-decoration:none;color:#000}#projectname{flex-grow:1;display:flex}#projectname #sitemaplink{margin-top:4px;margin-left:10px;mask:url(/images/sitemap.svg)no-repeat center}#languages{font-size:25px;border:dotted gray;border-width:0 2px 0 0}#languages a{margin-right:15px}#actions a:first-child{margin-left:15px}#actions a:not(:last-child){margin-right:15px}#footer{border:dotted gray;border-width:2px 0 0;font-size:small;margin-right:15px;margin-left:15px;text-align:center}#footer a{text-decoration:none;color:#000}#navigation{border:dotted gray}a.active,li.active{font-weight:700}#site{display:grid}#header{grid-area:header}#navigation{grid-area:navigation}#content{grid-area:content;margin-left:15px;margin-right:15px}#footer{grid-area:footer}#content pre{overflow-x:auto;max-width:90vw;padding-left:5px;padding-top:5px;padding-bottom:10px}#post-content img{width:80vw;margin-top:24px;margin-bottom:24px}#home-divider{border:0 dotted gray;border-top-width:2px;margin-top:24px}#content>.post-titel{margin-bottom:0}.bg{color:#f8f8f2;background-color:#272822}.chroma{color:#f8f8f2;background-color:#272822}.chroma .x{}.chroma .err{color:#960050;background-color:#1e0010}.chroma .cl{}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{background-color:#ffc}.chroma .lnt{white-space:pre;user-select:none;margin-right:.4em;padding:0 .4em;color:#7f7f7f}.chroma .ln{white-space:pre;user-select:none;margin-right:.4em;padding:0 .4em;color:#7f7f7f}.chroma .line{display:flex}.chroma .k{color:#66d9ef}.chroma .kc{color:#66d9ef}.chroma .kd{color:#66d9ef}.chroma .kn{color:#f92672}.chroma .kp{color:#66d9ef}.chroma .kr{color:#66d9ef}.chroma .kt{color:#66d9ef}.chroma .n{}.chroma .na{color:#a6e22e}.chroma .nb{}.chroma .bp{}.chroma .nc{color:#a6e22e}.chroma .no{color:#66d9ef}.chroma .nd{color:#a6e22e}.chroma .ni{}.chroma .ne{color:#a6e22e}.chroma .nf{color:#a6e22e}.chroma .fm{}.chroma .nl{}.chroma .nn{}.chroma .nx{color:#a6e22e}.chroma .py{}.chroma .nt{color:#f92672}.chroma .nv{}.chroma .vc{}.chroma .vg{}.chroma .vi{}.chroma .vm{}.chroma .l{color:#ae81ff}.chroma .ld{color:#e6db74}.chroma .s{color:#e6db74}.chroma .sa{color:#e6db74}.chroma .sb{color:#e6db74}.chroma .sc{color:#e6db74}.chroma .dl{color:#e6db74}.chroma .sd{color:#e6db74}.chroma .s2{color:#e6db74}.chroma .se{color:#ae81ff}.chroma .sh{color:#e6db74}.chroma .si{color:#e6db74}.chroma .sx{color:#e6db74}.chroma .sr{color:#e6db74}.chroma .s1{color:#e6db74}.chroma .ss{color:#e6db74}.chroma .m{color:#ae81ff}.chroma .mb{color:#ae81ff}.chroma .mf{color:#ae81ff}.chroma .mh{color:#ae81ff}.chroma .mi{color:#ae81ff}.chroma .il{color:#ae81ff}.chroma .mo{color:#ae81ff}.chroma .o{color:#f92672}.chroma .ow{color:#f92672}.chroma .p{}.chroma .c{color:#75715e}.chroma .ch{color:#75715e}.chroma .cm{color:#75715e}.chroma .c1{color:#75715e}.chroma .cs{color:#75715e}.chroma .cp{color:#75715e}.chroma .cpf{color:#75715e}.chroma .g{}.chroma .gd{color:#f92672}.chroma .ge{font-style:italic}.chroma .gr{}.chroma .gh{}.chroma .gi{color:#a6e22e}.chroma .go{}.chroma .gp{}.chroma .gs{font-weight:700}.chroma .gu{color:#75715e}.chroma .gt{}.chroma .gl{}.chroma .w{}#navigation{border-width:2px 0 0;margin-left:15px;margin-right:15px}#navigation li{padding-top:.6rem}.menudivider{margin-top:16px;margin-left:-12px}.menutitle{margin-top:0;margin-bottom:0}#site{grid-template-rows:auto auto 100px auto;grid-template-columns:1fr;grid-template-areas:"content" "navigation" "footer" "header"}#header{visibility:hidden;display:none} \ No newline at end of file diff --git a/css/tablet.min.8aba29eb35692e2677e7c26e41408d9ed27ed94e71fe6634ad016f9e91a712f9e1c53317bdaf09aff9d1acc503c9dca0b3c3dc5d3a5f4c287d7ebd48eef1640a.css b/css/tablet.min.8aba29eb35692e2677e7c26e41408d9ed27ed94e71fe6634ad016f9e91a712f9e1c53317bdaf09aff9d1acc503c9dca0b3c3dc5d3a5f4c287d7ebd48eef1640a.css new file mode 100644 index 000000000..705cadbd0 --- /dev/null +++ b/css/tablet.min.8aba29eb35692e2677e7c26e41408d9ed27ed94e71fe6634ad016f9e91a712f9e1c53317bdaf09aff9d1acc503c9dca0b3c3dc5d3a5f4c287d7ebd48eef1640a.css @@ -0,0 +1 @@ +/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}body{font-family:dejavu sans,Georgia,sans-serifs,serif;color:#000}.subtitle{font-size:small;color:gray}blockquote{font-style:italic}blockquote>p::before,blockquote>p::after{content:'”'}code{font-weight:700}.post-titel>a{text-decoration:none}.post-titel>h1{color:#000;margin-bottom:0}.post-titel,.post-tag,.post-titel>a{color:#000;text-decoration:none}.post-tag{color:gray}.icon{margin:0}#header{display:flex;align-items:center;align-self:center;border:dotted gray;border-width:0 0 2px;margin-left:15px;margin-right:15px;max-width:100vw}#header a{text-decoration:none;color:#000}#projectname{flex-grow:1;display:flex}#projectname #sitemaplink{margin-top:4px;margin-left:10px;mask:url(/images/sitemap.svg)no-repeat center}#languages{font-size:25px;border:dotted gray;border-width:0 2px 0 0}#languages a{margin-right:15px}#actions a:first-child{margin-left:15px}#actions a:not(:last-child){margin-right:15px}#footer{border:dotted gray;border-width:2px 0 0;font-size:small;margin-right:15px;margin-left:15px;text-align:center}#footer a{text-decoration:none;color:#000}#navigation{border:dotted gray}a.active,li.active{font-weight:700}#site{display:grid}#header{grid-area:header}#navigation{grid-area:navigation}#content{grid-area:content;margin-left:15px;margin-right:15px}#footer{grid-area:footer}#content pre{overflow-x:auto;max-width:90vw;padding-left:5px;padding-top:5px;padding-bottom:10px}#post-content img{width:80vw;margin-top:24px;margin-bottom:24px}#home-divider{border:0 dotted gray;border-top-width:2px;margin-top:24px}#content>.post-titel{margin-bottom:0}.bg{color:#f8f8f2;background-color:#272822}.chroma{color:#f8f8f2;background-color:#272822}.chroma .x{}.chroma .err{color:#960050;background-color:#1e0010}.chroma .cl{}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0}.chroma .hl{background-color:#ffc}.chroma .lnt{white-space:pre;user-select:none;margin-right:.4em;padding:0 .4em;color:#7f7f7f}.chroma .ln{white-space:pre;user-select:none;margin-right:.4em;padding:0 .4em;color:#7f7f7f}.chroma .line{display:flex}.chroma .k{color:#66d9ef}.chroma .kc{color:#66d9ef}.chroma .kd{color:#66d9ef}.chroma .kn{color:#f92672}.chroma .kp{color:#66d9ef}.chroma .kr{color:#66d9ef}.chroma .kt{color:#66d9ef}.chroma .n{}.chroma .na{color:#a6e22e}.chroma .nb{}.chroma .bp{}.chroma .nc{color:#a6e22e}.chroma .no{color:#66d9ef}.chroma .nd{color:#a6e22e}.chroma .ni{}.chroma .ne{color:#a6e22e}.chroma .nf{color:#a6e22e}.chroma .fm{}.chroma .nl{}.chroma .nn{}.chroma .nx{color:#a6e22e}.chroma .py{}.chroma .nt{color:#f92672}.chroma .nv{}.chroma .vc{}.chroma .vg{}.chroma .vi{}.chroma .vm{}.chroma .l{color:#ae81ff}.chroma .ld{color:#e6db74}.chroma .s{color:#e6db74}.chroma .sa{color:#e6db74}.chroma .sb{color:#e6db74}.chroma .sc{color:#e6db74}.chroma .dl{color:#e6db74}.chroma .sd{color:#e6db74}.chroma .s2{color:#e6db74}.chroma .se{color:#ae81ff}.chroma .sh{color:#e6db74}.chroma .si{color:#e6db74}.chroma .sx{color:#e6db74}.chroma .sr{color:#e6db74}.chroma .s1{color:#e6db74}.chroma .ss{color:#e6db74}.chroma .m{color:#ae81ff}.chroma .mb{color:#ae81ff}.chroma .mf{color:#ae81ff}.chroma .mh{color:#ae81ff}.chroma .mi{color:#ae81ff}.chroma .il{color:#ae81ff}.chroma .mo{color:#ae81ff}.chroma .o{color:#f92672}.chroma .ow{color:#f92672}.chroma .p{}.chroma .c{color:#75715e}.chroma .ch{color:#75715e}.chroma .cm{color:#75715e}.chroma .c1{color:#75715e}.chroma .cs{color:#75715e}.chroma .cp{color:#75715e}.chroma .cpf{color:#75715e}.chroma .g{}.chroma .gd{color:#f92672}.chroma .ge{font-style:italic}.chroma .gr{}.chroma .gh{}.chroma .gi{color:#a6e22e}.chroma .go{}.chroma .gp{}.chroma .gs{font-weight:700}.chroma .gu{color:#75715e}.chroma .gt{}.chroma .gl{}.chroma .w{}#navigation{border-width:2px 0 0;margin-left:15px;margin-right:15px}#navigation li{padding-top:.6rem}.mobileonly{display:none;visibility:hidden}.menutitle{margin-top:0;margin-bottom:0}#site{grid-template-rows:100px auto auto 100px;grid-template-areas:"header" "content" "navigation" "footer"}#header{visibility:visible} \ No newline at end of file diff --git a/favicon-128.png b/favicon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..6335e338b80aaa5aab3a2c30af99c9ca53c3a2d0 GIT binary patch literal 37424 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 Eg4mA41poj5 literal 0 HcmV?d00001 diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..63cc2aaeeb93a97c00ad7465ee2a8864f7756e7a GIT binary patch literal 67646 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 + + + + + + + Sebastian Hoß + + + + + + Sebastian Hoß + Mr + sebhoss + Sebastian + Hoß + + + + + diff --git a/humans.txt b/humans.txt new file mode 100644 index 000000000..2b991e776 --- /dev/null +++ b/humans.txt @@ -0,0 +1,5 @@ +/* TEAM */ + +Mr: Sebastian Hoß +Site: seb.xn--ho-hia.de + diff --git a/images/cc0.png b/images/cc0.png new file mode 100644 index 0000000000000000000000000000000000000000..62d704904b03d6d519edecac82613b238b426166 GIT binary patch literal 1066 zcmb_b`!m}I0RNWJdhfQAF3g!$iX}@t{e?+NdT(z@yvIYRa4tFgy z-sH(9x}GsNX#(V6+GIPiQ3+uv6iTI1O-@d_y1F_zI2;)CL?V$!qphs0w6?aA$z(hp z&t|g?2E+35az;jmLZLW5J_bQhCX?y)dbL{Zu&|)fXv)jWPfkvx zQmIa>*_6vM7)*VAy;v+(Y=T2WLsTktdU|?eV`Fb`4~xaFt*y<@&MK8kKA(?3AjZeX z6$+5SUzPY(Mi9}LcTdULQ2m}HY z3XP7ACXqC$vy z7=!D`U)8rV_>T6#JHU-}?6O%ui{u9SraU@ z_tWUoG|Mu_k)$t@omrnCmgqi6-dI6pC&Ejjh^Sd;Z*ng?7r$TeOH?!_TEM zTC#XI;~)R)&E)!LjJAF7M`Vl4JvX550TC8a041M(|W znB%~@tNZ0WR|auG3I$c|@B`akf|C`*GBYA4_whj*se7QC2fyXDsKYRPUwIJwjf-yR zhvvU0*ChE)VS+A}=)bo{0;q0 z?!kH)&B)Cw4qsiXaM5Zb)s4+YesRChg)iQIabgkRE8Omdnqf=_0B~V3pwt C$5iqF literal 0 HcmV?d00001 diff --git a/images/email.svg b/images/email.svg new file mode 100644 index 000000000..f4c5ba07e --- /dev/null +++ b/images/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/git-distributor.svg b/images/git-distributor.svg new file mode 100644 index 000000000..1cd116534 --- /dev/null +++ b/images/git-distributor.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + GitHub + + Codeberg + + BitBucket + + + + Gitlab + + + + + + + + + + diff --git a/images/github.svg b/images/github.svg new file mode 100644 index 000000000..4c4382f3d --- /dev/null +++ b/images/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/images/gitlab-mirror-settings.png b/images/gitlab-mirror-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..149c3c5e68d17f6be7e1bf53e0145c1812615407 GIT binary patch literal 39421 zcmd?RWmuJMyEUqhg@K?FDrpds(g*^AN(x9xgCZRw-JsYYB1j`0N=r8wgp^1(C`@XI-#u(?g=98-mQpEd?@7uO*8?lV^CFO0~ z_6%>^ww;vdAN%yb{_4fHZS>n@E{Ur;znmU$(jn{G{I#X& z<9+AOF7o}^HT#|%pJtCGwRp<6OtmEWLc^fC`^ZvFW((C#w!MShk9X}#pr?3Byq|?R zmW(flo+hztV}r}T+4H-={wbfgJ{8ekGcV5x+c_FLIxdTt3`)f*r|iTX{CUNa>|VR| zpI^4ka=c&s&oAS|H(dYo^Wwv^BLDf>^wc?uE;&Y;27mKYnazXlQQka?<`;|J2VfO*O^f z)~#EnrmtrUg0?@9O=@+b`u7iFhZB>MjEs#ZOUapc7#kahgoLc!s-8OepWyyN6Zrf& z{c>#9wTy`H@bK*H>LD>PG59Swn1RSUJKJWaH-Bj$zDwdi+3~hiVp=NeDzkY< z>g78XIk*$WD_2xgRD;VEMJ|5)H&&gQ$S5d$if=t`sieU@T6Cl`5J}(;Zrq5gh*WX? zuN^Eq!p+USQ=Gn~sjsTLyGSeF{QcHUzpT%H((;azgq~hBJ&}Pbr8O}R56{;v$6E}S z|LazS#i`GQWG-4Cvw7qrTp0LY54P?9&e~9Bc6O_l_{$=0%hR11ebYU;Bg4as11C3B zQdGsZe%n=gZU{L{^4{t%di(Z*M2vy<=N zwY_S_tjZEx(f8B#$&)Ae&k>_Xm=s>}m^}N@u)6v^#_30Yisp}riJl^xo0?w(8RW{! z%GzJ(mO2h(-+c6A%9S-r(8i!A$7oV!|Ni}AVq!;*91-E)8(fX67L}CL)YPOhnS6SA z&SlPIPVUuPfmZhy z7Z<%YSEcm*#7E~T=+4W?$h00UrzcvO9qLlQ`S0ydsQx(XaplU>A1w>dpFe-47&BRM zTTV`n{lzR}liyq8yU*;!oLgI4h3v<_{C)lSp|>gTrb|IVv3vJcrQMmK{eM4cJk$2k z#4holOG_QuV-sz!^v&vS);{aNogC*jd|X^y?AqGXQ>+jrNnWczp{k-wTr9;@<+Pm79((CtJ?YnpH z5PR4qhpEnv|K35>`m-MQb`!@ZBvf}b$B2>a+m};V*tHqbS7=>ZTbsr-)s>Z{%A#Kq zZ`>5YXVE$B5V5wLqLfhcS}9?1V4tdN3HABCd-oRJ8b}RW4$9!rEwP_yy*!lhcU>-w zs+@T-D0uFi43GF!;EH6gSRtp$f`ipE9 zzBg0qe;*s86?SZ_tz|lY{`{#^7fJ6QIwi@bnRDr7WLVhvw{MAhA4eNP+1c2ty5i#E z9v!49aal0d(Rmpbz5aW30Z-U&sb^^T4U1;oUo?l%3JMA;D?4vzxA2gdN=Q&}ajL8N z-8=CW#aC_(iZP-yV`IsB*x}V+R&_(U5Ra1mlpO7^m2g=EJCbH~ef`n>`==Qh$3{mr zwY2!HdfQuC=5m`wk4%-Ry1SPZ78X`kRw}1lk&(&H%S%_!(rbjLuqtequoV2yA zt<-5YGgHiK^S8}N%|wo2%Da1e(xj*cOB^g_2g|Fwu=aaMXv?tZxhBoFwzfOPHA)>b z($dn#4ogT#e2NjHBPIQb2MY=!yLt1bg~b%M@tly*^6ZfQ`lH}XkN5ZYPQFyQd-rbb zGn)N-_dfCWZ*FKvQ%k!R$#1o`JTo;t%|=zJ>PsbXOQ+a&ba|%F#>OTkCB>Uw0}o@? zTGi6h($SGGes6aP+cyNhr>7^P_KA;=th{{Fr%#z$`Kb>cT>kKoSUb%S3BZU~kCFm($?|y*Led*oZ3Mktp-Rai$m1>-XsH-*{ztAbTvixv_D&D=S9m3LPaS zB^g;F!|$&S1{-no2Z`OJ)b*T4_RL3JY_jx~$j;?TDwe2ZqNK#HwW%2Ov(9So{$C`!iHbf3( zyDW@7e@?3Df_TL1!iBe9TkHDj>RtGEv!0Q#O!Mm1>xPCw0Rh?9-~H1g|3KM`Q z8zrY+y3zTkZLbuq2g}ZJa41}h23`mWp*VKT1s{tzY`it$1xLn0a4R4Gi z)Sn&Wk(AT77AHlKz?1MeOtzy%hGvTJ^78WWIn)Q8(Td(8A|{UFHEX@Mha`{fd*P7h z`byKsw%FVHRIf5cDHW*=txuVXns6C{A+F`>qvq+ zOYlgozmtC=X-i85XrOX}HnsOyB%dMu%fjGR39vMhVJ7 z25?xAg>TYn76$|bOl;{Rs|+20NRqFFA3k&_ZlnYiF594bpRT#6`>)$`!`0Os#r8wK zmwhSB25+Wa%h1Zu$~RP0|MvYmr)~-F*|S%#onvFOTwk7vkmKN=3Qb9wFp2TF_CJ?4 zQNYE;#hV^oZWd2!Ami@-d!W=w?Dx-$P4%0bo0G9+0Rc)w6&@XO%ys}F!H$9C7xR*n zlaa<+-mVoD6*;$h4Z1l51PXz6Cf_490UhgIw+mQwT7G$Z*N;kopPSpq&#(OUSMT(` z*;KVOYHI4^=q|MET@PHdu@f^xm0oDe=IyToqM|%d2C;Z`j#Vhrp1!HE=dKPwec6$0R-|Akpc2w8aS`Ss|M*P+#qoyu1YM{`(fjCCZM3apS4PC*T zus#TrwC~17Mop0dvmXOasimqaQ6?uQN?SYa_5Pf1(S?>NU_IF1-p(s5ypCTG_Dgef zdk-Ao<>o%lXCAcN%#iTo9EaSzC_2ZJBTVLAv#m*T=uL?XeCF*(&pY2Pb)3N>qF=gW zsc^;7@88o{on2f!7F(n)K0MG;@oj0UtD&S^TT4qzQ#0w+tI%P$Y_BcP9)5dU+X#Lu zEnf=uo#G#xn=8>rN*tzwWRnC7V`IOd&nzr2he-ZZj6Q#+e8d3l5d8*~tSePbE=iez zi3zwyr`)xpt4rYAIhT*mPh>4#`BxWMj8}d*Q9YiB@(s+`KG%l5prA?)WqdZ?o1d1H zlyvph4OfRtSw;;Z`tEa=fq3}b_E;#d+z`N13p-9fKXDOvma5tG;lss{F@wf1E3w|{?iAo>z975{J4D0cslsZ0bM`q3@KO-WTxj|-x0Ypts?@m(7r ztVP8N_yJf->tQLWT6XOM={;wnxQ*&JH`e(QRK1bEl-9 zNxMa8YiRtQ_1XejPzmks?d8zQ``F*Fv!?MD3xObIX|PC4!F4M)mLmlvGe358IE;Ue zBjm-H^W5ShdRKCK?&>od;r2cE=!tgk-i@w&DCUe(q@c}k2)j1ASqB3^q{!_rZ-Flo z8Cu=Q@g^#M%d4~hAn@=Oz#H4e@fM)28#itsU^+MB>Sb@=egn{$_w56TSbu?4B=32` z2gtl&ln>{=>`D(LRaaeIT~&3d;eE%DFT&_blu$=?byEAeSX(NCm9+mDpbG-G;{~owsPtKSQ?7 z808~^GV}j5z->m=9UXl9{EokVenqS5aMB*s3Fe;gJDOZrP~d4c7`eD;Z#HY|V6S|< zh|LZw+h6Hb>Fe9TdbuPcJ^diH;92lIgPV(&FJA`qeIAl$)#v!mdsoCEJrk4fbuXCY zZ1^NgKJWNN&nFf5#5XCDXpkL`(j)q7Gqc=A` z)W0sT)W93`xtOI~hgg10k`wl^Fc~HJ3vmkk}ujqS;VIdEV`<&5hQgvgrmjRn^4s6n)iK z3Q>CHu4e1!jVArYHlHg*L_~m0E9LHY8L9ExT8P<_mP1!kj1o#}V8Q)6O?PLbjh1~9 zB*tF`IyPo$=8hp|c+)yEb=@Z0Q(6O&II>Je!Y6rna=SZ@9Y5{}jDkxf-8%bc;hCdY z)iVW68I0GrC9>Fr=@D2WrNa#r?I>X z)}ln1IRop;MNk1_Ljqx6?W){Vv*b4yC#~_PL2C`v~0 zr4)`&c6|2i8N;re%Q?cN*y({12i6o@0D7@sUv`*18M_b^A=_oEYigEi9BS7i`1;VH zLph{|ASC&088<#WOfpa42ge51`>gyGY1LW>Jw>{-v7u~b(5+R5YpIL^eV=o}^C>74 zTgwZ}H<~@3)@{jtArBp9det0XNUklT1Q(dKNeT(@^4cSv03kr!d&MMHpFDl)jwY6u z2m+xs+3+J^e7^iBvUAElUi}5l=^U!v%KZHNtvqUujsC7y+%r|Atp>60K214y zlrP98-h4|T$J102#CFWrRdCpVYPt1;WPz2@qb6t?Md*kIeGB6)2`IIoL)Lu-GY$nc zX{|b;%gwJ{ewHn!DKSnA=-Wj~lN^2^k?lC6r5WR@1nGZnq!z?N#Adka&Ye45ny3Co zJ&SS@NgF}@X)O}~(VpyTeJTK&fKtsdTN5O7cS&xDnJQByBtYjyx&)xS1^*X^GKI{z z>1oS}L_p5v>DAGe$n_*DDGF*YhKLc`2GizCs!6is6N#2~bv2aiXO6kbkn@s6(u%lJ z3p-k(`9<8M^iAu-8pn#?mIy7mD|hkj!>xC)~StFETQ6Ooxt+&gNqe zIEoJgbl!LG2>q5Nn1z*fDpcR|7{8@PD2J|)(~t9(-EWX>p=A3W`}*2BImJL?AnbMA z#Yfm1Vqiz$^avL=KAo={Z)|F6sE`+GqdnK5K%vgg%*+gq^@@5eiBY(#JzY~Jgj>%S z?cY2rR^%C*Z$rZm<&OucG>zm-sFCFStk=?ifir9Y=Yni8F)=l;Bqk?+0@y;u;*=j< zTr;G56H9nqXz-(M;#8=1=i~MAqo$!j2hSYo1CHg}udAyICg3(Ve3^uV#BBH!pT53+ zl5#Q1%f6#8{-!~Y1b`R*p&ZG8tRqAcI=@OYlt!&`*L->+Ty7mVku+OhS6A${wK-hv zo8%I9>LlTHUPJ^P?nJ|0Tvo*S=a={IC2U$heq?51BJ^~?%DLs`&5e~INJA$qG*nep zuV4QHR(5LUfTSb%D+iWYMR`c+djoz=DHr9Vr)P@j&al zk9$n{u!!OX=0zjy8046g1v&v;YpJ~?=fu?KjL`zMY;qss1+{$Ei)*nhp41J@tw3IiY z+;nw*6oO}q$YEWf2r0r_Lr%*@=)d$?H8;KW2oH%{UFD;+Fg1nnh}R-0GGv^QswYQ4 z3S0M)(4OxucPn(>LH#FP%kC0r|H_aTFpLK@9z-vmAVi}A$Q+6ix*hnv-hwXffpX`& zrlt&Jd}n7TQ0?I0AY@+|Y3VTFiGWj5iCpo!iH}1Ah=b6vWA}kKkQv{+kyca`19kz6 zhq8rW&N`Qvp`1bhslmY=@t1u;r%i9&%2Z_`p%ZlnHo+aE3~hn=x2{2uA-lN=(ZGX5^sp0q?&iI z$jLQexg9QT0&#-#deftUNYE68$|@>?`4V)Q!C3Jf&@qLDOS39BT_H(((^u8h(2BZi zT=PQqqxVg8W<&~($3A#a{rJ$S&7~}_z23K{I@z0UWw8Xy#$7EcEhRb5`x;OVvjm`g z9mx8ET6z~Q+_9&wc=_^WjMt_n>3vk5+5X}Mz)TaL-- zk-W6@!>3OQopE|@TJAml zBx1kcyubAYMP5P3W%KtcxBde^zoyBKw93^nDzh8r&~~-7!n6KPQ_9-kKiI#Aytb=L zT`L3e+Yri$a32~PYHQ=+;6T(kc+(%C6D)-~ntqQ&^f~#nqg)bCUr3j7TAbjB zd;OaE#0hb`A6R?yu1t1TYF^Xlza)+?7rU?KgdN!$+S)8+Tgr@!`-&ZQFgq~#@GOvh zYok0=3;=3XMSj!Iv54z6Q&mm#-n^9bk)MRjQ{Jg!cMT-JKFew}6%Z=t^8(tgTz&SB zsj0M3E*{8ntio1SPz9oXPVqr=jXCd<)2)e}ctOm;A}qd=EY!{OA1}Z}n-s;v&6&a> zA6;~az$Wy;&h^>KEH`09&l%xH!x7pkX@0}B<=RtZ`yN2C&a3L{D^1is1bw=Y?VKV& zr(DbXR)8UY`+4k*dZx}9e{y2l^BUKbOKASil31R;c+m!hhmq(#U?l~IPFQsGEfW(c z4rPIplzoLcm_He4h;ehzC*dN2TnBw=cI9?kmKPKh4DX-Vv3+t*+--3@ZZ!l9wqD36 zUc*`@OfLjuoa&++652HWTm6C)h8h~(*hr&+m|fz69&3w8kVFRT5uQS`xG<~#z|pf8 zgu=tZifl(^o9LY{U8-7JnqqyUyX7wt_ndf?pl$;61h?-;1&8p7@yTO<&hqxLrbxQ; z&h)H{D=VQhrmU=KNEI15OH0c{r#Cyr1#f@3WBj@D&70d$O>>^Df?mB4ttCBlXb#M- zs=7M3{Uc;OsLsIbH0rt^qra6vZ(9DXaI@xVK=hQIogEl^A6kHBK7?ajUq%j+GViJ0 zQ4`1pIp==?cB6K;%!h=99i0o{N&!XI2ViWFr=R%T<4w;@=7U~a;p+Bho!?HaVIO~x zOFjMAz^3sgRw+ReqhtI%7dQ9XTrHiENLvzb+a=$sv#v@?0hVt*;F`$!E70yh{^>jx z0|0IA-TQ#=z@7MgXHB{?qcAtD?mdA2@*bd5+eTb8>RB-}(}wGCUmg2yh>aFDhJ@kh{HkW^IOdmkf*y4WH794I+&&7wYX55)s)zY~<7?>=ZwW zg1e@Z!f(;Z=H=xzLDQZj7wYQf29Vp=+lwM7dHJ%7n_ELkIDUObeNI_x9#{fx9JiZ5 z7#Wv30SsF~tV++wfT#z#_x*xOxKIB%17{;b)*KP-%G?vD=2gZ7WP2osSWX+QeJ85N;INq z9}FZEGC;Jp18ty4@z8`^%@o6VxVZ@+s>o&}@gnb~%a^SNO4J%k$C_iZHnca!kXmcd z`b)u)%x-9Cgz@GFeEj_RGvv-QXHtfbuXtUNm+#8Z7Qh^SB8&0op1}iJ8%B{_H0C>6m7)9YFs}+=;J%?V_)Y#|~MMX``Hj`y#XqdPv z{fM3j`O7-+m3n<#SS(R4c`I)L&-d}T3Ug@pxuFLvwl!qXFeu>mWR zi=BpLhkAN?aN}~d-!B2+V;#j@7suyssqH!{WdE9j&af_!0h&SDon6uV+L-1;dO{H`iExLec~+Xy#Rn=bEfF z+J6sB6Ju}BFhEp!ix1Z?H#53ES|5zrD(gZu1x89#2{GJ8vS8W`?hbzfE7o#!bd+7E z=qv`Cz}b`+ze9xyVGw(+%o1E3A|)xg&-*$YGYWU7zJ5JHB!MDIXd-?s`x$E2^n;HU z%SuZ#^VgOe@1`Z2n%Ub1E!32;2Q$qMxtsNh7+#>-aGQRnpW*t9F8ybk^{GM`wKWm~ zj?9EG8yg#Xz(S8njEr!udKunBBjRfR?PEYm;xRL*wB_X+kb90D1I$&FJJ5^38W_;! zfB&Wt%<&+QafOE)y90V3ghC9xQ3+n3>+0>*iZYQD#`F+4Ist{YM3tix*%=iTr9Z)r zq(jby6E07({qbaHMydPiJVyFK_HqejxDtx)G}e>Q(m8^4b#x3L7{tE{t>~($857%D zZ3e55?Ya7A>8{>|@QM-34NJ>1Ow7?`(f74ziZ}~4!4r7*BPyz|yl2 zrhNq&#^=TP>#wKeL%t#187!-2IsXt=4TBFW&t2RC2Z_AD3=i9`&KoU#+e7Dmby{Oj za8!Q&68e6!BKCN30yFkYl9DJo$B!P>(A4Zeg-2zF!o$qps+Dqv%;$_svip1;6Q~pB z{mSTtuC68d`3{JH^~DJu3^2J?u4PA+vIZiYuqKt8YZmaCKo9u&^Ct=;Nd3i&7j3GZ zf&f5bN```gAZ{~yIfX2PkO9sPcgZTq$=9!6!L_{U+f&u10Ism*GlS)&9K;qa@zvIq z8x+*k(^FG!=-hDFFr7M;02l#1*|hsj9Iz;$N{EIRg2}_(odDKw4VY`j$H!4(p`Bx0 zGPL;6(9xmXf4` zcZ|?LU{IOO(daEK`XIgo;y_%LcMR1gy^s0dl?YxK5#-R}F!Qs~(a{05$9scW)T-nc zH`ixZ;0%G(otXH?m=vpALt zbM-rx{s}yFIVhms^s1OCV#D=4mej!hGBPrb96tPvR)i(Fz#2*Y=+UDvPCz*Zxsb>A z`sAa893Y&dy#p}7paJcz5N>GQ$F6O$}3yd|JxeGQGH0C@z32yZqZ_4+$-@MyA1yS!_<$9C!3QgW@ZLXvV) za}}L=!lb@US~|206G%`O412F>=^+kLmQ!vndedKt5#4}{kJ!Ty4nKs(#bHPjM^A+5 zG?H%$fu_g!WQ0jRViD1T4;d(PvBQLW(iWWx6H>@Cgtskq%{#ie*|zO~;FhDKj=_A( ze;jJt*s-#%U*GQ%Ck1+Ep4xru`t>)63;aI@aDWmz%aUNL1oIvs7{Q%@7Z;b=VA(1G z-(go8%yb|okdXL87--MZAA)fb*`Dr#g(E1<*x4(hkMG`n2jdH3eK6NV(Za$4DUHWL z2snZ7f{GS%TwJ`HDB$cypxlk$7*elg^a5~M8LHON9t&V74~IXgkC*K4OyUl$+GIQP zWTF5@6?Qf@ElzfWY{9P0`xcX>l7529?%ciGJnIN4DLvJ%c&uWBekHvUVPiO z@yBOHSp0tdat3_G%i#&)4Mv*mV2oIL%Ul+G%#Z&Kg>6BHWvs702_{RR!^OJvy>#FC z{iRLY?kL9!h_Kv+V7PPV&T-|oPoHj~lffu5I&k{mKW091e?n>RZ^#Mg{^;auKoIfq z@x#NznD79c?%THy#v!PDn6h*iSoI-zgoK19OSAv|`=9&BAOs9q{rl@3TgoFxP7!${ zohqojxv%xCuoeIBI;;Q7eY)*`7`F%aq9AzF!!IyZdV*PQ_#nB{|KT)^L*RMS8yXo+ z5dJfG!2fMj^@gv7o(TWJ{Hc(wR{F3dhm_h0~|Cz8Mz4=eiA&u?kqWLXY|zm1Jovgyi^HQ)mk z+KXEMNNG{@3S!-9E1@ENG7D1*x85Un%R9fP~sryA#gc9kO$XtD=GN+_`oy? z5wfq?&IEIEG_=yNj28Q?ScW5@!ZjKK2~ob0v!%=wF6C( zvYCZNLq6T$F6)ipNuUSsd-swUu)Ek&LfglBp)+@2G6|Tn;7|T`>h)8Tviff0ZPq!m zpCc<`W8XG4S-}Yx9UV=8&YwPUFf&Wb$+=-*2C~4Y810VndzYpso&^vgTGT`98nGmU zfu&`Cf)t53DYQBkW4|QfL51k^F2J_7=E|2*SUd6F`Gtk0f#*4KMwRZ+&Ect;?C{lK zss9Wnxl8;PY)XQeu~MV1j9uqHLY+KOGY$~@7c-4cOcaB?&1+&#eDKsf|#VC;}2IEY6e zjIFzSdWa#o>GWOO4#n34y$+#Pu}fTXW_?s2CJSI5mg%DRrSzvypFVj~f^=te)FaQ} zq42Wqd%r=wD0UL+24)tx0n(ju=&?qndXI}vd-4R^C#~`8s(AeR`Uw4%T;F#i8~x6H zxa!H^P~bZ@c6NOea>%z?eX^>y5AU8I4z+nOJL+#byTFDB(%z!t%=nKdr=RcKBh>M zb$^kmE9@^&t`_I!To6iN2qkdk)KXO36FhN2pJn8j|CYzL`ufP|MMf5uR19TE?;~;L zZZ4W<9ZT@~{VSV|?&MWZS(`GXTC|Wu>dRi0cM370?4D6W|5&$qiYHw~0Tk7cRCgU$ zUyZ7h6Z(kh?k;h9N&Ug1)0sMh$rh=Bkffk|_-4WLw$vBqM9j3aOlj}|kDiPVvG;ao z-|~s_iG}(3DoX5a+lDTj7L*)1g073Emo8m`I1Ee&cddx+w~rXGq)k=9+yoa%ZLL(2 z@?P(=c4JqT(&2)_yHXP$=mT;?>Nvdf>)X3KU^WS8YO1fFY)$f|CqnEH;6h3NA3Ml0 zOb`TK1d0yND7qu8TaaT1;0S{-QNC#N9=yj4SqE(c(@8Mn2y%=J1tAfMlXhn6PEAgx za*z-ci@5*#i6A|E`0xQ@VxS6Cr|&Rzn~L&m3emwMGy-z#|M)S2p%&92cw8ZxC3|$= zQMP;m7z!^GyT+UA(2)Rh0n|#I=M3Qamra^qSt-oPIh;27?He^%7N*RQw7?QU7%ib@ zfQ16~^Tq@oiR8i!0A`zeej}W35OQEaD;E|nMF8M>o0xyex%T4$2HV~}zb zpl40UZfY7DxxpQ_h*>}wj5YV|-79nD3W#;OW^OWyITBA4);zN&&?K8W%yz;BZQ{q` zCH-k>jvt?#o-X?X;UTL6%GiJZL}QcbO#h?;^2~{g4_`Csn+-jwz1RH^#B+M6QVcUl z5s`ART`-}SIGORs7zUkbu+TcDveFA{CJ|=}Z#&Qq{2^IAQ;^xmans;|m;}Jn*Hl-R z{pJk{S2YY=yAM*o&P;CbJk)1wV#4HjFt`{^3Y3=iyz_G)ZurUIy#^k`ok%w;fgS+# z6$3`YxPk12UWq=^hxen>$U2ojgA*R*5x@#IrECZ+22)Ru^Rck8O_#3>3fl}{l4&@H z**Bb~D89=OLt!+>j1Hm7aN-1^`asm7!y1GAvru(q+cSk=QBVwBU0p?8=1+@?g0kS; zO}=GM4kTIdj?M$!$Ec{77#NC6OV`)eA*6zp13*{ev;q7buQ8W zd1GTkMMVWt?Ct|cW6!CtER=QIVv>dNhp+DjLL5W6F!&ddv+S!Ctd?D&!SD?$DmH`Q zgWbo+pQNIqLR3O1ugg(=5(#6kJq$_$xB8`JWx4tItYF24lmbf1>*VvyWN*MCH90l4 zJ7$DXg%}waz=IyA!BAqKy)FWqDv&=28{E8@?!d?or56MS+=9T0uU+dz3i6UnV=73E zX&lUO*ub%|F?j9=zkr2e@59eXq^N>B#$eFs@9T?WHK5yC36g%W#C)5cPA`7dUO#aq zlTr91_#;q9)dM2$+bchGm+BmGCIO>&^7`+ov5j7uK5ATh0%engyf$%IVg#fZs%$l9W#l>l9X-}LufpCF<4!sVXb*3fZ5kWNCk{=ek2Goh9sWT@0J2I$|UV<8O37W@B#ho$ubhrAOo8=U;}PfT6NZo@s#d#d*T) zes&OkY-0%tZ+aqZ3#U> z5lPLF0*W3c)t#N!LIpw$A_1y^;}s4 zeLcA*IP`*f6Ho!FF#2^yG(-7?mAN_10!whe87Bp>Pv>Wuu5H?L@)O>4mnsN$i z^C21x(G$$j%?9CHW27iu_F9S_FD1a9#JC8}~{? zfR^A+#0$oMWO-mzJi#0V`~r`kgAAA+(iROI#{F6YJF67cRKh^7zgXp^Dl-|3I(VU^ ziK21CrI;wj3y_3vr%pGy@4-oo4zY6BpPvg0e;WKo0U$YtDfPGEZGnGONIn69uI_FP z8sY#E8BJRCT}Hnel+*VKJ_l65p%SN!H3tV77?osY306jeo(ZwbUvy2H%_~VR2xt_3 z3EvfkOT!(Xu)Tz{1x<7DfS==IV~nDQ5i~3yB5FA*@gFY$tY4T?Mp{=W&Dwn3@>HxYG}V(ct^5_*>E1CE;d3sHQm*!h zH%48HAy1)JVEQOccs=&__Xo%!s4dx9Yi&q5e$u_xt)SkleAv?S34`YCR(B}Xrt9Uyl>cBqC3Nn7M3Aj+L=cBb8No5(;id39 z?ocHsa`Lf{k5;z9Wtshi0}KcYU@RP4P4s$(v(N449I^1s``diKyIj2ZL4A@$-5fNP zAo@bP?|{v39v<{zCn*nBtr@j@N4E{~TDyknO)uem;BDy3xElg$>yBJd9#$*DsTPbg zK_1M_%#Kk~uAoa!_Z2pO_>h1a36uQco$p{8K_&t2V|H?6=Z$I~@?NhK^;HCC6gM{` z9D(rDLpFeg0|?Rxlmpa3E`*IRwF=ohNPL`^RG=Cyg73p3vee)oak#FR^4r1rtLSi2 z$W0NGeIWP;4<0~*?M1ajRe~yFVr;xDl&zkE40dsLhK|03gJk2NE$AK&)Z-~+eT34= zWAf>#uP+rQiD-%#NUr6aIdS4EEGtg7wmC^jqY!q{U{DIfgM#e8zJCA;@(|S<{Sw1w zP7xo3Kcvp%baX=?5SS34I-o5Io;+!QK|cyFK*uT^Sup!#WqM)w0wEg7fTX>GW8MHI zn8E;E6g$mEBh;X|$vTy7;GCv6ePfhxS95a~XfsYew5Hnu%wp41RZ~G3r>2Gx4nRl{aELs4_Dm1i0<6iGrQ<-D z>R`qNM0vIBJnBr**aBD5pc4uvOp4WTP;vw80O4Yggo_Y(7t}_K4sawLs-h0s6XaqH znF(zKgLFs;(8ioyUF-jG^Yiwv>MHvHBi15m-rWW1zK%)0)Ai8a1v`8t65k@G%ki`(MaP$FH zLHzk=#}3fE65PGIRdhSJI7~2TJHe>$%Q$!pO9k{&7M4lK^%!VH_^C6KVJQl7aRdQ-xgU0#pA?QW$gQ7rhpcZ1`2qT?- zPE;~V3p`ksPI1ngH=kQtY@qhzna=X_L-Vfy%)s)4isxUlLkr4ud5(YCK>hHhcgLYj z_^}AbXAHTB^5=_Km_O);7zD4}Rn^e=1yj&qnM>Q-x7SglpdrDwgJZ}j1cjS;K1nGl z)VD6Ag(c?EKYc>0kPvkZ4Us%1(0)9sUPM<`ZeenOQz(3ju2>X;D*_G)#0Oe^3#cyA z7St^hhel8~Fmn_FHH6k_l*T;JgPL&zWo7&J?Jy)0s$Au+$>@7Ro5IgsliCJYt!{rM z-eja$w^C*%BQR39w$SupPvN7RBrn}pm+9AV|5t(;D0;b@(|>xFZi*@fGi~+t=J*l` zbaI@wq0C}H)x+ttGL%cyYc;K{B+9MU`m8l=6)uw{g9&Geag=a8JOCDY<2pvC>M6<$ z92}nz@1Um`H4|LLaBin$>iEPIkh5L``GDPI1*V-Tcz+<^Ls5ar47CPXD@&Q+)nV+b zxAay6mdT?Dgd^n8WB^A#U`w%+1eXw2y+nWy`XFQ~m>AA-aUH`6!@twYk&zKPkX&3E zMxfUgA#2LFF?&b1f$?t|BG!{9GBAt)MYS2J$E$ zJ?WTlV}2$qBm^Xpx*v*R^%`d5?b#wY{zx%oyS3@jR18!x+>i4W(hV?5RLw)PXc#=n z*aRnW48K*xvKrwegpM>!rIwxOgw>iD*B_C?h5#kbMlK`b*!LL97RT&!b`HLbgKdOj9wx0sb!n?REHaML7hruFxF+ zqR2NVo!Wne>YvJ2`t4>#R$k6(o+x~y!KR0St$*zlj*UVj zgE$S-CwidTtamGUhZrLONpf0IQBi+|hqg^5QY_96*~KD&ZxVTU%MOLmrw-q>P(EFS zy=@)2=(f(CnwmO3kK$m=D0?~(AaTLD3t$H2f~YX{~%(G1Ij+(yg`f^E6lkLZDf&>V=XY!?nR9`~!*jbU_<592&5#9l?NG?2S_x?}kT5t4HQgon8rXdRCd}m=) z^N{sFXXos4KH5Oj9s-0A>&cVe;TlRxT(JMp`U`*(%+K5Qj8+>EzA$5g#n$N15+s3n z=Q4~?kODmuT0sjrXY}7(zpEpwYWh-zGb8TGHCOR7!O_tQ{&ldPUpg7!!w~+DA()Io zwuWQKRp!LHFu0jADw#8igRXlTpd#BoNqPPHsHnT3e{Cd;wbS;lnJ(w`u3wMs&qDLy z{;sfW(ERb^c-t!y|JpvB=;>!c^|N1v9MN_L!;(Z5$j|LPe3zH>0#*2oY*SQAOj}Ok z?xAj1=N}(ViM9E`CfjjS5~JJrwBHbCZ7Ty~V#F-F&(iv@Kxe8~P~UibQro`2)JgHy z4-~u0CXg!R9oJxB!1AKOehWkrOOR~BBa^(yl*bzNjsN54#hDAf!wPLXGFyUQ=c!s2 znXn|Ot2jFHvuhDLN3kY2l>OzIXsfdFa(oJmFFspJIQ|w8U;}Wr7Rv`;Px8*2rW?y~ zYv;@{m4a?!oA%l8=jMv0#Kt2p_8)wHI;pHl1H!(lEWm=926OQf85w0|FBRb7Nomk) z=jzcie2PhX%M;{6h_)yvr!_vtK6Cnmi#$|zxdn4byFS&E^UUMZ(=UU9PM6AhdWx14 z&yfv|Bywkk01Xo7@uiG03<8hf77@-CLYG)n)zn(Ze!#osNN^7}jLe~|O$`sfADJ_p z{iUX+26#9!=M4Un+Lpf9qc)+fEg$Cn3H9+-C%Tp?HwC-aX<^+2*#p~5BL?rzFmxd+Ot(tP{3OpDaH0&A5f@@qLN7gO4(BPFQ)`zBAuK9qvGh zk9cg@dDPMYI>m);SS3r7n>5g`UAEk5P>|9uoxG8)f$C?we`;!~jr0X7OkRpsIU(&S zOht;@6v=};aL0+C}dF9d+aMI!{N;Zw_=H@H{P7p7kEZu*OVq(5=1hBVSw=(?k%ukVEx#nO7g+A1e1>2*oa0{Q|0 zS>#&;#P1eaa&UqJ<+$<}3~#UIs&oO*jz-uVJAS;C`!sx)QS9wnvhH)$l(tX4+7>S| z8S?Bsao_Q-IlPNVj8XpKA3rj?-$#%hrlr+WQ>*@Z8)^)jfCV(W#ZG*?i^8k3?3ECE zGCyUSC2}yh$cO956VIuBj&-6)n&dha1sRrf7?TvEda`0aI8Hy0#X}GY@Y795Mz?7d z39cq9$O&!4fg%_$_MSj1d?DuuBs{*FI@Fqih1yTScJ*cEQs3RR>Zs&BWp#Q_5+A13 z_4Y-^3b+_>>BuT6b=^b6v4>#FOkw4sa?f4xI+vSA7ok=gL| zw&^~=khfRTQ&Z!~<N z!kQ2G*cn~Ck_!80z2sJxgoA^Fy*=*Z@J{`%$Hx>8RDQnHHCL z{N%}AKTWpCTFe~B>14AyMLpKe#_^95j26J!_ep#1Zcjm;KrjxXs#KCEcAcDhM)48t zKrMGz62^pr?}GP?X#=EBAAZu_^UOz?U{;tASNXj+?y7WiK17?=-?6+gZJ~8Qip-7` z#<@Mbj_fLXsz&C+$UEvj^I5x$yR z)HZxapouj=*_AKAt75UzeKoK+N@?+s!P`6Aecs)ke4UthnwNJG+A+?j$x?nEp1J7j zR?=#+SDtQ@ucV7NN?CXRB2hyHpQ(*STU#5Ih%4`jZy;5`&X)Jx z-i)ekcal?5YLi1d5Y{kXWI%8Rqeah?mytno!x2mvj7-?YU7(LuOoH<;WdC8Nv}CDr z$+!J}-_Gg_TUebCHfufY<+X*m3BE7F|0tKY!U0%BGc!XhY9wl(qMTXDM-qw}6iOqh z`IP_trJIQ#eg|ih?4nHx+lN(n$ zFuSNuUL+eZ5|U2o4|DU``Hwihi3VI5Gdnd6jgRNkdA&tvW zOvd?8`ZT^)P1(@U5NakA;memF40CJYO9x+RtJtX3%gV?Q9G^Nm{V3Y9a&ktMjb<76 z4j2$SeCy2qg9nYZ7S+|&+xRt)=igMb8Le+qgS*LQ2E4@-02Z{9Cd1jwhp{i zD&iu8nnC8Dp9V!n%Edgg=(WL99#7TwWpv;pcU`<)_hVO6IAEli$99HbShZ;Qp_U*H|6f_x8?O&;=I;+ST zF%vin5Kak-whD>#%q=TGFN%E+z^`yz1g|N~ydcbjs!Y-n>oon%8>Y+%j&k(-*WuF1 z&M8YS$sGRPvc7^5RtS9Z%9OL`WG9HzJPVUWF(d>0biny#2w((*AwmdqT^zXWNSAbS zaA3IqxOy1UcyGWsW|Q}yL(6*8GYta=L~zb?=faPL-1CgLb8yH=agi@in^34SWMV8d z)q1jhn|xcTs=T62aX&#Qp%qyJKtZ;AYG~-oH6fun@nT{Y=P%P2WgfR?iaC^DUGuz5 zgpq%&SGyKhFTRWl-%;4z-Hii<$q~_JH7B(nT^2}+Z#Zkk7c12GY>iBKSDK9PP35*7 zT)BqwZcTZyO66UR{g4k+injBRbn=dzc{bERG|s^~^XvlGqj(Hv9QZb1S|*@W0aFtb zsmPz7zrVs#cACTor!*9+cwA1~@(GNHAY@1H1JIr_%sl{J@=ilp%v=vXsePfN3?JYlPw3mXlSFF#& z&9aIw1Hxz?=EPNZ0JRtqZT9B&h>@sit5tg=9x)}Caa9!5qNwtAcNmi@# zC5JQL^EcDGKfkJYHvTD9pWY(KE{~O=eQR&8{j>I$o=I&Jvyi=B|3Afjc{G*lAFsxv z38{3V)HWn3GEWt45kls9D48jdIg)5VnL;6C*v61~N}9+#Y*QpEnL}kLGThI5`gQJI zcipw_UF%MJoj;D!-n{SgKF|02nZ7j}^5PvQCMF2-3EcW`JGTkQ6tJIoAR)pqCEvF? zRUT&OykF;(%+Vd~U9qz1H=E=ef-@!1Dxlsrgtc{U0x%@9 z77(;ha_-f7fCZ(TT2H-%W{w%P-SWOWDXg5TnWJEBJqr903Tzo#RD63RoS^G^v{TK- zCd06QJJz}(uPfdO{qgjVAC-vd_$T-6C7OqdiVEP3)6vJ3lWEd3I9h zLj6yY-!-)vpF;PY6wd{$g&!;wm8#z3RME=T#}GwNc6?x0YiJs+ZAv5EHj{0f2x5{D z2cpdY%{21Y8MbLN*h3v>Why)PI)t|iH=37uq+gy!(FEF+(n^6kAPViOWPDvkKcJuc z;o(OpF3M6FSMT|)&+tsYf1Vr_6VoPT(_?E9lkp?ERvwsT>U(*uB>Fn%JGT?}^zax~ zLFb>|H;Ao)AV&@{Ns5bSnN-G&ZaZb&`c$p0SKVY-#Wo@$0#w7t>3QfWfGaYKU3&E8 zRU8Tdp%?jB((D48o)et}aH$ogo-AOjK1 zU6q0{9-XCnZCP!HOP6%>E$#*fpFtUos|kV^JPdj#Ab8puzK61eNBC1mSi%E7s44np z!X~z1?e%EW<<2YOIYDw_v(D!%EVOaPFtFJBsb6<*%uWwi-Aqqj?jD5I4y?9p<6hJJ zZUSFL5QL-SchpG$xADwC`U2Wa=do^r(I@{R&eAa@b5@c1D72owPDN6YGnbbo}2pc1jNqe6%7BmLYW-})#IQ{dp_B*7Da_=m2k`t|FU zFFg-BW`xbT_S+v3Wmo3mr+H-UA!^#z<+r}vZ1M)qSfNjs2VTEZUyy$>Bd8V7Jj@fT za_cRdUmES`b6kuP98D+LuxJHHe$N&mMyL@!pYT3ObF=rUn9TrnOjP+*Qr5?jQ%IvTD8Z=d#xw%Gz;;7*mnde;<$xjj8Ydo+HPX|Y$Gl0 zKEDEFMFIbblyq=Dvmvl5aljR%lindFz&r~SjW(CZ899cO`&lmQ0&(kb4B~P?mhkf9 zB|tCenUmDiJ6a#Td1$(EBPA!D(cf>*Iz;BL@o{kRRMs3~U+uI?HI|$q+unY2P3{0E z9HFBY*uFBIR#BG1NBO%h-Updc1Z)-pDQ34HfWP$baM!hDbLhm@j+2McSu&H@p$n%L zISLEYnaAl0F<;Tp5VC~fU3j@I^l{nKJ!yKq9ZT0lZ}6z?0bZZ7ip}a&pP%1aX0P0! zt@fJd$$go7(-QcnMz@4k#NgD|R8!l!C2|Cm`NYKGt|vy{Xl-3>@4b78UYXDMsr;!q zKxE%g*sw@+_axAh^rzB$U2ZdXOLGNZSB`}AI40&Dx>;z5yTLU3pUguh?T&t*1Nx6) z^SB=ofn6<-feI0GN?lB67lcet^76v82VD`h+vt-tF01UC2!yLe>H%53?Khw@VdLSE zIpA1YuKcYliSjfb~y z(~@t4*%{MyKFS)*x^aP&tle-;9DkB=zn!g;-&#S1d(EYc4b)BKF@=DLSoGR?G)=KfS#fBSXO7pXW zO@ZpW>24Jn2j97)x#wW){%Yh-I3(+JBq?QD1E;qWDSz^WXyakof-+{qdjI7}z2dqa23Kj=@FsErXkvH-)08 zEW8isrE}NMRAcr6X&Cj=Q}%AMrO0w8aI>?sFC>J!)aq>CyEjKHRAf|9MI}4k_z|95 zZKaq-{hXYI!?m`JpPdt?B?Hy(hJ?<{&SDEvqAhh8xJi9bi5@!1yM`{*iPjJaAirk> zWV~k&*jVsMhAmWxJaTkLft%!6tVL_r=Q~EfDr)ZCBR_{K2K(Vm14O6?_h^HOUT1Z}ia*OX#HU75b0zGO$@JH-MgL5D~JaGE>F|1kRi%O@RSfS5? z07f-hv<)yYGiUsqVM5Nf2^Z>Qty5-rIPli{{UBQNxvHkaaYdeh-~_1zcq`G6$uxgg zFAzJf$P6+Id6w=teEo2uYW*xWODFj#Y*-n~8+x4Y2bpq78gVd!E45eDNhlq}x#O5A z&k=^!EB)X@0cp$^h2#f z8M045Yi+F%KWhKJOm>`Z;m1_Y)#4pchp1-H7N2CBAG~ie(m%5?vEYENO@>0kg-&ME z-M5M5%K#uW`a3?B``P(LkZb{&hR7@6U6SwGj@guzw9^aeRC2m!MLp$px@xJG z;UK`6b$W1m9`_?WhLeGIL^l@LM?_XOb#klh4Ro_06N4DO!`{)^enqbFTgAA1Tk)Ek zzY(}}BmXfL#G*qUfxZ?|l*$#~zMB8F|h|Xi?jSySl6+{2QcQOoPLy??6<5o&e;M(-U4|K>=6$iHkjs`C2fa< zt0kui4Ih+eyR3U}izeUf2?K9?!_SX_3@1ud599BH+Gea{rV>hOs7n?Hf~6oAT|gBE zvY7}}($plldrK&?)YRaVF9FeDF%NK&DtZi#T!_-aMoDsUrNYnwVKTPv-U91;hR!ZP z9Y=Xy;~gE~O$e3O)Yn(66z@t#FAp@G-46`ndHfH=D1fj55rRAg?Th3q4ZD&T*tLmZ z9F$+iPu=jjq4(|)f^aZMi0h=gm5&xlnEo^>YO8H=DEb)5FbZvt$wiJgRo4$v7$B!8 zuXzv+#m!CVGQg@`X!?rMsDAG4Kf67ir9#9gqxl>g8zYo$fQcY~L5xT9FeK7^h!s2- z(bL@xwf&>ci9-fOPzNBGix-76z!TtY*h=u48E64t@C!zyBb)NOzhI>>l7D(WgBlfB z2%s0}s~~(KB)kw03!b6Z|1A3b%%xOtzd&|?SKO{LJs#hD)E!9x5N}><9&U~n1|O9O zHzoXc_;vAl-Np8MNv~hM@(uDyT77KFG}jGG?o5{4J_gtwVB0*#&8;v>Yb3Pa8lh)M zo536eAS+N(VsCo_j1O|RgI=tkzPSl)LRP{oO4*K%j@3TURK+x9;V_CtuSBIQ1>6!8 z9GsJ#&CA1spp;fLR*3z2@XRkyGkzxp0tWIsNpOSb!M{WB+TGm^74_YK0CdVM8n)e_ z^T;*Wg0P?KXlinV%0re4-T36-Amx3CUVzn&m$*$AOzuyl^c*P>0IHq;=IwoF)z?SiI3Cyyiqy>(W5O2YfD3P-@s1zX#C`UGs z0Eht-M<^N(!ai`X0|U`UlzPl&eA3#seLEl#kT1&YzSe3@U1~D%3uWW&>Gz6@MM|^QMMP20Miw66EI5Kzjk(bd5(F^v476+2(}kqa@6AtRS!?w0 z)pd48itGvu3R(tdFGD7y-T)E;-ycllIHw^MS-W7WK#TDBXyA3&2I8f>Rgxn8PbPxC z#jYI50HG=QOw`6%gRrjX+Hv_}qLAZahqR;3BlowmP*7O7Ffoz5kHPQOEttp!5jE%T zbk6F>?Zh5{nm zIP1o^^8RdIK8nuKdyx`R2~C)O+oMEjkm#vD(s5}7;pE86Q|DkrjMLJv)QVo2Q##dR zZ**S|B!S20Ki>l$0p%yv*D~Y8f3Uowx?1kH*Vd^(amM! z*A|dfL`j~zQOu&UaAD*^$P}A0SA)?O8UAB@vV5@I>z*NA7-*nnE7uK+- z4H73?OGF`7#g{cTX(xWsbE6}Yz-EL^aMai7+_{f%&)@>^G5U+`4l?XcQ~OO%JZH$m zE{5AMBddb!jaiV-(1#w71TcBmkQWJS?TqvbouoOu3e4*^e z<%ovD2X@Z0#A}~m?5mH&^M=|*xGH7wOMWvN&y}Cpo|k{hxRgOL^(iQAXmm6xBt+VG zeY<8A{$J17_!$ICH8t|3yitdkY$5L`;7u}iQMR3RQ=%ndnWHaU7>cn6F;6H+foF6R zn8(|E%V3GdX~@l>BPlSg3P?^)hCAp$(L%DT?af=a-k|L*vhPQ2&-N?$|lsR00$8^FBd;*4(*s7E0Guc&AaD;<3YYLEx(mw|OG zK#nfI6e~FaeMWPvpk@qcStv6{HE3N`{Os8> zmH~|R3MQ+~u^y*zVur`0S-((MSATgZJtwExH1~lT1N=e|G8^7i&ji?javaYRk!X)+ z3WD$O3LB_<2;&k!l{TwN9E{8p*Rv%4zRfh~X(R-H;Ld)}X=Qi!dF+v_s81#N1joKx z5Jmbr{W0uW_gDK6sXX}ayao1QFVK~Wr?$_`%`pX5;d-Jlh1CCUz{F_^67=QB zxDoeQw8$c2z6*1k}x(`IICiESEdMMYj7`uPwq78^vo;+#W#s+#R)raDOdprC?_v!<~T7Rl`KEB`s<^0&F^K}pD zUqrZka5cM)5I{~COf{b7qv*o5%Yu+EZ<{c|=Il>P*J(q?+5h4N2|AtTVF(CYRBk|% zH-bDoxXTdYljJ4^avj8Verl>E)X*^N0mawSia@q5fT!P9r_YglVaS4zoKO_Po(Y&KVo?ae)%NYMbvaM=x}(gb3liV72k^Afl|(iD)K1)(z3;W2m{xL>fh=SgMAULCk3hGr8j=iS@4YwGF*;0L>O zDP*apADcTA32_j|0ut~%UitKCFOYNm3D_T1K^NATM#5nM;TuM!m^|}L$Mr?_K=Po)Ag8%se*sfkAOi%QfEcoN=oWjzb9nK1LC@{>;rXgW zJF`H2|EfCN_;D)(1Ma1&x;mM!1N+GANUO}na5{4u3sCwRZHtAqLlfdyO;ZZ<4lD=Ee-YkftlRCwQTHcP=e3Q5{e&*Onr zkbowYs^xlw@JqE;1fnDpm+i4)oR$EKjre_pWoMq%hj!JcQ}9?CO#S(ksz!IXf4Y zmb#<8LfZDH(b4oFYiF!V5s?&=T{|*IM+YP(UbNI~wSQ|zWTrwoerc#-m;QrIH^_Lj zaE0MPIW_v^^FQ>2#w)T@Mm0YeN90`qYCukU*|Q>XWEaAwg+c)asFG(z-4B>QJ+O+s zgKC0D#BY^bFu-VpeT6V!UVs|&^HY0JR~2mdh!eLXm~qq8s)q|H`>+LI`&nzb>JcfS zB(nn#0Qwbpy>KT|M(E5GIrPxPf(tB$6mT)p*k4UKTC*c4o?V_mKFFC%`fFsW3~VIoDFR5;zP z)ikr4s$$g{{W%UDy2BuVfhFy&l`;U7Hjq#_*{M6vBws8p!u{Dwc00;CHnNK5!_qaI z1=k&0oV#F7k=ryr|15Fp>=ynolJq@*4!A6e%ezi9(e!N)di*3d)<`4MY}K_DDIC}l z*m}RxepA`%e-cMKj!Y%Psgxw%Vv&W37Z`7~d~Y2M&7)0e(Vkr2<`*Hp-$ur?t}&jV zzLhg5Mv6WlqPojjpN6>^cLt9co(%*+JRUEgd0xrEsGzK7iz5RsT9OT&#A+JVsgrE2 zxGx(>DAy;!>}KCvGX~vp+T+K&fpetzERV~sdEaxAO$`ITkO+$c$gqn8<7|YsSlyj% zI>5&#iV%E73VKAi*B+;(J;}{=r?0-y<&Q!an`T?9ErdFxv&sml`8%8;KDk zc%PO@G2Ai=h=A<2bu=pg=+RIjVK^8QTVCO=!W+xN%nT+8BL3D4egykqe;SpDqWQXV z8{(9}DhQt;I_&}7i_3uh_5NXllyN7Y^~8qs>T;~rBLkLOuiOD!tx4WVLnCdes=U%9 zhKdOXn%?8eQ%k}^LIjqP*k)siEeFAm4c^cT6S&o@85*JMAWFe!@xQw1jSwdkk?j}4 zXrv*FfeD{z)puCJbxa)nuzTK(KS=Y_n+LOqh&@S?mV^+3H!vX;r(bv7bdd#2YUCVtb^QQI4*C_88^&g!(T8pbqItw7KR^}(95~r+v@|P~fA87_ zv;*)Ysuid4&I!zLoBsN>#X0eV(RBk1o4gjie1Yjd0Yd+z`rO_8U-M(|2P2y^{!R43 zmrm$+^Hq%UDp3zk2Id5>}@nH+tJOV#$+U;Za#w3--s*q#oeXm*b*JtJdKjw z$1ZF5HWj7FlodZb`n;*vFDbL&-*b%;0|#NRfk+(?f`f zv)k0^p#XrTl1RuVp8BTuAX8D9Ak%ArOG(G0*gp44q#(&pUco)$r`E;!!Jl6)(+h5_ zKRHcbWo&$`#P_a0u}=Q;`ugmItw+mqbVVo(onr$$w(Z)L?%}hd<2>WXzf9mq$L%{4~|P5{`1*1Qwgr@BaJ8}k-rH!6RL+Bp(}r z>xN-YNCLYEFI@&Vn>)@O_&nX*enOZ5MH3oLL=(CljYCBWWamp5@Fw8IcQ9G7sR;pP z_`C_^^q_)&Wx*pp1S$g|Enq?hrsoeaWdh?vLGcrIp!IM7}DvRaAiRB?H09 z%DMpa1}vB;db!HY0JWkr*+2s1^b|kW79X($>nDm^>9 z2;YGSv*CuQ^A|6w(7L0l!s!dyQhZE|Eox3|KlnNraGZlLqBl|XR)EMug^Owm^#Sf2 zCL*B#?8(&OD^tR7K+tWtM}VqnNw^4jIcWpafFQ-JO$65+*l68SfJC=6TPXN{E;hDL zfOl|9$O$bdgHR{p9`6;jP-+~0BB%d(dV$)}^riOQr-zA(ReHPDy*wY9!yfnfStFn2 zc>b3|cdBP>W~0h|sU%CmO@1d@qeSC}IHvt=Od_fg&K?BC6^PKeoUdB!(qaygcd!FciNS^8f?sgo%y&Rk1+a{jt(7cMmI z>3rqU`gM*eWxZd>>zPk0#@=4P^o&*1D7gt&UCS*5dv{m=u%E2+`t{IQ`$ihET{n&D_#JaZf#+yZg0j&3g~oqOE}$D5LCW|+DcWm1qZQJ zhafvAr#Yq?LA{u#fi(eD4A7d)Wgo=K?}a_d8Coa4m_{`7_)t)z9WXpWg4KhwZMJy- zF!YRI!s`cNrx1yG4<(^REux%-C6a76ivQ@%bMWyO3v*N;uX#zhbReOS7a-%mGk(Q<$JV%wdtU03#6)OxbG zMXP-mO8VSzY&mi39-;R)EO$}L$r{=F7q3K6Ebdb*DmVMrZZ;A>aOk*gqlto2Pe5Ij zs|oGoGX{>7ojK7hvKKXK7sJH=GNrED(GZRk#KPxmXWL?bPQ~}7msL$KL=T`G`gvvw5H?}g#@BBV(e@9;g5L%i z%VF~J&45Uj;giM4V3d8W`==)-CvnW- zz*_**+i{d`%+ePLR2b_Et8*Ws0}<)U7Mh%r0<2>iod6OM(9$?LIH;}syeIK_y6nnK zSqKBQwo}u^^h^EAX8J1mgv5_X4`2k_uf`<(`awZ?*5(|>rr&!1g*ac&rkk++)2_UD~>%vT_SmF zhAnn#c}%#6=%{zR^qZdGioGeJ7Zc<7Js|Vtr+gO^CHKXYk+ur@mskHbGke5$|C)}t zc|QF~OD5V>dON|4)GSKW3F{jVnhqY^dFGv$RAySUBBrCtr0aHc_caD2c_Ynb zJ^xAvWI$9(JxV@!$bmB99fY$|xO#@BB_!OrmdsVeNoGCIMe^Dze+iyo<3s_vxO4X0 zcrGnz!Q(M~x!%%`%sv^>zPf`MM;#|RT+d6?X0sV=Jwt1Kv0=HPkyKn_qcaj%&iDQF z4O5NDe$FtH&$c~=UYT8gX&9|6zZYSWi@#DZ(OWlz^&|W~@TI`kEs`=T@7_0k>FRE> z7B_du3HW5PvoQCF#IFgOZLccFJ5Ncx(~V|U>U9rha1%;>GE zUvRabcAfSXr(fERKV;h(5?3VD3ZG-PVn0SUacAlT-8@q(KPaSQrt=gcsyv)y1yD*Iru2Ye6ih4KrlY<$6)5D^|#y!3l`37Ag4DN1YEx8b`)lU z5W>m2o6Zt_v12|UOLyiftPmPU-8dT+o;zJ9)4qQ^@T7%7K(*x_ycLyNwf$?}r6 z1KewRY7S5JHD;uqznn-qFSFF#yXW)%ph@%7D-V8!vTK}bPOhW2Ut9uL(966*Gba6oUs709>I2aT;gB`e!{n5W3V626^F?jWOL)gicuj#i{a z8+txh_oNT?oQ`l+yF;d!hx4gl#~8>~$MSZI#4Mq8W+Wr$AWN(ZSP9|_F0yUgZ(MZ7 zx7W}^-KNSSfAs9rhzRG5aZM?fH0d+9I)}-Dp4rd-N?*s@Z<4gKe(7)V^Zl=I0ql-- zdIlG*7_}G_i7d;R@{;_LngP0z9VBB7Vi?KBL*ZIHnOTvq+JiUAdecj(8tW|`%?xJ{ zXsFfiHb1f6Ik){%v;3JiLpL}2mKTYvT&uSp{?c1@>ndBcbK%i_`QF_h)5vMYrl{eMiE~$hqb-EH_JEjYCZfta{Q)NtOf_;=h+t(*Gl0$ldmOOOV>ayv5LyS2uJ*7va%BL2K79BP6*%+jhl_$tJS0DV zF7`m}H#~`2W$L4q!grJ;ok|&a0<3sO;b;KV5FJ?y--BnHKLr_hf(LE z4FrOQjg1?0L`DYHYPRgrmJSpn=`;0z&0FpTCWH<$T6878vjx`tfOn)$rFrk=VoTrYDv>ZE`b->PoFaibbGxdNp>PpO1!?dfUcyEkW zh?=lEK|BH{j1H~n&$<2;l<1iI?o8-)YR3DW8og!uwS_i(2Qu0GMsno^qfVIasP!CU zi)ogUyu_(|kkjK3YOeO$XHlU(#{)wasTsex*X7wp=q$az_Wrf%eER)Os!4Rbr?i?$ zr6tFNxpNjJ&l#>6G!R_oI(J=tUVnCIe;vK%iKyGltuG(Q*r?oi=E41YWEe0x*H%eY zeLBc^*1=VLYE`6<3Xg^Ff*=20H zv1?nTY%auJ_(xe!3@k)v)?TKUGE|mw_7GdBeB<^w@ypc^2`~`RHyC_)dD`38?I_qe z=mvEzONKMuZNd1w6Pj`f=PZO1(GeqGyufIf4QGt>J~}R(f5O~+ zFpTpwsLs;Td8lE)xF29?wL;0NbZy^+km2$B9lZ}KEKGOCOEI!0Gd;ZRF@Ef=vCBn4 zb~@7QvZ!-2OohicFS`GlSgyDB3*lb#rPITu@W_&Ab!+VK_4M+odR{xbudbz<8kesy zyQh5PujS2otX(GEEj9bo!q7Re(?ZUhZ&#k<+44x{AB=xi?zJD%8o|nZu*o(fhZ*M@ zpdzAnI?2`zZJzUN<*YgM*Dmlht-KxuHT@`5cZ_wz>$8|eEeSe5T{tnHzU#5 zE789yPw41JUEV+vuZrLlNO-69wQ@e1f!f+>Hf(vcTf&1ZFB{=WXV?8@YMt?LDWNjK93+mz2J=n7=iJlKJijt!l#ycTG}ZSW zk1qqV2;%SEW0wr1d*Y6=5@U~}QL|un;`gSE!)(DcMsBQP8Qo)TVdPgrO6v!=O<5Tp$u|7ntr@Eg|X(!Au zfGdT%gprKCR8~}dYGf;DiF4I)=IDA38p&2o9-vr=oIPHKHm{Tr0q&A6;blG$UwFgQ?s(O>893mxJ0lV{Jv z?irfADA?h<-A7+iwcoI(@QCO~2J*{+{0LqKrg&vqev$U6k1d9ys{=fW1w{w*HjqgC zQM2Jnf0mKk8=+Yn56Xp#4J1MvO<#K8BQcv8dDH$Up)Es$CqXiUeA4;MO;q79vdhzM zprgZjQq3j&tKD`*ng^GV@nEyjoLy3E0%kf|5)NqezxNCd;ZlO`ut^EgF6X(*f+iDT zS>d0)H|$FjG7fl*+KMQnjKV`6uToXCn{J=+Rux5Qq>WQ<)gEenX)*WDAz3Z7( zGWqj3%9k^JOt-i-Z%erwcpl@|afpz6-g#cx`GO3s_^*8f-&p2zHvQ1pNFK;TheMY) zJd(+x7+2pK^R`%+#PM~N$sBEc*x3H+fB6QXuoPqEO3=W_sKTfWpXOo6Z^`m{dfh}K z<3x`v?dbdR;@-2#aCF~X7DFH%G@W{fI-z75<0KSZU{@d(fp87neGPy#5czFXD)KDh z5PBhWg&=MZFQMQ?I$;|QGw?ZpI?!}sf(#lv;`l;^+h74`SHH+kftFw+7MGUrE;!#Y z=n4a;h#4odw?Zc13?qF;fD4`$o`VO&lahQ0OGj4xq|F5F6@GJbGwCH4L#4-CzI?=Y>82 zAPYVFwJdzyZhrFx|JF=|?FE<}5GJ=M@(7r$@?5y`OHGsvf_yIzg4gJ;uaA?9*UF zKdT}IK=MdTYphVu5dF3?G2LAX6tl?}5B9DJGo^049GK}Cm>Fjt8Rr%9jbh&$mh^2) zf#jcOyJfr3EXfSQMIb*`ceVnf{dg%OV>u}Ff~DmToP#{s;M+%shflpK!dh$Y>?|@? zH?_1ZhKL(!QlE#0@G#>4D;%Zj7GbnJ%oK>>J@>$ifV%34L0Hs0a3&&+gn<_Q83bf1 z7R1z8WTy#UnX_{aD+UsUH-I_lcbw@MLPUaS!4TpQhs2JN_Va)rlvp;3RCHpv!_HgLe2mL>&& z2OmJw3}pihV~{5f(PE(ARChiXxMGDP=Mjhl-6NI{R8)z3ui`P${EJzgi{*c$?Hk#- zpIifRGN26Di#2~NX~j01zxkeZUdU5}_tzT*qn*F1rq{ik*cq1-(|%XQ)y3|r)46!_ zQv=hA86vS)ibi_{OLv@_TNG934;pn66z5AVacrivAG@zUJFDBl$W2O)E&OIuzYyHV z9HH}FDtF4@!KFo}nPoc@r>oW-3(r%>*Tf6tmlcV;`!xIFb!^>$fUpqznx%WE7rd^D z?f;>D;i{Ewo6;`xXjHDNh+(nS&K40gjn}2xf1Cj*qy4$rBN#n-0DgA zKH&gHy@Svlh}rN<*g`>uj1FDpepH4ioi2l8!}lWH0MlCTNd_yX-`f(&Yt#d%0qq6O zSS@XB%xwMcb`%35Q9K~FA=!9N$N4xB(+68SP$Q{@5%$tm&V@%3?8gCzt2Ix1SI*Vt z=NQr7sx05{`L=OvA$Zbqa+R?N>bPPNIfWtn~T zzQ*PqRm`f?*2>O8RmsB9LqWxX0%hP4;Snp_vA)K-eEV^a{(rk%D+yid5B@LTu*3W- zq^TZsbdKG9ZM}~t-JfOpmDOr2ceHR{s637`S`yaasrQ^ub3&T`CAa(Nr_Kp4VSNG2 z-arD*OeN$e8^4SHd5h=2K6+)Q?DRj0?Sjj11|NvSpiG5M+E@DcKO@M9Z*;SNO5ZWV z7McbLJm6>w1?|Kah^av4;8<#NE~6RS_TRka75}X9we3Xb1z~!w?h4(lairrRoBf^U zs&>B5m;ZeBwI9@9s>=4z*_oSbA*=4}*%thy&BuQ=ym}?m<-a=3vS#?t_tH#JybXr_ zl(6_yY4$67P5)a#!qX&G<$tS*|I6xy|4*Wc{}&(ov(jba(b}{7Xs_?{NfTS?X{xT4 zF0q$Ur=0C;3<>?MG}*`g`@&1h{T7H^yQB2`%I}K${UZtp{8pKmnf(@@)YSaer2MbH zf$OmkW>ZFI{<-}h*gN%S{dV^K=RHa-Qu=-`3#q~X-0}Z&pZmZ4ga6OoV49{u_^)#_ TzZM@7zk`CT%E@f<+3WuWKlP~? literal 0 HcmV?d00001 diff --git a/images/gitlab-pull-mirror.png b/images/gitlab-pull-mirror.png new file mode 100644 index 0000000000000000000000000000000000000000..19260f3a5a76e019839c585008cf566ff1ccb925 GIT binary patch literal 252443 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�HEWtyos|b5@WdP%;E!x5@^ym?(^%K}n zB;rth^-KXi0WkCEkt3nJmr=>l#Fv!1Y7ig39h#dq7ZIdh!L1TQO@!mWKv zJ0-&^Qzo7C)AacAw$G0i;ILT9TcZy zE=yRE=kH(Jk%d5wqLd^FeEBPa7>|u2rT>+QeR`aZh}w53y<6YC1r3HB*XRqAp#3JX5^i&2pUlxx&cW(RFW_#(t@^h0TseVw`7gMd< z4!--cYxpz{>GZTTzcAHS=Vi;Td$!M^On?Crg|j43htaTMtMh_n1&~^|m68x7f${`9 zb#*1BsF{U}znZ8?mbs+;p=;ppy@(6Re?3#xLStkE`e3d|k1{1@Myjgl5^o)Gs)ue!|@x6ZZ1qZ@KcO5GFR zfEvxVu_^fNOdYmz>kk`pdf0~@pdihJTShX44+H28Cj^5Mgn4UpFpEFmAM6K@LkLdM zBhGKk9M;&d_G5>sjrW#)-{bqBJZOLy1WdpoBr;ZS8ds?67D1IQPE!8_L zFt0biJ5V>=ZGy{g^_dw%hE8pKwd}ig`E|EBK|A|E!`09b3+RC6pF{(@GeYScBPqj| zZ47+bv}siJtdi6M3UM%8 z_+-b5M;<@^3uKlyy`Z!(t&*|dWnKq%NZlwVg7!AKb>ZA$&|E3|?4!DhadI3PQn(Ab z(CDL;53jUMr2dARD?YK`j8PbY%4Z&Kzp{66aD(T z&z{-pdNXf?av(MJBZ`dR8^__v_vy3H$_jl9xDhr7Bb^KPV;@3Ft1!|6XcW472a#Y; z>`^e77AmC=U`?x^^wgeN(XFhVg-cuYKU zdh`V06C^E3Ng|QFqT-{~_WrVw;; zn7Eo$sV0B>iS__rkgbFb5J%YMyi+nP6gMt7_W;CvtTatFQ=iklceh*EEI(QO1uGdK zS5^H$SP?pd2g8T=D@|My;J>$?h{B2=JoprH_n8}jEQG<4S(l*WLtY;V9&z&IWazSa zdCrW91H<~Gh4CBL81?@8^$0)pSpD`QlC~;RguO3Auy4Mo$zF7u+4Ib zJ(GFLRl=cg`ZRI=)9267;~B+DvNaK-bhZmN3lBHnJ^4q;&EjT;lcSV#Pt8@EdG*(6ibo6yc$Q~#l2p8fsjhQ>99 z{oe-NnAD_WUsgLSGrb#OcxEDZ1j8nh`h%32ZI7F_tGj!zRb^toKl30-Hs;&eAw?G2 z6gD>4K4SVWkDJ@svw}%1e!n@}!otGnlyne*l^!3lG(cUk#@`Po-o-0cr;d*&U~c$ic%RA+kTsR|k- z@NB?+5c##0(N7tyt1BZ)UVgW4@8vyxi(eX!8#nT>CBbdMp8PxF&K)Kwt}+6>7T`7P zL}}8UKQH}xBvLBbZ;vIuROJPBGPeeEpB!T)9R%*l(&5F zcNY|iEWxZ#kO@fVhMv_K505rh7pWf9E#$AsYo3k%plGA1>Us8ATrfGxUl+7O(pd+AH%+a>ghKe*OC*btFG0FBuZ2g zmiS9#0>I1-#MX)mlwm!ihdvFpcn)ZhEO~bE^+jyrFBv;|^4KGJd`~C$CV>B$pt>`* zZwu5Sx*eEz&9k=78OlXJIZ#j6oxKUAG_UM2g~o#ibXn1oy&)A*(sdMFqYsYKf-Z~g zqD2-O2ePszZ0@?aa`F%F-ux{=Ru<@bia&qu+}->4qerjlJ37eXv0UBP}uFnqVOGjWOO78WiMYA0x6iZkn@|2Z? zfZ2v?ZnnF_AC_hQydj&=IhwAkZS`PU0W^e`Gkn#tIH5s7w#*Y#i0}UP*WAuNo0dWv zW5tj!+`yEIgueBe`xx7B!8Hc;u;iN(b-{r560kZg%}m^}2aG`xBDHI}$6Xu+v3RS%Jh6f}PN%`w>*3EwU>uZOjsU8I?9)1zpU(T5S9Lc*v*#L8Y za{>f1Xlc_D1{ir*l}%KuzNDVDLbGhy)bZ9Jvvz1MDB4g6QJJ?)1UO%+-1eXxa#>5; zw%3;*{HAxr%BOWH8D1#zVzpDR@nIvPziAz1S$BO*POYfaU*Hs}B3qSsIKvEwR^^^O z>l+)V{_tkSGcXg%nrCxjtXv*&^OQdy2Z!jHi5$}P56fE=k;0i&w(_0(l*TPzx9$c8 zGi1m_7S`VgEiEk$IDS-3O>kUo`{X+H?Ejk%&S%h5SO&e*1QJ#v}4)@645yo(R)s9MbhRSEEM=lji8O1XFeQR=TyuoqGOHz2dHE zo+LWtkHRmo0~{P~Ac$jyGbl6PNf;HKlha{hA3B=+U3wYYikXAh?!4c+h{;D)|ZCCC4CZ;26%0 zutRa-qp-nE@h!+h+^|?G+1gS+st3f~UWL&Hm$Tab4fm8(|~OTo6FpEW(Ir&?XaJRd=>*YobHSIa#b?mT*=SpARS;;lOGC$l1xq#!94Lo5o7-zleS>((_#>DU!mz?Q(%2r$%k2y!(-iXdYfat?u0%$|ah#N#bd&Tbj z?(>cb`}eBIb}A+6DK-Agrk%ok)&C? zmLY(H2MY}=$knlJ<{eo>$k`2OvqUah_@!s5yHSZ6}((Btr;wv2N&8ZJRWYWn*t&N$1Y; z7ndsNzM1v|tB6kSwl5@5YYci`1(6}f`By0@Dt6ga4$ukl3O+n^9#R;Axpe2wr>|cp z;=i3VX7k5W9a}R^Vy$rIMP}GS^slLLgsRi#M-Yc-BNAaAy(@az)Om+Hh+x0qO6fok z9Bv&I;hLpOK@mu*Vg5|jH~{-b@V-26-}Ps1M-d~RCfV&eVSw(gM%-s}j*`=g6~a6R z=vDM!JBpZ5XOp8Me(`IxoOLnC9AH0vON>61kJ__3GUMXvIK6q+1@HS5r)0>Ed@}W` zVsNJ0)WC@gAL*F~)C*7+yoQ2$^g6_qs@u2mm^pXt+7Fg8kC9SKB$7x!323s(+4%&+ z&nRXYq!Uj;p%#ikugki1(I|b8Ih%ZT!`JE4rZ;tUW%1*`Op{uDFYnpKl8gQmr&dg4 z2aB3c0tsQ?xNP<6JI;Ef@jd0`HT>0AkG&Kny=}RgeZ+&g>4eLq)2A<`rza#P5}ft> zY7ZYiYwp~zyUPj2Dc7!H7~%cog6r^OmYN?|rYV>MQ?TU(e-6B!A^j1`F3&5vbI0#n zpF866`;)(F&bbtHzBBN(l=pl2qa|W;rxHP)#@kz4`@bI%`%`}TQh}mFTEw`9Ve|X# zne2P?@S)=pP#<$aZsDi+KJ5-!DQd#@irn0I@C|ZjkI!xh<~*Y~xnEVpfqYP9CYHL8 zt&#{Jmi7$WHeeq^F0cxiwEB&}YZH=EyI|VXH%D%lQf;hkjn5ynR zbckXeFVjOi-oT#bS$_U9%3j1|H5;z5>e~G0(&XN@ZQNl?X(_3j%Uk|r%MlP=K_ND@ zEj1voWW?i$6dXuO%FDwnHDUhy6*+Tk&OEHwdAeY>p%cj{n;p2a8ceAdZHH-voOth2;`A(9@!B1==Vr|~I(f5oxY{Y{H{W6Ne zm4fr`*1EF0s+!82Lj(CAxN*rxaU;G*_uEkY9)Z0E%ZI>92+VD#3yLs}g{c|o zp&B*OdVkVdOkkFCWRm63%iO$S1K#`dfSVDKa84tN?VKy!^THjY0qY2Bb~=xz?kG}0X=l}@Co8&~7ztKr-&mBAw)GwH6*D+*??it09(WS9M4}+xzPQ-U`8WAd^o^Ml^ z?=H0`$V$S=GEq`fM$FdUF=r0zKgM>1<@oNKcEX;cv(_TDe2w<1s8qTWStHL;UbvQa z-Iwk}@+=ZDwM@HV^A;xHiM%2r6l2#r_R?PDH*tQKoO=?*He zeh1x=BYkf*>egtSV2W~=1d<7bG#$ZxTK#M59S#!L;toeewaZY$d~L0tA`(oE+l&^0 z9o^j0#V)YelreD(i5yK00hlA@U-#}^_Z8iE)B9VC4md4YQv3dWk6FhW-@H*~*%Dhro^-^E@(D*MjkY*prnX)fj`fGd=Gb%E(^6QY&%gJF>)T(6CR{sO zuAHmFe4@)QP&ou7uE!8NCH=3~IB`#KuxjrGmLcK{Jv9D0jaRPlK5k0HBX948L%1c` z_9+L{qTE_6#>;&4Ub(Yn!id_Eg>&YpEsmiTe*E^=PoG|l3z?}I2936xsPN967at$0 z2jFGavZVzcBJ*zhh`cbDcxP?C$wkh(6Ef8638Z+O74ggQ}s*(=(9Xk3PD|F0X6ehje)=TFZa65%|g(5HK){~HrZ4bWji4o&pN=m@iru z8>nSuT0Fn(S06BTesLyEE6w-iQ`O&z%pUx=!D-Dx`Bs|7S3FX7m3F{88*J_aIuerZxt{J zD`w%C9Ipg&1aiKz2M;thhv{bR1OBO6Se0#cAeD zX}dv70|&HuS?|qpG#(it9a`s2%OiPA?lTW5wWV*e>FgaJ^^c-Rg^mcs)D1RsrVmo; zwy=xS1Pcv0H_HbV6(RP1nH4zEkm(Z9`fuD?IEAluyQ$^Y`{qRx*Y}6b)g-MKIO>e@ ziwG`}o)NN2wCVOR#jfwiEB<(#8c1U5vGZ!l(}5yKjBiYU*rogWqz zOLX2ib(l;4?jn^?3*G3FKn-O`po)vEAVPl;d_O$u?GT%9SFiCM^s^X+D;hsOoXUsj z&%sBNaDN15v>g-sx`KN1@N*}AZlozAawL7U+LSFGYS7=OFyUQc!itFXr@N%zA%xHu zbJmL;UyZS5%>_Xzv$KPs#CP1T_~7192?9{;@*1CShwr_=E*sM2zctj`6Wq@4;?ctn z*7bEUSb0cyNyn2>#J%M7^z8t%q%yUSwC2s)b>vl3W25BfW4qoxUZUEwPq!||Y^dO5 z2N=mqrH!sL)4iwgOCe4%`y##s8+Z)_htkGR0El>cYzF)UwvDTMu*=ltUe3;u`|gij zo~k~iEIu?;HoDrik8Jc9OIH#7Vr)N--c>wp7}(-*4Z#Rl%c+keT}o84*2^}B40ZZ$ zV&rqrc1hZPa6!dg16|&C>lK(6-D7;$Z5|#n>!+bMe@KTP3jNcJ=Z$WLt9|2|4(;75 z-7Q9{-o9l71#h2`UWo<4+pyHa(Fm-o>hk({sKsb9k`(jACpg&#T4*$5)2y1WpQ|6Dl|<+&Xa3bWRz+8#Fx5IE`W9EmYt%{jt0iz zQthFRA{qT(FSlJdGElHa0WwkOeG^ik~xI&)kCH7AXN)hbxeu{Llmn>UDE(Y)?1 z1eYJ@2T;9?>@dRYM$M!JUaSWvH!-p47t9Q|oK~@~MCkq$AOvJ9R%$u=p*YW0(yrtR zBIoV(A20O84iCMpVmF0&s~1mp%9+ng_e&g0FNTF4*Ja$PYSU}eBc0m99xs}#r8|)_ zGwxWgoF614qoF7?mW%%mJJb#{)z-tqAgsu8=FD4{dIQWG^xu2nz=7ROjR*^*41eT! z{QNe`!QiS6ZNUOea`4w9S&};(5H!Sg>h~2-I!x2o4W6=i&6LICQ-{q>r>)*diuCXy z#bBlO*?CepVzx*oyTsCax|1)#UvO)m8*?kLO`Oo4CqQzll9l|)9hfUadiU*IWE}v z2XH*Jz}56?;JAWWJ(Xz$?+yt8>7S3th{F!L3nlTMJ$FIVf|6#d_}%uTaT;}d&4fG$Y*8hwsH&<;NfSQ2*Oc>l zcM^kr``inXy$<&=tVq5|**I%J)wW}oX^vi6TM3B8Y;mb`9$01xRfCO<&4iJzr$XmF zFLA4=th7(oU1rj-Td^i&l2xqw=@r>?LRO7>@|mwv+Y(miytugXc+gweo~Vg@qD}-kK`Oq`j+K3Is}g*cTLp(c=EKTb7N)CLhJ-I6Psks3t^G%UY?1Z=%JJSyt&;A>5W_Q9)gWUa89Z`iT4H(a zPD^02ftn2$%pUZc^l(<}s4{>-&Be;FJqr+78Vw?)o+d7Ha@ zuO=(C@UWyjYOO=}<8J)^MX3gnaVRExBWEOf+kKzqBXc3*!@D)+57+!%vTKdPi&?T( z4Rr&hCjzng&l<12d;-?Rj{W)_r8@?sw6tkugNqYn^tiP4-yLxg2Za|CdFA{(vG||A z3v)rWM&Cnu>K87lUl6j33g}o=O3QC|AHU6D?AV<2-4I8~F*a<_n>Q=h+HA$vFDg}L zn|Hk5W2OK9kf3WuZS6bGfDUB*nKQ-91$TqmEah8_r1SJf?ym3s*Dl<=OFYV+>)??oq<_sA` zbW{lSxPI=rt$4R&v-ncP63UFXN-Q@sHO0PC0nn#7;LPr_P&XB59KzU@r>nnE2k!j+ z(|tm&^6ZmW8Ma(O-;Zqt%!Pp{;T`c^8_)>%Zu+smr4}`SX0x}6ypu-PSxaRcUQf5_ z{$JvUM~?(H*5&t{n#ftBqLg0wTgoq3mVGrm+czpZ-z?S&Vmre)AI&(HVERH&SZ4jl zZV)>o35Jw<>C&r_O$XET%w&rnD|f8^2!NaaKwp$i4p%GQu;0cAEbqjeQ=}1egtL$q ztmwPh?e$ZC&u#s#-0=5^)cNdjIwrlF{MJCZUU&Y7D}dD6w(!4}03EMx9}m8K*6QWe z6F*Za+`+cYJB7JfRBV3nY~*yhBBb%K&`6vRasyI{yX*d=Km#9-2yXh~)78VesYhqM z{GH-n-C>6P`}($7Dl+qp#X7{w=Xa1c@0{=7Nq$iCLX&gzs->H%YD`l?6xZ9xYx{Io zcw=m`OQl#cWVQK8x&0y&33cz?)yl6Qf4@6Oa)h^I%!CU!Zr*)a6?XOg#(P_4ikF67 zsEXO7@>SnuIQ9b`qVcMzcUr%GU010`1-M=@5&bymjhdg}J~Lzysr=sP3Kli1L&j9- z*=~DdL{4QF|73uApJ|il-3r;$f`3d-j;lFX|DHZ0Z#{lI2Ak|BPYxB-GeRkC#Qlp} zY73suX|4p@X+J-EYnwM@h7ZpMEw+*Pje7tV6Q=DW*uOi=4sSV)*Q zP!&W>FLokxc&>CcA@3r2k4LYaqfUHqP%Cd9t*3`ynNWyRM7mL`KbKcyV$Hiw0fm`Y z*OxbfaiquXN^s_tIwjV3yJAt}^w~P^>ip>|U4zYL-*zYs=`5%I+9K<7TKUr@%!hXeNrQ=INtQl-aVv;wR>&=#l!l7We7h^>aO~moZbCy_S|OI+@flJ?240Ye9ZK) z=>iJ~j9r4plAN9Fue)w961%o9Uh^VLv2l3gnrqRwf7*9RR6ce3w3^{diJX`7Y;1bH zi`RTgvz%!F`wF(<#q%v=g)hkAiw+uI&KDO)x2DdW{{F;=X#@P7C%RYE-+mr4Wz_R? z#|r8JHM6#SJeiU*+sw?j;hzbs9%$(uwzIJz%pXB^SKmqvG^Y81TEbq>&t1-Ss$E&K zIBDK0#z2FTv*-T#bBgN2YXREByCQ91rCgRl76l^zp5!m9-Qv;-)}Y7n2)%8EewLc` zw+7?WwPD}8X(vyg4!Lm6DYpdlWX_|UFVyr5E|!QreR@b^L0iNezF~dupL7l4sy}#K zV-#OwWWaahqB9$gUKTIN%PU;wy}^5D{J{dby@eJ}JB}YZboyJt4Se($&0N)dNT*ZF zjcf2kAnAh>^`WoSWQ;4K-$V8Cku;OMysodQ#aoZQAp^??XnYe*8@cn0dxpP%K1k2{ ze>#@>7fw4_85I`hYrD!~?tRrWs6yh3j;k0+UEzh?yQ|%uGwXT7g@h%OS_e)7QP5S; zta|J;+~M4U8m|tWTot?K%ziK_{moNuCX+B@H)hzDhMN~w)mMdnp1gbMNiHZ(c5&mr zc0@uo5fyn}_An{i@^VT8N7sF9znp0=ky4PLyx{V~zz)qN5n*9OVJ7};F*?rqqMsv= zehN)N%+4v}3j4q@<|IkP{e(}W>ubx*VqtK>BB{nUJ}G+HELh1KUL z5?k0~xsxd6!d~%7&(}Lw9Prr40+=Ron=u4ERN-3=GMwzQ0P1u0yCuFpJ{%$^hVRx7 z+B&y1dT{ldM9&`6|9cspyBZyGpwWKv&3A`-$IgCxJv_HoLsL`D57+u(%j)Di`kM;c z_HP+Cd;C7n>lmz2UcWQo?Bf-l)pzdQQ#`tDPw(ET@0`cGpSYnn{jziL!1DMbG}qRe z2Yno@>IafFZ|MMWfgK%I&PEAx`i3qZ4oL)-76vyMKj}_I*s;p z4J?YxV*`9#Ju+irL@T6a$C-9Ke(31YT}Q=kpULcTBc{wnKTa}y|Cin`WO9{4I3@in z3L*KYPOu11~~zF_*wJ@=`*; zfOi@17x@fbsgdE?>&W5We$y_(x{#=fg-FzVswhd-TVvsv^+zzb{u54 zZJSSu+mO0r+BB)f!fb`kV^o^Y&Rw)7A`W|PuwJAL-Dw=xRF$8v|sTQ^hsE&QDS!yuvr1sc#RYuo(a*X8b{H9d!c#rYytZ$-fvM%^8aKZSM{KdN#?P-`i z_)^CH{?R%wwk^2*bCr5^y2Z|^>+;`Sc=ujUZfyatz`$(>j-91l9w1sYnD_!lh{gre zvs7Pde(*F&W%eIF9Bdw7w~CqYu<-;P;ryBovxP9bCpfn@C`%J^WOe4I>5DsueSRML z>-MnP0|O(cjIx_`EHu>n`w=?WxwDQ7NY3mpugXX{%7e(I2c0?|zDRAQA|es6{b+ zb`vbb7ltOme^v9t6_8d$OLOHo%`kMQM-h{9a*~jQOo+B;Y9Fa7D^|a6_v&owRlqJN zv;Hy+6LHx#&zpI&6hl{1QiO?#03lqQoz7Rv2r`SRsw%xb2fW{`{@V^jFnfP_>NAhu z28@P0YzfDgwkw-p5wH!!=%|^rT~Wu{r@yuABmFNIU{!d7_POh|x6COJ!(Kg=zfX5@ zl0zr+Wh+*|ms6hjiE<64di=F($51LGVSD?7y5z4X{g&xPbt61|1%z3D=}C0I$=9yI z5fPslU%U(FwY6azup)%UK|2az9fdp%@>j3s&z^0f83qGTyLV%BhJC4#Yq*XrlkHIqL!<-H07p8W3+$b!k zlYO%uDG6#ggW;?2Sjifgu5TEfL@%+n>NfO=?$Xja$h=dZlGf3NM6m8u&!CaXP>#$c zuH&BZ^x`t^o}rzzT&FKw_!Nz_4rAdF<*E($6I1#)+vMcZdg_hKYbppM!Nyh8BCI>& z2^ql@^9JYfhl-r_)&|XpFNJB#=nY>g6%IT2l=OlxEKChpa4|ewVePcdP}pD{=|mPg z>Siq(yK41n!QVv9k6u6WhKBd=1@#Xk0n%uEm>8y_G;v3N)d%3chjl(fy=d!r2*eC$ zI72gtbMw1q9f?s6%=m>PJcAwx+AzQ48Z(4gv}3{T)>Z0X(+l0EMt=VC_=|g=qKta_ zuC&uRxWu^rfnJxJNi9%f`d@L5oRi}p;0{ByL%2Ed0r%BfmEZ^QcaxUcM7 z_0K=}NBb+AL3C}&&N)|3h7V7N`ZYSo@fiI$a4yr4GPx$6BMDwTBqWl~o)utn+TA+} z2QZ45Uat8H+$G+#{o&!P=iNhnp(QUDeB%819XMl_9u_Q9pjr+Xa6Tbnl94|!CXmjh z%rs)ByMDy0@nHq^wLU*TmCxF+F7bVBE#A_QM1ffNbiNTc=M(|6%-!#IgmZAP5+u^EyB3!kc=UbfD>o-wCS3oQDD&;9|H6yRKH2?SXJ%Gsfp4^Tg} zNh{xKr~^>vRWQZle83xe)-}vl_AX3w^48SUr13(}corBDNS*(p?p^oFXho*RdE#O%rO43op*zI zzPu*t8=(uZ`qKIH%{0XDwwuUJm-eeAO-5Hkc!oknxBIdkVAxt7Bemx^*j{6Az45n1 zvd$n;pu|543S5bVm5`E@tpJXU)2g3mx1y?95ixe^{0fAHR_oBFv29=yo$ya<^6wI4t*)waod0_LKm9 ztYdn@&o(@Y@Uqv%+z^8RW|%Xu;L4TslpZE@9wUnp-rWjz7)W(Kb^ZF3fPh;E22QWI zI44D*XE7?q;MyUz)1VfBfs|>jV0NT3A1xjI`w#lfb!I1(fWz!%G>3Oaqe{}b- z4es1mCaENz^E$(ZaSpw3HDLQ9;${AP_{p)?#Pr@ZbF@(-sWi^XOG6)6&dmy$)qnwu zAyq$l;sPxRmT6N{MP}b4w3ZzhrzOm|A%s9OB5wi52X!>m)wJ zQM@rMW38w=Iu8{&+pCzmF!3xlHWu<|LF5GlPIP5BMPRp@v{XF#k_zfm4~3KOB|U+w zNUB8UR9NUjfki4UyEUueoh^HE_)nLx4Nu3FRRhIQc8)}g021$L;~%G%yuzVa+ykM#U#if;uq zUbyfukr`47xgRPeuw5))^1fV$xd=CCHL?W~J!A`qEl<)y;th*;>kV(qj|TkCP1lva z%r-f)lrBAHlVQ2FEV^7e0WOlzp6UMMdNbeHIGeag2Z5v|rE^!sZpsq`avvKcvAhur znt4OgdHoavK9NVzAs;<*go!>Ea2vk=8XaWteE$$E#pf`7fOR9N0eELXfOv1rnzasM z6aQLZV!SZ4=mpj4Io48ER#icPpu0G?AZP*4k5`RQh?;$Q$^w2w;q1Wh$K{i@^^f?F zSy8xM`{LrVWtl(If7-t~ld+U&LXHNOFkDmfBoHIEyEhgitxVnXv>RLg?VC5d-mAye zHi8FWcp5!9etWu{gv5cyZ_i;va)dA&d61soMciRdox)a9V4JbsIp#@~HuMji`e@^n z+e0@_j~sS*z{5pxr%$h1y3}`ykF4!SL#A@8O+S6M&yyP|^X;w=td{ob7@D>HmUWDl z!7u;sjvZU};AY##b0Z=hTzd1ux-MUjHPsMPT*qA%2spAuFHzsE!WeG#=rSX^{s=8uIHqm&H7lWJ@oKq=r9Ew=xB{?BWF(ekW%~ht=;_jqnVM#14?(Q zHP+<4AiAyp*iNoui!+(c>%i;ktAMmfTi>3@m?X;n+D)g?Asu!ztC`9)Lm%69QCi=7 z`_`>qeYU};u+NL*T|}N;4Z;&Y`5y4zt8QR$=kL?lZ6NKyHq9XPXV*8(6mURNf zYCeDJ)D^hfrb+fkyB4|YYnL3rZf-*K1;nluI+h)ujEi$TDiRqEzU-O)f_Ls=Iri8i zWs%5BU*(FpD5f%H!LaAco-jzJbXfpaM?r$|X>8XZ#%Ouo6L#e6)l z(w)&1+Aw%5H92W^_+Iqh>@Qw&RBxHoJVRmNtdHxmYkT+J%TRvCuu;R|63hdAQP11O z7h|>FWl}1mFQ)zGUKuPE{K@{c$$?W!Y4Pz(ZGHA(wfVmMW7PY#hYQoL@^GIWc%7-< zzn@nNqFBF@xJkR@?Pd7-m`<^!E(S)|IdH%9d(XJxU)unKMEmy<{79HBj~i`4<1Ofo z$`ZOBwxmfREh%|r+gBaaBn|;qbcoLKe-Nfa37-^MceO;eOSM;J<%mswOFPLpdT!(| zh?odqWo=D@#X$q+;IedSJYhk<(@6e56=PK{oVf z>YKkr9Vxl|E@Vijx6RT?+=B3(BGcxo>*$82yWOA}gsSQ&!6vS_vq%&rdF^cn(F=yb z+VmVWLlXjzt`h}b>!2dcilI;i$5^g z`*%*Ts(q##xEWgXFdq(9iM+j*NYvo0lKaa2L|U4>>VtE(V_0zeoYzUsbs-P@?MUnvx$i| z7@XC&%G>Mw=LbcGBiM^An+P9f60^1B(J>AQ50PnN>Ren`*5SV&WKv**moHh~lwB;)hs-?7`m|Gh!1XuROVjCA^R3&_ga0U-|kJ#6GbTkL58k#_H3$Q*hx zQ>{^>9wOY>`LeE#F@Ks)GZl9U=Gr7KpgI)Lq(l_EK#AMSPD~{?S@WaFbHZxg)IWUKQu_^J{re^1vEL$eUG>7^Vki!{jtf3tXU_cd zZ0ERtAF4>y*29-n*%_5SIs-nzg8@xwhVbE#JHGQNzD1I=1FIaF!o z-~;o2FaE1wBm~ONMNh$p!jG(R$BRN)HKz$qc|o8boY@>0{qqWd8c3~LUsJoqw&*Wr z&0TbKov5R8ue`ixMxQ~U7M>HLi<~)@X(oS%1NfftY*vy`-^qncEeX)zHb9E>S=WB< z@n0k1g-7vwivVy!FquiTXP5$IE6aPTS}2GLUJ(1Ct;)+gVP6TMQ|0pW zgfoI7%mG+8d^y1qZ1JC!_G~Zd9zCW=P;t^)Vi*2m27>8FKtl%kKyX7F0IIf?RDy5d ziNlSi$b`2dw65uKpu4ecUCO{NpdmV-fCt#Y7$Qf-HImGOh4>4;8N~r_hzZV%$hT!o zNzYKH4mx^w=Je_6PLCvnL96Qx`Xfu>!LdxJY^Ba4=$2H~hYUqvH5xd6yr95`C(Ypm z-kO@6jQ9${E3=VUx)nIpySOAdy8XhQz+m)vW>}z`p>Wa+Yv;m1d)}hwQ@8=&>fev& zAo{xv&Ma*V4jC(#f*r=}Q}Z}DkKK#Ib4((Z-(o_E)2pN;t>~)s!Kq+!A9~p>b$OC>i~mp$f)3N0Zuvm9m9?&V z0#XrdGQeepS#{Obc#)plG_n7`PX@hb4^jd{<qE&>M$vNhD?$HcW|=opSv75KrD3cb)IBEDUH!jbI5*Wt@30Y$ z7^RIumrj{|*&;3-=-YEBnWdfE4j#;pSWfJR{)}TTSib^)xs;JLIbHg6n@lcywkt-+ zC|NyfaaK$4SN98&7*{1hkTh7MZ`weKM9#t06Ls32vGbiIi4T({AaU`A>jqPDOp%~a z;CbviFS{-c+-k!WJEMU(TdO*=C6SmK-f-M2`=8e!@-uE;SyF_+xodh;e$qVjZ=3Nn zjG9?67Z>flN=PI*Ba5}QlYto@`U3>zDnshYyJu_(`i%34xuS0CnQZij@O<|(`>6;k;4oH0W_H9s$`<5+Z z7vDsqx@bu6==&GWpT8AvUU#l?jwCKS&fkjubG##YGKV;e88RcZg{r){06Tbqo)UH zU?T!2#%6dJ4OlyLJo+@h{j73!NYqTI2GALI;Gn!VO8byU-*x(gm?7aqVpn@kP~Mqs zYfQ!acD445{}DSyCLVp=49!dC%S(sL$u41||5!o{e0NX`Gt^!?T~>Nda>}H=c(!Xn zr?Jx(7_^4h+~oc_tiCdK!p~~aqP_zzttU`OsLBQF11{WvfTgOsgievriyCVfbWN+F zde6xV7nEgXRcENuHUlvi3;aLSy=hpFZ`bz!Eul!7i$Wz4MJ0q%5+$k3$xs@|lp-N% zP%6@7s!)`9u0$CdiA*K@LMa&%MHv#J=X3g9*K^-5{@efkb8gpm-`ACf^Lw7hv5vL& zweR~nr1+?+O7EWK|2(Js?^3HB!t9T-`mp!#3!opk^R|`b%WNe{O=u&0l5j`j>{z7; zo80+MPJ{frXl1S)@^C)llb6Oi`kp12e*kVx5YkEo1>e)7VaVVlf<>^TCZwHoUE|mp zXm-y(ZTw48fiHIo=<{ndjsYz@j_7Jz3cUBm9N?P0TmI*+LOv;k9~(cg=v51jL{`U` zqbnY)G2L|MZ4Xmp7ebKXt^yyucP3Kh#_GQSQg_?3oZkwTC~HZgE@w9@bWK2+S2j^GdVylXgiC90 zd2kXAw;W_-Bwz2Vyo+&rVONn2DUaR%YL48SJk*h8bCAFT!b)m_m_bBvyBr&9Px{pS z3^gTe4YXccS8>p_v|{8w%iDYC&%9(aSxdU)I3N$S6r@osLboXca?3gKg$&)=ulY8K z1oEe?3ko^9U~~9l{Uw#x=YKvzd%dvwj5yw?6!^}mlP6<)M7$8(z;nF&*q_S;4=Ko0 zU?eE+9kQ16P66A3Ff2>4XUqM;6#|10M46}vYi@q#On$~l(>rej`4#+F!I}{cD@GvHq7y%nwqP*i?Vn^W#3O24#EQ46an~+2 zlLY5Q$cK*+<5ax;{YR$Qqg);iZyHP&@X+_|a!uhCmz4<$`MSai)MmV1YV`?P$!3zw z7Nujq*vhIHoP#Z^Djk930|_wv9$dME!HXtqNMvw13Z#zUb%4Wjr&yLEH(~%U*fmvr z_btc3)kh%CCZA)TcJjV`IiA?~{r3v7k9l;n>dHZdf)n9a)_yqpZu5WtMfm?7oED+7 zMk7bg_8b3NdNmNp|GFGS0w(gmuK%aMkd@y3-;eUApHS=jkjkZZ|GRve`=O$>(jfp# z#)=)Z@#*sStA3Jj-qRoc{h1o+xRM9X28aL41z66jhd4QVHpcDShRTR)bZtKVt@#hS z29C=^%j8z^_vgA5yje{gRa($ZbbSUDh44kk3|=CL*SqO|za=ma%FModv;A~MZGDMd z!WS(P_*&wfS$s{nuCJRvRK;0Z)ZFxc{~gAAdi39a6v=MxI$WeER`~B<+$J&gU-V&& zQHK8BxOC&c}&Q5$Z>R0H&;f(3Z&dKT7z59$? z&y?0q_nQPqH!CNnCTG=wcXR$fQG$}-#>}y1X4SWE<4hTZU>z7KW>V2V4+#x-e&!iy zX$iMy(N(PHVo~5mkBKTG(Fv2WV}azPrhaX01^vphEbWy4{UG+;L|e%;tZ~S?hg>$P zxeuGJqwQ7CaL2&FtB_ZxoEOxq&ISYNIR}P;Co$!lt*kWfr2T8~Y)B^9A#NKg z`Oc1#4l!kzzy-4Wb>ZNLQQl^tMtP` zJa#HIJ8Y*~26*rmo-s$Gs{28K?B+;x!SobQZNIs%&t+vjBqBb0293-DH1yyXTs+cH z`p_kLuEH!kuiwFa$V{7}Zy@qszH%kiwf6>nSy6MpH7K-mZG{Q!u+Y<{iHkaV{rz~@ zggIYW_89jM7*Z6Ngmc9O>GpyRHXRcD>*cyF5YtFLaG>A5do28>>~^q8di>Cve5sn6 zvb#;}W$Yy@Gq3qapmOvqK8uRVcS0GdIzEdX)UmjZ#lLz_OXNC3SIf<|On6CGg^q&l z5v}lm)<)fuZ~n4GZmL48OlD8t9PquPUyA+Tg2s#T=;8iC0%DzyiQsE7YSgNZhZhog zN}w^rnD|1q`Z=Xnd;R1!rFiHIRb4QngoW|nIYau1K4S}nm1yDO^Qi5I^Ry?bShu)_ z-LiUO9WBErP0!7(#D-}U$ey1aTeUXt9JWIYeJVd9N||+K+$M%n;fI-B^I0j%GOfMs zRb24_I9LEiqyf+Iq2c(T8>L$^a$Rxni397hZD?C{w=UVFAjI z=bSy{Us5LBu1#z2v*S23*d>k+UBBT?_zMe}< z)5Kx`ccMv$@LS=~MpPe&xYDKR@PX7+OcGA;JYJf2e%=|&^co}Mk&rTob)>BjG@|a;hIUFjNwFD@oi%~p>B|Y5GQ1ef5^tRa8>)Zl> z9d0@N+mR!0U}-qpZmWJjNtyrX@ zk_eM(^1|5gHDV8RJ}3vFwiE8=+D6hq?ryAC*+G^L<2l=~T46;hkI8SY+`)^IQ=7?1 z#_s6gdM6l}nzjRZ{Utuj{WB!ce%gsge6LnGhY_$JwtX~Xz<+b`kEyHiyu1r$9{ZCc z?e&{CnNJ(p+cD~+A3lCOtI_bmo7L-pdr+DYKss8VuZrJz_&1jPEFjw0(3dQ)4-fMf z2KT*v+xp|n61uGf424eH*qxk+S3x=G4nrxNzCu&1Z5U!J<(;>U4Glvx-xyYEh(u++ z!glMfvr|`bpAL;FA*<$(tzLESpR%&}ojY;Enmco*m7N~t-jE@`s3V!SInaLpps&NI zf?m9MK{;By3%fBc2n4LoAId#7i|d8yl+eARbg7d5MVeWB6^|ePZfiO8E|@Q_e44eT zZJU*r5D)T^H~H%)0=lj$Dmwf{J117`{R=bZ1>l4vfwNBDZ0-E0f| zgIg58Y;y|E@^0xAasdOwnd`%|rUARz zlM^R4CdMqNJ=ZIzm}z~qN2-GMSZTHV2-Mk0_P-HK5aWq!K;|w_>*RTQzd3mcvai4V zSZ~WdgQ3X2;RqEZ$Cq*z`1u*fl>p(oXFAcT%Md%{Xy*u|ig-Jt=ZLU^_24diI5-1Z zdyOyZAG&+^p-!P<%N;k@5jN3fkSUy-a@G>ECBgnWQ>9i zv$z-(c3ShyWdrSW@Y(4qA;AojElHKvHm++Q|71!p%-pg(DTBHhZkcolcixFsR@fsr z`8DBzhNH#^RGf(_1c%xT-c z04Z&`xw?`WF|`Ls5awPF9NjYT{$c};wH|6C1v8DIL(j7L;c|is|Df2LxN3-G8gD)_ zBI4+=V@%yCDSZ_czhZPV|HGG}p5~Osdh;&YHBOj@4;~#RNFr=TFrBjtukB4r za+)>`#|<-FL72S(0;CyE)l8>M&Jz9^gIRV2hHHcqiHHo$bKzO}jEmtT(xY1qJZX8M zoTf36mgpfPDR~QXH5vj0Wxx-{T_Kb?dFE@&tbr+I6txvo!0diSxMctT-+kF zc(dKH?Wdjeo`5MJS-`^TonHW$75#OTCWz*Ae>&PSfpFZqkMkj;Or07-qnb7bmfdiO zK9p)i-FY)EsNIAO%o##ePQ!}h0i59gkOX|J+Wou`|#MA-Y63}oTrJlq1)_GY`{R_6YebrYmR=A0d*g=23pWn zB523&Pr)mnJ|jqi5A%P?yhuzzcF^|;#?PliN@z26z=tJYA$!GZkHEQ#q}NhK_G$yP zT}&aB6ci4fIdcsT0#-EYR4l+GG#7cEPa$u$>>sx6-eNXoAI)sH8{OQpH;jkna}^gI z9vLAXhEe_9l>%iw{H+V2@o0bDjE(z9DZ~}((lb^-tA9Rr+u@zqU8_Jba9y3vd8+9OM753#7@k)pM*&#?iENtsGPJeXZt_XkWAyx$UEp(!o?5z)s(X*DA8_RuW(12NEqXQ zI1y)^^!9{?j;G}WJM8W2*H%}DT==k7KJg`q6@j1F0QA9hEDUsO(C|^I(c{oFR@A;e zRUw!rA7J!!zhtVUQQo;0xr}&y@hS;44V-n}g2g0xl)c%}FvFr^rn*BqAhmbG)3EBF z43U#j#)SwxQ!Z<8(_u)zMxM?H|8#(_5N5PqS*2gL+Hpv}VELn0aJ$=W4 z1JJk{_!t}r94IeyW@IvwlEzXg{Wo=iU5yOz-kuu|$Bxx-&RXA15lDPkA2TG6 z18C}g6YQG=2k?xHW}Xw3s6s$&N#;_3Izk(A;Rd|M5)+ZSF)IPtC&B;^G`|EQX594ZZ7L zkMTMl$5&}?VQ4vG1lVLCX*Jk8jF<6VXa;BZ=Tch-2DVezg}(@* zeb`dnRrBznL%9FJOJoWgx*6^8V{^_pe`D@pcsw{Oi{bE7BQm7i-on_D&nZ7ndHjq) z6~#j>min%%*zLtu^dh;vos7LEa=^7?EEu&=g(yk)L_nn#YPtH?BA6%|3Ma_;DZ zJ=+UhS3Jv>IyQa0B$+tS1@#RVWmtjC0Qlj6By4;IOF#fD0T8WlQE9p4`i#%c*<)ud zx#MT=huh9X%65~MF7BzCD>nh}NLXqd@wWT*_$<7A1%b=BfUmbIDrRK=v77y{W=FWq z3k@gs920A|T9MeFJadLWZewR>>h~_*vFFV3t7|kk(2F0RhMEHFv@mYRx1l=|5;7sl zT(#qg0!c8a#dL2Dc{tXYsb?0P6HIA^bd00LCz-8ElaV5?V)}c1{s&5j_BeTQ85U3f zi}Sp_KawEBPL%j?c;0*G9wJ9Y+5rBcMOr(Sc*Qk;>bg*6%& zE9Xblh5(J;0#i{fyRu^r{UGwAy(1C8#nJirq-$$^I#qJEE9^@LAE2nB6TtvaI_XOH zkB8)u+7%VKVb+%9b;b6`bch)wSizjHRMm$MA6`@xap9Wl=4PF}s8h?2PbKUFW*E-_ zT~zd5=u3mBuAG9whwcL!E`TQr8j`u|jv3>|jM;0a^XtbCxyblI4RMBy-d(#nm_Hta z=7n>j<5x`wxihh36qLcVS$KkX>D*cFfgK<2+m|o<$Y_~h7CW`ySbyo@iAScB_&@wR zg;sFQMr2gbr5wsU?(1Y_^kZ_(sCPa?{pGOAB=V5*%F938vu8Z6L&A8q2!g>hR;?8roZpmCqID->Fw)lXn9=! z)3utA@HOL)>fZ&tJ7A6})#FqDfKo{1HAk|C?0{uh6 z9;pMkPzSDl%6d)~GkxU$i3Ai7{Rj+Nv?%S|xmC0~!PWuLH2jXj;QUCL z2}aJzS=?m7nU8%WStYaQnBhrsqW57H619nv{K*)nDTES|D{v8E2F~&)dRNy)$*7Z+ z@3>84cH0Vk z*qXea-MY!|eOgrXifog<0Z|c012AcPXUP#(`TDkywf|urds@lBhB#Tf(&aWoF81Ad z+mu?1e|dX9VfNWY`V80mfqGmuRQ4)NqcX zTp*;d=BS>5hc0Gi3GSmjI^{lns;95td*@BvG_xDAUL|gsG`}&^a)qAs`c?wR2yg*z zz*^oz3kU|ugje0Dk0&af|SK=aDNQFu`QE^5&Gx zU6DIwZ>xpNDk>UNxRBuc3l4_qb)+Pm^=y1WZ97G28$q4AQA_+Cs9i3mEmMx{{x%fF z_GQv=?6yGrgatwZK5NzmvM(rRdD-&MzKv;UYNC*5>oov=)Ye*PC7_vMoscMK_y@tJ zY1h=znRNV4q6TFeSNqfIYBR`m<>k{d`pSsP$}ME9%7$t`@~|B};e*rMxm)3LF;&f6 za-wo+K0vX7u_g7yNbP-70MMxZ8>ZDW7S4N%;R!*EMm znCt2!SnO5O1;{7{&6z~$)a)Qk_<8MgqEkWp^;dqUZBtC#$hlC%TNM$NLzo_cWzV zuVzd#l8K!0gNXt=g!p2*Uba41b@XcOkH*guwwej~Dyyd`c?ZHC)q9L zTc4{8-3d6%kMFgDG^jguud&pU;dxNjy%%f|Fz8XE7$HQjPIj&ee-GLuyY{t{YVVkc zu5))E9g%G}Jb1TVUJLvIo-U`rI7(ga6+3EP5WHKyot6OqJ-lwVMBlhb_bBolX3z;l zf4n1UbA+y1viY#7B|ULCj?ct8b%6t=70!qVx`<51iZOcApCA`r>$gZ+zEG+1Go{6YnPa@R*HTq2!P(WJUAvk zvgT+jZ~c7Sjl3^+3}f*siU++xR!OqNe5i~uKR}Ji`o>Fw@%gYiazhXEFt~plc`*@d z?!Tw(!LHAenl@{CEWPoB-t%4#dWf-PRisQ5%FJq&wIs-}aC8=*&&({?ltlMqSlcHs zlU$iB{qOJiOm)sXF&=EJx};EYRL+uPj}O9ed${&)?#LXv8k`&^7i4zZu3fjT@@El0pu(_ikII=gy<)oY9b+JNF03ac)|tEtbA( z*7Q$ZHDEmp761g94JqhThVf#@7R@lM(P01FsMy{X)-1*E#N! zB#(o8BeNUa^1l2mfKpVAHG$HQf)5NFHW~Vxg3>9X{B`1m6}9FQ{cep7kEb0U)J>)F zSu(E8=21sKtQ<%|-xlVzd~SF=q1Ohpw>_#>+7|X-ed^`0*E~RoB;z)=ZnxQ>7hd$THfMSIltc}; zD_dg@u-@jZ?seGwrZxs`xsTPbDEvJ;+kJ%PE$m@64|8MBX70C{p^>!hH4yCjiEfDz zqYC+?TNZBgNI!jg1$o+-KGyogxXvz}pSyv1y8V)_3!Bk=tNrG&?H+8=8GlBQ-Eiu1 zYVExJnxv&cU_rM{c>Qmr$n84F(q_e(I12u7z2wytI@L#IBkx6vLLXRbZoV}2 z`E7wZlj9`R#A=hY3V!F1HaI-mwupR!8z-!~Y%rLwiB=7Jc@O977cPwO{%SS9N}F`6 zcj6u1U`FMT>GKx<8Qc)uKV}kxI40E<){&MLt=*IEyJ$1_@1;K5_!`{vDE}0? zHd>vFx3XL|KXPhux0VA|rE>hFOrpZ~cTmB2!_x=t;t*#{89jYrD7?Y;!ul`Zj&hb9 zS_NW?cAPq4->sH5$VcwX-gd3mQU+cQn4QX`81HJ`i_QydO^&W$BtUDqO~zVr?|+!C zJm`_yX>#zk;FjyM+Hd5vO!AEXiJ!f~EIhs}>2+Hv8Ia!0;W3@cXf>(?CSlM=_mBk7 z^2W~FC`W$;UI&%09Ai*0b%BdZT#YrEqQ=*mAy=ee7eO>DXwYBz9#l>%-1)LBK)w1` z?PWUQ48+n3M7 z6RDr?yJ+5J-T3@N6Zd6nLz}VOlxb>KPp(MsyLZ!fQTep4KA}g{t**s1XCC9=ebgKb zqOqeSLrm2E-C}jmvY`UQYI?Bx>6G{G^WH~AP6##a8<(`X-}GX?t_4xWetA8!5v^gC}hF0 zp?Pq1-?9zztoOoA0(CZVSWdQad1M>U;Nq^g+ckb0PEveJ_CwGT+}S?A>0T~tC!Q(S z>uP@j^~9PdQJb`L{jbKsb15quu(>S|Uycj!`k&Z^qhjYG35oQ)OYfd|rR=#l`p?UB zYn`97kJ$}r`|;z#jGr1mmNm?eU)w$S+hvBH4D5X=p5Fb>sFa}}j!4&-gx}mcATVt9 z{`6B%Dk>;wW$*2%ISXtt&`%T9JJMNq=H=C%si7bRUb}ob0QMbcP~UNZxn#A*O2aXE zc&I|y?e^Vo&)*Ym%7mXa2=>OdhuX~4mzP?o1xCtK(b0NDhL$@s zeQw;1SnVfi958|(2a$mG2$6mBI)eD5zHtNn@|~QWyUj9XL42dvKrkT< zY$R{rxidk3MP+EGu8=OoA85?9Y#;ww&NBXU@68@H0-@Bry*aE_QA=IV&z0B43=AZZ z#=5#&C7-^!OBr%k_TK*f*Z5L+{FyUNb4o1kKX_2`z#x5|TF(ZyJgs64le+z<_w@!R zK+b_^%<_b#WbT|(@5=liZco$y@jiUrpci?TE-x!+nq4Oz>W^}4=sB=(&Zt`*B`-GI z9I-pFXRy5G46W%iCB)WFoV)vihMSm@+Nr1>YEhTpxy@6WSg`quUS;5nHvJ*P7` z<;9Ep!+)lq?q*dv?PzYU>PXdsl1WE;tk_7E^xA30puvM_$}=t9ags1s+epM9hv^0P z@5lC^Ikc;|xNTIYKMQ8Be=)yzOV1w=9yHrxs?0Yr-}6s7(6iXc2-E&WQKCN&!+OmC z1d&yor=|J!(o`~7XGxw-}Hc?bo<&K-9v4(yZssDt2aB~-tqC3d!&j#8d)ZP z{X7MzxURmwYsb-w2KE?))2kSE?*i^ z_tTnyMHZp0zKuSs9)1-!)jwH#gOjF_#!@KXAj0ek6ex(2gFcW>t99p z3Aro~RJP57e74wb@7i7ywKB@`!^qV9KZOc1F>2<`*|MNV6tv?Hbm7;LIvS%igGB#jzv-=$7U6w7Grq1b-u$)ppVqboRvNM$Zg|6LD#305h5fN|F147=WV4ul z$9-ip6cxLsDrfoF>bpjiG)algI8k+ZZS=;t1rhnOFGUP+n?Yso@-(MAeoqO2YNz{G( zU*g#8lf!9O-#n*Y08xDc@GkHjoKHi0OS-M69cjOc4_Hg(0hITdqk1a(rwYvS6F>*yrzcl$ZMXP#R9A5>bshBb9c2BK@TVIZE^P(p_q53Ns5a@GQOp+*sBn3)A zw=2&J68b(ERrplps8jgRFcbR~86D5>s2S`?zj9?G83+fH;6+8smlo%Gi!otVl2i4* zoSEt*SGagW_WPad8+@Ti?F3ZVwaf1>k6QYdz$S&=pI_`|nzACcXmBiIdHLFSca z+ob(py|iDvn$i+@NMeg9RlJ~6p58b6(B=u*)4GIOXk71leO*mrR6mvA;NaBy6D)qZ z&l_>-$oF(W6!Beic3SPyVr|gX_JpDdiCbG7rwir`z0Ape$y?uR~8_| z0m?*xwmxP75EiaOl#0HM7+UJg6pwk{uo>YU+Gq=QtL`WIkN@4<{GhJYrcKw9^xI1; zr!VNT=8}xV!7rWq+Ptpc7#>E`Z@hUe!L;yl-R~gTi{XoYg8Zkhynf}%Jd0_~=fyuv zGpV(9n%1R9Rom!*J!ut53vo_N_@!kW^8TN5{YutCFaj~&7!UyIf#agcD~Knu#eQV* zYrq5O)c_`-dpm5UBYUQ%?rp2yT(6d<X*gxZ`&()Ob83v1I-l}fR)!&fU526e0J#i7#ZewKml6uR;eoyh3lYw9P$-C>j zc)Yd1^}cy&vw@mHs7Y;tX8P__N1QTdFwWBO;;PEV6t7wR zSA^DDS5E74{%J|Y>?2(jmz#DEUvf`NHY(T<>)d!{52MlA+Qrq?n;?|{8gg;)(XQ+% zCDkxu-2?Ibx*J=Smvk9^_VtNDFMCx>ju{49f)+(p-u2*))OjmcdU!~LciU>U`QmL-r@HVz3JQaFcN3|?m;9b~X<_r> zE)z_QlI>K({#@yNc7pa}qXCaSLB+sFL?Vuy_J@j!iupx1Os+}DN`_N}Bdo_sjYzR= zr&%wp6;j>zYy`A3RhibTWIA91^3=BrrUiy8d6o@Z0?Yovm%`?mZQncT{J6AfgrrTJ z^J;WyTBhgNExZ1Nx8kSz}S#@QeOftas&$7c%bIpVu6!=`SHJwro9U z_6v8n%IFQPj;c+weMXgZ>f1}pxk0LPs6~hZfeMl?^!`$kJAwakx8x5VO`;IK*!M!X z5c{%szVXH->_$eE)mgvB(FUdmiNYZs&H7bOIq&7`n<{r^{`BlR@)*j{tW{qoNZmbi z`t|h`vz={`+WR|iE?A(`3`s23tM9dNec5)&v8AfLdl#-5y}npHB4c%ywX5uAlEl~x z7W9KnUbK$RH!m7b>L5Yt@bCt0p1M8aFQW z)j-u7TYf2{Z#9X-V!3yY(MN#TsvP`benT}7g`gd#CTY1Vm0IMT^i*!0v08aXRu&8= zJ>?IySKD=J%of{)8@(QNDx`}}qfBjW02*R9oyUcX9Rd$-i+S#yed^Q)BsdR)eu24w zLUj=d%|TDk0S}C8ihP$=3I?GA+@=jZF_i9LS_TpP)rqvDV{QL9RIWz3bN;EH`^s!DYPl zsto6#8!sv>Tt7}zORMnlW2L@opQdJ{xQdB=&#icsnZC@+Ydym8U%TJE^Xd|&IdEV} z-}Nyy7ZtYOkXRS8el`3futfgTV~yF}osd?s8mB#FYQMjEV1)OC(O+GN=%x>**LF;T z-IO~_Al0Tu9uH{B=ziMk_(u=x_E4#gQL(*8bZd7|=_JZHe;(pVcS*@y_m)WdDM(7>%Bd~dR+kt@?&{^;^FT5!x^4T(Y^3&bj@`SdpR zsTCA=nt2T!BI_aaq6_*?0if(G5-jgaS`K2x<@-WLWH54M#Kcm=%#PM3HB%S2#fxwD zt&z8_WuNKAON&D6k9GH%6}iS{bFH2%_L!2bCt(YO+;e!nP~+b?IfIEZ7NIajKBJLp{Q#u2=b(ap`*3w{C>&;{rg(xC1|97hYGKpqaO$LX zYs~~3I2#fo#}kigf_~}i=o}U7gN~Aa3*bM1x6;+TXIRjgqzRs|pm%5%sekF_lhg{X zgwLLpnq|Jj(g)p@m@u)OcJ$qQ_f{@neyE~>atx>(xXyIcOMt<&Lx+moGGV#{cRb|* zLr>)*C!+UDxTF!CTM{?I$Y}Eq>H<5oVe8h7Las3SRP9zyI!r%B8e_*Yyzd)9>*Vp{ zZM4Q=lQ0XDqKpx|Qos93T7(-_ahLoD@`2&0Z4_#7J@S+5AUI_1qPG>oK;9D z%|mA?I>_r`ObGO`g4e0h(NmzwI84l^k`WW>Dc`}7VDxCAjH49fv4W!Va1vZ4EXfiv zsFU{Thae5Q5g0D>F?z?ef(k-LOxdI#GyceQCZF!vv7@e`LHFp=lNpfoC#XH<5!Q}= z-+gK|wQ^j2?N$6Ij4j-al9|fIpAm(Sr=0KFhR0=fuCKd?hxW)S5^fiICxE6Fwa>;e zk&$nq8%|D*+Ro?9@>Q#9pw6VpV`w5hY_r_KR-VVlQCD-3OvN&%$z@% zEtu-#KDK|cpTGZNPtOej0nj2d*6q2T(ZhP;#PL06ib6of=^b&rwzN-5%l{FB-GYP& z`G|yf2WNs{$p<$WSPuC22{b_V=b~|jP^Cs`CA86BM`U}vmK6(h(BA4Y(V}u_r!^Gp zuv|);dUO#Pptp5840N0EYk2ZqOeq1LyZP$`L7)M5MAqk)30r}!BN9PHt5aFo(;;zg zwwIjoS;6=YZ*rY;G*5i6NASjKmFLH&dj-+Ph%>4AXDlVApoH}&{ZMVn*M<-laGjy;A+X$D5Uv_?A z!~_)7Y4kfy9he=pnn=4oDZij#{QkCj;=j+SPD3pum<(shiQgqGbIF6gxk~@Qgarjx zQ0Tw+q~al{31u}DcxC7}4;E(PWkiJxg#zx8`s}-o;yD*CFdemM!c53_C710%uvxQq?U@rNrp=rg-fw_-p)#|`#+I(Axz4mjj9-1pSQvKZ<9>lz zC;j`obL*TBRp(@9+qYJF3KKa_e*(9!OnwV#fD%!%@+MZgMGOZW-PmCHvc6u0y~Z){ z;H?y)teawQxfXXH0s};X!dElxbk^{^=%vE7tg{-%Z1l>!;5-aSCr<2a9PV+g*xIWX;*QSshh()9-qqFZNK7orTqP|6OJyNL zn?RH|4b32^q_uc%y*Mg;HXzfd0H8Y8*Pn_Y z-ChBX=c}fsK5}x5y&m*fdV629pKN~CE`n0Ts`gKw@|ok8b!j$WiV>wIc_}uTZk^?2 zWknAHqC>1MOADF9V+Kz>etZFcuO$lj=?DqM{bP&1Ha%ZQd}Of`T!|bUnLVw=gA9Md zW$!$1M{DoGJsH=oZNklw-~!|0)w_353kJ$>h4>hULiWj32gV3N)Pa!2FW{?7IuJfi zh612_2uTRQzdmfzk0(Fm64N!?pmNpkwRx* zxbV{U6B!aux86RF#uAx4$J9l_P8~U4>58twV!F{~ebgTaBY5aGZ-WVyy~oqKa;W=?6~Yi@ zo)rOc{l<+&8@50fICDndu8dA4Pz5ytm@s@jK0jwQ2LezA@Xb!uZWBsZV>&XL`Hs7PQ?~tyhqLCISReTo+Yy6QY$k=AnFF{SONnp_AB7tU~OV~E6AIE~PFL@t?=osGxd zIGTI*$D;eEsQ2l^p+bH{tVSkU{;&z#ilRczU^}6>ck1h#pSv!vhD@z3y6;y%Fxn?#T0-i zjvoh2&F|b@r?gUTwtZc}y?ae8VPgNYOm}NY3I|ulfGp6C*U2^%s1A2E)%@(M%gY7o zm0kgKFFs_6FOtm1u$f`e!K5W`>7hSG$9{j5^;tKCOA96Ygj=+iFtV;VZak;&m44F) z?}n~~*71h)*=zrD44{Nr?%WC46U&d)uWtcRf6rYsmMMTJnAnZL&gofMJ8ro3z|Vv? zO5gEuMFpOQaybEeZM4DG*E9K$#tI<=uRzF*n1S69h$FFmqHjpnSwJ4*MoINxB$CMC z$UyPUw?SAUn^}g3TkOaU(`lLo3Gm058q##}%RwLh>Kb zD;6E66#LS+_f)q0gbD5p?E@yNPrdeS96MS_X_(2t$E5QCQiyvv_%s?ED~F9B65zx^=7jv5m0nu-903@L+P_? zJ^3DDq_dTk6{J%H#mHBL3C)WaL*2cMAhAckar-Bj{jP8@Cq)~EpAb{p>ZUAM16 z2oYXX)IrBVNiXH|zU}6)Cdq(61o52teA3_go4U_w{ZwPlWL&l;VT|OC z^b4jc6Qp3#3z7`TnE=DY6pkSra(4aBh?aCsT{Igtp1X1PH2F>a)R@pOBsV6EBlkeyj}IE#b@|ez5Ud4FYgzxD*&N2g*eUWM zcK=@sk^RO?<>x;*f3A6Am-t>Hu|Bwq^|32`rN4TVD2UwItWtWm)U9BvQ& zZ>ZG-iN}u~9Sqo8UGNXv?b5eKpZAg-Z~u`Ge{UoTVFoa+!KRE4pu^;^Ec0msheRJR zqPuHwJ{6P{cavVedpF1;7JnPYGe9{cVMK+C=mE(N?YC{gKEk9;_6x5zHadF9-w{9W zYx2w${RkgR9GFQ}ivckE5rSv5WG082a*m^88=i)7 zM=RPAcJ3Stm6!8N@Tq11Pgxo6m#$u3D49LHW4=(Xje|$eFQ!vUHvxbVeimD5{@vtd zJTt#dVdq`q)J6?uj2nOibQg@3o|KnAMcvHwq^c;-PcDV&2M_vC?DIY*v0<5eLn=T| zo5xCIij+gmyk?D>iI1zD9i)vI$}dPC?8ObvpDlex4qVh-qOP_U7vM_nj1SMD13ehK zt;u&+N-yeeH3)p3h=SQ@FJ)z+E}=#tX64#IIH}8t)}y|agFVlX2$R7u#}RJ9yAmv^ zn-31RHX~RFNZ)Tx5I2=J)0kDza@d1w-jv>*KO6fy4bfz)@iTXmQa*d;y#ARt1FxRPthXys+aL7!GEhD- zQKtTU)Tf=FtRgZX+OlAnhxw|muI-Y;4>_Uyb##nJeq)ka1XL08D;r8(ClcXb5@B7fH`C2(RTfAVe;dZ65NO%GjW3=PM%Ry7Q{UyL z*Nwms4cS8Ro-Is537+F@V~E`8a&X7Ewgan{CkUyz$U?A7zVC0-bFxy@-OyYrQ_zDqkU(g!-n5i8vZ?Mw1rhgI3=aZ8fqE7yt3%-N%oAAb1^I zqUQDR&$%?D9D@hHr&rbog6N8+tTFYQvEnhMRvN+HRbn|mL^LE=Z`>O*2V(z_8xNBijhhFpVMJ(CeQp~>4JQ3v| z)gg(Xj#W)TnjuAie(>ng%qPh^vb?wRIr_`<-0!P?^hYnR#S2Gt$MLAlKy^xI0ee>SB zX2*DqAgllN*%*Az$u_rMJTzXg^ipH~2ghZt2jKGT88a}^IY$*YWXJ~yxNKRV7@&2FVB<9+PRG{$2eVvO6rXTDH)*HAY2JVwIzpm9QsxkKwO1 zWhJ%c$CO3*?H`f8yKXdw}$3-7ox((Tg~4Bu2?u_a*=R>Ed8T zt%T!5{15Y4924We8UN?EHsWw;d0pl|f81j;pPjVe^qNqMcI)v^D*m$@YC6-76^XF6 za5ab@FycQyn~lGgw!GA(xf3)zy1x(o@At}i#SpFj-jRjIpRxb5r~dN|e{WTcw#onf zrvH4etYoi{NB{kk|4)5$RPex=gV$0?Vx_(!-Xb{z}q9l6(6i< zpx_4R<64sEy>g`)#V0Z3yo=wubq9=>?tm?}YvH!|k#;)6O-%#AA^2J1g$tBZW|pp7 zmvj2dx0(UNhOJ_eksJ7pS@2}T_}T$u#`Koig%hsHs_~W%cGq_ns62iZxB2kVA(AqG z!tO|vkNYuJZm#-4-L9;<9LpVRyU0eR3PV;o&wX{Sqi80A&HNJ9mWBgf04@z8jgce$ z(Ya;akM+3q;K49`{mvp(;B@m_UD~Hhh-#^ZK|m|>_{?a_fL@I5%9{fK!d(_5?wk-H zgF&~Uw4#Kfl%as6_eWX7$A`HK-4JzP;g>!5Qjy}L3FsYjG(Fvj1c4m|G6|G^Vp9(n zl5l8(F_VT28kA(@Y5QqO?bv!?5LjXI@j{Q36!_YnehGYN_3H=t(ETl+DJm;twAEK6 ztEwuv)ZZC1R&rWfkd<;*uTpl=pEDXCmP&{hsOaqMB`X^eysGw!7H%1wNLBv;Khc-q zFjZE*ma4lHmp>DxZw?r6gnZk!^|tu4kgp4WzM4LFZo{WfE(;e9y*9^V@nXyBugJsr zkj!-^D|lO1Cmu`;X#M{E6UL$oyEN}iO3Fow0cs^>{{*jt}Ai6_FB$Q2aJZ&`EAQkJMce|Fh36Fc%wNqx9`i|{&~zH3kUY`ix+<)=M};f5(FvOs8bhL<^61H z16PCE+!}QAa5(0HluWElUarRhy@Ne$J{>V1O6cSf;C6zCL*m0Bspi2lBFC9CO#|Bu zlI^;Pq}4`B{}N&@X&fd&i7Ss7s7jNH!ao;<(1?ziq!8amK8$ur6aw^*dfyWS(tFja z2c@N-zkSmmHjJV4$gwCb`e%M6AuE%m94sdQk1#NR+z}fW7b*DaHkfy`&nT@a*-D`R zP6ClGVbHz>9iI-r*jS8`tbX;+U)jIwyGWTCz8uxoy1LV(Z&C4s0WiwKieW606NEe6 z^0}sAF}O5PSU$4T#p%wOi>i7r;i0sZ!2acLRD2ly^IUvt>d+5*Q8qY#(6|Ya=+LP) zhE{Cro;o|{5)v$Bc0tb(#C29jnU7a&nhb= zTQ36bGg3|@>YL_iO&OA(kINO*`~`;{2ho5Pg^+{!s;A7wTZow%A`#(4{^zcKVZ6uS z`SE+FUp&}lds|`)164)&yL4xCkyh3Ca8<+Pho;219or-&>hh#m_BPQV1kAAkZgQQ& zn*b2ysDJc;4}ID*%w#zx^;0vO>e*|wqah?_!m3?&L|7 z+=A%~8y_Y5Pmsm>7(oUR6m%V&I4-W|_j8D)DcJZUU=EcxNC08SkV^je`4ck(iO!v` z1^&ol5D>=pD8&%U%)h$c@WJ^90PTQtOhx-^r{f=VnhGBW4lMR5^FKUb{-`vs_Laj+|L1BwU$0gwQ;D)_aEzH>GZ zyS&u7^}0icGLk2dY!=PP^5v&E`YCiNl3|rX9^dfdGwYiU82ABGrwN7tSKCz$3(1Eb zJ^D(8hHcf%!Aj#caP(C!pwMkrg9!|a7mKE#cV(Kf1K0WaQRoX^S(a%ee85xEea1Y( z;09>0>Ijej53dbdOaddILI*LMT~BwGctoH~8MW#Kr-g)?Nm;{JvVVhQK~e$~^{LUE zeZxF)@c!LaTG(6*76rfmA;ZCdhg4|SuEQr!0yLpMgFE8im;@EFR=lKB>d5?0M>5tU zPvDQBWMv?WuA(hSBP)wNC?Hrzk9HBQhbhEkqXmXFC{MyVR-O84bx<-y5y9j;I{MJ` z6Et-}EB5l?xW~%h&R?S(cMq9qjhToUomn?Gtw-5o#*TH@WvB(MHHjtC4(CqO9Kk?+ z*|Nb~EX71u*~iPDlP{C_kLE`(vAk;Jep9;fC3rcYbY))K6#0>#xDg&W**Y?V{+I;) z1;aQ3q~ar~l1`sH2b2h0*;ypH&}~tVeMMmkhY22{{6A-bR>VbQQolx7Mp>m}iox@D zc78@bK;o)md(C|Ywe`XU5LM=}3Ob&^cDb}}j`uoN?nMuD>Xa$GN-bGQN@etmRI~b# zqm_2O6ot^NLPk&+oe{?R0R2C6{`?o1Ka=h4|9owj0p_h9@#O4+JBHFguc#;S*BpK` zWhMDNVjW)N{xNI2i4YDCYzc^r>(R3(qgGITHobetGy-9M1-}#=Dx-_;N*Dz+{#h#+ zn$u?(McWB!5PaY@OH}s^PqvE?Fy*CqaPl~Wl{fngb99?p7BKOG1D#Ds8zrL7i)}_^ z=839rlP=gX5>a$)1P~-!0#|_y_~u4N$ZdgPOdrTacB+i0CBBxjh(UU!1HF3v0FnfN z23--VH66N(fXht#B0hn>&6puLo?gt%d>Pwa|8wK!8*W$$!5m?PG^Y1VQD!}WbCbDD z5W6q7+jbK<6MLBxfW6ECB3S)^RMX3#8tXmfreRy%s(dZ!d*U96wE@NEQY)4&Wv@@R zwI!fHb>O+7*=c5&ncJd8C=EU2?y7CsEmF1oHaFSsd7D{1E-wTkXdE?*O;30QHl69^rtP~4Nv5T_ITxvi!w2)NMIJ`%<}}N$BzRoO@$o(+ z6y(UO4+}mGJkqpuikb{3y)h{Zr%zvvZ7iq6iAg59!-g^Ju_|X(znPNaBB(%c0k~+Q ze`spDH_gp&O!d>J`2Kdi!Z4;HT!-Ag>miZxQ7KY=Vb%4&LQeyP}gg z_%3R7_I0IF?)CBuu*!6KG?%IVGY3HkHv@7tp6Uy%PL<8shvh#Vf(6dO@q5zK-QZv`Zt&vbcf@~e zV|YFw#F(RsiCCVn$^Mj{)ss!rrp3YPhbuZNn%Rx7O)_H?bFf-^Ac&BY#yi~M~FPCRe-IavF1!Bb&! z%*OUM-_}hoqI?!GNWG!vBn?@tfJphcND9`kH!)u`aeogd;|BfBjpBuN;x+Wo5s==^ z9G75&C?~SVH+Cy+vTGL;!}1=KdOW+Sz$6VIJZ|`WPg%#IM8l*$;4Ut2Q2|c@MzF|7>i*rmWi*OXho+Tsw|K z?%*PB@w(mLFD>`j2w9D!i=r~ET1s^A{CS0Ki3s?AU^Kseeg0FqR$GeNspYD2n>CMJQtayNCd>-(`^KfD6qiL0x}JD-!I|t z_epSHzWmDKd%byixLohwAIRaIv?z7)F?I0Z3Fo(vb8J^1J$?N6G0)^|Sq*THE1!i9hMB9|y6Cufw%m7|MU#RlC;ePk8gh6ojbTc ze*|WhxVjGcFeWMxT3$t6r_re(3ZcH{#DMJcODnX%HUDJd!dtfcS6YZNrU{B;f*w(g zYL90C7{Y%c&mgBCX<$&9<=OqwKn{^7ckZ0Jd>L!`=cGTR)HhNm9IN0kSvYSV5>SRu z_9a-6{de0^_u<1Y8kiJ0Q0VaJDoB>QwAvSNLp-LK1>8eqLdbbAQO?1qwUppr)cYwX zEw7c1)h3RF)_M%uTG+w7AH-s0@>TXfCJwm`a>(~F8dq;_7U#Pqt(Q$={l`g&FXDMZ ze&9Huk58YV*SJ8F=o{O%ZVj!;A_2p3#kJw3`0fMG|WFLDSd4kVHnODVm4qR z%`RSWE#LxK#YR>6ZNxs?i!W%vA6(`RWtD7(v>RO2_Q+!X;NioSPoHkxP8U#qFb7nzu8622VUcKcv7F1?$TVudwGK{zC$AV^}#e zS>f@>%YOzTgyaB+VLy(>B?G}oWZxgfQ@x&ao7_AI2PG_;0PhLj`6p^`9zS=^962g( zHCwmdX19^6HTaFuQA)dRH+Sv^hOvV$&cTF>3V@4DtY=T0%NFJZnU!?fvUdSUBX%|( zo}TbTX;e7Roy+4SBGP1hOJj|!1e_ZSgMz@Q3a@N}u>ZPs`;wC2l@!t0TDUNE(jnd^ zJ)+0(dGn_QnD%h;5F)YsGWqp{Du6ti-k8m=SV2xyLoz_s4JNqjwnyt;Z|CFVp8u*C z88n~MmFFcAF?)ua5S%amolQjT$1d6YN$KKNH_5T{6w=O#iE3@k7gfAJYATZJiXub5RU0SdcoKkAXagl z<66e^(>ro2YZLh*;AQadZ;9;C9XsT=Skko8)zb^a0hS#}(M;>vebp*+zjtM%mxO2X zN`iIjn6=ATT~)^>0DA!_kOP>ke1>@Z0$mW!Vk~+gO|Z~FwD$MU%DA_b%?kgN^*|EC z(DWrn$AaNeMegD2N zX5rVEfg?vMzdtHCe0S~Iwecs(W2M8*tv4Y=0T*wzLNUTzD($Ps)6=t<)xa7feIbk2 z)zENdf2fbbXMhv`4{VI25l4tAobHUrp2l)PfZ=2YrK-2AY%?bwHSnSv)I$fWTPS4U z-hbv)`}om&#g3H3#I7r^R6h2vQ6pJwUNNE_JZob>KoG4)#FHoi=gk|dp&{D0W6 zr6n0-lC}-CxAD(oS8!AwBbzatP$nwtbSo^&|#=p<- zrHXb?2EcN#v+2??V5cx76qX*lGNS$`t>>^y=|Az|;*iQ=OfM%YY|-}j4HO*YGj5%Q zr$>7Gn^YQRgOnAL zY%)suJ?}o}bI$i4_}!25IFH9Ual7y9dSCDN>-AhO5iDis$>0iohRP-2*ATSW=*iT) zCX&m38R5hPf|O?VqM>0B!EkUUAe&FmzyP+e6Fw%5Oc9Nt)GIi&;8>H``X2a9kx@GK zylc2IbSVOkO*7-;FEj>_6%|>LiI^Q6M$ud}Ogs;%&W6f!+-a2oQFsPI;pQahb-1Y_0P4 z*3~U88(LVMDz+Z8jqt7-T=!qHi`icU8A{Nns&lg_oqmD!#3~TgjY4GqKU#oyIJocJ z!IiiebY4-LNPf7i>8oAH?x%@Uz%n{)`PO_8`iM;h+sPRe z!cWe;uBF2h_~?WM&Ffa3*ljEZp(lSZ~}*z`-}vhG)R8@ zD~^&%&o&lTK4MN-sKJxxG`YS!M9MDjqr#%PFd#+PyBq{>aWNx7!nQ9T>Lb~0G7qAQ z&{L{JQ~YUrD30PLE##Dz$Ky$j4uLo~OSl%BYneZ#>H%~z9ZF>U=nMgbZ3@0>12b|3wQqci+LYZMuds0=l41<`->vc;A^F}R0R=JyR z^X45-dD^$IvtWn*ge1~sFI8HSS;|7P#R8BZ0jgeDLnG_g-n9qukknHA?Q*HAK;(SV zl@b$cs3Eh^43QKU2dhOMuBnGB!%pKLDjbrr1$3qe5@|c;wuP zY7B9V5q80b^japvGB4rwTffV15fd}ke-1}?RBLZFUO^fx;dh#{K9%>@3 zH+yyYY60&`X{_5VM<6bklZ`3bL_4O(<+M`}%?hS=>fL~~*<*nGv@~pG&uSzMD6;pe zU_D~jAVR^mPYSICK!2B3&aS4OHBCoY9spf8Tr&!!k_d(4<9wf2lAcJ~5dYUlVK5Gjnz&i#EJll1pbhH zNjryu(Ufc(-LXM8tX*;dD`>RhF%bT4tH~qUw%$F+f=A6vG<^D$sYVfMJQHyQIaO?d zu>dG=okgq5w-hdN8hFzw(TT9f0ezGt1m)yrK~^gqKKNzkL9LeS7n;neiwKpXg68M?Be znE`?~!qKA*w(hLnu5i1leUG@6jLrg+9+Yha`h&Fe^tk|@_!d;n$u=90XCK03f@{~_ zC%R;vptf}o!$!U&pcf=9ZwuK)cQ-O~FaKfPq z$?})ejUPKYi0_6x@;l2q^w;<=TbCm6qvJG`QRlzglMxm@XO~VryNWgo1jY?0Rj}8z z9U>V&h|sZPB&-`|Fhj$L{v61xawT^8UoH|QZ+~|2Tesgc+u;w`(ZZ!!sVWuENmTk= zIb6Ci^o1zG{@Yv+2_HpV8w5Xel;MbQ`X{CuR2wN;6{9XzR#r&eCx&)jJ!9(e#%)WC z_49Acdk!7CYUvgG=pY*CY0JS<4R0s!6{g*$y0kyU?6JO}Za~c99}qtPfgzbdTxKKu zBS3Zdt~qy6ESZt%*x1^x<6nAw$|)jZFA(*Kq$(J+fX0NSeXKM#@9h1@^+meC^OWV< zs19w~fAB6!9_NXKMhs^7S2_GEZh_xXqONeF7!a*O7-IL|T}gWxW!bb4WR^nN8&=A*ZX5s5e4u-yU|D=1lg6 z&9hq8{MGtYYZ*z&6BWr4-bDxri)enSnKPEV3+}j6i4b zyn8>rkOiI4{?R3J_Q@1-*2f}LuXfL279M>u+r|%uTi(90e_O1qknY|{f2{nUU`B!*xIVU z_9g?K%0`+7dp8cb;)WbwWg@XpqR@DdfQD|0hKl96_Tbmqh@t|MrCnESv&nMIE_rx| zr8ChxJ_Kq7PmS|2fR;k(QAQ( zg=7=aPf`HVwX_DFKYxx6vavDDHq4MBGpJ&@$aO7r2OHMZu?khc;{WXtdobCMwOT~N#qt5 zDr2ORLONrH8At&iOAfMg1{To|ut>w~X#OUeeEbNpa#{}ABT`uR?V>q~cbo8F19|ES zh#%Bvska@PPYaqaDlY!i)z#D!CLx#}#?FS*2E+tT<;g>PVZ7tsK|^7wq`y|P!*2W7Xi;3lJ;P=>Cp_6G z>mZ+h(bk51w}@%W9Fj<@~w8eNy zV5ln4-nckSIwy_Uv$x;E8D&E0@8QVl%1(QH3swV^&RC=*_Lq{f#{b95_I48<9)aLD zj3)otbN1`YTVTb0+d3+WN14UBI}AuWOG| z!}i&NAQ2rt9zSq?V|Vs!WKJ7Z^!KoAzb&(GcYKQZ8vS+d_3W%W(E#1dUmxWMdLb!x zyHii;Qad54jF^Q6A4!?lnlr=H4AV?F&O9A&VAfoC@Vil+@j~GELAj2NJZ+}-WxjCZ zOq!1;&!T=Oh+Elz+Kh`tsosyZ-1a;tVgB&)MS*|sY$E^>g21jav?@8)!^YrFDa;e} zXKfQh;dAE2fbH-?pHUM@Wba8z`D*XC^{TkbqR6?4EIpCy>FJ1nr=h0CISK_Y8el`E zpX4jHZX9+IbOCoyjLOv~a#cLx?_+&Q@F zWrJGO{qob9+3LK!A+YX3!oqpNA4p&o+k|@;kZIMche%htiOET{;2iFO~Lz&zu^WZY37 zotQ}}&Ug(AvB(yO#;Y`I!J8YCOnvO z?b^KOd!szlTz1{E7*_kC9rJ*)wQHRcWQ+&hUptgT%v2QuMOtd|2SKUJXOS0F=Cn2Q z0SR~J9+a1szEos;xTl54v54o!UI`sZSjaN>c{6v4=|_1-*YtCt)&X67F=JaeL6dJl zob_Gm?a!&0qgWaMa>`IWKrQk?mr7X27JD2Tvu!;~*EDFA_o5y_2ai6JnmSFG;kGih zzC5ydRbxF8UhuuptyqXGlA|Qt&BJaqXr>1!f|ne;ZOSRgu{!rVSq@BiEF*~+ibp>R z=PB$EMn)GaE0=x7yD5%?EtI=82eNV1wdBh=bq%;l32o@Zmy@LACm z4W=EhN81l?!I4cvpjMwwlLo5^n(h-zi~;@G2q>RkP2GJO?UBtIbl2yOiGqLbQ3c9dWuAhI>0S%Z z_QA~kSEDdjLwEoFdoc5=s{?!wzz$!gqytNu>31Ym5{l(e94C*ewLX9v3M+fee6C z?b%EO0)VKW536Ia)Y)yMz`&2a*ZAe`9RM*CGl|)SYeJs-W&Nw-4srnpU2<}tJo$*= zV2Bfw-ub|(tuxw&t!f9)3FsuD4JjTI7ki0WWb!FvPK2wX|%t4bsv4K&*7Fx&$g!?J4PMx#W!& z&xf)naq7%|I>m~W)#)tC$Hs=@PvRrGbMi1&(h_4`7l3ddV)=qjIG00EPh#pwPY=`^ zmPk6|;h7-ecN}A<;FN;$HhvUdW@u3UGAB!aeg{DF0XUg9jfb5!5*@0vWrfJLZJ;Hv z6@YL>1G+1f8UO#}vt0l?IxfhOhKaRa#r^>vP?2W}*dA~^HO{wDn=$W(XuwVg8WB$+ z;%EW3LBoJja1s@S_d4Wbt!0c5NwSD6QwvRJzV0t3sQzSz5M4Behg-{blNK^mNlt$}FQQHgv7+zou*TH+C zw)%x91keTAd~}ptT%_z)(9O#$tZWkIk(Z0%9DBqIY7}4}Itn1h;U+Z~IwYw;%Tkt< zVfZ0Vgs#t@jSD*E9+m1E92^EM1{M?QL!dERMMZa|nEh?k6X8FC@$#0fb@k=@)ba4KzkTWm8B8DN!U3#eRMv}aD&Xn7e^)(-Y+rMq5B(fO~7#2pM#Nl z-yXfK(SP}G{yBo`q*4r`oC;6@fnmlDL_v@|r#pgSJSOS$FyyGHnhnh))A$`#QPGp& zRKWt`%Xse8Ku!lbJ~_cY+!|5e6=V@BVHPCno69x0%y7atH&cAnIe)Q6oslXJngX60hfV+I{cSR-YG7y zN2dFbg|`FhL_7lh{rd|#fnTb)ene-oPbd-Nb|P^-;E5LOS(=f~l z5F|&m*eIo771{Dw$HfqKgdNp^HUSI&b!0?4NHM+zd{j3icyzAT_9u?(d3r9A5^pe9 z2;(`9^GBu-E5C}33SHGVVJ<0fNkULb=?7vu^h`{O4&H_j23H3N|4gS+MVawo#mEU~ zQ>*du@rKqSk_Qs&01BhL-%o)^fE$*zenLrfbaz|U1?~uN_@+cnhfre~fZ>R2z@~bKFiFJfm17u*-p(L8=Ee>Fp3aoVdaZ8Y^#i z0!l9ALSv7whFAk740vLo2B(%k#cr{Xn7`p;09QOebS_9h!T8UjbY;?^MnQimR(mak z#7E;wW7Z1KrLphd<2P~IyNfaTDFE?>%w9)Lvr4@g(^r%5%;5}a=S2~Hg5v&0rI!vkRnV+%$O|IR?RgY+_LWFvh&d~CK@N-^XpK(*6u(8sE|D|e3 z3nEZ-C{6De)I`|7VcG%7C-v%80D{8bjyQl@XZsZZpEIgiaWOFx<#CUljDSvxocR&b z1c23cwzS-=s#?Ksf`PYR_d+Y)-`{`mhv9K2Cldb;G?*Z)HkNP4TVPjf+&N+VygR&4 zTULAPRiK={;~ja>!(wMb%mHmRXQ+VC;d_po{jiF_nc&e`PpDmBb`OUhW zAt)H0ugEsJ`GD!jQ}3mPjt76*rv2V81Wqxvyo_^b)G@yyCT6hVn_$-s8d}jUbRr@% z0$-*^yJ+<~Jv2i^t226g)89;QZ&zOkQg-WSti(QIZ=V+K`NdY88j@0h;FT?a?@;kV zAsxw!N3a@F!;an3yz{sU_$p7fzR5C1TMEmRpyvqKn$pCnQX&J3~cHL>;#kpH4ycN zyV>NyJUxiDOk%b4^^aD#Y?_h5z*{^<@h>-iW(r>a?Sm)}xZyw=gRQEowJ=bzdE#U} ziPX=S<~?Xk|Gs9Y5B}qxD78>eeFcNbGjfsusXQQBR|r#rZgnpguJBgL+P<+?AjbuP z1BUsySDdZ`78fTix$c)3kd>IJq+9G#a1w3xphlE-1PKU=-E@@=lUTXeP# za5rQ?V9y2#Ty!_z$uoK6TSRt>=by9^P?bAt7KV zz&XV>(kJR+`&8M!ofm0)Q2vXEpTPct>HxQ@6 z#FHYTqBvR;^+YyoxQeM5*aGPcx?xAJGSJAK-ijtBU;xtlB?=;N@}xAnrgk zRv+L%Osct6QUbdl4nOcCR)~*M84>DW3{}GfpT7{}c`)cGx`ug?tkE(su#T&TqYK0Uj01d%jve?GQ0BIbo{#z% z3x*SdK#YXJ3pAk%q`^r^U7ekXtbho*g;|(A4hs!ils|m-P~8AY$jh@Y+Cl*c75i5L z$RkLvrKOQG1?Cp&g)D`W7a;nRZS}WtH!Vz*i?%})YKQ`|f3 zlBDboJe)YLtz;lL@dEw{hB-^7Hpa8Sqk3*%Ht-xAgou!WXfVyF5Oo)J+_Ym}c)qi- z?ep^RP}9&LJmTDAE1bJxk@VjZuT7pq^#r}?3Th*q$v8(2-aZWZ0^CmE?XaciH9Eq7 z!UF*`4%^cT1C`cnt7qVq9SZR?>61YH571QiG zP)8C2qwiEx%P_c!E(dk+`fM``RxAXI8W#3YWB^BcQe6#9r#X@mYYz(;l^WnM65PC| zZ(hOxKpK}Co4lsqyoju90>)wLiQsq!odij;81<#0!9k=sA$}(fDqL*(#R@2hP$F^h zaErzMvrn+|gyBO%bAX5hlBuPnnDXkL`Q?Qg#5F-*9ACHPiWSk8pBG*%EZ}v>aS_AHkjQKgE9t%J)8qs zC?(Jg%A`8m*+G2s6jzeIP5Q^un~p=Ml0RXRkcbH7A7`u(SX&YkJCXjlBK52;u!7+v z4kgs~_?xvDCd-EzvW$$3$h9nU?94ywJn#7ujyhlx5X6(_kiCCj2vy$6laFx2Fp{Jx zxh*_4MB+kzr6vB1@5^LyD55|?e9w^CEyy~+Q5D>IU3Zbe)S$GRZ!xxRo1&s8BxywG zty^M7b(|!_7wyx^%P}$g{QMpx3fxMdBUSKw{s8h#@g`r+KPHV0@;@#^GiMC9(hj(@ zhz2Ojw%Ffb_-l~O58cFoGVo`$kOpH{_Flm{p(lw#q)b@6?FYKlS5fR5Se(Btm`+$P za-jjSk^%Us&wea^r9I+L9PPevjo^Q^0O+T*W62-?#~}}QdNz(PnyqV!3JMc5PXvF4 z{Z&z;0t`^{q5AvB@yVIbqX7e@N&1VQZ$Nl*%ZxwcP4u@PP=fk;jBcqD!1+l-tPI5n z;4}W`2W^$al!X3y2N>dwNofsNt{@c16S`7p0uf@SoRNhgv8Z*R7a>BiP$Tup+1BL) zgMxll)BopP28b8`v-f)DV84cNivBbDyZb1fH1Dze`xoYz!mzjh^BHM%Rgg;tR`3)y zTnvkW&H;QidmLOEBr6a?I?#o?t6GJ8VEcO!@mnkYjdUEZcCT%_edi82;ppVFm6BE|pt za{qmc*kIy+_vgQFLEgzCxcncl_5XdRpJXFg5&!=Ae}7z8EA)R?<9|OsPID#TKW6^_ z`(ge+f9TnRa(r2?)!fDgWlskdPs8?1B(2DdVBYUPZ{|i-oZ9EDHEy=p zLe%*UDi{oUMQ@8tkP(P{FlKEzNVNXS`m=!>hbsEpHa(R0?@CljS{D(!?bPvXd>3y) ztF&wF;k#!u^KVsnIcv9#53n1lJiqwqN7t7V=Z(CrWsj8dF77G_8g=<8*>~RZ)6@r! z-`0u!H|uS*gIZCi;amj#=BqN@SK0ply*EU9HN5Kq?{hm|=`Kp-U)0D8blY81x)8Qt z-P=q{DG>SR9)V{9*d@*t?3QR}5TKa)3dT(^aNAW>e2~!tKWj=#3o{A^87V1B;_36} zEWFB-hHVg0!U;@GG@u88Z}6!=AbJ!K9P%_qP-BCn?)d=aum%Q^qTRucijYuUAxQmU z(SjJ}EV%s|^S24`(!n`_3>+M80Mq{5+aJO%AYhVbitI};!N{ixF{kO=mm568;nw^8-OdJr=Odnp(Z)HQsV0ya4Go+1|`RzXKi$+IL!zd)l37n3J0Y9~N3$Va5+^+dZG%b7Yk1&wGXW1;lr?nK}io!TwuQ~wpY|@ z@N&-9p2eU?2wGsW?YKLAMx9@Pn_JA2i*v&=q>&H}kdb0N!2icEZh{HG=;lFqZ17wy zpjt-NuxP_n{S>f6m0cq_8lSoy)<{SN=?C-68b{rYeV2GJp?w?f>vQ3}8#A<|i+c+f zm;C^jVL@kqeFzINJ3r5(6|7EZV|X1fkkjpdFc&-4e-zOL@OkpJ!F+%+o033---WIO zHl`{o=uTa+Zmp~`Q6c&cC~AjXb)%8Ensuy;lCYR?kb8HGfzTE*NRU7D{nvHMqIa2o z$6QX{inYoQx3b(G-a33GHfYi9=X@RVyh+}gnQOSMaP$#G1*X`7GVQm%b0GO^w{Y~1gsZ)^Bs9iV(?>}n;0y6c_!f*Mf+^P7$BARTa`pY^5R{aVWsc7r6ZDUA`#rYiHv4z?l*OSr zzUS8A3CKWp>*%0YBqPvY;C!$tvkS|2@QKzo+PZ+@LZ=_}>I!vYSGvdzv9WGBKbixy zVxMp6Cp4dWkSA*gaOV^mqtyJBpSFy)_cxPm83JCl^$h=e>~0{Jo?8AtO#u&U|9YjIq-aZxYSVwVF3L9 zL@0(Pn{S{xY-ZL59EflSlmq`b8PU|-j5gdZ3}7TaWDnZhM&PK{CH<)lqv!3pbrM9h zNDgC^%P?%AzkswAJr}&R@E=1>p!-|**s<%)LnN>vIvSt6>zgNNzhi|FvFt14j?B?u(P|ad&#OU&<`eI z2!sGx3OdpF6k1Yhq)%5h@jLlmMg=oH5<+-g%urf2_v0 z{8l02h7kCVXp}9asaA+Ga7O%?jEqd+6H4ID;F1&+!0IQ#5Q4@L(r-UML>lTjmg6^o z5FwBrWe^57J%adtX$i{tMD?yij`htk7eR^uegwEoS~uuv4Gq(caFmb5vI)bkB$7uf zyB7Z&C>Mz^z}-jJ1rKU>PY;A7Cy@Jyz~nSX=2P}~3^<;AB&vsSN`8otcdp5@w6U3< znaN2`K5T1SiXX-hJ|N+E{>g|QmnHO~Km_fa@o|B7`$MUI?ASW{0gga}-Jd>v!XpeB z`Suepu0YQT(K{X(M1I)OQQIj!OF$#Ea?bZ2=~Vr>2+HC&Kr%G$;iK-4KcU~ox`Dir zJ}MtEftdV>o)EwW!#&B=8%D|*BqJJJnN{Fq7G&g zp?KEpX&{OLSL1az{s|qzIR)xQQbZ=^dTL{oW%*=cUy2L_SROGocFwJEme9A{Bq7an zd{8QPU-=L3wt|rfYY!uH6Gea9_w(j!MU{Jue@MHElv-`7f>x#Q$?{GpFnE4$PphsNebbr-KlGdj+sm9X>M>(mR1Zy4a+LMuCH5c=xa=LkigX@9PMg9IqvaQ}8?tUtw zH{^BXgw^YJwfnwjjL+_)NLG=1#_wAqV)#OJClYY3TKnuMlq$Wj_xSzPC*0G^8y$YV zQ>@(VH@`)_mXrEi>64_esq41OLdek$M+>QIWy*9aaH}55n%$=(Y!b{aq>{{`$G)WDJX`fAYbg zW#iH3AI&~x!3V=m6A_fd^9rl@%!V;9qcvI_DY;PzX|+l40khkw%liW@QC{{w1hg0Dn1?L7*Bd$>VUEpmJd(RDlU4yM)Us(P=#XEcka5j8~C8iP98j2gY}! z78^XCo{A?H2g3ZRMU?e$$)lIXMh*Yj55z~vWq3gF20x&!#qO#mB~j5)un(ZUp<6_o z0e+4&ocUIsf%$W;a2yL5p$%F)&S)#b!Lj^d6hno(J4H_-Whe3@VA;e|jK4)MZ4ZJM z1wTe9G1AkMhh#XRegH_RkGczc3A}EQI_QaXL4^Wq#qj8;=)|QZJbrjSQ7ZvFVvj?X z#2GlpL67+CKzRu68Yl{IQ-oRK{KhkV9p?ccNV{|#)L_n0%ftWS;_Tf0_ATn~#UxR~ zCl4>`kF(AgO8IFHz6n9v2>4ZiOxPR%5n=cnz9h0Ca0by%q&2U>Dbxl}Ij0n9(j^EE ze1CYPYWiAyRBX3=_8puJn;bgZM{3AOB?SjsupBp@FStYypOb73<~e2%ArJ)OO8E7? zK>eb&LRQ*e?K>c*9vQmLej2oidWv##*nKc(G9@9QJyj|;7`j6^AkvMtR@}swhY&|X z;w5%>G!8m@bO4$_*|h@Aq6xA8+7U1OGXoQo1`H2HA1^08{0g`Z zl?__G`wt#aosL664d9TB0DxHY%nZjAh7rIRH+X`A@*3FwbE7XgGy{@fjoLtt3*QU< zW^LRoyv99umIge*ag3Bfh?Sb|@$i!pGg11rUssAR?ZMGHPv3|h4RHr}!H$NIL5ERK zUbuI2a_V8|!)z26;Wc4oR3i`s1xPD9=Mfakm|_if%g$14<4Zh}C{4-%^?@~;*XRAg z1+g#O6Qs&ZCDy?>Zr>*PLIVQS zJ~b_qSTfL<$ZPFS-0sboV(=MBtfgkB;KIdg|Nb^~=Lr~8p|)FGKOOmJ&}`wBx(o9s ze05jF`HC0xkf83;Fb6L(l^X7Gd*n%AF}du;)8i86I^d%?)&VR1_lc<3qCOT^MxLv+ zbET6#ne8treEDwOkdqXDU9@SWrH%TR#v7{IDgkH9(%Kczl~$U`b4?R=MXAd8PfUGj z?StTrIj5SI8b0OTkeHO@r{S?Y8Yduz|CBQDTxMA4Xnm!Zby-St;URb5!4F-36eHFY zBWnEfJMZl%WPivsc)sylQOoId8Oj)n*VmS?``<{o#L{nOee6t0;jHYt`{7O>6$Tcs zk16*{Q@wlmTwKX{8%y%Y#Y$Uq&a0LjCurqDkL^BiY;2~m;d){-RY)}F&z7}?LTah~ z_Dt1fg+EGH=B=j-Up;4Hm3kc2c1OMbi!U4wzZ>{k-?^=pp^WipNYu z?ma={q(4?nN`Aq46-^)CzccSw>0Z|t`khz!W4G~loYXYDt~6)nOh(k(@AP>}^4bV8 z(^Oe?7!{2k>6#WXv#~kpdh*s=eO~*oC3)5tWP+~gKdU%w%DvpLz<=|wfbEGL0m)bV z;&(a)(52TsUA7h-T`;uTzn6Z&_^WxRnQiN>T)DC@cX`v_U3zU!mCP%f9B6LwJ>F49 z^MaG5xoxZ1?O(+iVa;Le3bm0&^~;45FIYGiCcRcKx8Kb#sZT#*^{c%)?Nis@vu9qd zy;TUR;w*Y>OgeB7ZSOtP{~IF}T^Jy?Ddli;a~szk+rw^X2fZ`Mqd|97?3Q>&HmH-* zKgbAd3LS|ev4C-cgM)#uV@;Mu3S#7`v2j!m=98`(>8|uA!l}AJxa$FM^HhVI^q)ES z`4NqVz_|;quzitCWr(8c_>yO@U(^kO0c9n~4?2zehR}|nZ@|mm5}SUmd=-c2XRANIz%`w~dXO@+%d{E@Tvv>a_CdBWofd5qitOzjg zqLDMWMM~;51}n{zgMk@}Xm4!H+oy98p+0m(PhC&|qq% zX+b4@784N{KZCXon<-cYxL9%Yq$VapjZ6yy4A+H)g@&MgW=xR6xbO?`mde8&4DyLO zBJrPW0^mW|q7e?DpZ_^;3TH#LeRDLfAX+w%7!dVQgZHJPf&@p}`pHJMG@U%;z~vz_ zP*+GA#R?cI@SiwmaJHaEfO-7skh`3&KT^1G*U+h^ox6Arv+Gb_7Zf~z4g^EyA<=rI0HjS`knhz61_tE*_tT zc#$cD4v%03Kr94Si~dOwvIig&B#6M^Asid%nsFk#02%>*g$oII!JRr=CvOeS19tln zjX!W}fEEO7bMz=u%w9ZwdIKW^u(^W8g#D_=9{$Vc&tnh~#KQw~crUc_dLkHA2941Y zC`T-C=tj|kHC?Xvnc-hF#@%=8DQo=nsRnP8n@eJA@pmT0AJ;<2foL(DEN3BzMAd+^ z65eoJJD3mA8hf8VpCrewjNVQ}ghUwPFp54i4naF?6Q-u7?#t8GkZ{Rp=maF-d*KcM zP(T6*yrgBllepxl0niR2`~oyDAg|J6kDNeHf%iC}K&nB2TOeBsa48}e$|6rA`w3SA z%or2|y^s~2JaJ-Wj16LIc%Tyy;i9Id2ALfe7|K70nh8vucn&))KK6y@Ul8{;GBJVZ za^yY?)CTwfV8S+o2mcR-Jcgeeb$Cfq2%J5848i1VifV@PX zVZ@WE6M`x#T$S|n_TrxP_6U<9v;N_QoGMvUbj4X3(M^0QBJc;e&#>3PeSMA9)EmwV4y!u z>%4Sz`1OOI6pvTEzeBfo>&|&qz84a1JJ@U%@7-86aOowydU2j+*<81E5nnWx6;QET zRo%VYtCy2nZ8&J^PYjiKi*bE_Yi-3TN^iMKtu~{n2f9CPHEwXrmhbtz-`qG`@9dfQ zm&IxvM{8vDsvg88)CX6P5oqtyJ%lz9?oO?8jkJs1i7g!}8Z8DM?`JnfKN}iVM}9ZrZmGnlfeA(3sdt)Rap*1< zxRDVd{+32Oh2o2Pe9XmeoFIrz=(@cUJW9r(o+&ZRd4qvGr^1_3YEQ zV5PO!8i&ZaJbtV{=CR}v^LDRt(^8&hkPs^=D1Ds7_PF`L9tHiYyA!lb(hZ^3^>fW`sqPZ@%%Lb*_$zc+HK5^8RQDB=cl*X z1P)mnd-Q1=Kh~HoJpTIaO}Z6)!_L8`m9=PBpYv#ldl}dET__5BU$=J9-_?URF4k75 z(J}vK^mX#`ipLuD7p~9UZfUgOh@oI#+EYr3k;2Gg@bW+l@Q4F|LLLqZLUcsfGa*VL zh4u6_vL7SI|AYhw{S?l%@aZ!cJmNtQ?2D&o7}06zn{Z^P1tD7Ib~Glh3Il1rMitU8$1OzrybqhsX$m2HvI$C!~+D@gslIv-QE242p2joKKbE=f;g2MyBTr(VnamNgh3&lfSSN( z!mUzT>UQeX7x}Md+R_!@Y>uy&_8_4<_8ZV-6aDRZ%T?6Br<>~ z9cVlre?mdtto;k9^XuzM2!1JrsHnJj5+!bp=;@OuNkatrn4cq58XybY`^`fbDG${h zEX{!^#L-){ zitjeQl${({0S|OsDF2}uL<37tt!j8V^79+RRDM+l?Irhwp61vYK2Q~l^l3}sX%tUJ zEE^qMRtw|Tx36K_!-fiQj|5gBI~m+CI+yAj_Dw_Xxsr`9UX0_QNV9r`^ARtI5z#Io zd=2p#J0X*=8^FTBaUgr_A#E^@vn;^&_~yv?x!Q^wF!lDq>e{M@;~#*FApo1*3o0s> zurtE21Xd&`CkI-9u{6w*L)`IZ{sb+bX+F++7dyKKu2)jjFn$|5LF6f=RudlHgnQtt zk(Tf7=^0ree6{td4llM|90&;BMLL@sG<^aB?ccMb552*ml(wb652e5_6lm5kY5s(G zjg2-EgxsEeW8P}b|7rpDA>zh?+6u@q6f6WGcaM`awFG`*pzgRXTCi58Bqa%L_1)LB z1c?=R^S#LH?;tif)*(o_fuoq)N`@<@8Mp(7Wg&tJDi8rQu5Ge^f3)Pc!FxcTxetAr zoD2kf_j*D>S$Rxy2`M@!6posiEe*HCqvf}d#Po~vz!*u7M|*ZlQ){}>7*K370s&A@ zkV~NaN(s@tct9`!;hM_Ch#`piodHY5@HwpA$;~Z0(aH|;2MQ=mqJdwME|ngfF~X>D zDB5N3g0ca0!ry|{39dIdkV>~4|A%F*M)ryF64F=h;M&4iif-7(rXOuKn)N{$L{$Lt zfKUp$E`TcZ{oF~Jqrlbikr7bAKH7-*y13X_3;dgj*6*j4k`TB>&NA|@h~kcFC$;| zI_!g7r*^G0#V8YkK-iuc=UBPy2wM62W7Cs6H$G1nDjB~nTl|PaB0h`B?+9WZj6JVS z*`-*1-|O&`n|W(j$m+#RPu21}iJRu?uC;4uwCMz8wrI^I`0}-3_WAtFp{0N?-&{8X zgTsEdH{kh3$v5$m#pTlb3!Q{N+D5Y~9&KaqmKCzXo%4o40b^&( zV7g&F!bqf@EC@Q=t92--?UYl2@ey=Gv9C(jf7>_e8ZvoN{jqCaI3W5(>TQs@h0nzC z6h7{3-;jus!mh8~>E9&y<`vxXA2fA4+j5v*AD6Q6Sy*bYV&<~o5wlr8Lpd2Fu70-o z@z2qe_ucD(mffGcznokh+Y}+X!;2x}_jjocIpP#U=AuW#Qw#d#cDEc|PklP(j{0DA z{k2=I?x`zuzMp>NxI8<0YhmhVeObbfx8C_1r`w(!;QX1hZgxSW$>b{aWxgBp&Ao5) zKfd2?V(9%^na%d@mmvY?>wP09ZjcA=b?AK0=}&KN#**W!TAtLT&U4V$ty$~x+k!0u zIO=9qXH(ntsvC3MjtevGXtaD1a>m8Q>hbx}cCB^B`j@X>kz#RxllApwG!J1y(1L%w zNF^nR7O2vqWY&>f8X@@(FuCqKC&Z89`DUaRR#Hm&ps^WN6axFlz54nPAYZ$7ku3?? zm0MtbAx%)#1T)4AVQPr0k()h$C-;-QgrKY}i`vCEr?49d{Mv~F3EY2EJ1|8X4Ael3 z-SSVkCMhRFEDiVo(i zSlzPt@oT33c&8Q|yCC%kA3aCN9VYwbA~gvELwDuyFqVv3^3W2k{2y%sIPwsgW%8&VB7<& zCGc9LolHtkr*qSR>=7Cgrn4Z`GmwCF>Czhz$>nl=vU!HrYJBZ#q~DWc&xAHw9%CDk zlbrEp2`Un7zLnp_?R^%yZqi)lMweH)mO5##I}mYjgP=c^d|o#@f5SeHg|8ygIzK89 z&*UiCV4iZYRFBeS>8mLZd6+v8B}`ky?JKh=2I&U52^@b~{)@C%$v8{^%)wXjH=!te ze3^CHG8Lc!CfKEUMae^9Wun2>_M+eiA9?)792v5bKB_>Y>!Yf6NL87gYsF!!KbnL2cENs zFtjW&B{_NJVG|kQ9@&Ay$<|hO^(bj{i?b%xAdt0o6sI8j z?Bdr{`Av69X(QslEb#JJMx{Av9-^W9S!mg}Q>V*`AhMbL)7*HP&z8NE;&mmoO%HNp zG*ji5oORyD=q!NklUIhX4}hl>n~rtXm~3N z6r#ohyf0v%H+#%G`5- zmcyhd@Tw}DTTm_6k>+9KMQ!5Xh;ohektG_?*_Vm|_3%BQ5k7*=0dGfj{4F{F6ubn1 zQgEnNy86P%=Sy2wDT)qV(YUA`xCIo!igJTUt?whpdJe$ZEJ6*O&&iO9-GuRVh^>Y6 z0j)m;AqI{dMGKTFxg+JiWj!X1gN_!ef#1;qA9E+w#kp-fD}6DqM$h{dNK@KB=#{(A z?eDc3`h+s{D^*TbmT^%x84=i6W(_?%_{q(LJ1S&E_<6k>02zU4JxICTNP)-1pt-Y? z?b!S5z~~y4bq)o?(-?ktA01^AW_A%o&rah!z5Zyz2;2ga ziHJe?9puwrgS)-)_DsX2yK`q?@xR9J-4i5d_o2rD7AEmm|_eQH|7uDifP` z*v_2I11%$FlPm8ln2CnNcY`~=kF(=|)VV2_`=-HJ&a;(WkUg%Wb_E!5zwbdrVNe}e z_2i`tGdqVZvQRoBIa#-;8%gGfkRR+vg6yQP1<;v0k#UIu^mVBUe247pY;J%ca^Quw za&)n_&b2>Ll-O$lwXk=uHk6p#&dvJPTxNxaGe6ELoT~>rVPm^WR8&H?rLHu@cy{ zPRf=j13Dj;R%yREm|W?t*J7wjLgIoX{d^YQ?`xvjUnCf?(sw_G&3234CyGuEuW2wNiwkr3#K&jPzwFj7THlVG*h1IJ!( zn!9|!Q59O?3&%p3Jj@z4@^x&F-(oQFv0?CmRpCmd8W8qp)7Xf>#7RuZ70;A)iI@~@ zNk;ewrUpQe1Y{8};H;0Ut4)9eu!m`>^Shlsjd8t~_B0}Rpy|UBq6CqXkI~yuP+?w$ zRV2zB=F;-^$fB^G;8jdza4W(Ok`r*Qx1{A+>{K{_#}asJ$l4qYqg5>1JXz0H2jWR|KiG1xkE}?)OOU_Klwz&jY(Cs`_gf|!JwA;+s!yo3o-ut z=E10?x?2lv;MOozV|5jtCk)d&03R(jVnn+IoS~>Y1!DzXKuxggf%Y@ZGlit%u#HVH z4)e1&*#0ENcxyb`yz@t!hNR|lvBikJv}>T{!{n1EXmtX~xs>NB8tQMW5l0(zDph4qnKhcD=~yWu!6nAfo_0 zkER0Eb}}8tcL9QXFeds=A|1h9EPCGuC{Rf8nUXWZZXQJF<9;sv##kN4Chg+69qQIc zKEI_V?7ZBi|8Pm(QIz3Q?D+U7d8dhb?p+}(eYy>Ax2i0tgg*&?SaJVKk|eLz0AoH6 zXP=F*=~mgm1Qg1*dKU+wHZ83 zH)W}u0T+wZ1UHnL7@u-{#NH!L!Yt_qGrQ1ZQCa>2*Vw%Wyv36($~4)%RiZ~&GWYLV znhojU-@C~4moVp)u@Y0H5{TwLr;=wEd@#>227t6kyKmp};GA#2Ze>zv^Ux%$CsR}K z!jbG70Md}_pc%Yo@j6iLpnY|aYyCOJKvgl%(A!dbd#Y;B)kqYb8 zBlmgvjq>z_9K{y7i&LIz@!mdsr=#VDLtC{&%LN$7QMsu7r3~gF3J?c(N0r~w*@Cjddp3@p{S!z}rW7ah z%`os*Vaem`N6k!Vh+E=%mgC}Lt!y`Mx+7y+fH#U>6}91$`tk=VokuS&?cbYSc48B` zjoIE4t{i{*TkL|EuU}1N`?H?JWv!Q*TZw_u_w$cD7`-t!IP-ELPwME)#(P43ZpxiU z2|}G&EUqLjGMi=-+ojOBv)z4aE8gPqwERVtL6@yX^%-OjEzL~jPpj0`N}Sgf{J0wT zCd2Oes~;UxodKTj>439?%3#AuT;Gc#5B zm@lUA1LGtGv+(th_)Lh>Yxep3`a%aVN1qI3aN{?oyxr!l zGe`QJ6G!ya`DGO|$tk{X?y<_hFeUzlb1^eWa)P>nud0WsLqlZdtRBnQIjsIlXm9Iu z;G*c11f%!)vB$o{#3Sfh%s+SIxdR_b8aaY;W(VKpl_Lk%MZp7A;+7)A3!(!^6)&`I zCp6j|)?W0;>{1aeZBmf!$uO!p7t!ME)_Z>s`=#x#`+w^Eq9E^f!dz9V%vC zgeJU)l(nXj>`U$rzBUkH*n{n984F!$9+@J<80akILU{pC5}tGaUFT3yS{^$FH8#nP zE31B6WD0_EEhJhaG)AgHL`LRbZLM|EV8Q;d4_)7C!#?jJx#Ui?5oR`ruaXlY8~tXs zKYd78oe>*a*)Y47SiigMcGdozOG%Wo^YV5Lp#aEmGRTwMlVV~(*Ax`yf%f9e{M~eO zi|saa+K@cKnjBRg^V!{G_mkxgv!vfH_owH$@7sAML`BRsoj7M^-74m0hqaqyq3D^} zQh#GRXQrXy&mOUfnEaP(lS3Rsu}@8tgcUoFcE>np*?jafIB|6Nv~7mzO*Y7gx38an zJ90DQo69ZQEgP#0cP%;d8EZG5l@?7`p|Pr~QG61+H~06HGpEuK!C0QLPR;{c$G(|u zH1!{%`n6B}ql1dx0}qzuu7R8KY8g_i(c^bS2?DxoD0QD-3qerB34|)L-ERtq%!`tS zW_L<7Kz@Yy3$hL>sPQp>g+?A60_tZ>y8wQ@Yc;CQ%8MpZ&LYD+=a?LkzPDiRx{;2@ zx+jagGTp;KxrIVhPV(^SAUhgJ1q^i%EnxsMmId@#5MOb<5@RMv6J!(>rGV35PWbeh zIYSCjng{Ybm0AanI3xV{%*9w84Erj5;Jf|J;z|cx5-+ms1>2`#_UR7DxG@!Lx(n8BOg{HI~1~^-j_)x4l`f|t&5*CB=cW` zp3}+(^uFlgHppzk1)iD&<6j%-DCoK!Zru&J_9a>U(%vG#Hzih&6;xG8p=d-XWU*LG zb6O-fD@FqogJ1zXcmBqIhwk$=JaotdEAKBf(Cw(0_WHrUD|1kxtIov)JR_kEv64$V z###}f;5kxd$HFW36@3W*_DJ9NUD;z@s?8p%nfqHzex@M;G%w;3oPUm&7nGIw;x8B& z3Qg?&$!}_I9>XS?|4gYHr8rk2`-8Hd z9$&_3yXROh^bhz8hVT5AQ~4pV|Eyqzz1LyQobns-l}v?ue+%T^y+#mrK#=gb@G;YT zl$JY0ntvDBYnr<`EhlH-<45`jk4XV73rV}Rys%LsclMzK91-E0YHfM3tv=O6frr=Z zbj!j2N8NviWBLDo{P>j?X&7ac5($+`S_&ztG?fM|38kS>gscW3DUpyBDeXc-84)Ti zNsAOJrDe1T@x7hB-s|`szvJ`Q_n+_e$NPA{Dwp%TuIKrDJ|6eSeUynr^=|s=Kf0@J z^9f4K&W4%N3;?O{wS7i`0)JQO>dy(>%vuOV5!P?p(fcgac9dQP&sG_uKciCp(_(LFBz4ahi|6&@Wu%z0=Z^>Rpej}{E;Cbm;F_yEkQ zX^K*rbO|@dZZ|q}x9iz+zFunZMi9lUn>stxqGQj|l+*y6MQhV|lH7I=s?;7dYE%Qr z4>vn`p!1P^iFVRQWYVr&kvp__A7FHVEM>jq6E3_QXO)9sTg7n<{JEv`IW|W^*5?znX zr!iknr$6$MpmvDtK6=t5%bGIVlbj8gIh*l7|2?kNA3pf3Ua)F(eYjeim)e9- z!%Ot1H7XlKJM5xtB}z(N!oTe)>a|mPdTWqH6jZ2S6Vnd2>j+{B-RM7nK~f zd17f%%h~?(YTlVFf1?|`fk>n@!tF?LM>(5q6T;Q+Kj(CcfT1~^ z+q8!wHU>Z zq-)82X6*LjIg_97T`qm|uHExCGOwz>IeS#)(T?>muCDCA%4GkZJ-6VCRM};lV}p}M zdJ#YVmnEwcJpJ>*<0K+WTbdgg?Q)x7Q)5!?s9XR3OYe0(-d9&+(jr;gr|ij}+0Z37 zo(Y+$v##PzgJ1>+QE<1U%T)LpSw8KSp`-VHtgY+%pB0|LiO;}FhhTdxtuL?;SG~zX zCI6eKgAF+f3}Zz(xNwvxc5ae6efDhcv2*Wk+>5b+QIP}G zkK{k%Hd*c^K{lPcd0m5pQy8H>MfM`5vy!pzdr0iG-X*vIce38J=~Zy`yj3^hH1+NG z`*|ots++ws#Wvu=frjK>HN;bsC(V-@o;i*$lkGBJ1K(_Q_0&@P!rce*J6;I7SB>)C zXx}rHKx1Z76Ga2b0-Bd4e$xa0{D+Q{U4DLAoLjmdeTcVHfyV$8h}m)kE-~#AiMV^q zQ;PP!61QJwCM75P6xFWJ`R;rjs!YX*Zhl*6#|80F6eH7jYxefla9!+L{n>bquGzYE zn$FXmZH5#^7+y*^aXWeYtp>-cV$)TTqXx*H(v+>g73aQ8YkF=+QDwiCcR!4_yW|?< zvqVfZKV0Lwg|p103C}GfH2vOg$$gmNo;WYx!zIoB5`Niz7S-H+H#m7$v+C6r_v3C+ z24-D17_>{9-Fbeczfx_LhM!2JDCV6#wU=7^qxr2$FPG0BydcLa`9;qqqlyo96)8#n zu~1R{we(xR6R{7j^=%Avuvs>$BdWaN*{QhoDb-{TG<{dwp1KahU?HdFqj+U!W%QyoNZkt_aW&uP-p z3uww_bR$R$p^cHr5oy%C=ln9U5ZWd&5gb}p=hiJ-CY!52=R-?Ki3LxHk3W!C#;owB6(#_>)*1^f82>dI|k} z?^ULISe;9bmG;r8D+o)wS}ZUI_MU7U`(CBmV-1RzyeW4nXA|q+#^|hWFz^Lt8Nk z#;XI?#=nMj_BJ+CRS!a~Q#RO+z~4)hgTH|9L@9EHm%J)3rvrwm$J`4NQ=WAS zAP@?yZGsruo!Us-=rT-t5@kb7uz;;M%8;A9;SUT-h~A*#_HR zU`EDBXIExCgEE6(9$(+UfubcYm_cSZv?w#gr*b4=E@C zmT(qTW~aH|ea84Qkc%i9wBbXCcEewHrNkte1CTH94uk`^VnsP741fwkUNWjD^)Q&| zXoDKRly*EIB)w2tAPN%_|8Vw&2XeDhHQ)gzAK0dL1{NVzNZ(@Kyv-y|3&XVs5B?BK zL42eh$|8v8D3DmXRE1d92by*$yUjTT4+VOkJDf}u9ZEg17W~X%^62#UhK8!SvhM)< zjvk$StayZF=A9}>>>}{Cea6U6S-I}RhsMh*mnoyTBWP2Quc29e3ig+*v^$q!3H~+K zusU{5enEar#9b~wYv<0~7b!M$X;owF5UjD+c`mV%`Q_Cmb{^N-=Q=8nAakolGI-47 zS8qWB0pbQ2ivc0|*FZo>sBzvG<=nX={&1d4;7Ff!MXAL-O)q#InUdXmWplz2hO{{= zOsDMG;js4N>^nbR#59jr?q{>j#c=A5-6`D!qVvVN)w%8Nz4~uX%3oKfdGf^X=@A9C z&p)0HIUVhLAtfaI@T}I!OTDW9F!Da~d5B4s)6(kUw~Y*qe(qUi7Zp$#D_*>C@Yfid z?Ulv;fD9j$Oq1hXTC`EvV4yA(}0mvrp2vG9Mqn2;%9S^>9dZ9Vjesx<1P>XnzHOw z(PtOygU3I7JhXj7%a^x>hI4iL8ZMf;aKZJg-OFE=AGR!0`klS)miX5-Ha!Lp*=w91 z@4jr#MjMZ^&pqZxY+9WP7J0x=Zp*dFM|QNwpK*#9dd2-#Wn@hAo3dxe*tSuU8?@u*>+TfWcR0Q#?R?jzJ3B%C)2I(q^F~wt$1MZ#=df#+3oo&cx2f25vLprqLLd&brb>EpY!^&M2yF+m1&j9 z%z5l;(V?NKDB*a3k3>c?bz$F)HS`&J=2^h#62@6ix%l21< zOf=m3DIgvtDUV*ekJzK20v>*LM}oYf#;ljwCJ@npiBMzk6C?`}&lndsfQRS)eZhQ3 zv+%YzX;!!BcK?P2&IR>THy21%5%-u+fDFAg5cD5tY}gTnOi`#I%35;sV=)4NJGaV7qU2?BquXsE9uX49Rl)GUJi0rvgtzn7J!0C!NuF0|se155aq-Rq z>$8?w%Vb)nB8K5CC%1~d2CD9^Gf_-I!6drMr!_Won~PlwG)ZXBLUf8l1bsSx_j??E z9%-dtFjV6d0{FSm&+f|x4{UVj9vL$PI@GoqrcrgG9UNxlEkl%K@}O{if3DtRJbrNk zXyuyu@`ad22+rACnle`ck)1_vRn#b>Hm)p%4V3R0?%LvwY6|$No%wQZ*L}8~Xm4L#h&s zh6=V^6ct@Wtn(^I-8oG{?7Q}=b*tdqr19$Zl%Ni0V*ZSv0;+ z%x+Ea@;o>=Fu*5tnQZjJkqV=RP6}U<-2KU+7WZ#CK}EBJHZK1VJ1_UGNKQ(iAFY{i zGp^-k#Hlv@(tzTJ4Z$P&oPAsr)^)qy%tLpaey4ZP8rSI7bi3Esa}a0qmCvf0W~Od$ z`Zl|+-@32<@x5wbri>k!99QN&=H1(3 zskSv13(x)TGjCeThFuG8B_H1C6o3AY-kxQ9b{J+kM@F6BbyB%ob$e;X_Z{l{8=YEZ z#X|;OEf;s&y<|~uDQT&cSq4WJ>F?cj`P}6t=dLa}bJ!?rLm!88ZstWXo}z7wp-HVD5=P;<$62y`Q-0CCdmF*&_4^s<>r-Qu zl#VVQ;nn!cm4g{deWimINrb-brO6gwCA}#g$f8&1ejc+5}Y(vGKDN00-UGZviEAt zgb+-$1i!QgU4W$Ipeta3PA%@SL+tb}LFt@C1JCzN@ zoC_yUR<4)AA{dA6?_=E6bYI2ynee)o?C64OJ-hxtwmdTEG0A}G3>IU8%#W!%zl4d% zKQZob|LGSwKd3dKQPevYMq&TuMRx#*6#Vn38zmt^Jebbx5Eg^c5B!*FBv7s=~Jho$hlm;JmcE62G7@J zWzFox2>fT7n4m2ZuE);^@>jghnr^O-Qb?eWVb7*Z2;NS`fA3NeL%eFzUf}fvfxVYa5kej6$s`UyR@yJ7PpF-3)_3;_z@;U>$=C z#t;JN+A8ZC0=*UD>dF->_(TszN8hv6X?R-o`Lo`JCnqf*KUODYw;i=Vw@Z6a$bk3E znUY736zH(XFLl(MJ^+JSCiG}WaXWye%lW{6f+wKUOyB~T8s{AKjhDdxS4v8o;vO+B zAx0Bp=OxU?CX*|r5Qp?KxKv2ET37NZ*><$WXFa=xEeY27Yb?h-ZN0bP`s$)#1LPK& zd)l9}ySyO0&)iExZ0?V}(f3$UN!JZ)PD{76wqCrfRi8dXFayvr-<8n|MY?H7#_>v&k16I2k8y zxI49b^MqwJZu>uEseakB?p4c!w(YZiXW2Jrd{Uo2b=cyiP8Ypzd_4AZdQ@9`);Obw z1sE=|SrS5a>(3$c?ELGpjPuIHI(BsWIDB&>$Lz7dQPHjaBUq^{>|7Nm4|m&M`(_<) zZfPO+aF4(LTWe*NB!mo`HW@%v+!;J%&6;YOD{9e6Dk=$?ne>)qj60CYt~#KQhXckV zexib+3v{FD*IFJ`$e~gSKbDmL>U@BGgepLf0F3F!sun9Yv_o?&!YkOP&m`gT*oE`~ z2l3F?PbS~TWs~V05^-Lz{lW3T<&l>m#?znS0D&LvTF~CTH_xec*`1J-^yG$*57CJ; z(O^W63vS6{5=q5%4aG0MrnpP6e=_ce6vl&3@5jRSSls9gI>Nrr2P7*k-N5b-K#0JL zt88j+K60d6gg?~(bhX5 zCDJ!p*~MPWt4q&S#|gBp*61L0v#4mKj?QBqrY75o^%Fps5SiJJYaxB>Hx#+**qBLT ztE{QvKlw?+C#y_*Q`4TluQ+qv;~lZz3xZy%2ia;lYnNWOGJYQ4MK$N%3n;)Ya!(XI zA5tSR=nIybNEmW&|5cvvp+5{8I`sX+*o-yz@LkR`ePqGdD6T2#POcB z=$B%5)~*-raC00q*UC|%{#vedEhEi!*n!tw;$1~vvORa7KU%-OYtK&x^{s}$0qgzx zbn%kdof~lAz>Id+;Y$3Y)Gqz!h(ryjH9M%NO>rLj=r$k#*=WA=?KY-%;2#`~E}N*k z*k!o5O8>EIxVVvm$o^Fcs|e)w+q|ANE-VzjUM!l&vn07gk!aE&;l_?`iA(<>xG@HQJY>R`Z4!FQpqZe)d;aJODXM`mBURu`2Y ze`WV&^&Za;+H9Y+9(5C3YM~U$f!qWJG6RF1*B`~Q{CGfYYz8^`e8zn7?b|`Zd*poR zay_|&Xyxd!W4{90QAq>1VbyZOx}Y1#hH(2kv$H4p8Li*6>1j#H)@|D?Ni5~C$du)i z6a5%FXy4O{=VQi>ajn^Sdd}6yFSR#{OBk8XhM6&Y<;tv-6vA${!Y-vJ=s(UrlAqcx zO+F?Q>VdwnG%?$`Onjxx3J095(o8P){`mpvrjiOsvqqgSzu-Tw{P~L)`$=(}A9giL z>V9}Sfr|fq4eUzg^WXo3l-%>EN8kVbgB?nf8^`?bABZOGU^)KpYm10a|9@ZaM8Ze& zKd(C-oK1x<{Xegejx}+3aQ*l7|JsEd6|9zT^+VeCrjl<;rY~Bn@K;lhHIy}ED5ia`H6PM zAnv~D{p7e|*Pryet68=EsN&NH{lu> z@UjQ7dgi+vM6nnf3v9o=d&^R6{6S_j!#eIzh@A5t4H@-JeLO!xkH`Oho7+cf1UGQ; zqLj*^$VlA{Pc+>%k`H`dD>3qRm^a@EV}1JqGqWwu&c@<`PVn{mj~i*IrHmMwc%4ucX8YZ`sV6v#l^_Eg=Dk?6}JQOIaG3(F0QRAZFv9B*yj`jY; zpmX=GU7ddYbWZb?-;!#(tPCYE4B1X9W8CWLxhnx$U(sOm*(@5mc)G`-&ezPRZ!z6* z$#b*0^MQ{k9VNqEiaswkpWEw6i^%g+vt^jlk5Ss&`+AC9+5D>P$&>u7!H7`CjOnf7 z-0)^vH@};I4zsZG_2@i+CO8Q9=2wV1HP>d;W&ip0GTYfoU@i6TZOc4j^X3tCmwucC zBSMl#@9-dM^b9@-!GUDDIGdYS`=3TP$$fVZ%9XCsz*h=n7f--DwQYKMYf+K&F~rZD z-GqPxsl0&28YaEcs?yV2nmS?=ewp>>3P6grh7XrE;&|qj`Xb`Xoh+SD53!``tr>|P za}M;kIC<8?^1}5E+mF8PTDkCfXl{&P{ ztZ}AYGItI8gunPeS$zZNIE}^^9ZPpdhX4F%`D)9BeedE#>wlM@uNhEc(^YQh=eVxs zb60g<+4`;XZDr4g!e!hABw_ftDr@^Rdn^ZZT|O;hTFOq~=QpiLZTB2?#-J-f-RUkU z!hw?@aWl|gneM34E-vZn+Q((?J#VB_)0E3wR;+>-6 z;jn(a0OA>~!{&ghs8zYXp@AH7>bh#n*qP-9@U^&-v20JNsb(ac5RC{xh9Xxjti7o7 z00Lp^>gsU)#SxQ@1Rx8)U?JE6XIQW?lzc;dz9G+1kdZM;m@gwE5PSq%d6y+P81a`0+QK-tGy>8?4Bb#CJaElOSXH_6?dM zX^9iaRi#Tu%iFD09!D|yU3wgR$|wQPoYAw+7Y7|1A^((cbReqz!NE+L#S>`uWgWQF zd-zJUIFVHzI1M#trdSpnTB=9j>*Hp8K}3M9F)R?g|?i&%*N4w z#>Iri#0bKA-$y>}f5rpW#?`G(v0yh5PoVA`j+#cLH-$&<-n_{ksl?)SbaVOsG~kRs zbl(=QyTmXM@+5%jUr~P!{(oQ&rj<#vnZ^Sk6B|fskCirwbesVk-+&#LE?;(^q%~#U z`)7O)AgA0K^Ct)uKk6!cp!5rVcjGqobuC*rW|^B?6F29i{Oqqmx1N7Rx9;8ZyC#oL zjPJbuqt0xm%JyZKW}3VqPk^5)Sexd4ex3Ap+oelO>EO?uJ`LFXg1o!0WtRb_A!`$= z{?`7+x?MUSp+at`qAIEIKyY+Z&5_?kc?AUd01eRaG_Ju>7862eIJk8CyTh+puA1>^xi|ffnd$ zVdv7og33$)Plv46rsn#-;rsWkuC6mY2egrnHZwt{d?NE@5Rp(EO!!M`1#7M~~ zE%)pLtB#W@>C&Y!y1Gpq;S3zv3Us(dBEDQLJmbBs4;ZtC!QzW42)$3H%}9l zL3c+^hMDXFyPw{67%0MqzE$PO=Fa5}@;!RTABrh7xBP~8m zcYT_wy87ZT{gkP-d$v0tE3-$|u3#V&@zH$AlFdX)c>eyz^txU=4SZ-$l+K!}n|@Re zVm@c1Vo=JeTEZOih0?Y>YVP9!A(d_!B~ z#%KZX)qwTa_H2pWZRNtVtA|~jxqr3-dmX>&@y8zk`q0*RLzzJI-0wKKsk7_vU4<>#27>=`}% zWqbE_rrLOu*A&JMgToLa<+pWZfTx>@SwiN6r&nNbZ09d=+N+JQaACxQu@&a(=@r|5 z)JQk%O7@s&Xc%8;tLnR5y>~2e!YnMh?G&cMq@M74jk=$GQa_#`rt&MB`t4s3AZ0dx z{$GW~Nm@7zmTPH}{q!%77%~LjiJ+ukytt2%32Bv)Dg{0#3m$j~$RSwb=2cbSAKL10 zin!pa4+w??HSr$KQp-M7S3hOq(N5kvK%$@_Ll%n#5zz-AbkC|IoYjE`9;{ucc?h>! zov5?P$ulwwwr)L zQOZ%+y|0Z?4G0gD0GDI9??vog3f@D!bT~_i2jCibF5Oxf(t|e|jj4jf7oa-3>F#AJd+ zopi@evxSN{P&Ctc$|9wQKyO$u1n4QIP%fk9lnWXQvcV-H>hIAbn4QQsBq0G_&`ssf zUyL{p#e4MF+mSXL9<;u8}iiZJ<6$R6qRW}B-Tb%v7Xfy$g0de$49m9$ni`xLhLfb9S2EmMY z+1m@bCoMleYit&CGnsGFZDf7MLSgqi()62A+Y4->Hf|hN`8%!C zN+}I^(c%MdQsYGe0k zhEr4Yh?;{jm+9QO)gR+9w^H#Ipj<2M0PRTe1gatO6V$e3oV{{o8=F#H)vK#0 zss-6AT#7ayX|w4+@B7L1>zBbGfxI?r$q_L)1Y!m2Hc4wqtmcyCXcJ&{>}X0MCxM0j zL3m--;qT`csH1aTqu-TsdKJifc$hCs^v%6}Iz3%?=+M3GcZhpAx9Y|u1nDonQ7QuL zBc)IR@4xp_LDUI{fq}AoseN9I55_Lx6SKqDuU{L4gz^YNYB`Tei;*lxH((nZ8s|0^ z4wjc(ZZex?_O@z%=Ds#MVd#Fk2<>NdH7huN?Ded%F!h+im| z0w1h}FnPfug`#@bgSE-WkMnKTu@RAgl)k09n^R*&6z3D_{CVc)u4o%Lt(gJOQ6G&} z7L_+=5(Nkh^WHx%e&f+G7N@8?b>J!S1fs>5 z+7fBo^=nN z$rLh$I2UtsjJE0Ywp&;z?9P2b4bA~@&9Ls>JKwH%=f+MH0^k5RS;VYh)-n5SHf0jt zCu&Q?nr8mbHrf||1_?>Yg$oueO|>ng5Wr9-Kxx=BOPB~@@$oQzGx=}@Ykh`vJdHvl zLIBv^yAVvTYX(XmwCH+xhQs-@)6TxN1?l(XGU1d9%Y+*`wdfNk>j-@8`t=K{`weDX z^yfD`4#~#39yB3MO>ozAqV7}ZQ%p zB}WRbmq?{N{>LW!F1y9-*?b;~jkWa`Re&U@8d-MwAxe00-|oLZf00U6PbJ;S3G)F* zK2=s?Lbwt=g{WKUu+6cW+m{oV@TL4(+OcDKB!)aay%>T4ftc3X3Rp7Xn#b;-x=psxHt+CqS|yNY zN)pmCN?Mm8KJFZ^6%1jqB2L3y&6vltE0O9pxNO-j`wbs zsCXbbBVj%%7%f~A7J;=d2YU7z!vvCB1kHs3RQp_7+PZHkBP29%vi-j=V;G|R+{9gn{okxBHt(egn~I1mL|7GnO;ApQM9XODbw#kYHCR+ zfG9?jwYlP(y?WD&2Ly*!7xK9dn6M>3H@8DOI67gJXA%=r35`$>iwX_Z0c2U=9Wm$L zYyQ~?o;+4llPQH7t$q)VVw{`p$1wq$XVQ4dV!OKvz1<%F54sa43ZYEv=JZt5F?M_K zV({Ob%fr#p#JcKHywqN`wy|+i*r=pnEQUWVhYW`olQE8_U zDm%vGl1*ckQv*NpvEqi|YaIxid_*7;P+n=JDu;Z?{{Z^Hn!k1@I?F9AepuufNdJ6) zciA$Rm`-R8!)F;ll5)!3PeaNI=FFqa6PQAUg(?H0aUrD)8sy74_w%;0Ph~J)Jp;7k zq=`an3s`VoKgq4*pn}dtBcEJt36z>_?vVnI@UN#XG+zd9{QPQ@2L*yER_uGP46c3r^fL*){N zFmY2nNomX-Z^t@)HB-VU;KACrQ~V4nIio8jcArd4+;w0=(KLUX_$ZRcNZa!agucu$ zP1=aEuRQFOgmty8T##SOIVPHc??=F)>Dx;w1DJ8&;T>VcOQj5E3-%|bvx12Q{;l@f z=(t(n?@*Z!GeKn%CrpVPyHU#sFKZT{{8$#Khnz8+ah6%5Slfzv`{neNt?_`xF$9G4Y zvlP-V`&FHGq!3BpF$<2}fQ-HVJs4{Tq+aY`bm!s2{f4trqoe!v{9IgmxMKJ_E z4!Ed(v7LzDYsU4DMpV5#3c1F5y_R~|OkeH1Iv_!r4wa}9}9891Vt5N^{~$SH|9t%zU?o4Z_bMR(cj~u)*mMc zQ2V@O_J_?IH|Fh@lrjQ`!2L#C1&_L&?x6~S>h;fNosEeJ3)>x?3J@t~xTCSaN_*Xy zBLNjda%!QB34A$e7m+sMQowjk0ZMx*m|+1lq9lzVDCUUP7E$of*4 zJ(_LRUXR+125-4N;>!t3OYuteGq4$+n17)X&rcZO7Uzd&Fmo>XKNU{RG8Zph$`2aU zFh}6_+)OrolTa$2K%&NqV-osa`D>6@RY&jw7Hlw6zBcBt!~uDy#PER(Sk-N!1^f8bEi^6c?|#Jm5Lt zVZP?P&0-Nhq>TPqQ*SVcp~mFo-Uxbtg9i0h&gk!5X6ijitXLng_#=Q0A}?7L-{b+- zR7e7r&_V@ocIpXVuJsgorA3_r(*{b_>c00M-3|=<+o5pcuw%X<&-|w6Pm(jpIaK44 zXz>?9&0NEZpxF#@bM{l}cSY>RjLL(#QFm|NG^$)C_GWDtk+@WO$2<8TT)u`AzC@kO z57<~ZXh72`-4y5Lb_8Jwk-#6~eU>a<{PlHK3$-ci49X+b5k76xrr zxjKn_&5q^q;E0wFgB0H0veI^p{_syO^T#ie0LDq7+Qr`oObX|nm=wi&5%znEy4rr) zdLy)i(28T$F)=!G$U;C{v99Z+k^`H*(x^4p- zNve6%ThV0;A_%m-i?t{~q^ zx%FrPGY0kv8K2RUTk%7=d)ANc%f!*rV<|?rbtEh3X~Yps@TR4 z3*Nz+npBwZT2d8`4V|Jg{(rK3`?rSt^E!P&sVWh-(lpzn!OhBJ+przN=j=0!$ zq2+e>HN($MoTn2TJA@Mybi&EiziN5K!~F>%GeRF<-+8ch@FOoC2mDs^{i`W;<%)yiDZxXl82`k72jB4B_J=+bJW?IIYl4vl!7e-Ew;yym!_|;q@?7w z_E0yC+k4LC44SI8hb?cKfutt6@ZoLM(UVrIpR)l&QBH6%-NmZj1!;n%W>PbTMcFW zS_(r1VuQ*Jf}c~SyC?%aT~WU^EPmo%YPumiO|>s8M2Oz8^^>YPq4}n)h@0a{4mUHpYF(v)T?$k z-vnJAbgj2oQ)+0jktIMFI1-EMpC5Q=F9hA1Z3?WRI2T6N!0@1Mt`%PXQr0?Ax?+Py zwVUf0uRjKmX*+Oqaar%i@~SPScUDI>ba(}~ZPuF=S~6hHuaS{?M06DMBB_m-{cx*NA+J3e9t&kTn50cC8>0}KIDnAV6wd%-UmuQl9cQ!h zkOATid?GWhUzZssb0+}SEaIi$>CEbAKW=TU2$(hiBO$n;?ZCBscT5EIpPT(qw0)&v z`^@nCjlk<+k+xR>B^1xu4^fQKW(#OLnv&FD+B~LJf(#midr(M_j zO*11-M1$pY_26uk9mAv*SAL;_V!(+3tr2)Jf(>+pP3A$ zYIMfoiApWd_|w|PF~y^ts%~~(Q1<;zoL+gWtj>%$ai1sOx!CU{i*@eohrEWf)M4!axFd!^5jKc9WQ^Y0%RVpU*ins_vW&s(0v-hT#OpoO&ZaN=+@;WoKLrwL-Ut>bT zAxdO6bv&>-Z_mu*ekI?*7M1$;$=>={a5 zX68=|zTWY6J9s;7fC(H=tYPq^*tKhdu5L)-J17ry*7#PVQI-i*)7}=o57M?|j5-LG z|I};J3sSHmYY3wh?@Be?3Gk?`S9l8jWarn3`VPyUT$;5QwV>sjLV7M5E;~`grCHh^DO;3nZKGg(3eMzhP{P~{jFstz3~B`1+wnPm+qQn_vq*p@dWg_f~n}io#}ELi%UxK zB>RyGo|KT#;rRjU2OGX>MOS@E5)O%Oz9(ml7?U_FykP0)dCOliwg=;7Qj=b&)VJ?Y z$0%3tc;h^|yhZ(DzlpG0^Zm2L8Uq~0t{c;LzyWnV5b)ckeTACbND11l zYTxrL;q(htOpx6UKa*eOIc4A8uN{yfSANm9UTLG+QxrAlR!Sbc#f*}xeh!^Oe%wlz z?j_>C2e*ZCK>9vHWD)pI+Huar0r?jP?cAhdJ-5$*rhCVK=E2iwcw@zJE;eY~8^xfL zVbweGtw|$3e%!C^YW4&}&-zn~Hn=AC3-XY=&hB#V=+PY>iB78qSnl=)pw&<4ay(RH z+RW}U8hU{oO(OjVzrsdzax5m2Zgy3#S<=sAtW&&4N_U+2ck(Fv_Q$l=#t~`$ptzRr zNEh2vX#3`o*(>EEo7SxH3!32>>^zf-NUtY&#XRi;4C?nz>Ne+k_yXKzNBHCE*Hkh1 znf0f&VoA$QW}R6!BkSAV+NdngpHI!x^Y(ijpW-PYo@szKO6A#IIluI-SJ+C*uWz+) zxcg3@&91(vs;7rv#FAl}nogsOyAF&BK6;v@1+A8-h ze67>4{y%Pv!|{79V+Rk9jPvJ5H13tu{p8FpNof)N0I=SA_+kz1Xcb?=k@raTg~VBt zs|qeU=vhkd^nmT-U2dF1YtRA%r0Tiq`S)etdn}iz9P?=1n4QJ)nDZ8g1$OX~fHzsX z#nlyg9t{g;qbqxe;kn2GC%+f>$d@e}v3pomR4Ct&zG)zu$DC>1jVsiC?JYo$+~I1^Ir8v?_s zF{DFz7aIDv%7c9ax8yU0t(TNm-tD>`Ub&l6J`Kmjerb=^+^j!0ynX8zfFiWLvF+dU zl`Qr59$pS|3d5rZKo} zE+0hiASSOSeK6fFp+)V^W8ELQFmCuXnGNsi^s{E>VAE|gO}2gVE*-YfP3aM>&GB!W zwhZaysAk;Le`{q4{sK89C=H8emG z8j&F>Oi>?2Fj#~cCUG}&&;E9bmvkzMJ$7u+!z>WEs=8;_9IJNK%naYW##le?or6D3 zs^pi8Ap<*@jZ`~scJ7#^`?T3!BX8bYTr1H+%V#Xk0a@ReyyCj^OcVVP-fsrH$X>Qo zmi6Vp{9<%{)PuFkuo7?F>MTDFHmL7>!iap9AEUCXbU0KQDL&zv!Bz>A9YzWC1^%;+ zV{^sNg?ns2llfR{^YPtn&%@eyXsm0-xO!dz10Li{Vd-dlfMDdHJ`!C`4_BAEAtKZ_ zj?jyCIn{N<7|0eYWdBe`i)GBpn0ofgHoML!_Xl~aY@dHJy6y5M8bE+jr#@ZOFV%rOP2|An(mFfVJyypan3Sp z;;>;sg|=L8@`)1&8h_E2v>Iq7)?w=G$cZVQfEfUIFANUaUf|>uSSkG4oAPMJ z4!CWPiHJx(dv@Q9Oc}?Z7Yp^rNU=1vwC%L^;BSN zeSN|ONFwpf3KjNob?uaL%#t84kSscKWZm6xlSS>_{>=p#Mo`Z}yFUe;^mX_}^1#@{ z!rG*jvO6*e0(_K9zeE13Tw4TWD7X@5=*R$tX;{aN+eX0&fY~~z|D*OULzokzYK0U- zt;f!*|9p-PKu43@2ga5DJ^SCJQ&ynb=agoK*e7aE+n>gY!EN4~Ibej0?3x-29MgHU z3_Y73p=n^BQBukzYq+OB&0Q*~O0{2%%F`Zs$=I%khT=IuFjX%vKg-^9amIxf_te=& zsS6wLSuV|+dVn*QQsl{t7x2y=KNxW{Ze*uDJ^O$9`ZZW;mWc_!XAP7N=Jb?mw9kC2 z*9HwoSXU)h#mv+l+mjhHx?*5FtW1(rtTEmE%qTyU$4aNH5gxYD=o=8MU^W1GM>a#Y zp~Vl*b2#G(HYETBU^HgMwC=pvd#UmeuwG>tu+_)%?ABx)V&v5y6cx2|RMWy)Xvu{Y z-oJadjEoCnr)v&aCM{P)yYgVSCRQHgI^duYf~&nuFBTuZRp;HJu8_a(bGQu>{$sJ z<>fMgPaZw0XHtu!-Y5m3QXpCn$EDWDQaE`Yy1#*i8mtadjY3nAlb6TMjJ3rM#;op; z#e(2yj+3UsKEt-YDfz?W^W{~B`Tapdui)N;)v}_>q3Gx!hoNe-(|QLk|F}=tFb?YJ zl`E`Zd8gsxzC4ZhkuGAbysGTYm3!nqa~J{sFSE3KU0&YGn2Cv1UD;(x6$(Q{^Mjks zf9Cj2R}aG{Vy_f@_~ne>YNs=L#7Phr9t*;GUH9|}dispzCrp;8Mpmy@@&1B~88&D? z84{}a@=0pEQTP?-@M4w~XrbE;9!#99D(IhZ#f45n7H8_3d&bucJ53&F(N|iu`X7Fr zuZ99|Y)o)Zoj*T`Q8f!+D1v%KF}VS(qV&G6yg^93VNWRGj@YH?XNFs6 z{4+Z|&|mMC^6%n;g6g1=*>~>U3knV{STIZ4h#^<)Jnd5AF%FtgM4l9w>6KQsSDEg( zek~h(P6@Lue)h`x67{~pzy8GuD6fOes|y=4I>BE}SMrs{l*8LxU5oDCy_vTGHCd;J zC11SbzJ!K+dGaUK{24qJNPxH?avJY@d6n})EN^Up$q2t3a_mX3@46&#r|c$NSTjDjWr1Kac6H02}1 zV^H8w0C+=Rr-{Ut5oZ{`4;|g?`%Wp}dKDWIHWcVkjPq-4a1Oc101haJ;w3P0(Z?mz z0oh-tJK1dAI@(*)MD<{O&Kb@#o;oGN`!#FDY!{f!I=-yV&-~Jp#+!@)K3Os{#>Hyt z43Nrz2!(>P=gxh25bX++2(DoHRile3DP-2j|A||;tK&=aVH@YXv0?ueX0r}h3VgUB zWkeA!kf=%(q@*q~c;}Nt3Q{XXrrm0*}R>ck_=tJ)M}i1zrxj z^!HVQG8Ya?Vy_O<4O|jXLR4R^@Rsb}ore7nTZ#MYukY(u7r!j~tDBZDQkyP51f!nu z=X-Zs!^x$3c-fZqJbW~ooV5hZ0gymh?3)s6l+e`D@?CzRC_oIMjnrD)4!9kdPD5du zo;d>p^$~W21Rk-=QUz&_9m9YR%>);PJq9EuZVY2)P^ubAGLFP=6+QtOGghryWoP#R zDlP;+P(5Y<3G-nRWH-GD8nW<2cM#m6x2JFL7DcQQ-kna2=x;Vp zIYq_73WIY=NzPma@Wx;@H6(LW&_6S@|1s9AQ{sHhf9P-Bd-WQrsd=>IWAASK-IL;C zCn#FFU5;N;P*j9wC)@3s-Xw#l^-S*hc27ZvC#Q2HsBVDRt`SRU29cm3t5ax^LF5(|*|BRXm|R z0oWPZKi1UH47l7dlokz~;XKg`|0?kW+)(b+20pYya^Fn_NK32E*F-V@U!uPFq{v8x zanTT~9hZxXM9o91HoowFT>tnpYPGRH=60+o= zYeqWQbP4$du20N20xo|UAUf*0{nr;o@70K^Dh};1?fEY`;SN(%esl2jsp7*eGc8`P z=O+gSvcuDBaH=rzxvMu^Bsy4`{;m~Kj){r55g^zU?ah#ZP89w9wL45Z{{IuHGRXKg z4;yXKObbTyi?Q>I-zx-ao<7a^Xn)_ack}aoXB|(qILpZ{gt*~NrgF)?RL4D8+H?EA z6_lQR+u-)W?Xh0t0HT?>PT}vbtWkvUfQax~&Q(@bmmpO!iGNEho5nDyg@Vn#2-wAD zj1dtv@?E_Je}6JQNWh(WnkmddjcIjYw{aY?Y}pV{(lBT^N_VD63temS2+ytaDS$FE<7 z^N^JiS879|G%Xt23&`4WFmfz<6>+|&bt9i|_8~#^K8Io&-7K7zzR`Pd>m<7=R zG*OUIms+j?!0F!QEypwMmRb9jf89gKSb)RH&Hrc|Ii=slN{OzoW&g6ZHBOk12K>(V zXKW2L5JMssxzOB(f?pV|Bm4B*Jjsy#>{qbgpsu@X7|R%)*O8X^{hP*<^XhNcm+DdY zZz=GEf%o(}h<;2F#I3)+L>)MAfcMHQ5t}T}Q;1gJ6_cN^l`qQ#zP;Vrwc`Z+_wjRj z%yh|Kr^{1Car_bUK})jn00)7Z_(-liJg-yCujKSHH8Gjb9CrSWQS36pYlmqsp5TFN z_|&lY((YikE=GC{bC23} zj^|=;?>4?F~*y09gXE&aG0=g+Cd8fln8TPV+{Mv=QQ?};|_@wOXM z_bmyYDKO>o^2#%{()T=@eYZ~utj`Y9;PiP3EUzhZp$p>14DA;lwG zC`MdN0H|SMn-a$eICP`=(-}f>FkV*m6@**NwxI2x&KN%Y9K<>dCD^U^w!f4zqI3rI zL=`=KdY8_f1!!xk9cGrEO)P4PwI&yOVN?ku$i>cc*u0sqK#A+clI+BsC?n75x&Rs! z*&tCrRA-(se zrH#f^)U{6dhxHXfje-t<0hTJTuD*UNd}1v*@<&-J(zf^e!Cw? z6ULL=-@c%rx34YnKBJ$-oNQvOrMWrai-5{-^nCg>Q4m^=g!2QHiM5AwjW^SMWY2MN zuQB?;{^`Y1t+#Pnxi!wsU=Tn86O?)ytrJup%bAybYi!hL>-{;j_xXFfYFKmBSG*A{ z5x$8xzZ=Dx#p>}A4T4(D(7gXsV8?(`@g_x*873|uzE6~Qn^Go{%Z=+?SNthV>aBUjj z=7an9IWXK*`&YGMp2=8}o{JCmn|}`NCN-*qrbE5u3+fc_SH-N zN43^UC4eIoufjWbXzbv%E1RymMHR+YGo|&^(WCXC8niFG*!c=`JK(`9escb8}2*g>lcAew&|?Fc&rFZ_4$ zzLKpd8LS?54I@_;ne}Ye052X3wnzpV*F01t*lu}(Al16HwN6MW2I{)^DhqWCq zWez9m`+D1;$lGdqk(`t>=g#FOyr59MHe0w5Suw9n6c-RE0f^=0 zdv6|%_1^aVw};AZJexER8VPAYDO6{rod^vI(S#yYnhZ&Mhcsw3B%+cjq=-_6<_t|j zqEK=sN-~s0{9fPK*LD4#XFdNu>%O0LpX*-hzE+#l_xl-+;eEW1_W?@+J$dn2Qdn?n zV!2YqbIx`o@E~%xV`=W%cjs2Qyo$;fZYz>{qS_y?I@@xM(VM*MOT!Bv61d@L>>}}_ z9_$J?2s2faeP@QnWPgyq>mc!e{nnWglQNl$dWpFOf*9MUXBHC&e4Fdw?93qczsl}x%H$Rozkv!~ETo|ZOqXGy3zhzE0gaFL&b zfA&F@9%8QUaB1oLIg06)@h-b|f!JQlSz=~3tWTeK&K#_de zqCSr-{jk$MKKC9x==}Td4+GVf+zl=nD1C5VNH(n9makPNYu5o8^3lEy`%G*z%5%j715Sxan*pW1U819VIy3x+=zH4Gaf(3o11X2j|(x?UMr5@bhY$tV! z?~RSUUjFY5bB$@A*((^#7#&2DN2>zz@|izx-f5wV;xF2FM~(Uh1%myjy2`k1op$hE zX+;n&?i!#IfHz~YkG1?fZh=_7)(6F>JKx{-$#sb9ah;LJ+~?jsb$^)2-DjT02hUxw zmb2cpC34|HgWP8(8>6f%5*C_-%4#opnJ{15)1~X$$h%K^7nmQN`+UK>h6i;Y8XukZ zzdL&5{4yp?Pb)7AT>n_Q@!{6gCwS#Um0byQx1m%DhIFEp_DA3HQeqFf&`v2^9e~~@ z#}5q2sF5Rm6^!iR2*J@Hd~W>qP4XQbmAhUjybQ^pn*-yH4~) zuE`w6Xh`gPe);V>dYAO`VAFtIVvQ37b!<{9K|J}w@>sk2y`ZYi?*cY03f8PDFVCkT z<3K-o)H0SuE|&N^ck9uk`0~2>RO2<9Ki?s6BAzTl!<@vQ1-Ql%f!EU#V0kUoecZj0 z5?3CH!$>cIhhq)~c>DCzpmuyIJesk3K{nTF2ouuRZ5U)WAnx;^tcF{M4jd3R%+D_$ z{a1pDkemv>_tNG!x1liY*|P^U8BEQ!`R#Dl4k*C$;6Vy1B?7n^V|ats?wnz##GW)O z<=(&lZdL1dzqB;uGF;})y-WBvoH!9~q|J*<1UJNAyNULeU0Aw6M@Joi5Qv`y1OVnG z2wD24JWEBu3#mztwPhq1><7PgYlAz_p84uLfiKQKYQ^PqK0b_($pu_Q2U|dQR`!TH zi#5f;Gc7I@(1Xwg`YH{GLh~3+7GUYE+|)tEvnlMOxxbc{dSVDpgrus9F+VOi8Cup$ zNOzzX5Z^P~>auwnKp~fI3*q;T?H}G%R0w4uY3W4H3rgciLkQW+nmZnunJhPn2;I4P z88)YKe-2db0n7D1;1m85=lnW`FPJr`&ML}p<_ z0~N#btf8NtfJKD-b;h{h+-M_GI>V?u4|jKj7MK|4FKu#?P?-inqNJBstd=C&6tGWV zlQ=|rWa)an$`W|2Kl?d&HHjnf5CRBC4XR;*>87pBXi&7utS#xmQqnu~4Ydy91vY+E z=x}J-TZ7ddODikC$GZNIZ*cj+kaD;uELO0~*8-DGDU!-cq>dehCiMqLU=e`SAC}(T z{K};^V;pP1J_+)Ql12ghr7%EHqVuGh2eME#4PP|WF_jpS4ine!72!Y+?^>FbH@3yF z!(E#%lNavYdxr>~lHV09Kdpg}gEUOuebcgUEGoLNOuq5&dHmsxc^Bvy=|6bz4OX5# z5K1JF9ENJ{x|aPMhm1(pD()MaaVmDNUSwoc0BF=&jEFH5jx1e%w>MN{WBZ8-AW(Wf z!x7-N;<;#pU&$3aq&jTiz{aATB`UUs^>=#Cq?k$?bokIAL7|>{jU+Y+Tz}9-rFg5( zo%fLg0OcBJE)c>@o`(|5vgj3C@T&HY{&u=Az+v-#tx-O3h}XUU-~oCgltq``za{oW zH&Z@G>p8nilgp;GAJ5;rmvE?-MH&7f^Zjd8)V34bcI*(;dr8;mnA{3r%&i@;BiDO)Wj7%0<0MuFRY1D7iF z{2x3Rpt8|-O}Lue*s+j+HRz*k<(w$K@8twa*^ITC; zkm9^E>*}c3&11Y`mPS59tYf@k^fWe`Iwd5pYQX2khP(?`1V@}eVb|HwfF_LOn)`zh zw|h^RMWjb^tB|aX0%~*i{Nch~sSSpCi!qH@92%{aIPf1XKqkHd4ja_PZf`4Y-kens z&_bn8U-R^d6HAcz`j8tr#qQO#teLT+n;r^1Wi2@c1xfU)vJArrGZuxLa7eK|b=P(0 zIXKY05++(pR~e4t#FQ*=xg111_`g#>j$w0Ru%ql~Z7{D(e?u^gYYrC33{J>)MbG&$ z<5qmCby+wlBWF`*dHE>n0rgcc8^ApY?-QCAujk6}E&ar(hW_xsXIA#0npR8u{Fem< zOy`z=a`D$1Dz3tLOS?^6f)v!!b947&W2v49=QU{}+lTdc6X6VuQk)Wcn_RWdyMUL) z7HlC?Hfu1uurzYdo(`dgdYs|bFR`vx%BODLyvbPt&<@x=@I+3vos8STgIUC`-OIh@ zmkWKnU?}Qv_%KX5Z><{3n$cnOxjHL3B?TyTLfZCe%r%Uz8r7em4z7fhjB%n(66vAD zZ#nDx$k5#hb!@BvKL6aVm*pQmG*!E{-k>xMm``Fm`B1pPif__=La-<|aw)~hCz?$P z1m_5;QMe)UIleFDOweWS{j6g*kE+NH;;xkid^5ei?efH~oRJ`|PDRO8(u{}9GiRcB z`OH?UD-rBrZR~rB1Dac#ShMJ5QNE&)W5?b<{#8nbb>djNi#V-Ijt|WMoFSeOdm;>; z-sQa56GP0B64(#`)FZ!Oqxt>)sICq2Zr`AHxQmE|Km<~=M}iXy9$f6njU1HvI(7pQ zi)XJ<3MGDPxwKmD$uu}ff+ZfCP901R?2Dx;IB2xP(-(_PTc?cMhUd}#ACnx*(tBA{ zV9d&*BuNi)d{?b|vUMVK!sFZCNtMo z+BBxaGTA>4N>tW7JU;vl(X^$BMH08;l*bYd#c>ObZVxO4B_<>}M5QP6G$&n;AV8WB zauvg~;@T>nD(7Xmz?I$jyzkil;R;6=hidmTn(;y(3Wh?fwf39WuZa_9hWL}8#~3H{ z7SoYhN(kgw6edo(-Yx2G8KA}TjIe&>DZYJ(Mhq=4eEU7CsP5HqkJ|4q&*F{+PP=#r zsSyb`Y#Bj+U0naMV2R<0C#ThogaGDjNX7SW-v;bX8Na>a*}O#q%&N#sjbjX@CLwDS zvfTCIus#dxEAz6h9ByWT&6Re?zm0uYe`7{}Ac~RYCHI8=SmQo&!N94vFL~!+Jk0$M zqdj-(uz1aLaz3x(xpD^Q+?rL^J5)=_&=R8PMp@x6tG}A43Q5Uuportj0{_#!cu*%# z|26i>ko-m2M2b1t&)h_lA4C%{S2$+^m?h+!^%?8B?A_d!UxI^z?ADy1MyYUTP<+3m zKH4`jS7mX)efki2O*Bp8^oJE$Jpw5uZa5`6heNV-ovU^sv*f-9NTtP_!`(1%CsVs9 zwp<)ZEFjn9pzSzajxFgUUYX*wEb{d(Tso=vd{Up`!;_ZoSnX||XTw0g!27?PP)NQX zRJZz+?i5M-J}mRp?sL6o_j)8fP4#10)KM2pj-YPS>haa-gMHUL;=WB!zYvRsMeb^8 zm#lX_-F$R13~tl1A8Mnfth`hTsfdTW%^j;>r8qB_yB2tZJRxAHlY#U%ur|YY=b7As z_@7iZk|XiqP5yZgb-`7iM0~zJ`k z_L2{O_}(Z_9@Aa&_Uar~!hF+n0?b9Ifsuxy^mAMD%j8i#x^^8*GQs)kesF025%KFj zsgc;um~pUGGY#Uxu|AehmNU{@LC2=Mf65x_gBx=rWV&4x%~AXD>9PC6xUJt!ZN66> zoH$_ith0YJ58%y>e>+05fASUMPeo6? zQ?kC`2S#oEeH2hV%{unU(XU8cW$o3xvnnl>t*B5*`ev6f{AkeK|^r{+ppn(HT{lkWL1whq$?E&f}k zpz-m?S?jfjnSVXLq|`FrWXg|ZZ8Cb(n*t)jf<8VuX%rbCp*ixUR=92fcWmn4A?#t| zDk+)xvyho%4CR-Mc65Kg?KkXHRI*p1Mcx?X5iDL*!PCU@oI-#bb zqW943x7)8bsOz~d@Y1TocstWNgob54CWEF&Kf@T~l!jtwoc)u?LWfWD@6>Ddb8o={ zY%RtD)V!Q6HHnHSSsM6t0}#d_FH3|Kq`x)`qVyoczyA6w0WA5h3Gw|;!bG7;xcq2W z`{`-51~v}6B_l#vR?Djh{cu@yej$>NpMG)~R=jicA~5EO)%ke3NIL5Em(w<&Mm3IhoUmvO(G0*%|_N zw`b&E4uj_Htxdd9v3jrCq`{uD0fY`&V`fkMIK6+bUcze-qjZ&7X=+NyEMW?9>qre^ z+#s_%%MD3yGRBNwws>^Gn(=-|Y~Ez+PdD?gikDNHHVtl-|Bq?XJ*KW7?dRg3nX@30 zH6vkx1IS7U(JGr#u-rIRcNJqw)GofpiaUR)^a%`9a5mJ8|9s`=?mt|4|L?5%PwuWv z^vz1&*YO-#IryCrc2RgB+G>kLo|A9lsh`KLD!J`-^2e`p1`II?-3!y`oalC8;q>q( z@Udg>|+Yt)s!bI|~n~76n4P#)51-x1W(leo2 zUGk;N)#kV>yRvHk7&|p6XHu`$AAc$xU9Z5U)gLX=3)!IGV&?MrwU*uZNs}(awLv$< z@!dbE`o&cn1%;3t{C?dNQ(to^BTI0I_;wzIl_rT&6 z%NPCCdtb( z?!-EBVatwsJXv7Y;Yn*rwM_?Qnu%}fX13p=!W`K_6g5YU>o$K<=5V9Ww{BMv zHHcHQn!&5NwXY)XtTt=)TvjP%a&2dU^8`)JVjHs&3x}_WJB=(vdQg(qx>Py(9V~w6 zh7MUTlf`bhu75;D!xiELf6QHkNT)&E6#peO8>wn&nC^BZTv3$t&fL#c$#R|8)juI& zJG?;Bv@5(NzQ}U@dW!fZw+Aeg?lW=BDnn>!fYA!QK2!0#Bfji@^Uj?)#b4qUgefF< z?k=g1l)N}{`}ORz=@kx40xM6A1tW5;xz}GH>5SPzYFhvB%`aQeKJTNjv!lbpcVA|Y zb_35ji5w3MGQuP7JN*?_TkLTE zHMvmoKH9fIyLE4$)EyGast0~A zwd&iQ82d!^ok2Yu{pd}1WU~GGi_y6S$}WDYii(duzPy`xuqtfmZ-Bt&4Jm~AM$d9d z?z_m;JEcSi*_45DN%!zGhiJao?%*wwJ^)<4%5SvhMqCLi(@CW9q(-=q*+-y{-%= zhKfBuN4YLyN%-O3D@?9+90eed_|bHuLzsQzw~i5rT#YQR+s-S%4lmmtU|C(mm^=U1 zCT8bHPM>ZZw30w8ClsK@(e|W~{rf_ptM=~QzUiI%4f4{GR)){LIrW5Om->IW4XAq` zdn3E>iO5m?Nf@^kS<`x_AB;1ycdA|yxjHJjn_c0~T4Wt22E@pAG>3|6aV5RDxCInN zS`6Jum`9aVZTvI5YW!4(f7)ryggV9?RWISFd9H|HV~l%pCdx^f`WE)yI$K54RCK0>5?XI^NkcdF48###;)qWDa6aGNzMX zbsox@8C+L2`S!Sl97HE43VuRX506^QWAwkk0beF_mz2`J@`%-%Hx3Z>$y;P-9i?#CLf-0u>i?WkK}L{rEaU6@ zFW*@c&GK)T!@Q%E!*|zs`1wknydb3GZ5LtrNbNfotmHbK5%tmwNe6yD#Pq>M;e%DI za|v*So21@s_;w$(VvGo*r`Grw=umLhd zgLl;~U{^Uo3~ZEESRs;@_63nd{I*xG%jB!z2Eezb1xr~X$;(pCD!N4#lrs=y0&`%r z)-;Hgv^8dR-I-{er`_@<1+Fidk|vU}Dk{sO@`kB9Y$j1fIfj^wQzC5_b5l}r{?Ex< zij%g6FACpCj&$S3jW>%2Dx;FUJUoY3hWfnmvu?tLZ(c1eU{76SD9f#XbP7qs@SFlx*H!T7?J1!8&}bCAeEWQm)irkq05gFNq-m<-*6SDx|0tk8Dzjv==r%t=S7OZzlDaKk*uJe#j z`|{jGER|w5b4%%6uySg>$oQcBvW_|*+3;kSUpZL2>$B&>0_9w!ZJT~tG;+$6I&2er zAuUFnh+34SNnh$q&}!jAmgqI}b7=ceg@S;qecJ8s>tpKbM*!t!BZ%NGLOyC3QdWUR zxC{>eA2K{WO^M$h?o|VEVXH$oXfcU_gNRs`yxWHSi>KX(r=9VZHCRjcq1cn6&JlEi zKt89RX4Kj!b@g4;j#?X^xYjDajuDANvf(ftMtE3O_Rpxiu+Y$XbLVomwt_T?J+aey z7foN@)@345=F86@z8aff&!Qcj9yt_UA%ueQD`g#5Porv3{~=#bs@ISqLWO}dNbG_6 zSP5@5Ja~8Jl6m$^hR)dGUA_(InD`GA+yRUZ>>9XraPQuespDdC<>XCT4hbCzxFDG5 zsm--nO34_!U`}-GC02}9EQdtuFZ3# z-hBL+3@`ywFAN4GM8ZI-ey_o=4`|s6)JzDBwr7(7PD_tw_z747Jx-4NoAmBVAU^Qz zD*huJVM4r#E6YB91R-15XnECGVW!+-${?W7bwn05B|aMGG1|sPMj20;Z?9)q~#&GQWkGkO2K(KB)PKX=JhJaUv4!6VF z`_A-a;z|=s!z@SuP}zG^-9+roOx_kljcz%hmu2YNcshp9wLF%}qkl8jA#-9(#! zfpGvE8XqXh64&xGX z2x(IIr{kefcP}ga(N4gV!Rj+tG&Xt-oI;(@f`k;)bId2Cuk`Lm{s(zh;nopgah>^S zE%bTQ$6e$*bDdBBQ4f8)33Z8|5!o28K`Jyp)p0CIo-D#u(T*iECp#NtxG~`_Y<6;p z6zV0ka`0YY0}#Br^c$ZGhzOR<^Xoqsuri=;_S1>~M?)S_F~t?o;;YaII4~+VYWN@A z+fO7Y{3WLpLEA8HS4>$&1>*;1cJA0wm~q0&VBE2Ad3g!iKI5T2XerGA@e|5^NSZVQ zXM#3B+AFK9Eab@`uH|vp^jr^9_j__m<-_Tn#Rc30z;KAlY%$jkxxoT}U;JiyMTb-= z-{8J7a{-jJeHG*m4nFzrICVlmxwy3WF+Li7rOnnUwcgsbn|cps9*&VNbRKjc_V*rx zjF{x<8k_>aSu;xyi-AJfajJBcmBv6kJiAb2zFpgkejWN2zY8@rT3>d`xJ6JnB7f=J zZNQo2tWEE|rWSuAgc1V2Cx;EA1Tr^i4&!+_^I%E(m*&qh;7U@iwtI2u4HR$O4Jq7( z6_?ji{Gy_Ha*5Z$l_Z=D#xuwJaKEm@nWw5D$n~eSvM@ZXZa$A!i`7r1K*#ot89Nq+ z9HMnm=toqiGz3jPbq+4JP!sLZrOS1)4!jeS;6Up6ZR$JEsf5NW?JDyjg{H7UoP<`# zcJ31MV`%;poNe5d4x1J%95xq?t9=STXF?A4y0(FlQpD4IA%P!82e!nt@Sy@=3!9QA z8-d2Dt$lz3E|3CC^%ASb_Yd^C8_>ihfm4NZf{7&I2$nUDr#8TLX?}aX7lZFYvB|N} z7(2##%BM{P%dh9L0SytVK%P0D)~^AU-+edaUC6^|GnUwyrU^o3vfJe`#$N>B2NdUv z(~r8PF_RdgEwpc;*X_t_Q7q^1!LDLL8@UhlONYl^D6R|4CxXn@C$249iG+~kmGrQs zUk#kVTSd|J{*L!g*RTsTGB7+4rA&LbP1xB&+lH&bXU z@Vq{2%LrdJA6f)Zon?P_2u~g!U1W+K-g@1(?fY4b;`XVSM#Oo`R-cDyOxf7Ic!Kc# zv6G#gf9Y^Egdh}yEfl<2StiWTav^JQZZ7^FqR`K3B9~}C9RLd$TFbdD1Y$K2#@YQ0^VMyJlcL)&v#F-JovkBxEPB=MhaK%XME!BNP>MNg3 zIwng&<$!54@kRt_G#*>N?5ps54w7Ds{W%)adyF>o!)v)K7#o+*uV>S#W@w1UZqCNo zly}uX>vTE(ePa*dH@p>1zJihPg={zEVD38U1+P@azY-}$M@Q${jpEDqEcfy$7`fMB z4DN20ltKGe3V*i4h@L-ph-&-N8!{aao$d7x{e8UfB5j|DJr4iOIXd&JutPsTd%Af3 zYjV+l$sfKx;Bs7CZ}IytPQt9FL=IVZP)J+e0D;Pa{{#=zL9nXXcCK?n~ z((Ow)QHg)1CoU=v2!v81F?>YPxxd%Xn_swg42l6VGPA>TB8xl0AOIYyKHQHLK&@ zf6bceC;Hb=sim{j{~2SY|I7dU@PEnG2+D%BAe6T;{e&iwMZW#zM~t}Hu!$3z*-Y*s zIB3XGv==$0nB9|pmq##(r~67|3b!V*NV@Gj^qqe<=iZ{#tIx##1xWucwS~B>TYkx< zHGAJYqf~C4P>4qXxv8I*_+64f(G!b>dN>Ihs_YR(Llz>aD#)(FzQm;C;+EV{Yx`!| z!^+lEJB+7T^S5CXqhP1pP-YSpyz8XqYf!JV=WdzMbJc=>xB%jkOK}M|oHH#T2xE=~!;v4gPIrYcm{=AWJ`Z>MO!?B(-mmBsC%r*s>_&tGeKP|Ub;-7z@*SC9%*RDo^Q2^Uw`Hg;-!u(f=t4YyM z3Bv@5Ze!>6Yd0!4XhJIfr#q~BnY-WvAQ`&L|J2q#?xT?jOA8-!+pc8c{le>>MG82E zB{%+P`^{N)?=?(!;mK62o|WPDNG1{Wg)pQgFYBX03?=9Wo+?U*iO8RX<+3FFG2xhQ zIYlw%-@q4hB@n`a)0MxmIJuF0)LlgJ9(r1TF}-sUMC#0I{m(p|6s#~w)nM1IIdRwBhUbZ81QenQKe+Uite!kKu% z7|#g;T;zTFw4&PpIuL+s4())OdDW)C9*z;%FRepP4i3aPA*3rXU5=Gl4_LftC(SoE zZZvTvgklsVKoCZGNwaFt#iXY3FBu2+6qaMS9a9fwXqkzc=+Wl@KNCM;w zCRZ;On3ks+#ZR6xMbP5=_Ib@6FD~OYqDx}7OJ#MZxcMJ*c9I6_P{uh4<$&h+hj#ks-GViDzeVxi5OKSO|-31hR| zs$bj6m`$1&ZT3qSeb$BpEvcvU1^KbN9~so{<*QWlHN*BmW|M@FsY;lzOF8(Lat_z+66DPUj^K3?2fV6(_~f~oaMU3t6W8RbEb z1ghbO4;wda5{g10+hGEX9tTDB8Jo)v(m>(N0rt2S8s%|tSOa=WYyP_51&tV|mhuTZWw<`TlfPZDxQXBq(sgskLTCnu;TA?G` zXnYw2*5wDhm;zx3&=Pn13+W0i=ZpzB#b9opmQy_M;#O`cL?yMV_wT1+`pIWlWnnHL zwjCPkWY<-UQxTm5_~DLGK5WL(t5l~wC z#2`bbCPoe!NF5`@t{&?1v*j#LYu2$!k z@2bh~msUCp6+P|#6`(boKPeg9O!eEhA<=;|0rZ_MRf}LO<%k;DoXp)bzrFq~Bvjje zyxTPJE=j)0Sw@B(IGncBDuIu?(fw0~d0ip=va2^M#82Q}6>i029c(s(owtFkSx{(AuvjQNq z|K_c25wX5^FTMV&;`Zz%m5WY1cJibH;xb9;5pfUdK8E5&e1E?or1KRzcNX8HbTQS~ z_^h~NsPC0ETPoMvsgC2ooalpf+rDFms`05M)iw($%ifC+#fwUDiJA3;0WPgC$((#b z+;QoBI5 z-rLq0`tm-?hsHarAEDR~p!mz0^K4bv6flT$I)b^~*3rUyUP#SvTT5&fdDjev38U*r zr?YXY+^DeqG2g27{AL&#jhQsbz<*t$dq=UAPr;Lo)1obQ?Mklb!l6_-E!Wk$z0Fc9 zLZf>&{D*7-55j#SsV=1Hbw{3MLm^5qFAcs^a1LcZG4kT@#5T2ij&>24~m= zS}#Zs>J#7j)+G}s{N|%hHx|Q|JoMc4@r0@*ST2t&BG-8JkN;wZL+FyK+x~v@dg;x{ z#ewodB>5zbgeIw5%MUBuev0|--+!<1?TMFRX@r@22;>a6g48@m1R!O{k>HwNUs_>e zs^4v9_KBwN9-*rCm5MFRw1ru?^pw09G&#@+nq&Xm_QbJopX6;lW!&^L=M0A( z+Ckn~Jsc23Hiy5eYCNc4ztFOD?1EEEkpP452VurcaZC-~{k?WY&78>Zr@MmSjX9G> z!@(_8t+PYw@ZtWx!zZbC?P&k7{YVA!N>Y0roPY;$GUxWNZ{gS^MxUiuaD@gdLYG

54A0>EU~MMn+RB10SrtulKBaS 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 literal 0 HcmV?d00001 diff --git a/images/gitlab-push-mirror.png b/images/gitlab-push-mirror.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf890657d4f4dc03e9993cf2b4c97c5714ad0c9 GIT binary patch literal 179022 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

g$8V=nHUucCiS)F*kR=m$$c# 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 literal 0 HcmV?d00001 diff --git a/images/history.svg b/images/history.svg new file mode 100644 index 000000000..167dacce7 --- /dev/null +++ b/images/history.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/mastodon.svg b/images/mastodon.svg new file mode 100644 index 000000000..67ce0ed14 --- /dev/null +++ b/images/mastodon.svg @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/images/matrix.svg b/images/matrix.svg new file mode 100644 index 000000000..49a521c21 --- /dev/null +++ b/images/matrix.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/images/pencil.svg b/images/pencil.svg new file mode 100644 index 000000000..5d78ebd07 --- /dev/null +++ b/images/pencil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/rss.svg b/images/rss.svg new file mode 100644 index 000000000..b70bb39a3 --- /dev/null +++ b/images/rss.svg @@ -0,0 +1,3 @@ + + + diff --git a/images/sitemap.svg b/images/sitemap.svg new file mode 100644 index 000000000..2f9d9adcf --- /dev/null +++ b/images/sitemap.svg @@ -0,0 +1,3 @@ + + + diff --git a/index.html b/index.html new file mode 100644 index 000000000..aa76e20e1 --- /dev/null +++ b/index.html @@ -0,0 +1,36 @@ +Sebastian Hoß

Welcome to the blog of Sebastian Hoß. The latest article is displayed below, for older articles either pick one from the navigation or examine the sitemap.

The source code for this site can be found at https://github.com/metio/seb.

jspecify +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-java" href="/tags/java">java</a>, <a class="post-tag post-tag-jspecify" href="/tags/jspecify">jspecify</a>, and <a class="post-tag post-tag-nullness" href="/tags/nullness">nullness</a>

Every Java developer has probably encountered a NullPointerException at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:

String someName;         // value is 'null'
+
+someName.toUpperCase(); // throws NullPointerException
+

Modern IDEs have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is null and therefore a potential for a NullPointerException is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be null. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.

jspecify is the latest approach that tries to establish a standard. It has gained wide community support and recently celebrated their first public release (0.3.0).

The following snippet shows the dependency declaration for Maven projects:

<dependencies>
+    <dependency>
+        <groupId>org.jspecify</groupId>
+        <artifactId>jspecify</artifactId>
+        <version>0.3.0</version>
+    </dependency>
+</dependencies>
+

In case you want to declare that nothing in your module can ever be null, place the @NullMarked on your module-info.java like this:

@org.jspecify.annotations.NullMarked
+module your.module.here {
+
+    requires org.jspecify;
+
+    // ...
+
+}
+

The tooling support is not quiet clear yet, however if you are developing a library there is no harm in adding these annotations now and let your users enjoy their null-free life once tools have caught up.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/index.xml b/index.xml new file mode 100644 index 000000000..f2e4274ad --- /dev/null +++ b/index.xml @@ -0,0 +1,884 @@ +Sebastian Hoßhttps://seb.xn--ho-hia.de/Recent content on Sebastian HoßHugoenjspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/Mon, 09 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/jspecify/<p>Every <a href="https://www.java.com/">Java</a> developer has probably encountered a <code>NullPointerException</code> at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="n">someName</span><span class="p">;</span><span class="w"> </span><span class="c1">// value is &#39;null&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">someName</span><span class="p">.</span><span class="na">toUpperCase</span><span class="p">();</span><span class="w"> </span><span class="c1">// throws NullPointerException</span><span class="w"> +</span></span></span></code></pre></div><p>Modern <a href="https://en.wikipedia.org/wiki/Integrated_development_environment">IDEs</a> have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is <code>null</code> and therefore a potential for a <code>NullPointerException</code> is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be <code>null</code>. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.</p>Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/Sun, 08 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-age/<p><a href="https://age-encryption.org/">age</a> is another tool supported by <a href="https://www.chezmoi.io/">chezmoi</a> to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. Compared to <code>gpg</code> it is much simpler by focusing on the encryption parts only.</p> +<p>Add the following snippet to your <code>.chezmoi.toml</code> to configure <code>chezmoi</code> to use <code>age</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">encryption</span> <span class="p">=</span> <span class="s2">&#34;age&#34;</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">age</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">identity</span> <span class="p">=</span> <span class="s2">&#34;path/to/age/private-key&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;age...public...key...&#34;</span> +</span></span></code></pre></div><p>Adding files to your <code>chezmoi</code> source directory remains the same as compared to using <code>gpg</code> - just call <code>chezmoi add --encrypt path/to/file</code>.</p>Multiple Git Configurationshttps://seb.xn--ho-hia.de/posts/multiple-git-configs/Thu, 05 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/multiple-git-configs/<p>To split yet re-use as much configuration for Git as possible, you can create one root configuration that looks similar to this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span> +</span></span><span class="line"><span class="cl"> <span class="na">name</span> <span class="o">=</span> <span class="s">Your Name Here</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[includeIf &#34;gitdir:~/git/personal/&#34;]</span> +</span></span><span class="line"><span class="cl"> <span class="na">path</span> <span class="o">=</span> <span class="s">~/.config/git/personal</span> +</span></span><span class="line"><span class="cl"><span class="k">[includeIf &#34;gitdir:~/git/work/&#34;]</span> +</span></span><span class="line"><span class="cl"> <span class="na">path</span> <span class="o">=</span> <span class="s">~/.config/git/work</span> +</span></span></code></pre></div><p>The <a href="https://git-scm.com/docs/git-config#_includes">includeIf</a> directive supports multiple keywords. In my case, work and personal projects have a different root directory, therefore I can filter based on the location using <code>gitdir</code>. The personal Git configuration simply looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span> +</span></span><span class="line"><span class="cl"> <span class="na">email</span> <span class="o">=</span> <span class="s">personal.email@example.com</span> +</span></span></code></pre></div><p>and the work related configuration like this using a different email address:</p>passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/passage-fuzzy-search/<p>To fuzzy search through passwords managed with <a href="https://github.com/FiloSottile/passage">passage</a>, I&rsquo;ve written the following script that is inspired by the upstream version which is using <code>fzf</code>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">fd --type<span class="o">=</span>file --base-directory<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PASSAGE_DIR</span><span class="k">:-</span><span class="si">${</span><span class="nv">HOME</span><span class="si">}</span><span class="p">/.passage/store</span><span class="si">}</span><span class="s2">&#34;</span> .age --exec <span class="nb">echo</span> <span class="s1">&#39;{.}&#39;</span> <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> sk --cycle --layout<span class="o">=</span>reverse --tiebreak<span class="o">=</span>score --no-multi <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> xargs --replace --max-args<span class="o">=</span><span class="m">1</span> --no-run-if-empty <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> passage show --clip<span class="o">=</span><span class="m">1</span> <span class="o">{}</span> +</span></span></code></pre></div><p>This version requires <a href="https://github.com/sharkdp/fd/">fd</a>, <a href="https://github.com/lotabout/skim">skim</a>, <a href="https://www.gnu.org/software/findutils/manual/html_node/find_html/Invoking-xargs.html">xargs</a>, and <a href="https://github.com/FiloSottile/passage">passage</a> itself of course. The detailed breakdown on how it works is as follows:</p> +<ol> +<li>Use <code>fd</code> to find all files within <code>${PASSAGE_DIR}</code> that end in <code>.age</code>. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.</li> +<li>Using both <code>--base-directory</code> and <code>--exec echo '{.}'</code> ensures that passwords are returned in such form that they can be passed back into <code>passage</code> again. The placeholder <code>'{.}'</code> is a feature provided by <code>fd</code> which strips the file extension from each returned value.</li> +<li>All passwords are then passed into <code>sk</code> to allow to fuzzy search across them all. Setting <code>--no-multi</code> ensures that only a single password can be selected.</li> +<li>Finally, <code>xargs</code> calls <code>passage</code> and replaces the curly braces with the selected password. Thanks to <code>--clip=1</code>, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.</li> +</ol> +<p>To call that script, I&rsquo;ve saved it as <code>passage-fuzzy-search.sh</code> in my <code>.local/bin</code> folder and added some checks into it to verify that every required software is actually installed.</p>chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/Mon, 26 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-update/<p>To automatically synchronize dotfiles across my computers, I&rsquo;ve written the following <code>systemd</code> unit:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-systemd" data-lang="systemd"><span class="line"><span class="cl"><span class="k">[Unit]</span> +</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Update chezmoi managed dotfiles</span> +</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"><span class="na">Wants</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Service]</span> +</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/chezmoi update --no-tty --force</span> +</span></span><span class="line"><span class="cl"><span class="na">RemainAfterExit</span><span class="o">=</span><span class="s">false</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Install]</span> +</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span> +</span></span></code></pre></div><p>This unit pulls changes from upstream first and then applies the changes to the current computer after I&rsquo;m logged in and a network connection is available. The <code>--no-tty</code> flag is required because there is no tty when systemd executes <code>chezmoi</code>. Likewise, the <code>--force</code> flag ensures that no interactive prompt will be displayed which we cannot answer since <code>systemd</code> is executing this unit without us being involved.</p>chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/Mon, 12 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/shell-init/<p>Many CLI applications offer initialization scripts to integrate into a shell, for example <code>starship init zsh</code> or <code>zoxide init zsh</code>. The documentation of these tools usually tell you to put something like <code>eval &quot;$(starship init zsh)&quot;</code> into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the <code>init</code> command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.</p>awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/Mon, 14 Feb 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/awsenv/<p>To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the <code>AWS_PROFILE</code> environment variable which is used by many tools that interact with the AWS API, like <code>awscli</code> or <code>terraform</code>. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses <code>aws sso login</code> to perform an MFA login into AWS. The AWS profiles must be set up in such a way that <code>aws configure list-profiles</code> can detect them, which is typically done by adding them in <code>${AWS_CONFIG_FILE:-$HOME/.aws/config}</code>.</p>Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/Sat, 29 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/clojure-java-interoperability/<p>Clojure has several <a href="https://clojure.org/java_interop">forms and macros</a> to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.</p> +<h2 id="using-gen-class">Using <code>gen-class</code></h2> +<p>Clojure code can be <a href="https://clojure.org/compilation">compiled</a> to standard JVM bytecode using <a href="https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/gen-class">gen-class</a>.</p> +<h3 id="adding-static-modifiers">Adding static modifiers</h3> +<p>Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class <code>com.example.Computation</code>.</p>Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/Sat, 15 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/<p>One feature that often surprises people while teaching them <a href="https://reactjs.org/">React</a> is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.</p> +<p>In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.</p>Proper hostnames in your local networkhttps://seb.xn--ho-hia.de/posts/home-network-hostnames/Sat, 08 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/home-network-hostnames/<p>Thanks to <a href="https://www.rfc-editor.org/rfc/rfc8375.html">RFC 8375</a>, we now have a proper domain to use for all our local devices. Simply move everything underneath <code>.home.arpa</code> to join the fun. In case you have <code>hostnamectl</code> available on your system run the following command to change the hostname of a device:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> <span class="nb">set</span> hostname +</span></span><span class="line"><span class="cl"><span class="gp">$</span> hostnamectl hostname some-device.home.arpa +</span></span><span class="line"><span class="cl"><span class="err"> +</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="gp">#</span> check hostname +</span></span><span class="line"><span class="cl"><span class="gp">$</span> hostnamectl status +</span></span></code></pre></div>Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/<p>Both <a href="https://www.vim.org/">Vim</a> and <a href="https://neovim.io/">Neovim</a> have a <a href="https://vimhelp.org/repeat.txt.html#packages">built-in</a> <a href="https://neovim.io/doc/user/usr_05.html#plugin">plugin mechanism</a> that loads plugins from <code>~/.vim/pack/*/{start,opt}/*</code> (Vim) or <code>~/.local/share/nvim/site/pack/*/{start,opt}/*</code> (Neovim). All you have to do to install new plugins, is to <code>git clone</code> their repository into those directories. To automatically update those clones, create the following script:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env zsh +</span></span></span><span class="line"><span class="cl"><span class="cp"></span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"><span class="c1"># This script git-pulls all installed nvim plugins which are using the built-in</span> +</span></span><span class="line"><span class="cl"><span class="c1"># nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack</span> +</span></span><span class="line"><span class="cl"><span class="c1">#</span> +</span></span><span class="line"><span class="cl"><span class="c1"># Required software that is not in GNU coreutils:</span> +</span></span><span class="line"><span class="cl"><span class="c1"># - &#39;git&#39; to fetch plugin updates from upstream</span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### User specific variables, adjust to your own needs</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># folder that contains all nvim plugins</span> +</span></span><span class="line"><span class="cl"><span class="nv">PLUGIN_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_DATA_HOME</span><span class="k">:-</span><span class="nv">$HOME</span><span class="p">/.local/share</span><span class="si">}</span><span class="s2">/nvim/site/pack&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### Script logic</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;updating all plugins in </span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"><span class="c1"># iterate through all directories and git pull em</span> +</span></span><span class="line"><span class="cl"><span class="k">for</span> directory in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span>/*/<span class="o">{</span>start,opt<span class="o">}</span>/*<span class="p">;</span> <span class="k">do</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> +</span></span><span class="line"><span class="cl"> <span class="nv">plugin</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;updating </span><span class="si">${</span><span class="nv">plugin</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"> git -C <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> pull --quiet +</span></span><span class="line"><span class="cl"> <span class="k">fi</span> +</span></span><span class="line"><span class="cl"><span class="k">done</span> +</span></span></code></pre></div><p>In case you are using Vim, adjust the <code>PLUGIN_DIR</code> variable to point to your Vim plugin directory instead and save the resulting shell script as a file called <code>update-nvim-plugins.sh</code> in some folder of your choice. Do not forget to set the executable bit with <code>chmod +x /path/to/your/folder/update-nvim-plugins.sh</code>. Since all good developers must be lazy, write the following <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html">systemd service</a> to execute the above script automatically:</p>Manage tmux sessions with tmuxphttps://seb.xn--ho-hia.de/posts/tmux-tmuxp/Mon, 20 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-tmuxp/<p>To manage <a href="https://github.com/tmux/tmux">tmux</a> sessions, I like to use <a href="https://github.com/tmux-python/tmuxp">tmuxp</a>. It works by having pre-defined sessions in <code>~/.config/tmuxp</code> which looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">session_name</span><span class="p">:</span><span class="w"> </span><span class="l">cool-app</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">~/projects/cool-app</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">windows</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">window_name</span><span class="p">:</span><span class="w"> </span><span class="l">backend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">backend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">window_name</span><span class="p">:</span><span class="w"> </span><span class="l">frontend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">frontend</span><span class="w"> +</span></span></span></code></pre></div><p>In case the name of the file is <code>cool-app.yaml</code>, you can open the sessions with <code>tmuxp load cool-app --yes</code>.</p>Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/Mon, 06 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-cd-versioning/<p>To automatically version <a href="https://maven.apache.org/">Maven</a> projects, I like to use the <a href="https://www.mojohaus.org/versions-maven-plugin/">m-versions-p</a> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ mvn versions:set -DnewVersion<span class="o">=</span>my.new.version -DgenerateBackupPoms<span class="o">=</span><span class="nb">false</span> +</span></span></code></pre></div><p>This will update the <code>version</code> property of every module in the reactor to prepare them for the next release. In case you are using <a href="https://github.com/features/actions">GitHub Actions</a>, consider using a <a href="../github-actions-create-timestamp">timestamp</a>.</p>GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/Mon, 22 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-maven-packages/<p><a href="https://github.com/features/packages">GitHub Packages</a> can be used to host <a href="https://maven.apache.org/">Maven</a> packages with the following configuration in your <code>~/.m2/settings.xml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>github<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub maven-build-process Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/maven-build-process<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub hcf4j Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/hcf4j<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;servers&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/servers&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>You will have to add another repository/server for each project you are fetching from GitHub.</p>Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/Mon, 08 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-google-central/<p>Some time ago, <a href="https://www.google.com/">Google</a> started hosting a <a href="https://storage-download.googleapis.com/maven-central/index.html">copy</a> of <a href="https://search.maven.org/">Maven Central</a>. Configure it in your <code>~/.m2/settings.xml</code> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrors&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (Asia)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-asia.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (EU)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-eu.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (US)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirrors&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Pick the mirror nearest to your location to get best speeds.</p>Backups with emacshttps://seb.xn--ho-hia.de/posts/emacs-backups/Mon, 25 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/emacs-backups/<p><a href="https://www.gnu.org/software/emacs/">emacs</a> will create backups of your files by default. Those backups are located right next to the original file and are called <code>&lt;file&gt;~</code>. Unfortunately, emacs will not clean those up by default, which annoys me from time to time. Therefore, I&rsquo;m now using the following configuration to keep those backups in a different folder:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-el" data-lang="el"><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">version-control</span> <span class="no">t</span> <span class="c1">;; Use version numbers for backups.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kept-new-versions</span> <span class="mi">10</span> <span class="c1">;; Number of newest versions to keep.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kept-old-versions</span> <span class="mi">0</span> <span class="c1">;; Number of oldest versions to keep.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">delete-old-versions</span> <span class="no">t</span> <span class="c1">;; Don&#39;t ask to delete excess backup versions.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">backup-by-copying</span> <span class="no">t</span><span class="p">)</span> <span class="c1">;; Copy all files, don&#39;t rename them.</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">vc-make-backup-files</span> <span class="no">t</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">;; Default and per-save backups go here:</span> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">backup-directory-alist</span> <span class="o">&#39;</span><span class="p">((</span><span class="s">&#34;&#34;</span> <span class="o">.</span> <span class="s">&#34;~/.emacs.d/backup/per-save&#34;</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">defun</span> <span class="nv">force-backup-of-buffer</span> <span class="p">()</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Make a special &#34;per session&#34; backup at the first save of each</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; emacs session.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">when</span> <span class="p">(</span><span class="nv">not</span> <span class="nv">buffer-backed-up</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Override the default parameters for per-session backups.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">backup-directory-alist</span> <span class="o">&#39;</span><span class="p">((</span><span class="s">&#34;&#34;</span> <span class="o">.</span> <span class="s">&#34;~/.emacs.d/backup/per-session&#34;</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">kept-new-versions</span> <span class="mi">3</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">backup-buffer</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Make a &#34;per save&#34; backup on each save. The first save results in</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; both a per-session and a per-save backup, to keep the numbering</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; of per-save backups consistent.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">buffer-backed-up</span> <span class="no">nil</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">backup-buffer</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">add-hook</span> <span class="ss">&#39;before-save-hook</span> <span class="ss">&#39;force-backup-of-buffer</span><span class="p">)</span> +</span></span></code></pre></div><p>Thanks to that configuration, backups per-save will be created in <code>~/.emacs.d/backup/per-save</code> and backups per-session in <code>~/.emacs.d/backup/per-session</code>.</p>Maintaining dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-maintenance/Mon, 11 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-maintenance/<p>To make it easier managing many dotfiles with <a href="https://www.chezmoi.io/">chezmoi</a>, a shell function similar to the one below can be used:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="k">function</span> m-dotfiles-ok <span class="o">{</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># public</span> +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/zsh --recursive +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/sway --recursive +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/tmux --recursive +</span></span><span class="line"><span class="cl"> chezmoi add .... +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># secrets</span> +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ~/.config/npm/npmrc +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ~/.ssh/id_rsa +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ... +</span></span><span class="line"><span class="cl"><span class="o">}</span> +</span></span></code></pre></div><p>Whenever you feel happy with your current setup, just call <code>m-dotfiles-ok</code> to push changes into the chezmoi source directory. Files will automatically be <a href="../chezmoi-gpg">encrypted</a> with <a href="https://www.gnupg.org/">gpg</a> and <a href="../chezmoi-auto-git">committed/pushed</a> into a <a href="https://git-scm.com/">Git</a> repository if you have done the necessary configuration beforehand.</p>Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/Mon, 20 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-gpg/<p><strong>RECOMMENDATION</strong>: Use <a href="../chezmoi-age">age</a> instead of <code>gpg</code>.</p> +<p><a href="https://www.chezmoi.io/">chezmoi</a> can use various external tools to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. <a href="https://www.gnupg.org/">gpg</a> is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure <code>gpg</code> with <code>chezmoi</code>, just set yourself as the recipient like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">gpg</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;your.name@example.com&#34;</span> +</span></span></code></pre></div><p>Calling <code>chezmoi add --encrypt /path/to/secret</code> will now create encrypt the file with your public key which allows you to decrypt them later with your private key.</p>Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/Mon, 06 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-git/<p><a href="https://www.chezmoi.io/">chezmoi</a> can automatically commit and push changes to your <a href="https://en.wikipedia.org/wiki/dotfile">dotfiles</a> into a (remote) <a href="https://git-scm.com/">Git</a> repository. Enable it with the following snippet in your <code>chezmoi.toml</code></p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">sourceVCS</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoCommit</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoPush</span> <span class="p">=</span> <span class="kc">true</span> +</span></span></code></pre></div><p>Every time you call <code>chezmoi add /path/to/file</code> will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.</p>Waybar on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-waybar/Mon, 23 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-waybar/<p><a href="https://github.com/Alexays/Waybar/">Waybar</a> can be used as a status bar for <a href="https://swaywm.org/">SwayWM</a>. You tell Sway to use it with the following snippet in your Sway configuration:</p> +<pre tabindex="0"><code>bar { + swaybar_command waybar +} +</code></pre><p>Configure Waybar itself in <code>~/.config/waybar/config</code>:</p> +<pre tabindex="0"><code>{ + &#34;layer&#34;: &#34;top&#34;, + &#34;modules-left&#34;: [&#34;sway/workspaces&#34;, &#34;sway/mode&#34;], + &#34;modules-center&#34;: [&#34;sway/window&#34;], + &#34;modules-right&#34;: [&#34;clock&#34;], + &#34;sway/window&#34;: { + &#34;max-length&#34;: 50 + }, + &#34;clock&#34;: { + &#34;format-alt&#34;: &#34;{:%a, %d. %b %H:%M}&#34; + } +} +</code></pre>tmux Status Barhttps://seb.xn--ho-hia.de/posts/tmux-status-bar/Mon, 09 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-status-bar/<p>To see the currently active <a href="https://github.com/tmux/tmux">tmux</a> status bar configuration, call:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ tmux show-options -g <span class="p">|</span> grep status +</span></span></code></pre></div><p>Change on of those values with in the current tmux session:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ tmux set-option status-right <span class="s2">&#34;&#34;</span> +</span></span></code></pre></div><p>Persist the change in your <code>tmux.conf</code> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># disable right side of status bar</span> +</span></span><span class="line"><span class="cl">set-option -g status-right <span class="s2">&#34;&#34;</span> +</span></span></code></pre></div>emacs and systemdhttps://seb.xn--ho-hia.de/posts/emacs-systemd/Mon, 26 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/emacs-systemd/<p>I like to use <a href="https://www.gnu.org/software/emacs/">emacs</a> to edit files in a terminal. It tends to start a little slow, therefore I&rsquo;ve created a <a href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</a> unit to automatically start the emacs daemon and use aliases to connect to the running daemon. The unit looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span> +</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Emacs text editor [%I]</span> +</span></span><span class="line"><span class="cl"><span class="na">Documentation</span><span class="o">=</span><span class="s">info:emacs man:emacs(1) https://gnu.org/software/emacs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Service]</span> +</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">forking</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/emacs --daemon=%i</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStop</span><span class="o">=</span><span class="s">/usr/bin/emacsclient --eval &#34;(kill-emacs)&#34;</span> +</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">SSH_AUTH_SOCK=%t/keyring/ssh</span> +</span></span><span class="line"><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">on-failure</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Install]</span> +</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span> +</span></span></code></pre></div><p>Enable it with <code>systemctl --user enable emacs@user</code> and define any number of aliases to make connecting to the emacs daemon easier:</p>Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/Mon, 12 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-push-only-mirror/<p>In case you want to have push-only mirrors for your <a href="https://git-scm.com/">Git</a> repository, consider adding a special mirror remote like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote add mirrors DISABLED +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git +</span></span></code></pre></div><p>The above will create a new remote called <code>mirrors</code> which has no <code>fetch</code> URL and therefore can only be pushed:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote -v +</span></span><span class="line"><span class="cl">mirrors DISABLED <span class="o">(</span>fetch<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@codeberg.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@gitlab.com:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@bitbucket.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span></code></pre></div><p>Calling <code>git push mirrors main:main</code> will push the local <code>main</code> branch into all defined mirrors.</p>Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/Mon, 28 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-mirror/<p>In case you want to make use of the decentralized nature of <a href="https://git-scm.com/">Git</a>, consider using multiple push targets like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote set-url origin --push --add git@example.com/project.git +</span></span><span class="line"><span class="cl">$ git remote set-url origin --push --add git@another.com/project.git +</span></span></code></pre></div><p>Note that the first call to <code>set-url</code> will overwrite an existing remote creating with <code>git clone</code>. Any additional call will actually recognize the <code>--add</code> option and add the new target to an existing remote.</p> +<h2 id="links">Links</h2> +<ul> +<li><a href="../git-push-only-mirror">push only mirrors</a></li> +<li><a href="../gitlab-distributor">gitlab-distributor</a></li> +</ul>Using multiple clusters with kubectlhttps://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/Mon, 14 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/<p>To connect to multiple <a href="https://kubernetes.io/">Kubernetes</a> clusters with <a href="https://kubernetes.io/docs/reference/kubectl/overview/">kubectl</a>, I like to define aliases like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">rancher</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/rancher.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">work</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/work.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">customer</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/customer.config&#34;</span> +</span></span></code></pre></div><p>Those aliases allow me to write things like <code>rancher get pods --namespace some-namespace</code> without worrying the wrong context is active. Using multiple configurations - one for each cluster - seems to be easier to manage since most clusters allow to download a ready-to-use configuration file. Instead of mangling them together manually, I just specify another alias whenever I get to work with another cluster.</p>Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-encoding/<p><a href="https://maven.apache.org/">Maven</a> projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.reporting.outputEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.reporting.outputEncoding&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +</ul>Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/Mon, 17 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-reproducible/<p>To create <a href="https://reproducible-builds.org/">reproducible builds</a> with <a href="https://maven.apache.org/">Maven</a> projects, it&rsquo;s enough to specify the <code>project.build.outputTimestamp</code> property like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.outputTimestamp&gt;</span>2020<span class="nt">&lt;/project.build.outputTimestamp&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +<li><a href="https://maven.apache.org/guides/mini/guide-reproducible-builds.html">https://maven.apache.org/guides/mini/guide-reproducible-builds.html</a></li> +<li><a href="https://github.com/rodiontsev/maven-build-info-plugin">https://github.com/rodiontsev/maven-build-info-plugin</a></li> +<li><a href="https://github.com/phax/ph-buildinfo-maven-plugin">https://github.com/phax/ph-buildinfo-maven-plugin</a></li> +<li><a href="https://github.com/Zlika/reproducible-build-maven-plugin">https://github.com/Zlika/reproducible-build-maven-plugin</a></li> +</ul>Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/Mon, 03 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/<p>To analyze <a href="https://maven.apache.org/">Maven</a> projects with <a href="https://sonarcloud.io">SonarCloud</a> using <a href="https://github.com/features/actions">GitHub Actions</a>, first create the following <code>settings.xml</code> file:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings</span> <span class="na">xmlns=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xmlns:xsi=</span><span class="s">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xsi:schemaLocation=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0 +</span></span></span><span class="line"><span class="cl"><span class="s"> http://maven.apache.org/xsd/settings-1.0.0.xsd&#34;</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroup&gt;</span>org.sonarsource.scanner.maven<span class="nt">&lt;/pluginGroup&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfile&gt;</span>sonar<span class="nt">&lt;/activeProfile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>sonar<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.host.url&gt;</span>https://sonarcloud.io<span class="nt">&lt;/sonar.host.url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.organization&gt;</span>YOUR_ORG<span class="nt">&lt;/sonar.organization&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.projectKey&gt;</span>YOUR_PROJECT<span class="nt">&lt;/sonar.projectKey&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.login&gt;</span>${env.SONAR_TOKEN}<span class="nt">&lt;/sonar.login&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Finally, add a step to your workflow:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Verify Project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">SONAR_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.SONAR_TOKEN }}</span><span class="w"> +</span></span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/settings.html">https://maven.apache.org/settings.html</a></li> +<li><a href="https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/">https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/</a></li> +</ul>Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/Mon, 19 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-login-shell/<p>To use <a href="https://github.com/tmux/tmux">tmux</a> as your login shell, use <code>chsh</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># list all available shells</span> +</span></span><span class="line"><span class="cl">$ chsh --list-shells +</span></span><span class="line"><span class="cl">/bin/sh +</span></span><span class="line"><span class="cl">/bin/bash +</span></span><span class="line"><span class="cl">/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/sh +</span></span><span class="line"><span class="cl">/usr/bin/bash +</span></span><span class="line"><span class="cl">/usr/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/zsh +</span></span><span class="line"><span class="cl">/bin/zsh +</span></span><span class="line"><span class="cl">/usr/bin/tmux +</span></span><span class="line"><span class="cl">/bin/tmux +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># select login shell</span> +</span></span><span class="line"><span class="cl">$ chsh --shell /usr/bin/tmux +</span></span></code></pre></div>Lock your screen on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenlock/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenlock/<p><a href="https://swaywm.org/">SwayWM</a> users can use <a href="https://github.com/swaywm/swaylock">swaylock</a> to lock their screen. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># lock your screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Ctrl+l <span class="nb">exec</span> swaylock --color <span class="m">000000</span> +</span></span></code></pre></div><p><code>$mod+Ctrl+l</code> will lock your screen and turn it to black. The <code>--color</code> flag allows any color in the form of <code>rrggbb[aa]</code>.</p>Peeking with tmuxhttps://seb.xn--ho-hia.de/posts/tmux-peek/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-peek/<p><a href="https://github.com/tmux/tmux">tmux</a> uses can use the following snippet to peek at files. Place it in your <code>.bashrc</code> or similar file.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">peek<span class="o">()</span> <span class="o">{</span> tmux split-window -p <span class="m">33</span> <span class="s2">&#34;</span><span class="nv">$EDITOR</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span> <span class="o">}</span> +</span></span></code></pre></div><p>Calling <code>peek &lt;file&gt;</code> will open <code>&lt;file&gt;</code> in lower third of tmux window.</p>Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/Mon, 22 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenshots/<p><a href="https://swaywm.org/">SwayWM</a> uses can use a mixture of <a href="https://github.com/emersion/grim">grim</a> and <a href="https://github.com/emersion/slurp">slurp</a> to take screenshots of their desktop. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># take screenshot of currently focused screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Print <span class="nb">exec</span> /usr/bin/grim -o <span class="k">$(</span>swaymsg -t get_outputs <span class="p">|</span> jq -r <span class="s1">&#39;.[] | select(.focused) | .name&#39;</span><span class="k">)</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># take screenshot of selection</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Shift+p <span class="nb">exec</span> /usr/bin/grim -g <span class="s2">&#34;</span><span class="k">$(</span>/usr/bin/slurp<span class="k">)</span><span class="s2">&#34;</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span></code></pre></div>Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/Mon, 08 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-readme/<p><code>README</code> file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a <code>Makefile</code> in the root of your project which contains the exact same instructions. Thanks to <code>make</code>, all your contributors can now use TAB-completion to run any of the pre-defined <code>make</code> targets.</p>Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/Mon, 22 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-schedule-build/<p>To delay the execution of an <a href="https://github.com/features/actions">GitHub Action</a>, use a mixture of the <code>on: schedule: ...</code> configuration, and a <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif">conditional build step</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">schedule</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&lt;CRON&gt;&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Count commits in last week</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">commits</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=count::$(git rev-list --count HEAD --since=&#39;&lt;DATE&gt;&#39;)&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">steps.commits.outputs.count &gt; 0</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">build-project</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;CRON&gt;</code>: cron expression - use <a href="https://crontab.guru/">https://crontab.guru/</a>.</li> +<li><code>&lt;DATE&gt;</code>: Git date expression that matches <code>&lt;CRON&gt;</code>.</li> +</ul>Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/Mon, 08 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-ignore/<p>In case you are using a <code>Makefile</code> to define a complex build step - for example start database, run tests, stop database - consider using the <code>-</code> qualifier in front of your actual build step like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">build</span> +</span></span><span class="line"><span class="cl"><span class="nf">build</span><span class="o">:</span> +</span></span><span class="line"><span class="cl"> start-database +</span></span><span class="line"><span class="cl"> -build-software +</span></span><span class="line"><span class="cl"> stop-database +</span></span></code></pre></div><p>Thanks to <code>-</code>, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.</p>Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/Mon, 25 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-serviceworkers/<p>To use a <a href="https://serviceworke.rs/">serviceworker</a> to cache a <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/javascript&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;js&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">ServiceWorker</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;ServiceWorker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/javascript&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;serviceworker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.serviceworker.js</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">const CACHE = &#39;cache-and-update&#39;; + +self.addEventListener(&#39;install&#39;, (event) =&gt; { + event.waitUntil(precache()); +}); + +self.addEventListener(&#39;fetch&#39;, (event) =&gt; { + event.respondWith(fromCache(event.request)); + event.waitUntil(update(event.request)); +}); + +const precache = async () =&gt; { + const cache = await caches.open(CACHE); + return await cache.addAll([ + {{ range $i, $e := .Site.RegularPages }} + &#39;{{ $.RelPermalink }}&#39;{{ if $i }}, {{ end }} + {{ end }} + ]); +} + +const fromCache = async (request) =&gt; { + const cache = await caches.open(CACHE); + const match = await cache.match(request); + return match || Promise.reject(&#39;no-match&#39;); +} + +const update = async (request) =&gt; { + const cache = await caches.open(CACHE); + const response = await fetch(request); + return await cache.put(request, response); +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://github.com/gohugoio/hugo/issues/5495">https://github.com/gohugoio/hugo/issues/5495</a></li> +<li><a href="https://github.com/wildhaber/offline-first-sw">https://github.com/wildhaber/offline-first-sw</a></li> +<li><a href="https://gohugohq.com/howto/go-offline-with-service-worker/">https://gohugohq.com/howto/go-offline-with-service-worker/</a></li> +</ul>Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/Mon, 11 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-webmanifest/<p>To publish a <a href="https://developer.mozilla.org/en-US/docs/Web/Manifest">web app manifest</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/manifest+json&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;webmanifest&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Webmanifest</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Web App Manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/manifest+json&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.manifest.json</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{ + &#34;name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;short_name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;start_url&#34;: &#34;.&#34;, + &#34;display&#34;: &#34;minimal-ui&#34;, + &#34;background_color&#34;: &#34;#fff&#34;, + &#34;description&#34;: &#34;{{ .Site.Params.description }}&#34; +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://web.dev/add-manifest/">https://web.dev/add-manifest/</a></li> +</ul>Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/Mon, 28 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-bundles/<p><a href="https://gohugo.io/">Hugo</a> allows <a href="https://gohugo.io/hugo-pipes/bundling/">bundling</a> of assets with several built-in functions:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ $normalize := resources.Get &#34;/css/normalize.css&#34; }} +{{ $font := resources.Get &#34;/css/font.css&#34; }} +{{ $header := resources.Get &#34;/css/header.css&#34; }} +{{ $footer := resources.Get &#34;/css/footer.css&#34; }} +{{ $navigation := resources.Get &#34;/css/navigation.css&#34; }} +{{ $navigation_mobile := resources.Get &#34;/css/navigation-mobile.css&#34; }} +{{ $layout := resources.Get &#34;/css/layout.css&#34; }} +{{ $layout_mobile := resources.Get &#34;/css/layout-mobile.css&#34; }} +{{ $syntax := resources.Get &#34;/css/syntax.css&#34; }} +{{ $darkmode := resources.Get &#34;/css/darkmode.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat &#34;css/base.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} +{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat &#34;css/mobile.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +&lt;link href=&#34;{{ $base.Permalink }}&#34; integrity=&#34;{{ $base.Data.Integrity }}&#34; media=&#34;screen&#34; rel=&#34;stylesheet&#34;&gt; +&lt;link href=&#34;{{ $mobile.Permalink }}&#34; integrity=&#34;{{ $mobile.Data.Integrity }}&#34; media=&#34;screen and (max-width: 800px)&#34; rel=&#34;stylesheet&#34;&gt; + +&lt;link href=&#34;{{ $darkmode.Permalink }}&#34; integrity=&#34;{{ $darkmode.Data.Integrity }}&#34; media=&#34;screen and (prefers-color-scheme: dark)&#34; rel=&#34;stylesheet&#34;&gt; +</code></pre>humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/Mon, 14 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-humans/<p>To publish a <a href="http://humanstxt.org/">humans.txt</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;text/plain&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;txt&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Humans</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;text/plain&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.humans.txt</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">/* TEAM */ +{{ range $.Site.Data.contributors }} +{{ .title }}: {{ .first_name }} {{ .last_name }} +Site: {{ .website }} +{{ end }} +</code></pre>FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/Mon, 30 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-foaf/<p>To publish a <a href="http://www.foaf-project.org/">FOAF</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/rdf+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;rdf&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Foaf</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;FOAF&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/rdf+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;foaf&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.foaf.rdf</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">&lt;rdf:RDF xmlns:rdf=&#34;http://www.w3.org/1999/02/22-rdf-syntax-ns#&#34; xmlns:rdfs=&#34;http://www.w3.org/2000/01/rdf-schema#&#34; xmlns:foaf=&#34;http://xmlns.com/foaf/0.1/&#34;&gt; + &lt;foaf:PersonalProfileDocument rdf:about=&#34;&#34;&gt; + &lt;foaf:maker rdf:resource=&#34;#me&#34; /&gt; + &lt;foaf:primaryTopic rdf:resource=&#34;{{ .Site.Title }}&#34; /&gt; + &lt;/foaf:PersonalProfileDocument&gt; + + &lt;foaf:Project rdf:ID=&#34;{{ .Site.Title }}&#34;&gt; + &lt;foaf:name&gt;{{ .Site.Title }}&lt;/foaf:name&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .Site.BaseURL }}&#34; /&gt; + &lt;/foaf:Project&gt; + + {{ range $.Site.Data.contributors }} + &lt;foaf:Person rdf:ID=&#34;{{ .id }}&#34;&gt; + &lt;foaf:name&gt;{{ .first_name }} {{ .last_name }}&lt;/foaf:name&gt; + &lt;foaf:title&gt;{{ .title }}&lt;/foaf:title&gt; + &lt;foaf:givenname&gt;{{ .first_name }}&lt;/foaf:givenname&gt; + &lt;foaf:family_name&gt;{{ .last_name }}&lt;/foaf:family_name&gt; + &lt;foaf:mbox rdf:resource=&#34;mailto:{{ .email }}&#34; /&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .website }}&#34; /&gt; + &lt;/foaf:Person&gt; + {{ end }} +&lt;/rdf:RDF&gt; +</code></pre>Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-atom/<p>To publish Atom feeds for your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/atom+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Atom</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/atom+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/list.atom.xml</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ printf `&lt;?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&gt;` | safeHTML }} +&lt;feed xmlns=&#34;http://www.w3.org/2005/Atom&#34;{{ with site.LanguageCode }} xml:lang=&#34;{{ . }}&#34;{{ end }}&gt; + &lt;generator uri=&#34;https://gohugo.io/&#34; version=&#34;{{ hugo.Version }}&#34;&gt;Hugo&lt;/generator&gt; + {{- $title := site.Title -}} + {{- with .Title -}} + {{- if (not (eq . site.Title)) -}} + {{- $title = printf `%s %s %s` . (i18n &#34;feed_title_on&#34; | default &#34;on&#34;) site.Title -}} + {{- end -}} + {{- end -}} + {{- if .IsTranslated -}} + {{ $title = printf &#34;%s (%s)&#34; $title (index site.Data.i18n.languages .Lang) }} + {{- end -}} + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` $title | safeHTML }} + {{ with (or (.Param &#34;subtitle&#34;) (.Param &#34;tagline&#34;)) }} + {{ printf `&lt;subtitle type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/subtitle&gt;` . | safeHTML }} + {{ end }} + {{ $output_formats := .OutputFormats }} + {{ range $output_formats -}} + {{- $rel := (or (and (eq &#34;atom&#34; (.Name | lower)) &#34;self&#34;) &#34;alternate&#34;) -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=%q type=%q title=%q /&gt;` .Permalink $rel .MediaType.Type .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- range .Translations }} + {{ $output_formats := .OutputFormats }} + {{- $lang := .Lang }} + {{- $langstr := index site.Data.i18n.languages .Lang }} + {{ range $output_formats -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=&#34;alternate&#34; type=%q hreflang=%q title=&#34;[%s] %s&#34; /&gt;` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- end }} + &lt;updated&gt;{{ now.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ with site.Copyright }} + {{- $copyright := replace . &#34;{year}&#34; now.Year -}} {{/* In case the site.copyright uses a special string &#34;{year}&#34; */}} + {{- $copyright = replace $copyright &#34;&amp;copy;&#34; &#34;©&#34; -}} + &lt;rights&gt;{{ $copyright | plainify }}&lt;/rights&gt; + {{- end }} + {{ with .Param &#34;feed&#34; }} + {{/* For this to work, the $icon file should be present in the assets/ directory */}} + {{- $icon := .icon | default &#34;icon.svg&#34; -}} + {{- with resources.Get $icon -}} + &lt;icon&gt;{{ (. | fingerprint).Permalink }}&lt;/icon&gt; + {{- end }} + + {{/* For this to work, the $logo file should be present in the assets/ directory */}} + {{- $logo := .logo | default &#34;logo.svg&#34; -}} + {{- with resources.Get $logo -}} + &lt;logo&gt;{{ (. | fingerprint).Permalink }}&lt;/logo&gt; + {{- end }} + {{ end }} + {{ with site.Author.name -}} + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + {{ with site.Author.email }} + &lt;email&gt;{{ . }}&lt;/email&gt; + {{ end -}} + &lt;/author&gt; + {{- end }} + {{ with site.Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }} + {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}} + {{/* Range through only the pages with a Type in $feed_sections. */}} + {{- $pages := where .RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- if (eq .Kind &#34;home&#34;) -}} + {{- $pages = where site.RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- end -}} + {{/* Remove the pages that have the disable_feed parameter set to true. */}} + {{- $pages = where $pages &#34;.Params.disable_feed&#34; &#34;!=&#34; true -}} + {{- range first $limit $pages }} + {{ $page := . }} + &lt;entry&gt; + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` .Title | safeHTML }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;alternate&#34; type=&#34;text/html&#34; /&gt; + {{- range .Translations }} + {{- $link := printf &#34;%s?utm_source=atom_feed&#34; .Permalink | safeHTML }} + {{- printf `&lt;link href=%q rel=&#34;alternate&#34; type=&#34;text/html&#34; hreflang=%q /&gt;` $link .Lang | safeHTML }} + {{- end }} + {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}} + {{- range first 5 (site.RegularPages.Related .) }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;related&#34; type=&#34;text/html&#34; title=&#34;{{ .Title }}&#34; /&gt; + {{- end }} + {{ with .Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{ with .Params.author -}} + {{- range . -}} &lt;!-- Assuming the author front-matter to be a list --&gt; + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + &lt;/author&gt; + {{- end -}} + {{- end }} + &lt;published&gt;{{ .Date.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/published&gt; + &lt;updated&gt;{{ .Lastmod.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ $description1 := .Description | default &#34;&#34; }} + {{ $description := (cond (eq &#34;&#34; $description1) &#34;&#34; (printf &#34;&lt;blockquote&gt;%s&lt;/blockquote&gt;&#34; ($description1 | markdownify))) }} + {{ printf `&lt;content type=&#34;html&#34;&gt;&lt;![CDATA[%s%s]]&gt;&lt;/content&gt;` $description .Content | safeHTML }} + {{ with site.Taxonomies }} + {{ range $taxo,$_ := . }} &lt;!-- Defaults taxos: &#34;tags&#34;, &#34;categories&#34; --&gt; + {{ with $page.Param $taxo }} + {{ $taxo_list := . }} &lt;!-- $taxo_list will be the tags/categories list --&gt; + {{ with site.GetPage (printf &#34;/%s&#34; $taxo) }} + {{ $taxonomy_page := . }} + {{ range $taxo_list }} &lt;!-- Below, assuming pretty URLs --&gt; + &lt;category scheme=&#34;{{ printf &#34;%s%s&#34; $taxonomy_page.Permalink (. | urlize) }}&#34; term=&#34;{{ (. | urlize) }}&#34; label=&#34;{{ . }}&#34; /&gt; + {{ end }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + &lt;/entry&gt; + {{ end }} +&lt;/feed&gt; +</code></pre>Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-toot/<p>The <a href="https://github.com/rzr/fediverse-action">rzr/fediverse-action</a> action allows to send a <a href="https://joinmastodon.org/">toot</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;NAME&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish Toot</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">rzr/fediverse-action@master</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">access-token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;MESSAGE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_SERVER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;MESSAGE&gt;</code>: Message for the toot.</li> +</ul>Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/Mon, 02 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-email/<p>The <a href="https://github.com/dawidd6/action-send-mail">dawidd6/action-send-mail</a> action allows to send an email in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Send mail</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">dawidd6/action-send-mail@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_address</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SERVER }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_port</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PORT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_USERNAME }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PASSWORD }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">subject</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;SUBJECT&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;BODY&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_RECIPIENT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SENDER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;SUBJECT&gt;</code>: Subject for the email.</li> +<li><code>&lt;BODY&gt;</code>: Body for the email.</li> +</ul> +<p>Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define <code>MAIL_RECIPIENT</code> for each project.</p>Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/<p>The <a href="https://github.com/actions/upload-release-asset">actions/upload-release-asset</a> action allows to upload a <a href="https://developer.github.com/v3/repos/releases/#upload-a-release-asset">release artifact</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload Release Asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">upload_release_asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-release-asset@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">upload_url</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.create_release.outputs.upload_url }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_path</span><span class="p">:</span><span class="w"> </span><span class="l">./some/path/to/file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_name</span><span class="p">:</span><span class="w"> </span><span class="l">public-name-for-file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_content_type</span><span class="p">:</span><span class="w"> </span><span class="l">application/zip</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/Mon, 05 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-release/<p>The <a href="https://github.com/actions/create-release">actions/create-release</a> action allows to create a new <a href="https://help.github.com/en/github/administering-a-repository/about-releases">GitHub releases</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create Release</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/create-release@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">tag_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;TAG&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">release_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RELEASE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">draft</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">prerelease</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Your release text here +</span></span></span><span class="line"><span class="cl"><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Some code block: +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> yaml: +</span></span></span><span class="line"><span class="cl"><span class="sd"> inside: +</span></span></span><span class="line"><span class="cl"><span class="sd"> of: +</span></span></span><span class="line"><span class="cl"><span class="sd"> another: yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;TAG&gt;</code>: The Git tag to create.</li> +<li><code>&lt;RELEASE&gt;</code>: The release name to use.</li> +</ul>Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/Mon, 21 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/<p>The <a href="https://github.com/peaceiris/actions-gh-pages">peaceiris/actions-gh-pages</a> action allows to publish a <a href="https://gohugo.io/">Hugo</a> site in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Website</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-gh-pages@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">github_token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">publish_dir</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PUBLISH_DIR&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">force_orphan</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">cname</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;CNAME&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;PUBLISH_DIR&gt;</code>: The file system location of the built site.</li> +<li><code>&lt;CNAME&gt;</code>: The <code>CNAME</code> of your custom domain.</li> +</ul>Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/Mon, 07 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions/cache</a> action allows to cache artifacts in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Maven artifacts</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">~/.m2/repository</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-maven-${{ hashFiles(&#39;**/pom.xml&#39;) }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> ${{ runner.os }}-maven-</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/Mon, 24 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions-hugo</a> action allows to use a specific <a href="https://gohugo.io/">Hugo</a> version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup hugo</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-hugo@v2</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hugo-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;HUGO_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;HUGO_VERSION&gt;</code>: The <a href="https://github.com/gohugoio/hugo/releases">released versions</a> or use <code>latest</code> to always use the latest version of Hugo.</li> +</ul>Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/Mon, 10 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/<p>The <a href="https://github.com/actions/setup-java">setup-java</a> action allows to use a specific Java version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up JDK &lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-java@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">java-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;JDK_VERSION&gt;</code>: The required Java version for your project.</li> +</ul>XDG Base Directory Specificationhttps://seb.xn--ho-hia.de/posts/xdg-dot-files/Mon, 27 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/xdg-dot-files/<p>The <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html">XDG Base Directory Specification</a> has been around for a while, yet not every software has adopted it yet. Here is an incomplete list of fixes:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># use existing env variables or define new</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CACHE_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CACHE_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.cache&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_DIRS</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CONFIG_DIRS</span><span class="o">=</span><span class="s2">&#34;/etc/xdg&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CONFIG_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_DATA_DIRS</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_DATA_DIRS</span><span class="o">=</span><span class="s2">&#34;/usr/local/share:/usr/share&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_DATA_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/share&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># gradle</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">GRADLE_USER_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">/gradle&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># httpie</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">HTTPIE_CONFIG_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">/httpie&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># npm</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">NPM_CONFIG_USERCONFIG</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">/npm/npmrc&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">npm_config_cache</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CACHE_HOME</span><span class="s2">/npm&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># password-store</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PASSWORD_STORE_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">/password-store&#34;</span> +</span></span></code></pre></div><p>To make your own software XDG-aware, consider using the <a href="https://github.com/dirs-dev">dirs-dev</a> or <a href="https://github.com/shibukawa/configdir">configdir</a> libraries.</p>GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/gitlab-distributor/<p><a href="https://git-scm.com/">Git</a> at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. <a href="https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows">Pro Git</a> rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.</p> +<p>While the central server approach is easy to use, it might not work in all scenarios:</p>Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/Mon, 29 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-help/<p>Use the following <a href="https://www.perl.org/">Perl</a> snippet to automatically generate help output for your <code>Makefile</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nv">GREEN</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 2<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">WHITE</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 7<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">YELLOW</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 3<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">RESET</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm sgr0<span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nv">HELP_FUN</span> <span class="o">=</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> %help<span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">while</span><span class="o">(</span>&lt;&gt;<span class="o">)</span> <span class="o">{</span> push @<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span><span class="m">2</span> // <span class="s1">&#39;targets&#39;</span><span class="o">}}</span>, <span class="o">[</span><span class="nv">$$</span>1, <span class="nv">$$</span>3<span class="o">]</span> <span class="k">if</span> /^<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">)</span><span class="se">\s</span>*:.*<span class="se">\#\#</span><span class="o">(</span>?:@<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">))</span>?<span class="se">\s</span><span class="o">(</span>.*<span class="o">)</span><span class="nv">$$</span>/ <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;usage: make [target]\n\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>sort keys %help<span class="o">)</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WHITE</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_:</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>@<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span>_<span class="o">}})</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="nv">$$sep</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span> x <span class="o">(</span><span class="m">32</span> - length <span class="nv">$$</span>_-&gt;<span class="o">[</span>0<span class="o">])</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34; </span><span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[0]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="nv">$$</span><span class="s2">sep</span><span class="si">${</span><span class="nv">GREEN</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[1]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;\n&#34;</span><span class="p">;</span> <span class="o">}</span> +</span></span></code></pre></div><p>To use <code>HELP_FUN</code>, add the following <code>help</code> target to the same <code>Makefile</code>:</p>Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/Mon, 15 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/short-git-clones/<p>In case you don&rsquo;t want to write <code>git clone git@github.com:orga/repo.git</code> all the time, consider using a custom SSH configuration (<code>~/.ssh/config</code>) like this:</p> +<pre tabindex="0"><code>Host github + HostName github.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITHUB&gt; + +Host gitlab + HostName gitlab.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITLAB&gt; + +Host bitbucket + HostName bitbucket.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-BITBUCKET&gt; + +Host codeberg + HostName codeberg.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-CODEBERG&gt; +</code></pre><p>Once configured, you can now write:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git clone github:orga/repo +</span></span><span class="line"><span class="cl">$ git clone gitlab:orga/repo +</span></span><span class="line"><span class="cl">$ git clone bitbucket:orga/repo +</span></span><span class="line"><span class="cl">$ git clone codeberg:orga/repo +</span></span></code></pre></div><p>In case you are working with many repositories inside a single organization, consider adding the following Git configuration (<code>$XDG_CONFIG_HOME/git/config</code> or <code>~/.gitconfig</code>):</p>Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/Thu, 11 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/<p>In case you are into <a href="https://calver.org/">calver</a> or have another reason to create a timestamp with <a href="https://github.com/features/actions">GitHub Actions</a>, do the following:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create release version</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;ID&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=&lt;NAME&gt;::$(date +&#39;%Y.%m.%d-%H%M%S&#39;)&#34;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;ID&gt;</code>: The unique ID of the timestamp step.</li> +<li><code>&lt;NAME&gt;</code>: The name of the created timestamp.</li> +</ul> +<p>The special syntax <code>::set-output name=&lt;NAME&gt;::</code> declares that the output of the command (<code>echo</code>) should be saved in a variable called <code>&lt;NAME&gt;</code>. Together with the <code>&lt;ID&gt;</code> of the pipeline step, this value can be referenced with the expression <code>${{ steps.&lt;ID&gt;.outputs.&lt;NAME&gt; }}</code> in the following steps of your pipeline.</p> \ No newline at end of file diff --git a/posts/atom.xml b/posts/atom.xml new file mode 100644 index 000000000..6bc0e18a1 --- /dev/null +++ b/posts/atom.xml @@ -0,0 +1,1330 @@ +HugoPosts on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/posts/jspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/2023-01-09T00:00:00+00:002023-01-09T07:05:35+01:00Every Java developer has probably encountered a NullPointerException at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:

+
String someName;         // value is 'null'
+
+someName.toUpperCase(); // throws NullPointerException
+

Modern IDEs have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is null and therefore a potential for a NullPointerException is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be null. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.

+

jspecify is the latest approach that tries to establish a standard. It has gained wide community support and recently celebrated their first public release (0.3.0).

+

The following snippet shows the dependency declaration for Maven projects:

+
<dependencies>
+    <dependency>
+        <groupId>org.jspecify</groupId>
+        <artifactId>jspecify</artifactId>
+        <version>0.3.0</version>
+    </dependency>
+</dependencies>
+

In case you want to declare that nothing in your module can ever be null, place the @NullMarked on your module-info.java like this:

+
@org.jspecify.annotations.NullMarked
+module your.module.here {
+
+    requires org.jspecify;
+
+    // ...
+
+}
+

The tooling support is not quiet clear yet, however if you are developing a library there is no harm in adding these annotations now and let your users enjoy their null-free life once tools have caught up.

+]]>
Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/2023-01-08T00:00:00+00:002023-01-08T09:25:44+01:00age is another tool supported by chezmoi to keep data private. Compared to gpg it is much simpler by focusing on the encryption parts only.

+

Add the following snippet to your .chezmoi.toml to configure chezmoi to use age:

+
encryption = "age"
+[age]
+  identity = "path/to/age/private-key"
+  recipient = "age...public...key..."
+

Adding files to your chezmoi source directory remains the same as compared to using gpg - just call chezmoi add --encrypt path/to/file.

+]]>
Multiple Git Configurationshttps://seb.xn--ho-hia.de/posts/multiple-git-configs/2023-01-05T00:00:00+00:002023-01-06T17:32:06+01:00To split yet re-use as much configuration for Git as possible, you can create one root configuration that looks similar to this:

+
[user]
+  name = Your Name Here
+
+[includeIf "gitdir:~/git/personal/"]
+  path = ~/.config/git/personal
+[includeIf "gitdir:~/git/work/"]
+  path = ~/.config/git/work
+

The includeIf directive supports multiple keywords. In my case, work and personal projects have a different root directory, therefore I can filter based on the location using gitdir. The personal Git configuration simply looks like this:

+
[user]
+  email = personal.email@example.com
+

and the work related configuration like this using a different email address:

+
[user]
+  email = first.last@work.example
+

Additional settings that are different for personal/work accounts can be split the same way, for example to use a different signing key for work.

+]]>
passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/2022-12-27T00:00:00+00:002023-01-06T16:40:24+01:00To fuzzy search through passwords managed with passage, I’ve written the following script that is inspired by the upstream version which is using fzf.

+
fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

This version requires fd, skim, xargs, and passage itself of course. The detailed breakdown on how it works is as follows:

+
    +
  1. Use fd to find all files within ${PASSAGE_DIR} that end in .age. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.
  2. +
  3. Using both --base-directory and --exec echo '{.}' ensures that passwords are returned in such form that they can be passed back into passage again. The placeholder '{.}' is a feature provided by fd which strips the file extension from each returned value.
  4. +
  5. All passwords are then passed into sk to allow to fuzzy search across them all. Setting --no-multi ensures that only a single password can be selected.
  6. +
  7. Finally, xargs calls passage and replaces the curly braces with the selected password. Thanks to --clip=1, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.
  8. +
+

To call that script, I’ve saved it as passage-fuzzy-search.sh in my .local/bin folder and added some checks into it to verify that every required software is actually installed.

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This shell script presents passwords saved with passage through skim
+#
+# Call it like this:
+#   passage-fuzzy-search.sh
+#
+# Required software that isn't in GNU coreutils:
+#   - 'passage' to read passwords
+#   - 'fd' to find passwords
+#   - 'sk' to filter passwords
+###############################################################################
+
+if ! (( ${+commands[passage]} )); then
+    echo 'passage not installed. Please install passage.'
+    exit
+fi
+if ! (( ${+commands[sk]} )); then
+    echo 'sk not installed. Please install skim.'
+    exit
+fi
+if ! (( ${+commands[fd]} )); then
+    echo 'fd not installed. Please install fd-find.'
+    exit
+fi
+
+fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

Since typing passage-fuzzy-search.sh is way too long, I have added an alias like this:

+
alias pp='passage-fuzzy-search.sh'
+
]]>
chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/2022-12-26T00:00:00+00:002023-01-06T17:32:06+01:00To automatically synchronize dotfiles across my computers, I’ve written the following systemd unit:

+
[Unit]
+Description=Update chezmoi managed dotfiles
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/chezmoi update --no-tty --force
+RemainAfterExit=false
+
+[Install]
+WantedBy=default.target
+

This unit pulls changes from upstream first and then applies the changes to the current computer after I’m logged in and a network connection is available. The --no-tty flag is required because there is no tty when systemd executes chezmoi. Likewise, the --force flag ensures that no interactive prompt will be displayed which we cannot answer since systemd is executing this unit without us being involved.

+]]>
chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/2022-12-12T00:00:00+00:002023-01-06T16:40:24+01:00Many CLI applications offer initialization scripts to integrate into a shell, for example starship init zsh or zoxide init zsh. The documentation of these tools usually tell you to put something like eval "$(starship init zsh)" into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the init command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.

+

chezmoi provides a template function called output which replaces itself with the output of the command you specified. You can use that function this to integrate various tools into your shell as the following example shows while using zsh:

+
    +
  1. Create a directory that holds all init scripts for every tool you want to use. +
     $ mkdir --parents "${ZDOTDIR}"/tools.d
    +
  2. +
  3. Let your shell load all available scripts in that directory. This snippet should be part of your .zshrc file: +
    for init_script in "${ZDOTDIR}"/tools.d/*.sh; do
    +  source "${init_script}"
    +done
    +
  4. +
  5. Create chezmoi .tmpl files for each tool and place them in the chezmoi source directory that matches the directory you created in step 1: +
    {{ output "starship" "init" "zsh" "--print-full-init" }}
    +
  6. +
  7. Call chezmoi apply to generate the init scripts.
  8. +
+

The only downside here is that you have to re-run chezmoi apply after updating one of the tools because they change their init scripts sometimes. That problem can be solved with chezmoi auto-updates.

+]]>
awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/2022-02-14T00:00:00+00:002023-01-06T16:40:24+01:00To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the AWS_PROFILE environment variable which is used by many tools that interact with the AWS API, like awscli or terraform. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses aws sso login to perform an MFA login into AWS. The AWS profiles must be set up in such a way that aws configure list-profiles can detect them, which is typically done by adding them in ${AWS_CONFIG_FILE:-$HOME/.aws/config}.

+
#!/usr/bin/env sh
+
+###############################################################################
+# This script performs an AWS SSO login for the user-selected AWS profile
+# and sets the AWS_PROFILE environment variable afterwards. To use
+# this, create an alias that sources this script like this:
+#
+#     alias awsenv='source path/to/this/script.sh'
+#
+# Required software that is not in GNU coreutils:
+#   - 'aws' to list profiles & get current caller identity
+#   - 'fzf' to list all available AWS profiles
+###############################################################################
+
+# prompt user to select one AWS profile
+profile=$(aws configure list-profiles | \
+  fzf --cycle --layout=reverse --tiebreak=index)
+
+# user can cancel switching profiles by pressing ESC
+if [ -n "${profile}" ]; then
+  # check is access token exists and is valid for selected profile
+  if ! aws --profile "${profile}" sts get-caller-identity >/dev/null 2>&1; then
+    # perform login into profile in case access token is invalid
+    if ! aws sso login --profile "${profile}"; then
+      # short circuit in case login failed
+      return
+    fi
+  fi
+  # AWS_PROFILE is used by many AWS-related tools
+  echo "Setting AWS_PROFILE to [${profile}]"
+  export AWS_PROFILE="${profile}"
+  # do not expose internal variables
+  unset profile
+fi
+
]]>
Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/2022-01-29T00:00:00+00:002023-01-06T17:32:06+01:00Clojure has several forms and macros to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.

+

Using gen-class

+

Clojure code can be compiled to standard JVM bytecode using gen-class.

+

Adding static modifiers

+

Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class com.example.Computation.

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :methods [#^{:static true} [incrementRange [int] java.util.List]]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [max]
+  (increment-range max))
+

The Java wrapper has to follow the standard rules for method names. Therefore increment-range has to be renamed to incrementRange (or some similar name without the “-” in it). The “-” prefix for the Java wrapper can be configured inside the :gen-class form and will be removed once gen-class runs. The usage from Java looks like this:

+
package com.example
+
+public class ClojureJavaInteropStatic {
+
+    public static void main(String[] args) {
+        List incrementedRange = Computation.incrementRange(10);
+    }
+
+}
+

Adding generics

+

The returned list in the above code is raw because the method definition doesn’t use generics. To solve this problem declare that the generated class :implements a certain interface that exposes the desired method definition(s). You won’t be able to declare your methods as static anymore, but get a generified method for all your Java needs.

+

The Java interface:

+
package com.example
+
+public interface RangeIncrementer {
+  List<Long> incrementRange(int max);
+}
+

The changed Clojure namespace:

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :implements [com.example.RangeIncrementer]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [this max]
+  (increment-range max))
+

Finally, the generified usage from Java:

+
package com.example
+
+public class ClojureJavaInteropGenerics {
+
+    public static void main(String[] args) {
+        RangeIncrementer incrementer = new Computation();
+        List<Long> incrementedRange = incrementer.incrementRange(10);
+    }
+
+}
+

Couple of notes for this as well: First the generated class still only returns the raw type (List instead of List<Integer>). So instead of using the class, use the interface for the variable declaration (RangeIncrementer incrementer = .. instead of Computation comp = ..). The interface will return the non-raw List. Second the function definition for -incrementRange is now slightly different. It needs an additional parameter (this) which exposes the current instance to the generated class/method.

+

Returning an array of something is also possible with the following construct "[Ljava.lang.Object;". Need a 2-dim array? Just use "[[Ljava.lang.Object;" (notice the extra [) and so on. However, be aware that the method return types have to match, for example you can’t specify a return type of array if your Clojure function does not return an array. In the example above the call to map returns LazySeq which itself is a java.util.List. Therefore, the method declaration is valid, and you won’t get any ClassCastException when calling incrementRange from Java.

+

Make your life easier with macros

+

Instead of defining every Clojure function which should be exported twice (the real function + the Java wrapper), it is possible to use a macro to do that extra work automatically.

+
(require '[clojure.string :as string)
+
+(defn camel-case [input]
+  (let [words (string/split input #"[\s_-]+")]
+    (string/join (cons (string/lower-case (first words)) (map string/capitalize (rest words))))))
+
+(defn java-name [clojure-name]
+  (symbol (str "-" (camel-case (str clojure-name)))))
+
+(defmacro defn* [name & declarations]
+  (let [java-name (java-name name)]
+    `(do (defn ~name ~declarations)
+       (defn ~java-name ~declarations))))
+

The macro defn* replaces defn and automatically creates a second function with a valid camel-cased Java method name. The macro is available as a small library at Maven Central. The macro won’t add the extra parameter mentioned above to Java wrapper, so it is only useful for declaring static methods.

+

Using the Clojure Runtime

+

Using gen-class imposes certain limitations on calling Clojure code from Java. One of those are functions which make use of Clojure parameter destructuring. To invoke those functions you have to use the Clojure runtime.

+
// The Clojure 'require' function from the 'clojure.core' namespace.
+Var require = RT.var("clojure.core", "require");
+
+// Your namespace
+Symbol namespace = Symbol.intern("DESIRED.NAMESPACE.HERE");
+
+// Your function
+Var function = RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION");
+
+// The required keyword for the above function
+Keyword keyword = Keyword.intern("REQUIRED-KEYWORD");
+
+// Require/Import your namespace
+require.invoke(namespace);
+
+// Invoke your function with the given keyword and its value
+Object result = function.invoke(keyword, VALUE);
+

The desired namespace has to be on the classpath for this to work. Alternatively it is possible to load an entire Clojure script, as shown in the following example:

+
RT.loadResourceScript("DESIRED/NAMESPACE/HERE.clj");
+RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION").invoke(PARAMETER);
+

On a big project it is properly wise to move Java->Clojure interop code into helper classes/methods. Look here for an example.

+]]>
Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/2022-01-15T00:00:00+00:002023-01-06T16:22:24+01:00One feature that often surprises people while teaching them React is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.

+

In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.

+
const RendersNothing = () => <></>
+

Now consider the following example, that adds some if-then-else logic to the same component:

+
const MightRenderSomething = () => {
+  if (someCondition) {
+    return <span>hello world!</span>
+  }
+  return <></>
+}
+

This component encapsulates the if-then-else logic of conditionally rendering a hello world message. Instead of cluttering your entire app with the same logic, you can now simply re-use that same component that contains this if condition. To see the full power of this technique, consider the following example. At first, we are going to define a hook that reads the current window width, then define components that conditionally render based on the current window width, and finally use those components in an example application.

+
const useWindowWidth = () => {
+  const [width, setWidth] = React.useState(0)
+
+  React.useEffect(() => {
+    const handleResize = () => {
+      setWidth(window.innerWidth)
+    }
+    window.addEventListener("resize", handleResize)
+    return () => {
+      window.removeEventListener("resize", handleResize)
+    }
+  }, [])
+
+  return width
+}
+

The following components use that hook to implement UI breakpoints for small (mobile) and large (desktop) screens. Note that the value 768 is just an example - replace it with whatever your design system tells you to.

+
const ForMobileDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+  
+  if (windowWidth < 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+
+const ForDesktopDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+
+  if (windowWidth >= 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+

Both of these components simply render nothing when the window width does not have an appropriate size. If the window width does have the right size, they render their children. We can use those components in our application like this:

+
const SomeActualComponent = () => (
+    <div>
+      <h1>common headline</h1>
+      <ForMobileDevicesOnly>
+        <span>only visible on mobile devices</span>
+      </ForMobileDevicesOnly>
+      <ForDesktopDevicesOnly>
+        <span>only visible on desktop devices</span>
+      </ForDesktopDevicesOnly>
+    </div>
+)
+

The above code snippet declares that some part of the UI can only be seen by mobile users, while others can only be seen by desktop users. Parts of the UI that are shared amongst all users are not wrapped by any of the components defined above.

+]]>
Proper hostnames in your local networkhttps://seb.xn--ho-hia.de/posts/home-network-hostnames/2022-01-08T00:00:00+00:002023-01-06T16:22:24+01:00Thanks to RFC 8375, we now have a proper domain to use for all our local devices. Simply move everything underneath .home.arpa to join the fun. In case you have hostnamectl available on your system run the following command to change the hostname of a device:

+
# set hostname
+$ hostnamectl hostname some-device.home.arpa
+
+# check hostname
+$ hostnamectl status
+
]]>
Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/2022-01-01T00:00:00+00:002023-01-06T16:22:24+01:00Both Vim and Neovim have a built-in plugin mechanism that loads plugins from ~/.vim/pack/*/{start,opt}/* (Vim) or ~/.local/share/nvim/site/pack/*/{start,opt}/* (Neovim). All you have to do to install new plugins, is to git clone their repository into those directories. To automatically update those clones, create the following script:

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This script git-pulls all installed nvim plugins which are using the built-in
+# nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack
+#
+# Required software that is not in GNU coreutils:
+#   - 'git' to fetch plugin updates from upstream
+###############################################################################
+
+### User specific variables, adjust to your own needs
+
+# folder that contains all nvim plugins
+PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack"
+
+### Script logic
+
+echo "updating all plugins in ${PLUGIN_DIR}"
+# iterate through all directories and git pull em
+for directory in "${PLUGIN_DIR}"/*/{start,opt}/*; do
+    if [ -d "${directory}" ]; then
+        plugin=$(basename "${directory}")
+        echo "updating ${plugin}"
+        git -C "${directory}" pull --quiet
+    fi
+done
+

In case you are using Vim, adjust the PLUGIN_DIR variable to point to your Vim plugin directory instead and save the resulting shell script as a file called update-nvim-plugins.sh in some folder of your choice. Do not forget to set the executable bit with chmod +x /path/to/your/folder/update-nvim-plugins.sh. Since all good developers must be lazy, write the following systemd service to execute the above script automatically:

+
[Unit]
+Description=cron job that triggers an update of all nvim plugins
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/path/to/your/folder/update-nvim-plugins.sh
+RemainAfterExit=false
+

Adjust the ExecStart line to match the location where you saved the above script and place that service definition in a file called nvim-plugins-update.service into your local ~/.config/systemd/user directory. Add another file called nvim-plugins-update.timer next to it that defines a systemd timer with the following content:

+
[Unit]
+Description=Update nvim plugins every week
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+RandomizedDelaySec=5hours
+
+[Install]
+WantedBy=timers.target
+

Adjust how often you want to update the plugins you are using in the OnCalendar line. Enable this service/timer with:

+
$ systemctl --user enable nvim-plugins-update
+

Finally, add the following shell aliases to make it easier to interact with the created systemd units:

+
# trigger an update manually
+alias update-nvim-plugins='systemctl --user start nvim-plugins-update'
+# see status of last auto-update
+alias update-nvim-plugins-status='systemctl --user status nvim-plugins-update'
+# see logs of past auto-updates
+alias update-nvim-plugins-logs='journalctl --user --unit nvim-plugins-update'
+

With this setup in place, all your plugins will be automatically updated once per week or however often you have configured in the timer.

+]]>
Manage tmux sessions with tmuxphttps://seb.xn--ho-hia.de/posts/tmux-tmuxp/2021-12-20T00:00:00+00:002023-01-06T16:22:24+01:00To manage tmux sessions, I like to use tmuxp. It works by having pre-defined sessions in ~/.config/tmuxp which looks like this:

+
session_name: cool-app
+start_directory: ~/projects/cool-app
+windows:
+- window_name: backend
+  start_directory: backend
+- window_name: frontend
+  start_directory: frontend
+

In case the name of the file is cool-app.yaml, you can open the sessions with tmuxp load cool-app --yes.

+]]>
Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/2021-12-06T00:00:00+00:002023-01-06T16:46:32+01:00To automatically version Maven projects, I like to use the m-versions-p like this:

+
$ mvn versions:set -DnewVersion=my.new.version -DgenerateBackupPoms=false
+

This will update the version property of every module in the reactor to prepare them for the next release. In case you are using GitHub Actions, consider using a timestamp.

+]]>
GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/2021-11-22T00:00:00+00:002023-01-06T15:27:21+01:00GitHub Packages can be used to host Maven packages with the following configuration in your ~/.m2/settings.xml:

+
<settings>
+  <profiles>
+    <profile>
+      <id>github</id>
+      <repositories>
+        <repository>
+          <id>maven-build-process</id>
+          <name>GitHub maven-build-process Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/maven-build-process</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+        <repository>
+          <id>hcf4j</id>
+          <name>GitHub hcf4j Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/hcf4j</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+    </profile>
+  </profiles>
+  <servers>
+    <server>
+      <id>maven-build-process</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+    <server>
+      <id>hcf4j</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+  </servers>
+</settings>
+

You will have to add another repository/server for each project you are fetching from GitHub.

+]]>
Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/2021-11-08T00:00:00+00:002023-01-06T15:27:21+01:00Some time ago, Google started hosting a copy of Maven Central. Configure it in your ~/.m2/settings.xml like this:

+
<settings>
+  <mirrors>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (Asia)</name>
+      <url>https://maven-central-asia.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (EU)</name>
+      <url>https://maven-central-eu.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (US)</name>
+      <url>https://maven-central.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+  </mirrors>
+</settings>
+

Pick the mirror nearest to your location to get best speeds.

+]]>
Backups with emacshttps://seb.xn--ho-hia.de/posts/emacs-backups/2021-10-25T00:00:00+00:002023-01-06T15:27:21+01:00emacs will create backups of your files by default. Those backups are located right next to the original file and are called <file>~. Unfortunately, emacs will not clean those up by default, which annoys me from time to time. Therefore, I’m now using the following configuration to keep those backups in a different folder:

+
(setq version-control t     ;; Use version numbers for backups.
+      kept-new-versions 10  ;; Number of newest versions to keep.
+      kept-old-versions 0   ;; Number of oldest versions to keep.
+      delete-old-versions t ;; Don't ask to delete excess backup versions.
+      backup-by-copying t)  ;; Copy all files, don't rename them.
+
+(setq vc-make-backup-files t)
+
+;; Default and per-save backups go here:
+(setq backup-directory-alist '(("" . "~/.emacs.d/backup/per-save")))
+
+(defun force-backup-of-buffer ()
+  ;; Make a special "per session" backup at the first save of each
+  ;; emacs session.
+  (when (not buffer-backed-up)
+    ;; Override the default parameters for per-session backups.
+    (let ((backup-directory-alist '(("" . "~/.emacs.d/backup/per-session")))
+          (kept-new-versions 3))
+      (backup-buffer)))
+  ;; Make a "per save" backup on each save.  The first save results in
+  ;; both a per-session and a per-save backup, to keep the numbering
+  ;; of per-save backups consistent.
+  (let ((buffer-backed-up nil))
+    (backup-buffer)))
+
+(add-hook 'before-save-hook  'force-backup-of-buffer)
+

Thanks to that configuration, backups per-save will be created in ~/.emacs.d/backup/per-save and backups per-session in ~/.emacs.d/backup/per-session.

+]]>
Maintaining dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-maintenance/2021-10-11T00:00:00+00:002023-01-06T17:32:06+01:00To make it easier managing many dotfiles with chezmoi, a shell function similar to the one below can be used:

+
function m-dotfiles-ok {
+    # public
+    chezmoi add ~/.config/zsh --recursive
+    chezmoi add ~/.config/sway --recursive
+    chezmoi add ~/.config/tmux --recursive
+    chezmoi add ....
+
+    # secrets
+    chezmoi add --encrypt ~/.config/npm/npmrc
+    chezmoi add --encrypt ~/.ssh/id_rsa
+    chezmoi add --encrypt ...
+}
+

Whenever you feel happy with your current setup, just call m-dotfiles-ok to push changes into the chezmoi source directory. Files will automatically be encrypted with gpg and committed/pushed into a Git repository if you have done the necessary configuration beforehand.

+

In general, editing your dotfiles directly as explained in the second option of the FAQ seems easier though. Refactoring your dotfiles is especially easy when the exact_ prefix is used for directories. As explained in the documentation, all files that are not managed by chezmoi will be removed, therefore your configuration will always match what is in your source directory.

+]]>
Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/2021-09-20T00:00:00+00:002023-01-08T09:25:44+01:00RECOMMENDATION: Use age instead of gpg.

+

chezmoi can use various external tools to keep data private. gpg is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure gpg with chezmoi, just set yourself as the recipient like this:

+
[gpg]
+  recipient = "your.name@example.com"
+

Calling chezmoi add --encrypt /path/to/secret will now create encrypt the file with your public key which allows you to decrypt them later with your private key.

+]]>
Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/2021-09-06T00:00:00+00:002023-01-06T17:32:06+01:00chezmoi can automatically commit and push changes to your dotfiles into a (remote) Git repository. Enable it with the following snippet in your chezmoi.toml

+
[sourceVCS]
+    autoCommit = true
+    autoPush = true
+

Every time you call chezmoi add /path/to/file will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.

+]]>
Waybar on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-waybar/2021-08-23T00:00:00+00:002023-01-06T16:40:24+01:00Waybar can be used as a status bar for SwayWM. You tell Sway to use it with the following snippet in your Sway configuration:

+
bar {
+    swaybar_command waybar
+}
+

Configure Waybar itself in ~/.config/waybar/config:

+
{
+    "layer": "top",
+    "modules-left": ["sway/workspaces", "sway/mode"],
+    "modules-center": ["sway/window"],
+    "modules-right": ["clock"],
+    "sway/window": {
+        "max-length": 50
+    },
+    "clock": {
+        "format-alt": "{:%a, %d. %b  %H:%M}"
+    }
+}
+
]]>
tmux Status Barhttps://seb.xn--ho-hia.de/posts/tmux-status-bar/2021-08-09T00:00:00+00:002023-01-06T16:22:24+01:00To see the currently active tmux status bar configuration, call:

+
$ tmux show-options -g | grep status
+

Change on of those values with in the current tmux session:

+
$ tmux set-option status-right ""
+

Persist the change in your tmux.conf like this:

+
# disable right side of status bar
+set-option -g status-right ""
+
]]>
emacs and systemdhttps://seb.xn--ho-hia.de/posts/emacs-systemd/2021-07-26T00:00:00+00:002023-01-06T16:40:24+01:00I like to use emacs to edit files in a terminal. It tends to start a little slow, therefore I’ve created a systemd unit to automatically start the emacs daemon and use aliases to connect to the running daemon. The unit looks like this:

+
[Unit]
+Description=Emacs text editor [%I]
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+
+[Service]
+Type=forking
+ExecStart=/usr/bin/emacs --daemon=%i
+ExecStop=/usr/bin/emacsclient --eval "(kill-emacs)"
+Environment=SSH_AUTH_SOCK=%t/keyring/ssh
+Restart=on-failure
+
+[Install]
+WantedBy=default.target
+

Enable it with systemctl --user enable emacs@user and define any number of aliases to make connecting to the emacs daemon easier:

+
alias e='emacsclient --tty --socket-name=user'
+alias vim='emacsclient --tty --socket-name=user'
+alias vi='emacsclient --tty --socket-name=user'
+alias nano='emacsclient --tty --socket-name=user'
+alias ed='emacsclient --tty --socket-name=user'
+
]]>
Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/2021-07-12T00:00:00+00:002023-01-06T17:32:06+01:00In case you want to have push-only mirrors for your Git repository, consider adding a special mirror remote like this:

+
$ git remote add mirrors DISABLED
+$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git
+$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git
+$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git
+

The above will create a new remote called mirrors which has no fetch URL and therefore can only be pushed:

+
$ git remote -v
+mirrors DISABLED (fetch)
+mirrors git@codeberg.org:org/repo.git (push)
+mirrors git@gitlab.com:org/repo.git (push)
+mirrors git@bitbucket.org:org/repo.git (push)
+

Calling git push mirrors main:main will push the local main branch into all defined mirrors.

+ + +]]>
Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/2021-06-28T00:00:00+00:002023-01-06T16:46:32+01:00In case you want to make use of the decentralized nature of Git, consider using multiple push targets like this:

+
$ git remote set-url origin --push --add git@example.com/project.git
+$ git remote set-url origin --push --add git@another.com/project.git
+

Note that the first call to set-url will overwrite an existing remote creating with git clone. Any additional call will actually recognize the --add option and add the new target to an existing remote.

+ + +]]>
Using multiple clusters with kubectlhttps://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/2021-06-14T00:00:00+00:002023-01-06T17:32:06+01:00To connect to multiple Kubernetes clusters with kubectl, I like to define aliases like this:

+
alias rancher="kubectl --kubeconfig ~/.kube/rancher.config"
+alias work="kubectl --kubeconfig ~/.kube/work.config"
+alias customer="kubectl --kubeconfig ~/.kube/customer.config"
+

Those aliases allow me to write things like rancher get pods --namespace some-namespace without worrying the wrong context is active. Using multiple configurations - one for each cluster - seems to be easier to manage since most clusters allow to download a ready-to-use configuration file. Instead of mangling them together manually, I just specify another alias whenever I get to work with another cluster.

+]]>
Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/2021-05-31T00:00:00+00:002023-01-06T17:32:06+01:00Maven projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.

+
<properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+</properties>
+
+ +]]>
Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/2021-05-17T00:00:00+00:002023-01-06T16:22:24+01:00To create reproducible builds with Maven projects, it’s enough to specify the project.build.outputTimestamp property like this:

+
<properties>
+    <project.build.outputTimestamp>2020</project.build.outputTimestamp>
+</properties>
+
+ +]]>
Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/2021-05-03T00:00:00+00:002023-01-06T17:32:06+01:00To analyze Maven projects with SonarCloud using GitHub Actions, first create the following settings.xml file:

+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
+
+    <pluginGroups>
+        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
+    </pluginGroups>
+
+    <activeProfiles>
+        <activeProfile>sonar</activeProfile>
+    </activeProfiles>
+
+    <profiles>
+        <profile>
+            <id>sonar</id>
+            <properties>
+                <sonar.host.url>https://sonarcloud.io</sonar.host.url>
+                <sonar.organization>YOUR_ORG</sonar.organization>
+                <sonar.projectKey>YOUR_PROJECT</sonar.projectKey>
+                <sonar.login>${env.SONAR_TOKEN}</sonar.login>
+            </properties>
+        </profile>
+    </profiles>
+</settings>
+

Finally, add a step to your workflow:

+
- name: Verify Project
+  run: mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar
+  env:
+    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+
+ +]]>
Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/2021-04-19T00:00:00+00:002023-01-06T16:22:24+01:00To use tmux as your login shell, use chsh:

+
# list all available shells
+$ chsh --list-shells
+/bin/sh
+/bin/bash
+/sbin/nologin
+/usr/bin/sh
+/usr/bin/bash
+/usr/sbin/nologin
+/usr/bin/zsh
+/bin/zsh
+/usr/bin/tmux
+/bin/tmux
+
+# select login shell
+$ chsh --shell /usr/bin/tmux
+
]]>
Lock your screen on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenlock/2021-04-05T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM users can use swaylock to lock their screen. Place the following key binding in your Sway configuration:

+
# lock your screen
+bindsym $mod+Ctrl+l exec swaylock --color 000000
+

$mod+Ctrl+l will lock your screen and turn it to black. The --color flag allows any color in the form of rrggbb[aa].

+]]>
Peeking with tmuxhttps://seb.xn--ho-hia.de/posts/tmux-peek/2021-04-05T00:00:00+00:002023-01-06T15:27:21+01:00tmux uses can use the following snippet to peek at files. Place it in your .bashrc or similar file.

+
peek() { tmux split-window -p 33 "$EDITOR" "$@" }
+

Calling peek <file> will open <file> in lower third of tmux window.

+]]>
Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/2021-03-22T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM uses can use a mixture of grim and slurp to take screenshots of their desktop. Place the following key binding in your Sway configuration:

+
# take screenshot of currently focused screen
+bindsym $mod+Print exec /usr/bin/grim -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
+# take screenshot of selection
+bindsym $mod+Shift+p exec /usr/bin/grim -g "$(/usr/bin/slurp)" $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
]]>
Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/2021-03-08T00:00:00+00:002023-01-06T16:40:24+01:00README file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a Makefile in the root of your project which contains the exact same instructions. Thanks to make, all your contributors can now use TAB-completion to run any of the pre-defined make targets.

+

The following example is part of one of my projects, and I certainly don’t want to type (or even copy) that all the time:

+
.PHONY: release-into-local-nexus
+release-into-local-nexus:
+	mvn versions:set \
+	   -DnewVersion=$(TIMESTAMPED_VERSION) \
+	   -DgenerateBackupPoms=false
+	-mvn clean deploy scm:tag \
+	   -DpushChanges=false \
+	   -DskipLocalStaging=true \
+	   -Drelease=local
+	mvn versions:set \
+	   -DnewVersion=9999.99.99-SNAPSHOT \
+	   -DgenerateBackupPoms=false
+

With the above target in place, everyone can now do make release-into-local-nexus instead of typing/copying the commands themselves. Thanks to TAB-completion you just have to do make r<TAB> and confirm with >ENTER> to perform a release.

+ + +]]>
Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/2021-02-22T00:00:00+00:002023-01-06T16:40:24+01:00To delay the execution of an GitHub Action, use a mixture of the on: schedule: ... configuration, and a conditional build step.

+
name: <PIPELINE>
+on:
+  schedule:
+    - cron: '<CRON>'
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Count commits in last week
+        id: commits
+        run: echo "::set-output name=count::$(git rev-list --count HEAD --since='<DATE>')"
+      - name: Build project
+        if: steps.commits.outputs.count > 0
+        run: build-project
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <CRON>: cron expression - use https://crontab.guru/.
  • +
  • <DATE>: Git date expression that matches <CRON>.
  • +
+]]>
Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/2021-02-08T00:00:00+00:002023-01-06T16:40:24+01:00In case you are using a Makefile to define a complex build step - for example start database, run tests, stop database - consider using the - qualifier in front of your actual build step like this:

+
.PHONY: build
+build:
+	start-database
+	-build-software
+	stop-database
+

Thanks to -, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.

+ + +]]>
Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/2021-01-25T00:00:00+00:002023-01-06T16:40:24+01:00To use a serviceworker to cache a Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/javascript"]
+  suffixes = ["js"]
+[outputFormats.ServiceWorker]
+  name = "ServiceWorker"
+  mediaType = "application/javascript"
+  baseName = "serviceworker"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.serviceworker.js with the following content:

+
const CACHE = 'cache-and-update';
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(precache());
+});
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(fromCache(event.request));
+  event.waitUntil(update(event.request));
+});
+
+const precache = async () => {
+    const cache = await caches.open(CACHE);
+    return await cache.addAll([
+        {{ range $i, $e := .Site.RegularPages }}
+        '{{ $.RelPermalink }}'{{ if $i }}, {{ end }}
+        {{ end }}
+    ]);
+}
+
+const fromCache = async (request) => {
+    const cache = await caches.open(CACHE);
+    const match = await cache.match(request);
+    return match || Promise.reject('no-match');
+}
+
+const update = async (request) => {
+    const cache = await caches.open(CACHE);
+    const response = await fetch(request);
+    return await cache.put(request, response);
+}
+
+ +]]>
Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/2021-01-11T00:00:00+00:002023-01-06T16:40:24+01:00To publish a web app manifest document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/manifest+json"]
+  suffixes = ["webmanifest"]
+[outputFormats.Webmanifest]
+  name = "Web App Manifest"
+  mediaType = "application/manifest+json"
+  baseName = "manifest"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.manifest.json with the following content:

+
{
+  "name": "{{ .Site.Title }}",
+  "short_name": "{{ .Site.Title }}",
+  "start_url": ".",
+  "display": "minimal-ui",
+  "background_color": "#fff",
+  "description": "{{ .Site.Params.description }}"
+}
+
+ +]]>
Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/2020-12-28T00:00:00+00:002023-01-06T15:27:21+01:00Hugo allows bundling of assets with several built-in functions:

+
{{ $normalize := resources.Get "/css/normalize.css" }}
+{{ $font := resources.Get "/css/font.css" }}
+{{ $header := resources.Get "/css/header.css" }}
+{{ $footer := resources.Get "/css/footer.css" }}
+{{ $navigation := resources.Get "/css/navigation.css" }}
+{{ $navigation_mobile := resources.Get "/css/navigation-mobile.css" }}
+{{ $layout := resources.Get "/css/layout.css" }}
+{{ $layout_mobile := resources.Get "/css/layout-mobile.css" }}
+{{ $syntax := resources.Get "/css/syntax.css" }}
+{{ $darkmode := resources.Get "/css/darkmode.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat "css/base.css" | resources.Minify | resources.Fingerprint "sha512" }}
+{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat "css/mobile.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+<link href="{{ $base.Permalink }}" integrity="{{ $base.Data.Integrity }}" media="screen" rel="stylesheet">
+<link href="{{ $mobile.Permalink }}" integrity="{{ $mobile.Data.Integrity }}" media="screen and (max-width: 800px)" rel="stylesheet">
+
+<link href="{{ $darkmode.Permalink }}" integrity="{{ $darkmode.Data.Integrity }}" media="screen and (prefers-color-scheme: dark)" rel="stylesheet">
+
]]>
humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/2020-12-14T00:00:00+00:002023-01-06T16:40:24+01:00To publish a humans.txt document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."text/plain"]
+  suffixes = ["txt"]
+[outputFormats.Humans]
+  name = "Humans"
+  mediaType = "text/plain"
+  baseName = "humans"
+  isPlainText = true
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.humans.txt with the following content:

+
/* TEAM */
+{{ range $.Site.Data.contributors }}
+{{ .title }}: {{ .first_name }} {{ .last_name }}
+Site: {{ .website }}
+{{ end }}
+
]]>
FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/2020-11-30T00:00:00+00:002023-01-06T16:40:24+01:00To publish a FOAF document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/rdf+xml"]
+  suffixes = ["rdf"]
+[outputFormats.Foaf]
+  name = "FOAF"
+  mediaType = "application/rdf+xml"
+  baseName = "foaf"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.foaf.rdf with the following content:

+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/">
+  <foaf:PersonalProfileDocument rdf:about="">
+    <foaf:maker rdf:resource="#me" />
+    <foaf:primaryTopic rdf:resource="{{ .Site.Title }}" />
+  </foaf:PersonalProfileDocument>
+
+  <foaf:Project rdf:ID="{{ .Site.Title }}">
+    <foaf:name>{{ .Site.Title }}</foaf:name>
+    <foaf:homepage rdf:resource="{{ .Site.BaseURL }}" />
+  </foaf:Project>
+
+  {{ range $.Site.Data.contributors }}
+  <foaf:Person rdf:ID="{{ .id }}">
+    <foaf:name>{{ .first_name }} {{ .last_name }}</foaf:name>
+    <foaf:title>{{ .title }}</foaf:title>
+    <foaf:givenname>{{ .first_name }}</foaf:givenname>
+    <foaf:family_name>{{ .last_name }}</foaf:family_name>
+    <foaf:mbox rdf:resource="mailto:{{ .email }}" />
+    <foaf:homepage rdf:resource="{{ .website }}" />
+  </foaf:Person>
+  {{ end }}
+</rdf:RDF>
+
]]>
Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/2020-11-16T00:00:00+00:002023-01-06T16:40:24+01:00To publish Atom feeds for your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/atom+xml"]
+  suffixes = ["xml"]
+[outputFormats.Atom]
+  name = "Atom"
+  mediaType = "application/atom+xml"
+  baseName = "atom"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/list.atom.xml with the following content:

+
{{ printf `<?xml version="1.0" encoding="utf-8"?>` | safeHTML }}
+<feed xmlns="http://www.w3.org/2005/Atom"{{ with site.LanguageCode }} xml:lang="{{ . }}"{{ end }}>
+    <generator uri="https://gohugo.io/" version="{{ hugo.Version }}">Hugo</generator>
+    {{- $title := site.Title -}}
+    {{- with .Title -}}
+        {{- if (not (eq . site.Title)) -}}
+            {{- $title = printf `%s %s %s` . (i18n "feed_title_on" | default "on") site.Title -}}
+        {{- end -}}
+    {{- end -}}
+    {{- if .IsTranslated -}}
+        {{ $title = printf "%s (%s)" $title (index site.Data.i18n.languages .Lang) }}
+    {{- end -}}
+    {{ printf `<title type="html"><![CDATA[%s]]></title>` $title | safeHTML }}
+    {{ with (or (.Param "subtitle") (.Param "tagline")) }}
+        {{ printf `<subtitle type="html"><![CDATA[%s]]></subtitle>` . | safeHTML }}
+    {{ end }}
+    {{ $output_formats := .OutputFormats }}
+    {{ range $output_formats -}}
+        {{- $rel := (or (and (eq "atom" (.Name | lower)) "self") "alternate") -}}
+        {{ with $output_formats.Get .Name }}
+            {{ printf `<link href=%q rel=%q type=%q title=%q />` .Permalink $rel .MediaType.Type .Name | safeHTML }}
+        {{- end -}}
+    {{- end }}
+    {{- range .Translations }}
+        {{ $output_formats := .OutputFormats }}
+        {{- $lang := .Lang }}
+        {{- $langstr := index site.Data.i18n.languages .Lang }}
+        {{ range $output_formats -}}
+            {{ with $output_formats.Get .Name }}
+                {{ printf `<link href=%q rel="alternate" type=%q hreflang=%q title="[%s] %s" />` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }}
+            {{- end -}}
+        {{- end }}
+    {{- end }}
+    <updated>{{ now.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+    {{ with site.Copyright }}
+        {{- $copyright := replace . "{year}" now.Year -}} {{/* In case the site.copyright uses a special string "{year}" */}}
+        {{- $copyright = replace $copyright "&copy;" "©" -}}
+        <rights>{{ $copyright | plainify }}</rights>
+    {{- end }}
+    {{ with .Param "feed" }}
+        {{/* For this to work, the $icon file should be present in the assets/ directory */}}
+        {{- $icon := .icon | default "icon.svg" -}}
+        {{- with resources.Get $icon -}}
+            <icon>{{ (. | fingerprint).Permalink }}</icon>
+        {{- end }}
+
+        {{/* For this to work, the $logo file should be present in the assets/ directory */}}
+        {{- $logo := .logo | default "logo.svg" -}}
+        {{- with resources.Get $logo -}}
+            <logo>{{ (. | fingerprint).Permalink }}</logo>
+        {{- end }}
+    {{ end }}
+    {{ with site.Author.name -}}
+        <author>
+            <name>{{ . }}</name>
+            {{ with site.Author.email }}
+                <email>{{ . }}</email>
+            {{ end -}}
+        </author>
+    {{- end }}
+    {{ with site.Params.id }}
+        <id>{{ . | plainify }}</id>
+    {{ else }}
+        <id>{{ .Permalink }}</id>
+    {{ end }}
+    {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }}
+    {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}}
+    {{/* Range through only the pages with a Type in $feed_sections. */}}
+    {{- $pages := where .RegularPages "Type" "in" $feed_sections -}}
+    {{- if (eq .Kind "home") -}}
+        {{- $pages = where site.RegularPages "Type" "in" $feed_sections -}}
+    {{- end -}}
+    {{/* Remove the pages that have the disable_feed parameter set to true. */}}
+    {{- $pages = where $pages ".Params.disable_feed" "!=" true -}}
+    {{- range first $limit $pages }}
+        {{ $page := . }}
+        <entry>
+            {{ printf `<title type="html"><![CDATA[%s]]></title>` .Title | safeHTML }}
+            <link href="{{ .Permalink }}?utm_source=atom_feed" rel="alternate" type="text/html" />
+            {{- range .Translations }}
+                {{- $link := printf "%s?utm_source=atom_feed" .Permalink | safeHTML }}
+                {{- printf `<link href=%q rel="alternate" type="text/html" hreflang=%q />` $link .Lang | safeHTML }}
+            {{- end }}
+            {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}}
+            {{- range first 5 (site.RegularPages.Related .) }}
+                <link href="{{ .Permalink }}?utm_source=atom_feed" rel="related" type="text/html" title="{{ .Title }}" />
+            {{- end }}
+            {{ with .Params.id }}
+                <id>{{ . | plainify }}</id>
+            {{ else }}
+                <id>{{ .Permalink }}</id>
+            {{ end }}
+            {{ with .Params.author -}}
+                {{- range . -}} <!-- Assuming the author front-matter to be a list -->
+                    <author>
+                        <name>{{ . }}</name>
+                    </author>
+                {{- end -}}
+            {{- end }}
+            <published>{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</published>
+            <updated>{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+            {{ $description1 := .Description | default "" }}
+            {{ $description := (cond (eq "" $description1) "" (printf "<blockquote>%s</blockquote>" ($description1 | markdownify))) }}
+            {{ printf `<content type="html"><![CDATA[%s%s]]></content>` $description .Content | safeHTML }}
+            {{ with site.Taxonomies }}
+                {{ range $taxo,$_ := . }} <!-- Defaults taxos: "tags", "categories" -->
+                    {{ with $page.Param $taxo }}
+                        {{ $taxo_list := . }} <!-- $taxo_list will be the tags/categories list -->
+                        {{ with site.GetPage (printf "/%s" $taxo) }}
+                            {{ $taxonomy_page := . }}
+                            {{ range $taxo_list }} <!-- Below, assuming pretty URLs -->
+                                <category scheme="{{ printf "%s%s" $taxonomy_page.Permalink (. | urlize) }}" term="{{ (. | urlize) }}" label="{{ . }}" />
+                            {{ end }}
+                        {{ end }}
+                    {{ end }}
+                {{ end }}
+            {{ end }}
+        </entry>
+    {{ end }}
+</feed>
+
]]>
Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/2020-11-16T00:00:00+00:002023-01-06T15:27:21+01:00The rzr/fediverse-action action allows to send a toot in your GitHub Action.

+
name: <NAME>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Publish Toot
+        uses: rzr/fediverse-action@master
+        with:
+          access-token: ${{ secrets.MASTODON_TOKEN }}
+          message: <MESSAGE>
+          host: ${{ secrets.MASTODON_SERVER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <MESSAGE>: Message for the toot.
  • +
+]]>
Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/2020-11-02T00:00:00+00:002023-01-06T15:27:21+01:00The dawidd6/action-send-mail action allows to send an email in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Send mail
+        uses: dawidd6/action-send-mail@v3
+        with:
+          server_address: ${{ secrets.MAIL_SERVER }}
+          server_port: ${{ secrets.MAIL_PORT }}
+          username: ${{ secrets.MAIL_USERNAME }}
+          password: ${{ secrets.MAIL_PASSWORD }}
+          subject: <SUBJECT>
+          body: <BODY>
+          to: ${{ secrets.MAIL_RECIPIENT }}
+          from: ${{ secrets.MAIL_SENDER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <SUBJECT>: Subject for the email.
  • +
  • <BODY>: Body for the email.
  • +
+

Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define MAIL_RECIPIENT for each project.

+]]>
Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/2020-10-19T00:00:00+00:002023-01-06T15:27:21+01:00The actions/upload-release-asset action allows to upload a release artifact in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Upload Release Asset
+        id: upload_release_asset
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./some/path/to/file.zip
+          asset_name: public-name-for-file.zip
+          asset_content_type: application/zip
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/2020-10-05T00:00:00+00:002023-01-06T17:32:06+01:00The actions/create-release action allows to create a new GitHub releases in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+     - name: Create Release
+       uses: actions/create-release@v1
+       env:
+         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+       with:
+         tag_name: <TAG>
+         release_name: <RELEASE>
+         draft: false
+         prerelease: false
+         body: |
+           Your release text here
+
+           Some code block:
+           ```yaml
+           yaml:
+             inside:
+               of:
+                 another: yaml
+           ```           
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <TAG>: The Git tag to create.
  • +
  • <RELEASE>: The release name to use.
  • +
+]]>
Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/2020-09-21T00:00:00+00:002023-01-06T15:27:21+01:00The peaceiris/actions-gh-pages action allows to publish a Hugo site in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Deploy Website
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: <PUBLISH_DIR>
+          force_orphan: true
+          cname: <CNAME>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <PUBLISH_DIR>: The file system location of the built site.
  • +
  • <CNAME>: The CNAME of your custom domain.
  • +
+]]>
Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/2020-09-07T00:00:00+00:002023-01-06T15:27:21+01:00The actions/cache action allows to cache artifacts in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Cache Maven artifacts
+        uses: actions/cache@v1
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-            
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/2020-08-24T00:00:00+00:002023-01-06T15:27:21+01:00The actions-hugo action allows to use a specific Hugo version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Setup hugo
+        uses: peaceiris/actions-hugo@v2
+        with:
+          hugo-version: <HUGO_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <HUGO_VERSION>: The released versions or use latest to always use the latest version of Hugo.
  • +
+]]>
Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/2020-08-10T00:00:00+00:002023-01-06T15:27:21+01:00The setup-java action allows to use a specific Java version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Set up JDK <JDK_VERSION>
+        uses: actions/setup-java@v1
+        with:
+          java-version: <JDK_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <JDK_VERSION>: The required Java version for your project.
  • +
+]]>
XDG Base Directory Specificationhttps://seb.xn--ho-hia.de/posts/xdg-dot-files/2020-07-27T00:00:00+00:002023-01-06T16:22:24+01:00The XDG Base Directory Specification has been around for a while, yet not every software has adopted it yet. Here is an incomplete list of fixes:

+
# use existing env variables or define new
+[ -z "$XDG_CACHE_HOME"  ] && export XDG_CACHE_HOME="$HOME/.cache"
+[ -z "$XDG_CONFIG_DIRS" ] && export XDG_CONFIG_DIRS="/etc/xdg"
+[ -z "$XDG_CONFIG_HOME" ] && export XDG_CONFIG_HOME="$HOME/.config"
+[ -z "$XDG_DATA_DIRS"   ] && export XDG_DATA_DIRS="/usr/local/share:/usr/share"
+[ -z "$XDG_DATA_HOME"   ] && export XDG_DATA_HOME="$HOME/.local/share"
+
+# gradle
+export GRADLE_USER_HOME="$XDG_DATA_HOME/gradle"
+
+# httpie
+export HTTPIE_CONFIG_DIR="$XDG_CONFIG_HOME/httpie"
+
+# npm
+export NPM_CONFIG_USERCONFIG="$XDG_CONFIG_HOME/npm/npmrc"
+export npm_config_cache="$XDG_CACHE_HOME/npm"
+
+# password-store
+export PASSWORD_STORE_DIR="$XDG_DATA_HOME/password-store"
+

To make your own software XDG-aware, consider using the dirs-dev or configdir libraries.

+]]>
GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/2020-07-13T00:00:00+00:002023-01-06T17:32:06+01:00Git at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. Pro Git rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.

+

While the central server approach is easy to use, it might not work in all scenarios:

+
    +
  1. An enterprise wants to share internal code as part of an open source project. All development is happening internally, and the public mirror gets an occasional update once in a while.
  2. +
  3. To protect against outages of the central server, mirrors should be created and be kept up-to-date.
  4. +
+

In case of the first scenario, tools like copybara, repoSpanner, or distributed-Git-forks offer a wide range of features to cover most details.

+

The second scenario can be solved manually with tools like gitomatic or automatically with GitLab’s mirror feature quite easy. GitLab allows to create a single pull-mirror and multiple push-mirrors. Therefore, it can be used to pull from your central server and push into all mirrors.

+

NOTE: This feature was previously available in the free tier but has now moved to GitLab Ultimate.

+
               +----------------+               
+               |     GitHub     |               
+               +----------------+               
+                        ^                       
+                        |                       
+                        |                       
+               +----------------+               
+         +-----|     GitLab     |------+        
+         |     +----------------+      |        
+         |                             |        
+         |                             |        
+         v                             v        
++----------------+            +----------------+
+|    Codeberg    |            |    BitBucket   |
++----------------+            +----------------+
+

To create such a setup, follow these steps:

+
    +
  1. Go to Settings > Repository and expand Mirroring repositories +Code Flow
  2. +
  3. Enter the URL of your central Git repository as the pull source, for example https://github.com/metio/ilo.git +Code Flow
  4. +
  5. Enter one push target for each mirror. Since pushing usually requires authentication, verify that the URL of the mirror contains a username, for example https://YOUR_USER@codeberg.org/metio.wtf/ilo.git. Add an access token for each mirror and select password as authentication method. +Code Flow
  6. +
+

In case you prefer SSH keys over HTTP access tokens, just select SSH public key as authentication method and verify that your key is both saved in GitLab and all mirrors.

+]]>
Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/2020-06-29T00:00:00+00:002023-01-06T16:22:24+01:00Use the following Perl snippet to automatically generate help output for your Makefile:

+
GREEN  := $(shell tput -Txterm setaf 2)
+WHITE  := $(shell tput -Txterm setaf 7)
+YELLOW := $(shell tput -Txterm setaf 3)
+RESET  := $(shell tput -Txterm sgr0)
+
+HELP_FUN = \
+    %help; \
+    while(<>) { push @{$$help{$$2 // 'targets'}}, [$$1, $$3] if /^([a-zA-Z\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
+    print "usage: make [target]\n\n"; \
+    for (sort keys %help) { \
+    print "${WHITE}$$_:${RESET}\n"; \
+    for (@{$$help{$$_}}) { \
+    $$sep = " " x (32 - length $$_->[0]); \
+    print "  ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
+    }; \
+    print "\n"; }
+

To use HELP_FUN, add the following help target to the same Makefile:

+
.DEFAULT_GOAL := help
+
+.PHONY: help
+help: ##@other Show this help
+	@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
+

Each target in the Makefile is marked as phony to signal that those targets are not actually files that are generated as part of your build process. The optional description of a target can be placed after the ##@ prefix. The first word represents the group of a target and everything that follows is the description of a target. All targets should be formatted just like the help target:

+
.PHONY: compile
+compile: ##@hacking Compile your code
+	<compile some code>
+
+.PHONY: test
+test: ##@hacking Test your code
+	<test some code>
+
+.PHONY: sign-cla
+sign-cla: ##@contrib Sign the contributor license agreement
+	<sign some file>
+

Once in place, you can either use make without any argument to call the help target or use make help to see the generated output:

+
$ make
+usage: make [target]
+
+contrib:
+  sign-cla            Sign the contributor license agreement
+
+hacking:
+  compile             Compile your code
+  test                Test your code
+
+other:
+  help                Show this help
+
]]>
Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/2020-06-15T00:00:00+00:002023-01-06T17:32:06+01:00In case you don’t want to write git clone git@github.com:orga/repo.git all the time, consider using a custom SSH configuration (~/.ssh/config) like this:

+
Host github
+    HostName github.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITHUB>
+
+Host gitlab
+    HostName gitlab.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITLAB>
+
+Host bitbucket
+    HostName bitbucket.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-BITBUCKET>
+
+Host codeberg
+    HostName codeberg.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-CODEBERG>
+

Once configured, you can now write:

+
$ git clone github:orga/repo
+$ git clone gitlab:orga/repo
+$ git clone bitbucket:orga/repo
+$ git clone codeberg:orga/repo
+

In case you are working with many repositories inside a single organization, consider adding the following Git configuration ($XDG_CONFIG_HOME/git/config or ~/.gitconfig):

+
[url "github:orga/"]
+  insteadOf = orga:
+[url "gitlab:orga/"]
+  insteadOf = orgl:
+[url "bitbucket:orga/"]
+  insteadOf = orgb:
+[url "codeberg:orga/"]
+  insteadOf = orgc:
+

Which allows you to just write:

+
$ git clone orga:repo
+$ git clone orgl:repo
+$ git clone orgb:repo
+$ git clone orgc:repo
+

Git will substitute the insteadOf values like orga: with the configured url (for example github:orga/). The actual clone URL is github:orga/repo at this point, which can be used by Git together with the SSH configuration mentioned above to clone repositories.

+]]>
Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/2020-06-11T00:00:00+00:002023-01-06T15:27:21+01:00In case you are into calver or have another reason to create a timestamp with GitHub Actions, do the following:

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Create release version
+        id: <ID>
+        run: echo "::set-output name=<NAME>::$(date +'%Y.%m.%d-%H%M%S')"
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <ID>: The unique ID of the timestamp step.
  • +
  • <NAME>: The name of the created timestamp.
  • +
+

The special syntax ::set-output name=<NAME>:: declares that the output of the command (echo) should be saved in a variable called <NAME>. Together with the <ID> of the pipeline step, this value can be referenced with the expression ${{ steps.<ID>.outputs.<NAME> }} in the following steps of your pipeline.

+]]>
\ No newline at end of file diff --git a/posts/awsenv/index.html b/posts/awsenv/index.html new file mode 100644 index 000000000..d3ee239cc --- /dev/null +++ b/posts/awsenv/index.html @@ -0,0 +1,49 @@ +Sebastian Hoß – awsenv

awsenv +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-aws" href="/tags/aws">aws</a>, <a class="post-tag post-tag-azure" href="/tags/azure">azure</a>, and <a class="post-tag post-tag-fzf" href="/tags/fzf">fzf</a>

To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the AWS_PROFILE environment variable which is used by many tools that interact with the AWS API, like awscli or terraform. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses aws sso login to perform an MFA login into AWS. The AWS profiles must be set up in such a way that aws configure list-profiles can detect them, which is typically done by adding them in ${AWS_CONFIG_FILE:-$HOME/.aws/config}.

#!/usr/bin/env sh
+
+###############################################################################
+# This script performs an AWS SSO login for the user-selected AWS profile
+# and sets the AWS_PROFILE environment variable afterwards. To use
+# this, create an alias that sources this script like this:
+#
+#     alias awsenv='source path/to/this/script.sh'
+#
+# Required software that is not in GNU coreutils:
+#   - 'aws' to list profiles & get current caller identity
+#   - 'fzf' to list all available AWS profiles
+###############################################################################
+
+# prompt user to select one AWS profile
+profile=$(aws configure list-profiles | \
+  fzf --cycle --layout=reverse --tiebreak=index)
+
+# user can cancel switching profiles by pressing ESC
+if [ -n "${profile}" ]; then
+  # check is access token exists and is valid for selected profile
+  if ! aws --profile "${profile}" sts get-caller-identity >/dev/null 2>&1; then
+    # perform login into profile in case access token is invalid
+    if ! aws sso login --profile "${profile}"; then
+      # short circuit in case login failed
+      return
+    fi
+  fi
+  # AWS_PROFILE is used by many AWS-related tools
+  echo "Setting AWS_PROFILE to [${profile}]"
+  export AWS_PROFILE="${profile}"
+  # do not expose internal variables
+  unset profile
+fi
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/awsenv/jsonld.json b/posts/awsenv/jsonld.json new file mode 100644 index 000000000..5db2024da --- /dev/null +++ b/posts/awsenv/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"awsenv","headline":"awsenv","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/awsenv/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2022","dateCreated":"2022-02-14","datePublished":"2022-02-14","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/awsenv/","wordCount":"269","genre":["aws","azure","fzf"],"keywords":["aws","azure","fzf"]} \ No newline at end of file diff --git a/posts/chezmoi-age/index.html b/posts/chezmoi-age/index.html new file mode 100644 index 000000000..9eb0a7200 --- /dev/null +++ b/posts/chezmoi-age/index.html @@ -0,0 +1,25 @@ +Sebastian Hoß – Using chezmoi with age

Using chezmoi with age +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-age" href="/tags/age">age</a>, <a class="post-tag post-tag-chezmoi" href="/tags/chezmoi">chezmoi</a>, <a class="post-tag post-tag-dotfiles" href="/tags/dotfiles">dotfiles</a>, and <a class="post-tag post-tag-encryption" href="/tags/encryption">encryption</a>

age is another tool supported by chezmoi to keep data private. Compared to gpg it is much simpler by focusing on the encryption parts only.

Add the following snippet to your .chezmoi.toml to configure chezmoi to use age:

encryption = "age"
+[age]
+  identity = "path/to/age/private-key"
+  recipient = "age...public...key..."
+

Adding files to your chezmoi source directory remains the same as compared to using gpg - just call chezmoi add --encrypt path/to/file.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/chezmoi-age/jsonld.json b/posts/chezmoi-age/jsonld.json new file mode 100644 index 000000000..90571fc80 --- /dev/null +++ b/posts/chezmoi-age/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Using chezmoi with age","headline":"Using chezmoi with age","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/chezmoi-age/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2023","dateCreated":"2023-01-08","datePublished":"2023-01-08","dateModified":"2023-01-08","url":"https://seb.xn--ho-hia.de/posts/chezmoi-age/","wordCount":"70","genre":["dotfiles","chezmoi","age","encryption"],"keywords":["dotfiles","chezmoi","age","encryption"]} \ No newline at end of file diff --git a/posts/chezmoi-auto-git/index.html b/posts/chezmoi-auto-git/index.html new file mode 100644 index 000000000..62bfdddfd --- /dev/null +++ b/posts/chezmoi-auto-git/index.html @@ -0,0 +1,21 @@ +Sebastian Hoß – Manage dotfiles with chezmoi and git

Manage dotfiles with chezmoi and git +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-chezmoi" href="/tags/chezmoi">chezmoi</a>, <a class="post-tag post-tag-dotfiles" href="/tags/dotfiles">dotfiles</a>, and <a class="post-tag post-tag-git" href="/tags/git">git</a>

chezmoi can automatically commit and push changes to your dotfiles into a (remote) Git repository. Enable it with the following snippet in your chezmoi.toml

[sourceVCS]
+    autoCommit = true
+    autoPush = true
+

Every time you call chezmoi add /path/to/file will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/chezmoi-auto-git/jsonld.json b/posts/chezmoi-auto-git/jsonld.json new file mode 100644 index 000000000..151a65d75 --- /dev/null +++ b/posts/chezmoi-auto-git/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Manage dotfiles with chezmoi and git","headline":"Manage dotfiles with chezmoi and git","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/chezmoi-auto-git/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-09-06","datePublished":"2021-09-06","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/chezmoi-auto-git/","wordCount":"58","genre":["dotfiles","chezmoi","git"],"keywords":["dotfiles","chezmoi","git"]} \ No newline at end of file diff --git a/posts/chezmoi-auto-update/index.html b/posts/chezmoi-auto-update/index.html new file mode 100644 index 000000000..4116da19e --- /dev/null +++ b/posts/chezmoi-auto-update/index.html @@ -0,0 +1,30 @@ +Sebastian Hoß – chezmoi auto-update

chezmoi auto-update +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-automation" href="/tags/automation">automation</a>, <a class="post-tag post-tag-chezmoi" href="/tags/chezmoi">chezmoi</a>, and <a class="post-tag post-tag-dotfiles" href="/tags/dotfiles">dotfiles</a>

To automatically synchronize dotfiles across my computers, I’ve written the following systemd unit:

[Unit]
+Description=Update chezmoi managed dotfiles
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/chezmoi update --no-tty --force
+RemainAfterExit=false
+
+[Install]
+WantedBy=default.target
+

This unit pulls changes from upstream first and then applies the changes to the current computer after I’m logged in and a network connection is available. The --no-tty flag is required because there is no tty when systemd executes chezmoi. Likewise, the --force flag ensures that no interactive prompt will be displayed which we cannot answer since systemd is executing this unit without us being involved.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/chezmoi-auto-update/jsonld.json b/posts/chezmoi-auto-update/jsonld.json new file mode 100644 index 000000000..e794ff685 --- /dev/null +++ b/posts/chezmoi-auto-update/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"chezmoi auto-update","headline":"chezmoi auto-update","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/chezmoi-auto-update/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2022","dateCreated":"2022-12-26","datePublished":"2022-12-26","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/chezmoi-auto-update/","wordCount":"95","genre":["chezmoi","dotfiles","automation"],"keywords":["chezmoi","dotfiles","automation"]} \ No newline at end of file diff --git a/posts/chezmoi-gpg/index.html b/posts/chezmoi-gpg/index.html new file mode 100644 index 000000000..73d909720 --- /dev/null +++ b/posts/chezmoi-gpg/index.html @@ -0,0 +1,23 @@ +Sebastian Hoß – Encrypt dotfiles with chezmoi

Encrypt dotfiles with chezmoi +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-chezmoi" href="/tags/chezmoi">chezmoi</a>, <a class="post-tag post-tag-dotfiles" href="/tags/dotfiles">dotfiles</a>, <a class="post-tag post-tag-encryption" href="/tags/encryption">encryption</a>, and <a class="post-tag post-tag-gpg" href="/tags/gpg">gpg</a>

RECOMMENDATION: Use age instead of gpg.

chezmoi can use various external tools to keep data private. gpg is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure gpg with chezmoi, just set yourself as the recipient like this:

[gpg]
+  recipient = "your.name@example.com"
+

Calling chezmoi add --encrypt /path/to/secret will now create encrypt the file with your public key which allows you to decrypt them later with your private key.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/chezmoi-gpg/jsonld.json b/posts/chezmoi-gpg/jsonld.json new file mode 100644 index 000000000..814691c16 --- /dev/null +++ b/posts/chezmoi-gpg/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Encrypt dotfiles with chezmoi","headline":"Encrypt dotfiles with chezmoi","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/chezmoi-gpg/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-09-20","datePublished":"2021-09-20","dateModified":"2023-01-08","url":"https://seb.xn--ho-hia.de/posts/chezmoi-gpg/","wordCount":"81","genre":["dotfiles","chezmoi","gpg","encryption"],"keywords":["dotfiles","chezmoi","gpg","encryption"]} \ No newline at end of file diff --git a/posts/chezmoi-maintenance/index.html b/posts/chezmoi-maintenance/index.html new file mode 100644 index 000000000..1fa4c0755 --- /dev/null +++ b/posts/chezmoi-maintenance/index.html @@ -0,0 +1,30 @@ +Sebastian Hoß – Maintaining dotfiles with chezmoi

Maintaining dotfiles with chezmoi +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-chezmoi" href="/tags/chezmoi">chezmoi</a>, and <a class="post-tag post-tag-dotfiles" href="/tags/dotfiles">dotfiles</a>

To make it easier managing many dotfiles with chezmoi, a shell function similar to the one below can be used:

function m-dotfiles-ok {
+    # public
+    chezmoi add ~/.config/zsh --recursive
+    chezmoi add ~/.config/sway --recursive
+    chezmoi add ~/.config/tmux --recursive
+    chezmoi add ....
+
+    # secrets
+    chezmoi add --encrypt ~/.config/npm/npmrc
+    chezmoi add --encrypt ~/.ssh/id_rsa
+    chezmoi add --encrypt ...
+}
+

Whenever you feel happy with your current setup, just call m-dotfiles-ok to push changes into the chezmoi source directory. Files will automatically be encrypted with gpg and committed/pushed into a Git repository if you have done the necessary configuration beforehand.

In general, editing your dotfiles directly as explained in the second option of the FAQ seems easier though. Refactoring your dotfiles is especially easy when the exact_ prefix is used for directories. As explained in the documentation, all files that are not managed by chezmoi will be removed, therefore your configuration will always match what is in your source directory.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/chezmoi-maintenance/jsonld.json b/posts/chezmoi-maintenance/jsonld.json new file mode 100644 index 000000000..d56503a69 --- /dev/null +++ b/posts/chezmoi-maintenance/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Maintaining dotfiles with chezmoi","headline":"Maintaining dotfiles with chezmoi","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/chezmoi-maintenance/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-10-11","datePublished":"2021-10-11","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/chezmoi-maintenance/","wordCount":"155","genre":["dotfiles","chezmoi"],"keywords":["dotfiles","chezmoi"]} \ No newline at end of file diff --git a/posts/clojure-java-interoperability/index.html b/posts/clojure-java-interoperability/index.html new file mode 100644 index 000000000..5f1090098 --- /dev/null +++ b/posts/clojure-java-interoperability/index.html @@ -0,0 +1,105 @@ +Sebastian Hoß – Clojure Java Interoperability

Clojure Java Interoperability +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-clojure" href="/tags/clojure">clojure</a>, <a class="post-tag post-tag-interoperability" href="/tags/interoperability">interoperability</a>, and <a class="post-tag post-tag-java" href="/tags/java">java</a>

Clojure has several forms and macros to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.

Using gen-class

Clojure code can be compiled to standard JVM bytecode using gen-class.

Adding static modifiers

Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class com.example.Computation.

(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :methods [#^{:static true} [incrementRange [int] java.util.List]]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [max]
+  (increment-range max))
+

The Java wrapper has to follow the standard rules for method names. Therefore increment-range has to be renamed to incrementRange (or some similar name without the “-” in it). The “-” prefix for the Java wrapper can be configured inside the :gen-class form and will be removed once gen-class runs. The usage from Java looks like this:

package com.example
+
+public class ClojureJavaInteropStatic {
+
+    public static void main(String[] args) {
+        List incrementedRange = Computation.incrementRange(10);
+    }
+
+}
+

Adding generics

The returned list in the above code is raw because the method definition doesn’t use generics. To solve this problem declare that the generated class :implements a certain interface that exposes the desired method definition(s). You won’t be able to declare your methods as static anymore, but get a generified method for all your Java needs.

The Java interface:

package com.example
+
+public interface RangeIncrementer {
+  List<Long> incrementRange(int max);
+}
+

The changed Clojure namespace:

(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :implements [com.example.RangeIncrementer]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [this max]
+  (increment-range max))
+

Finally, the generified usage from Java:

package com.example
+
+public class ClojureJavaInteropGenerics {
+
+    public static void main(String[] args) {
+        RangeIncrementer incrementer = new Computation();
+        List<Long> incrementedRange = incrementer.incrementRange(10);
+    }
+
+}
+

Couple of notes for this as well: First the generated class still only returns the raw type (List instead of List<Integer>). So instead of using the class, use the interface for the variable declaration (RangeIncrementer incrementer = .. instead of Computation comp = ..). The interface will return the non-raw List. Second the function definition for -incrementRange is now slightly different. It needs an additional parameter (this) which exposes the current instance to the generated class/method.

Returning an array of something is also possible with the following construct "[Ljava.lang.Object;". Need a 2-dim array? Just use "[[Ljava.lang.Object;" (notice the extra [) and so on. However, be aware that the method return types have to match, for example you can’t specify a return type of array if your Clojure function does not return an array. In the example above the call to map returns LazySeq which itself is a java.util.List. Therefore, the method declaration is valid, and you won’t get any ClassCastException when calling incrementRange from Java.

Make your life easier with macros

Instead of defining every Clojure function which should be exported twice (the real function + the Java wrapper), it is possible to use a macro to do that extra work automatically.

(require '[clojure.string :as string)
+
+(defn camel-case [input]
+  (let [words (string/split input #"[\s_-]+")]
+    (string/join (cons (string/lower-case (first words)) (map string/capitalize (rest words))))))
+
+(defn java-name [clojure-name]
+  (symbol (str "-" (camel-case (str clojure-name)))))
+
+(defmacro defn* [name & declarations]
+  (let [java-name (java-name name)]
+    `(do (defn ~name ~declarations)
+       (defn ~java-name ~declarations))))
+

The macro defn* replaces defn and automatically creates a second function with a valid camel-cased Java method name. The macro is available as a small library at Maven Central. The macro won’t add the extra parameter mentioned above to Java wrapper, so it is only useful for declaring static methods.

Using the Clojure Runtime

Using gen-class imposes certain limitations on calling Clojure code from Java. One of those are functions which make use of Clojure parameter destructuring. To invoke those functions you have to use the Clojure runtime.

// The Clojure 'require' function from the 'clojure.core' namespace.
+Var require = RT.var("clojure.core", "require");
+
+// Your namespace
+Symbol namespace = Symbol.intern("DESIRED.NAMESPACE.HERE");
+
+// Your function
+Var function = RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION");
+
+// The required keyword for the above function
+Keyword keyword = Keyword.intern("REQUIRED-KEYWORD");
+
+// Require/Import your namespace
+require.invoke(namespace);
+
+// Invoke your function with the given keyword and its value
+Object result = function.invoke(keyword, VALUE);
+

The desired namespace has to be on the classpath for this to work. Alternatively it is possible to load an entire Clojure script, as shown in the following example:

RT.loadResourceScript("DESIRED/NAMESPACE/HERE.clj");
+RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION").invoke(PARAMETER);
+

On a big project it is properly wise to move Java->Clojure interop code into helper classes/methods. Look here for an example.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/clojure-java-interoperability/jsonld.json b/posts/clojure-java-interoperability/jsonld.json new file mode 100644 index 000000000..8c6e706f0 --- /dev/null +++ b/posts/clojure-java-interoperability/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Clojure Java Interoperability","headline":"Clojure Java Interoperability","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/clojure-java-interoperability/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2022","dateCreated":"2022-01-29","datePublished":"2022-01-29","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/clojure-java-interoperability/","wordCount":"819","genre":["clojure","java","interoperability"],"keywords":["clojure","java","interoperability"]} \ No newline at end of file diff --git a/posts/declarative-conditional-rendering-in-react/index.html b/posts/declarative-conditional-rendering-in-react/index.html new file mode 100644 index 000000000..71e0e99e8 --- /dev/null +++ b/posts/declarative-conditional-rendering-in-react/index.html @@ -0,0 +1,68 @@ +Sebastian Hoß – Declarative conditional rendering in React

Declarative conditional rendering in React +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-breakpoints" href="/tags/breakpoints">breakpoints</a>, <a class="post-tag post-tag-react" href="/tags/react">react</a>, and <a class="post-tag post-tag-rendering" href="/tags/rendering">rendering</a>

One feature that often surprises people while teaching them React is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.

In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.

const RendersNothing = () => <></>
+

Now consider the following example, that adds some if-then-else logic to the same component:

const MightRenderSomething = () => {
+  if (someCondition) {
+    return <span>hello world!</span>
+  }
+  return <></>
+}
+

This component encapsulates the if-then-else logic of conditionally rendering a hello world message. Instead of cluttering your entire app with the same logic, you can now simply re-use that same component that contains this if condition. To see the full power of this technique, consider the following example. At first, we are going to define a hook that reads the current window width, then define components that conditionally render based on the current window width, and finally use those components in an example application.

const useWindowWidth = () => {
+  const [width, setWidth] = React.useState(0)
+
+  React.useEffect(() => {
+    const handleResize = () => {
+      setWidth(window.innerWidth)
+    }
+    window.addEventListener("resize", handleResize)
+    return () => {
+      window.removeEventListener("resize", handleResize)
+    }
+  }, [])
+
+  return width
+}
+

The following components use that hook to implement UI breakpoints for small (mobile) and large (desktop) screens. Note that the value 768 is just an example - replace it with whatever your design system tells you to.

const ForMobileDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+  
+  if (windowWidth < 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+
+const ForDesktopDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+
+  if (windowWidth >= 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+

Both of these components simply render nothing when the window width does not have an appropriate size. If the window width does have the right size, they render their children. We can use those components in our application like this:

const SomeActualComponent = () => (
+    <div>
+      <h1>common headline</h1>
+      <ForMobileDevicesOnly>
+        <span>only visible on mobile devices</span>
+      </ForMobileDevicesOnly>
+      <ForDesktopDevicesOnly>
+        <span>only visible on desktop devices</span>
+      </ForDesktopDevicesOnly>
+    </div>
+)
+

The above code snippet declares that some part of the UI can only be seen by mobile users, while others can only be seen by desktop users. Parts of the UI that are shared amongst all users are not wrapped by any of the components defined above.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/declarative-conditional-rendering-in-react/jsonld.json b/posts/declarative-conditional-rendering-in-react/jsonld.json new file mode 100644 index 000000000..5ee780a51 --- /dev/null +++ b/posts/declarative-conditional-rendering-in-react/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet","frontend"],"name":"Declarative conditional rendering in React","headline":"Declarative conditional rendering in React","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2022","dateCreated":"2022-01-15","datePublished":"2022-01-15","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/","wordCount":"429","genre":["react","rendering","breakpoints"],"keywords":["react","rendering","breakpoints"]} \ No newline at end of file diff --git a/posts/emacs-backups/index.html b/posts/emacs-backups/index.html new file mode 100644 index 000000000..7033c4e71 --- /dev/null +++ b/posts/emacs-backups/index.html @@ -0,0 +1,44 @@ +Sebastian Hoß – Backups with emacs

Backups with emacs +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-backup" href="/tags/backup">backup</a>, and <a class="post-tag post-tag-emacs" href="/tags/emacs">emacs</a>

emacs will create backups of your files by default. Those backups are located right next to the original file and are called <file>~. Unfortunately, emacs will not clean those up by default, which annoys me from time to time. Therefore, I’m now using the following configuration to keep those backups in a different folder:

(setq version-control t     ;; Use version numbers for backups.
+      kept-new-versions 10  ;; Number of newest versions to keep.
+      kept-old-versions 0   ;; Number of oldest versions to keep.
+      delete-old-versions t ;; Don't ask to delete excess backup versions.
+      backup-by-copying t)  ;; Copy all files, don't rename them.
+
+(setq vc-make-backup-files t)
+
+;; Default and per-save backups go here:
+(setq backup-directory-alist '(("" . "~/.emacs.d/backup/per-save")))
+
+(defun force-backup-of-buffer ()
+  ;; Make a special "per session" backup at the first save of each
+  ;; emacs session.
+  (when (not buffer-backed-up)
+    ;; Override the default parameters for per-session backups.
+    (let ((backup-directory-alist '(("" . "~/.emacs.d/backup/per-session")))
+          (kept-new-versions 3))
+      (backup-buffer)))
+  ;; Make a "per save" backup on each save.  The first save results in
+  ;; both a per-session and a per-save backup, to keep the numbering
+  ;; of per-save backups consistent.
+  (let ((buffer-backed-up nil))
+    (backup-buffer)))
+
+(add-hook 'before-save-hook  'force-backup-of-buffer)
+

Thanks to that configuration, backups per-save will be created in ~/.emacs.d/backup/per-save and backups per-session in ~/.emacs.d/backup/per-session.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/emacs-backups/jsonld.json b/posts/emacs-backups/jsonld.json new file mode 100644 index 000000000..73e9da123 --- /dev/null +++ b/posts/emacs-backups/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Backups with emacs","headline":"Backups with emacs","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/emacs-backups/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-10-25","datePublished":"2021-10-25","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/emacs-backups/","wordCount":"207","genre":["emacs","backup"],"keywords":["emacs","backup"]} \ No newline at end of file diff --git a/posts/emacs-systemd/index.html b/posts/emacs-systemd/index.html new file mode 100644 index 000000000..563294de8 --- /dev/null +++ b/posts/emacs-systemd/index.html @@ -0,0 +1,36 @@ +Sebastian Hoß – emacs and systemd

emacs and systemd +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-emacs" href="/tags/emacs">emacs</a>, and <a class="post-tag post-tag-systemd" href="/tags/systemd">systemd</a>

I like to use emacs to edit files in a terminal. It tends to start a little slow, therefore I’ve created a systemd unit to automatically start the emacs daemon and use aliases to connect to the running daemon. The unit looks like this:

[Unit]
+Description=Emacs text editor [%I]
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+
+[Service]
+Type=forking
+ExecStart=/usr/bin/emacs --daemon=%i
+ExecStop=/usr/bin/emacsclient --eval "(kill-emacs)"
+Environment=SSH_AUTH_SOCK=%t/keyring/ssh
+Restart=on-failure
+
+[Install]
+WantedBy=default.target
+

Enable it with systemctl --user enable emacs@user and define any number of aliases to make connecting to the emacs daemon easier:

alias e='emacsclient --tty --socket-name=user'
+alias vim='emacsclient --tty --socket-name=user'
+alias vi='emacsclient --tty --socket-name=user'
+alias nano='emacsclient --tty --socket-name=user'
+alias ed='emacsclient --tty --socket-name=user'
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/emacs-systemd/jsonld.json b/posts/emacs-systemd/jsonld.json new file mode 100644 index 000000000..d8dc2dfb5 --- /dev/null +++ b/posts/emacs-systemd/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"emacs and systemd","headline":"emacs and systemd","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/emacs-systemd/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-07-26","datePublished":"2021-07-26","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/emacs-systemd/","wordCount":"104","genre":["emacs","systemd"],"keywords":["emacs","systemd"]} \ No newline at end of file diff --git a/posts/git-mirror/index.html b/posts/git-mirror/index.html new file mode 100644 index 000000000..8d59e5523 --- /dev/null +++ b/posts/git-mirror/index.html @@ -0,0 +1,23 @@ +Sebastian Hoß – Mirror Git Repositories

Mirror Git Repositories +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-git" href="/tags/git">git</a>, <a class="post-tag post-tag-mirror" href="/tags/mirror">mirror</a>, and <a class="post-tag post-tag-push" href="/tags/push">push</a>

In case you want to make use of the decentralized nature of Git, consider using multiple push targets like this:

$ git remote set-url origin --push --add git@example.com/project.git
+$ git remote set-url origin --push --add git@another.com/project.git
+

Note that the first call to set-url will overwrite an existing remote creating with git clone. Any additional call will actually recognize the --add option and add the new target to an existing remote.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/git-mirror/jsonld.json b/posts/git-mirror/jsonld.json new file mode 100644 index 000000000..70a29b739 --- /dev/null +++ b/posts/git-mirror/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet","decentralized"],"name":"Mirror Git Repositories","headline":"Mirror Git Repositories","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/git-mirror/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-06-28","datePublished":"2021-06-28","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/git-mirror/","wordCount":"75","genre":["git","push","mirror"],"keywords":["git","push","mirror"]} \ No newline at end of file diff --git a/posts/git-push-only-mirror/index.html b/posts/git-push-only-mirror/index.html new file mode 100644 index 000000000..24be4b8e9 --- /dev/null +++ b/posts/git-push-only-mirror/index.html @@ -0,0 +1,30 @@ +Sebastian Hoß – Push-only mirrors for Git Repositories

Push-only mirrors for Git Repositories +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-git" href="/tags/git">git</a>, <a class="post-tag post-tag-mirror" href="/tags/mirror">mirror</a>, and <a class="post-tag post-tag-push" href="/tags/push">push</a>

In case you want to have push-only mirrors for your Git repository, consider adding a special mirror remote like this:

$ git remote add mirrors DISABLED
+$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git
+$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git
+$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git
+

The above will create a new remote called mirrors which has no fetch URL and therefore can only be pushed:

$ git remote -v
+mirrors DISABLED (fetch)
+mirrors git@codeberg.org:org/repo.git (push)
+mirrors git@gitlab.com:org/repo.git (push)
+mirrors git@bitbucket.org:org/repo.git (push)
+

Calling git push mirrors main:main will push the local main branch into all defined mirrors.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/git-push-only-mirror/jsonld.json b/posts/git-push-only-mirror/jsonld.json new file mode 100644 index 000000000..d3e72bdce --- /dev/null +++ b/posts/git-push-only-mirror/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet","decentralized"],"name":"Push-only mirrors for Git Repositories","headline":"Push-only mirrors for Git Repositories","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/git-push-only-mirror/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-07-12","datePublished":"2021-07-12","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/git-push-only-mirror/","wordCount":"105","genre":["git","push","mirror"],"keywords":["git","push","mirror"]} \ No newline at end of file diff --git a/posts/github-actions-cache-maven-artifacts/index.html b/posts/github-actions-cache-maven-artifacts/index.html new file mode 100644 index 000000000..9878f37a1 --- /dev/null +++ b/posts/github-actions-cache-maven-artifacts/index.html @@ -0,0 +1,30 @@ +Sebastian Hoß – Cache Maven artifacts with GitHub Actions

Cache Maven artifacts with GitHub Actions +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-cache" href="/tags/cache">cache</a>, <a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-github-actions" href="/tags/github-actions">github actions</a>, and <a class="post-tag post-tag-maven" href="/tags/maven">maven</a>

The actions/cache action allows to cache artifacts in your GitHub Action.

name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Cache Maven artifacts
+        uses: actions/cache@v1
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-            
+
  • <PIPELINE>: The name of your pipeline.
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/github-actions-cache-maven-artifacts/jsonld.json b/posts/github-actions-cache-maven-artifacts/jsonld.json new file mode 100644 index 000000000..4c7dc4489 --- /dev/null +++ b/posts/github-actions-cache-maven-artifacts/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Cache Maven artifacts with GitHub Actions","headline":"Cache Maven artifacts with GitHub Actions","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-09-07","datePublished":"2020-09-07","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/","wordCount":"57","genre":["github","github actions","cache","maven"],"keywords":["github","github actions","cache","maven"]} \ No newline at end of file diff --git a/posts/github-actions-create-release/index.html b/posts/github-actions-create-release/index.html new file mode 100644 index 000000000..73bfcc517 --- /dev/null +++ b/posts/github-actions-create-release/index.html @@ -0,0 +1,42 @@ +Sebastian Hoß – Create GitHub releases with GitHub Actions

Create GitHub releases with GitHub Actions +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-github-actions" href="/tags/github-actions">github actions</a>, and <a class="post-tag post-tag-release" href="/tags/release">release</a>

The actions/create-release action allows to create a new GitHub releases in your GitHub Action.

name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+     - name: Create Release
+       uses: actions/create-release@v1
+       env:
+         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+       with:
+         tag_name: <TAG>
+         release_name: <RELEASE>
+         draft: false
+         prerelease: false
+         body: |
+           Your release text here
+
+           Some code block:
+           ```yaml
+           yaml:
+             inside:
+               of:
+                 another: yaml
+           ```           
+
  • <PIPELINE>: The name of your pipeline.
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • <TAG>: The Git tag to create.
  • <RELEASE>: The release name to use.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/github-actions-create-release/jsonld.json b/posts/github-actions-create-release/jsonld.json new file mode 100644 index 000000000..6dd2a8a5a --- /dev/null +++ b/posts/github-actions-create-release/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Create GitHub releases with GitHub Actions","headline":"Create GitHub releases with GitHub Actions","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/github-actions-create-release/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-10-05","datePublished":"2020-10-05","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/github-actions-create-release/","wordCount":"87","genre":["github","github actions","release"],"keywords":["github","github actions","release"]} \ No newline at end of file diff --git a/posts/github-actions-create-timestamp/index.html b/posts/github-actions-create-timestamp/index.html new file mode 100644 index 000000000..edc1300f6 --- /dev/null +++ b/posts/github-actions-create-timestamp/index.html @@ -0,0 +1,26 @@ +Sebastian Hoß – Create Timestamp with GitHub Actions

Create Timestamp with GitHub Actions +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-github-actions" href="/tags/github-actions">github actions</a>, and <a class="post-tag post-tag-timestamp" href="/tags/timestamp">timestamp</a>

In case you are into calver or have another reason to create a timestamp with GitHub Actions, do the following:

name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Create release version
+        id: <ID>
+        run: echo "::set-output name=<NAME>::$(date +'%Y.%m.%d-%H%M%S')"
+
  • <PIPELINE>: The name of your pipeline.
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • <ID>: The unique ID of the timestamp step.
  • <NAME>: The name of the created timestamp.

The special syntax ::set-output name=<NAME>:: declares that the output of the command (echo) should be saved in a variable called <NAME>. Together with the <ID> of the pipeline step, this value can be referenced with the expression ${{ steps.<ID>.outputs.<NAME> }} in the following steps of your pipeline.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/github-actions-create-timestamp/jsonld.json b/posts/github-actions-create-timestamp/jsonld.json new file mode 100644 index 000000000..f130fd617 --- /dev/null +++ b/posts/github-actions-create-timestamp/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","cd","snippet"],"name":"Create Timestamp with GitHub Actions","headline":"Create Timestamp with GitHub Actions","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-06-11","datePublished":"2020-06-11","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/","wordCount":"119","genre":["github","github actions","timestamp"],"keywords":["github","github actions","timestamp"]} \ No newline at end of file diff --git a/posts/github-actions-publish-hugo-site/index.html b/posts/github-actions-publish-hugo-site/index.html new file mode 100644 index 000000000..f6f63fc83 --- /dev/null +++ b/posts/github-actions-publish-hugo-site/index.html @@ -0,0 +1,30 @@ +Sebastian Hoß – Publish Hugo site with GitHub Actions

Publish Hugo site with GitHub Actions +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-github-actions" href="/tags/github-actions">github actions</a>, <a class="post-tag post-tag-hugo" href="/tags/hugo">hugo</a>, and <a class="post-tag post-tag-publish" href="/tags/publish">publish</a>

The peaceiris/actions-gh-pages action allows to publish a Hugo site in your GitHub Action.

name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Deploy Website
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: <PUBLISH_DIR>
+          force_orphan: true
+          cname: <CNAME>
+
  • <PIPELINE>: The name of your pipeline.
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • <PUBLISH_DIR>: The file system location of the built site.
  • <CNAME>: The CNAME of your custom domain.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/github-actions-publish-hugo-site/jsonld.json b/posts/github-actions-publish-hugo-site/jsonld.json new file mode 100644 index 000000000..099900d3a --- /dev/null +++ b/posts/github-actions-publish-hugo-site/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Publish Hugo site with GitHub Actions","headline":"Publish Hugo site with GitHub Actions","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-09-21","datePublished":"2020-09-21","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/","wordCount":"71","genre":["github","github actions","publish","hugo"],"keywords":["github","github actions","publish","hugo"]} \ No newline at end of file diff --git a/posts/github-actions-schedule-build/index.html b/posts/github-actions-schedule-build/index.html new file mode 100644 index 000000000..12879db03 --- /dev/null +++ b/posts/github-actions-schedule-build/index.html @@ -0,0 +1,32 @@ +Sebastian Hoß – Delay GitHub Actions builds

Delay GitHub Actions builds +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-github-actions" href="/tags/github-actions">github actions</a>, and <a class="post-tag post-tag-schedule" href="/tags/schedule">schedule</a>

To delay the execution of an GitHub Action, use a mixture of the on: schedule: ... configuration, and a conditional build step.

name: <PIPELINE>
+on:
+  schedule:
+    - cron: '<CRON>'
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Count commits in last week
+        id: commits
+        run: echo "::set-output name=count::$(git rev-list --count HEAD --since='<DATE>')"
+      - name: Build project
+        if: steps.commits.outputs.count > 0
+        run: build-project
+
  • <PIPELINE>: The name of your pipeline.
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • <CRON>: cron expression - use https://crontab.guru/.
  • <DATE>: Git date expression that matches <CRON>.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/github-actions-schedule-build/jsonld.json b/posts/github-actions-schedule-build/jsonld.json new file mode 100644 index 000000000..8600ad795 --- /dev/null +++ b/posts/github-actions-schedule-build/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Delay GitHub Actions builds","headline":"Delay GitHub Actions builds","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/github-actions-schedule-build/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-02-22","datePublished":"2021-02-22","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/github-actions-schedule-build/","wordCount":"92","genre":["github","github actions","schedule"],"keywords":["github","github actions","schedule"]} \ No newline at end of file diff --git a/posts/github-actions-send-email/index.html b/posts/github-actions-send-email/index.html new file mode 100644 index 000000000..f39e963fd --- /dev/null +++ b/posts/github-actions-send-email/index.html @@ -0,0 +1,34 @@ +Sebastian Hoß – Send emails with GitHub Actions

Send emails with GitHub Actions +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-email" href="/tags/email">email</a>, <a class="post-tag post-tag-github" href="/tags/github">github</a>, and <a class="post-tag post-tag-github-actions" href="/tags/github-actions">github actions</a>

The dawidd6/action-send-mail action allows to send an email in your GitHub Action.

name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Send mail
+        uses: dawidd6/action-send-mail@v3
+        with:
+          server_address: ${{ secrets.MAIL_SERVER }}
+          server_port: ${{ secrets.MAIL_PORT }}
+          username: ${{ secrets.MAIL_USERNAME }}
+          password: ${{ secrets.MAIL_PASSWORD }}
+          subject: <SUBJECT>
+          body: <BODY>
+          to: ${{ secrets.MAIL_RECIPIENT }}
+          from: ${{ secrets.MAIL_SENDER }}
+
  • <PIPELINE>: The name of your pipeline.
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • <SUBJECT>: Subject for the email.
  • <BODY>: Body for the email.

Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define MAIL_RECIPIENT for each project.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/github-actions-send-email/jsonld.json b/posts/github-actions-send-email/jsonld.json new file mode 100644 index 000000000..289fe5b3b --- /dev/null +++ b/posts/github-actions-send-email/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Send emails with GitHub Actions","headline":"Send emails with GitHub Actions","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/github-actions-send-email/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-11-02","datePublished":"2020-11-02","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/github-actions-send-email/","wordCount":"106","genre":["github","github actions","email"],"keywords":["github","github actions","email"]} \ No newline at end of file diff --git a/posts/github-actions-send-toot/index.html b/posts/github-actions-send-toot/index.html new file mode 100644 index 000000000..3af287b8d --- /dev/null +++ b/posts/github-actions-send-toot/index.html @@ -0,0 +1,29 @@ +Sebastian Hoß – Send toots with GitHub Actions

Send toots with GitHub Actions +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-github-actions" href="/tags/github-actions">github actions</a>, <a class="post-tag post-tag-mastodon" href="/tags/mastodon">mastodon</a>, and <a class="post-tag post-tag-toot" href="/tags/toot">toot</a>

The rzr/fediverse-action action allows to send a toot in your GitHub Action.

name: <NAME>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Publish Toot
+        uses: rzr/fediverse-action@master
+        with:
+          access-token: ${{ secrets.MASTODON_TOKEN }}
+          message: <MESSAGE>
+          host: ${{ secrets.MASTODON_SERVER }}
+
  • <PIPELINE>: The name of your pipeline.
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • <MESSAGE>: Message for the toot.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/github-actions-send-toot/jsonld.json b/posts/github-actions-send-toot/jsonld.json new file mode 100644 index 000000000..ca5e3b847 --- /dev/null +++ b/posts/github-actions-send-toot/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Send toots with GitHub Actions","headline":"Send toots with GitHub Actions","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/github-actions-send-toot/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-11-16","datePublished":"2020-11-16","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/github-actions-send-toot/","wordCount":"59","genre":["github","github actions","toot","mastodon"],"keywords":["github","github actions","toot","mastodon"]} \ No newline at end of file diff --git a/posts/github-actions-specify-hugo-version/index.html b/posts/github-actions-specify-hugo-version/index.html new file mode 100644 index 000000000..92f7956f9 --- /dev/null +++ b/posts/github-actions-specify-hugo-version/index.html @@ -0,0 +1,27 @@ +Sebastian Hoß – Use a specific Hugo version with GitHub Actions

Use a specific Hugo version with GitHub Actions +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-github-actions" href="/tags/github-actions">github actions</a>, and <a class="post-tag post-tag-hugo" href="/tags/hugo">hugo</a>

The actions-hugo action allows to use a specific Hugo version in your GitHub Action.

name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Setup hugo
+        uses: peaceiris/actions-hugo@v2
+        with:
+          hugo-version: <HUGO_VERSION>
+
  • <PIPELINE>: The name of your pipeline.
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • <HUGO_VERSION>: The released versions or use latest to always use the latest version of Hugo.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/github-actions-specify-hugo-version/jsonld.json b/posts/github-actions-specify-hugo-version/jsonld.json new file mode 100644 index 000000000..4dc3ac4ae --- /dev/null +++ b/posts/github-actions-specify-hugo-version/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Use a specific Hugo version with GitHub Actions","headline":"Use a specific Hugo version with GitHub Actions","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-08-24","datePublished":"2020-08-24","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/","wordCount":"63","genre":["github","github actions","hugo"],"keywords":["github","github actions","hugo"]} \ No newline at end of file diff --git a/posts/github-actions-specify-java-version/index.html b/posts/github-actions-specify-java-version/index.html new file mode 100644 index 000000000..f31e0146f --- /dev/null +++ b/posts/github-actions-specify-java-version/index.html @@ -0,0 +1,27 @@ +Sebastian Hoß – Use a specific Java version with GitHub Actions

Use a specific Java version with GitHub Actions +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-github-actions" href="/tags/github-actions">github actions</a>, and <a class="post-tag post-tag-java" href="/tags/java">java</a>

The setup-java action allows to use a specific Java version in your GitHub Action.

name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Set up JDK <JDK_VERSION>
+        uses: actions/setup-java@v1
+        with:
+          java-version: <JDK_VERSION>
+
  • <PIPELINE>: The name of your pipeline.
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • <JDK_VERSION>: The required Java version for your project.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/github-actions-specify-java-version/jsonld.json b/posts/github-actions-specify-java-version/jsonld.json new file mode 100644 index 000000000..ef45042cb --- /dev/null +++ b/posts/github-actions-specify-java-version/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Use a specific Java version with GitHub Actions","headline":"Use a specific Java version with GitHub Actions","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-08-10","datePublished":"2020-08-10","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/","wordCount":"58","genre":["github","github actions","java"],"keywords":["github","github actions","java"]} \ No newline at end of file diff --git a/posts/github-actions-upload-release-assets/index.html b/posts/github-actions-upload-release-assets/index.html new file mode 100644 index 000000000..ec5e99216 --- /dev/null +++ b/posts/github-actions-upload-release-assets/index.html @@ -0,0 +1,33 @@ +Sebastian Hoß – Upload release assets with GitHub Actions

Upload release assets with GitHub Actions +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-asset" href="/tags/asset">asset</a>, <a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-github-actions" href="/tags/github-actions">github actions</a>, <a class="post-tag post-tag-release" href="/tags/release">release</a>, and <a class="post-tag post-tag-upload" href="/tags/upload">upload</a>

The actions/upload-release-asset action allows to upload a release artifact in your GitHub Action.

name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Upload Release Asset
+        id: upload_release_asset
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./some/path/to/file.zip
+          asset_name: public-name-for-file.zip
+          asset_content_type: application/zip
+
  • <PIPELINE>: The name of your pipeline.
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/github-actions-upload-release-assets/jsonld.json b/posts/github-actions-upload-release-assets/jsonld.json new file mode 100644 index 000000000..5a4cc39a6 --- /dev/null +++ b/posts/github-actions-upload-release-assets/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Upload release assets with GitHub Actions","headline":"Upload release assets with GitHub Actions","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-10-19","datePublished":"2020-10-19","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/","wordCount":"63","genre":["github","github actions","release","asset","upload"],"keywords":["github","github actions","release","asset","upload"]} \ No newline at end of file diff --git a/posts/github-maven-packages/index.html b/posts/github-maven-packages/index.html new file mode 100644 index 000000000..5cf20a4e3 --- /dev/null +++ b/posts/github-maven-packages/index.html @@ -0,0 +1,61 @@ +Sebastian Hoß – GitHub Packages with Maven

GitHub Packages with Maven +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-github-packages" href="/tags/github-packages">github packages</a>, and <a class="post-tag post-tag-maven" href="/tags/maven">maven</a>

GitHub Packages can be used to host Maven packages with the following configuration in your ~/.m2/settings.xml:

<settings>
+  <profiles>
+    <profile>
+      <id>github</id>
+      <repositories>
+        <repository>
+          <id>maven-build-process</id>
+          <name>GitHub maven-build-process Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/maven-build-process</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+        <repository>
+          <id>hcf4j</id>
+          <name>GitHub hcf4j Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/hcf4j</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+    </profile>
+  </profiles>
+  <servers>
+    <server>
+      <id>maven-build-process</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+    <server>
+      <id>hcf4j</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+  </servers>
+</settings>
+

You will have to add another repository/server for each project you are fetching from GitHub.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/github-maven-packages/jsonld.json b/posts/github-maven-packages/jsonld.json new file mode 100644 index 000000000..fe0820bf8 --- /dev/null +++ b/posts/github-maven-packages/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"GitHub Packages with Maven","headline":"GitHub Packages with Maven","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/github-maven-packages/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-11-22","datePublished":"2021-11-22","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/github-maven-packages/","wordCount":"82","genre":["maven","github","github packages"],"keywords":["maven","github","github packages"]} \ No newline at end of file diff --git a/posts/gitlab-distributor/index.html b/posts/gitlab-distributor/index.html new file mode 100644 index 000000000..17bb39fe0 --- /dev/null +++ b/posts/gitlab-distributor/index.html @@ -0,0 +1,36 @@ +Sebastian Hoß – GitLab the Git Distributor

GitLab the Git Distributor +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-bitbucket" href="/tags/bitbucket">bitbucket</a>, <a class="post-tag post-tag-codeberg" href="/tags/codeberg">codeberg</a>, <a class="post-tag post-tag-git" href="/tags/git">git</a>, <a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-gitlab" href="/tags/gitlab">gitlab</a>, and <a class="post-tag post-tag-repo.or.cz" href="/tags/repo.or.cz">repo.or.cz</a>

Git at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. Pro Git rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.

While the central server approach is easy to use, it might not work in all scenarios:

  1. An enterprise wants to share internal code as part of an open source project. All development is happening internally, and the public mirror gets an occasional update once in a while.
  2. To protect against outages of the central server, mirrors should be created and be kept up-to-date.

In case of the first scenario, tools like copybara, repoSpanner, or distributed-Git-forks offer a wide range of features to cover most details.

The second scenario can be solved manually with tools like gitomatic or automatically with GitLab’s mirror feature quite easy. GitLab allows to create a single pull-mirror and multiple push-mirrors. Therefore, it can be used to pull from your central server and push into all mirrors.

NOTE: This feature was previously available in the free tier but has now moved to GitLab Ultimate.

               +----------------+               
+               |     GitHub     |               
+               +----------------+               
+                        ^                       
+                        |                       
+                        |                       
+               +----------------+               
+         +-----|     GitLab     |------+        
+         |     +----------------+      |        
+         |                             |        
+         |                             |        
+         v                             v        
++----------------+            +----------------+
+|    Codeberg    |            |    BitBucket   |
++----------------+            +----------------+
+

To create such a setup, follow these steps:

  1. Go to Settings > Repository and expand Mirroring repositories +Code Flow
  2. Enter the URL of your central Git repository as the pull source, for example https://github.com/metio/ilo.git +Code Flow
  3. Enter one push target for each mirror. Since pushing usually requires authentication, verify that the URL of the mirror contains a username, for example https://YOUR_USER@codeberg.org/metio.wtf/ilo.git. Add an access token for each mirror and select password as authentication method. +Code Flow

In case you prefer SSH keys over HTTP access tokens, just select SSH public key as authentication method and verify that your key is both saved in GitLab and all mirrors.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/gitlab-distributor/jsonld.json b/posts/gitlab-distributor/jsonld.json new file mode 100644 index 000000000..05f4904c6 --- /dev/null +++ b/posts/gitlab-distributor/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["configuration"],"name":"GitLab the Git Distributor","headline":"GitLab the Git Distributor","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/gitlab-distributor/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-07-13","datePublished":"2020-07-13","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/gitlab-distributor/","wordCount":"339","genre":["git","gitlab","github","codeberg","bitbucket","repo.or.cz"],"keywords":["git","gitlab","github","codeberg","bitbucket","repo.or.cz"]} \ No newline at end of file diff --git a/posts/home-network-hostnames/index.html b/posts/home-network-hostnames/index.html new file mode 100644 index 000000000..9ab8a1a4e --- /dev/null +++ b/posts/home-network-hostnames/index.html @@ -0,0 +1,23 @@ +Sebastian Hoß – Proper hostnames in your local network

Proper hostnames in your local network +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-hostnames" href="/tags/hostnames">hostnames</a>, and <a class="post-tag post-tag-rfc" href="/tags/rfc">rfc</a>

Thanks to RFC 8375, we now have a proper domain to use for all our local devices. Simply move everything underneath .home.arpa to join the fun. In case you have hostnamectl available on your system run the following command to change the hostname of a device:

# set hostname
+$ hostnamectl hostname some-device.home.arpa
+
+# check hostname
+$ hostnamectl status
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/home-network-hostnames/jsonld.json b/posts/home-network-hostnames/jsonld.json new file mode 100644 index 000000000..8033a778f --- /dev/null +++ b/posts/home-network-hostnames/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["network"],"name":"Proper hostnames in your local network","headline":"Proper hostnames in your local network","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/home-network-hostnames/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2022","dateCreated":"2022-01-08","datePublished":"2022-01-08","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/home-network-hostnames/","wordCount":"59","genre":["hostnames","rfc"],"keywords":["hostnames","rfc"]} \ No newline at end of file diff --git a/posts/hugo-atom/index.html b/posts/hugo-atom/index.html new file mode 100644 index 000000000..f72b11d3c --- /dev/null +++ b/posts/hugo-atom/index.html @@ -0,0 +1,152 @@ +Sebastian Hoß – Atom Feed with Hugo

Atom Feed with Hugo +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-atom" href="/tags/atom">atom</a>, and <a class="post-tag post-tag-hugo" href="/tags/hugo">hugo</a>

To publish Atom feeds for your Hugo site, configure a media type in your config.toml:

[mediaTypes."application/atom+xml"]
+  suffixes = ["xml"]
+[outputFormats.Atom]
+  name = "Atom"
+  mediaType = "application/atom+xml"
+  baseName = "atom"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/list.atom.xml with the following content:

{{ printf `<?xml version="1.0" encoding="utf-8"?>` | safeHTML }}
+<feed xmlns="http://www.w3.org/2005/Atom"{{ with site.LanguageCode }} xml:lang="{{ . }}"{{ end }}>
+    <generator uri="https://gohugo.io/" version="{{ hugo.Version }}">Hugo</generator>
+    {{- $title := site.Title -}}
+    {{- with .Title -}}
+        {{- if (not (eq . site.Title)) -}}
+            {{- $title = printf `%s %s %s` . (i18n "feed_title_on" | default "on") site.Title -}}
+        {{- end -}}
+    {{- end -}}
+    {{- if .IsTranslated -}}
+        {{ $title = printf "%s (%s)" $title (index site.Data.i18n.languages .Lang) }}
+    {{- end -}}
+    {{ printf `<title type="html"><![CDATA[%s]]></title>` $title | safeHTML }}
+    {{ with (or (.Param "subtitle") (.Param "tagline")) }}
+        {{ printf `<subtitle type="html"><![CDATA[%s]]></subtitle>` . | safeHTML }}
+    {{ end }}
+    {{ $output_formats := .OutputFormats }}
+    {{ range $output_formats -}}
+        {{- $rel := (or (and (eq "atom" (.Name | lower)) "self") "alternate") -}}
+        {{ with $output_formats.Get .Name }}
+            {{ printf `<link href=%q rel=%q type=%q title=%q />` .Permalink $rel .MediaType.Type .Name | safeHTML }}
+        {{- end -}}
+    {{- end }}
+    {{- range .Translations }}
+        {{ $output_formats := .OutputFormats }}
+        {{- $lang := .Lang }}
+        {{- $langstr := index site.Data.i18n.languages .Lang }}
+        {{ range $output_formats -}}
+            {{ with $output_formats.Get .Name }}
+                {{ printf `<link href=%q rel="alternate" type=%q hreflang=%q title="[%s] %s" />` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }}
+            {{- end -}}
+        {{- end }}
+    {{- end }}
+    <updated>{{ now.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+    {{ with site.Copyright }}
+        {{- $copyright := replace . "{year}" now.Year -}} {{/* In case the site.copyright uses a special string "{year}" */}}
+        {{- $copyright = replace $copyright "&copy;" "©" -}}
+        <rights>{{ $copyright | plainify }}</rights>
+    {{- end }}
+    {{ with .Param "feed" }}
+        {{/* For this to work, the $icon file should be present in the assets/ directory */}}
+        {{- $icon := .icon | default "icon.svg" -}}
+        {{- with resources.Get $icon -}}
+            <icon>{{ (. | fingerprint).Permalink }}</icon>
+        {{- end }}
+
+        {{/* For this to work, the $logo file should be present in the assets/ directory */}}
+        {{- $logo := .logo | default "logo.svg" -}}
+        {{- with resources.Get $logo -}}
+            <logo>{{ (. | fingerprint).Permalink }}</logo>
+        {{- end }}
+    {{ end }}
+    {{ with site.Author.name -}}
+        <author>
+            <name>{{ . }}</name>
+            {{ with site.Author.email }}
+                <email>{{ . }}</email>
+            {{ end -}}
+        </author>
+    {{- end }}
+    {{ with site.Params.id }}
+        <id>{{ . | plainify }}</id>
+    {{ else }}
+        <id>{{ .Permalink }}</id>
+    {{ end }}
+    {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }}
+    {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}}
+    {{/* Range through only the pages with a Type in $feed_sections. */}}
+    {{- $pages := where .RegularPages "Type" "in" $feed_sections -}}
+    {{- if (eq .Kind "home") -}}
+        {{- $pages = where site.RegularPages "Type" "in" $feed_sections -}}
+    {{- end -}}
+    {{/* Remove the pages that have the disable_feed parameter set to true. */}}
+    {{- $pages = where $pages ".Params.disable_feed" "!=" true -}}
+    {{- range first $limit $pages }}
+        {{ $page := . }}
+        <entry>
+            {{ printf `<title type="html"><![CDATA[%s]]></title>` .Title | safeHTML }}
+            <link href="{{ .Permalink }}?utm_source=atom_feed" rel="alternate" type="text/html" />
+            {{- range .Translations }}
+                {{- $link := printf "%s?utm_source=atom_feed" .Permalink | safeHTML }}
+                {{- printf `<link href=%q rel="alternate" type="text/html" hreflang=%q />` $link .Lang | safeHTML }}
+            {{- end }}
+            {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}}
+            {{- range first 5 (site.RegularPages.Related .) }}
+                <link href="{{ .Permalink }}?utm_source=atom_feed" rel="related" type="text/html" title="{{ .Title }}" />
+            {{- end }}
+            {{ with .Params.id }}
+                <id>{{ . | plainify }}</id>
+            {{ else }}
+                <id>{{ .Permalink }}</id>
+            {{ end }}
+            {{ with .Params.author -}}
+                {{- range . -}} <!-- Assuming the author front-matter to be a list -->
+                    <author>
+                        <name>{{ . }}</name>
+                    </author>
+                {{- end -}}
+            {{- end }}
+            <published>{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</published>
+            <updated>{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+            {{ $description1 := .Description | default "" }}
+            {{ $description := (cond (eq "" $description1) "" (printf "<blockquote>%s</blockquote>" ($description1 | markdownify))) }}
+            {{ printf `<content type="html"><![CDATA[%s%s]]></content>` $description .Content | safeHTML }}
+            {{ with site.Taxonomies }}
+                {{ range $taxo,$_ := . }} <!-- Defaults taxos: "tags", "categories" -->
+                    {{ with $page.Param $taxo }}
+                        {{ $taxo_list := . }} <!-- $taxo_list will be the tags/categories list -->
+                        {{ with site.GetPage (printf "/%s" $taxo) }}
+                            {{ $taxonomy_page := . }}
+                            {{ range $taxo_list }} <!-- Below, assuming pretty URLs -->
+                                <category scheme="{{ printf "%s%s" $taxonomy_page.Permalink (. | urlize) }}" term="{{ (. | urlize) }}" label="{{ . }}" />
+                            {{ end }}
+                        {{ end }}
+                    {{ end }}
+                {{ end }}
+            {{ end }}
+        </entry>
+    {{ end }}
+</feed>
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/hugo-atom/jsonld.json b/posts/hugo-atom/jsonld.json new file mode 100644 index 000000000..71177fd05 --- /dev/null +++ b/posts/hugo-atom/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet","website"],"name":"Atom Feed with Hugo","headline":"Atom Feed with Hugo","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/hugo-atom/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-11-16","datePublished":"2020-11-16","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/hugo-atom/","wordCount":"775","genre":["hugo","atom"],"keywords":["hugo","atom"]} \ No newline at end of file diff --git a/posts/hugo-bundles/index.html b/posts/hugo-bundles/index.html new file mode 100644 index 000000000..e8066861f --- /dev/null +++ b/posts/hugo-bundles/index.html @@ -0,0 +1,36 @@ +Sebastian Hoß – Bundling with Hugo

Bundling with Hugo +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-assets" href="/tags/assets">assets</a>, <a class="post-tag post-tag-bundle" href="/tags/bundle">bundle</a>, and <a class="post-tag post-tag-hugo" href="/tags/hugo">hugo</a>

Hugo allows bundling of assets with several built-in functions:

{{ $normalize := resources.Get "/css/normalize.css" }}
+{{ $font := resources.Get "/css/font.css" }}
+{{ $header := resources.Get "/css/header.css" }}
+{{ $footer := resources.Get "/css/footer.css" }}
+{{ $navigation := resources.Get "/css/navigation.css" }}
+{{ $navigation_mobile := resources.Get "/css/navigation-mobile.css" }}
+{{ $layout := resources.Get "/css/layout.css" }}
+{{ $layout_mobile := resources.Get "/css/layout-mobile.css" }}
+{{ $syntax := resources.Get "/css/syntax.css" }}
+{{ $darkmode := resources.Get "/css/darkmode.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat "css/base.css" | resources.Minify | resources.Fingerprint "sha512" }}
+{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat "css/mobile.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+<link href="{{ $base.Permalink }}" integrity="{{ $base.Data.Integrity }}" media="screen" rel="stylesheet">
+<link href="{{ $mobile.Permalink }}" integrity="{{ $mobile.Data.Integrity }}" media="screen and (max-width: 800px)" rel="stylesheet">
+
+<link href="{{ $darkmode.Permalink }}" integrity="{{ $darkmode.Data.Integrity }}" media="screen and (prefers-color-scheme: dark)" rel="stylesheet">
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/hugo-bundles/jsonld.json b/posts/hugo-bundles/jsonld.json new file mode 100644 index 000000000..1a469d347 --- /dev/null +++ b/posts/hugo-bundles/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet","website"],"name":"Bundling with Hugo","headline":"Bundling with Hugo","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/hugo-bundles/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-12-28","datePublished":"2020-12-28","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/hugo-bundles/","wordCount":"142","genre":["hugo","bundle","assets"],"keywords":["hugo","bundle","assets"]} \ No newline at end of file diff --git a/posts/hugo-foaf/index.html b/posts/hugo-foaf/index.html new file mode 100644 index 000000000..d90a71652 --- /dev/null +++ b/posts/hugo-foaf/index.html @@ -0,0 +1,54 @@ +Sebastian Hoß – FOAF with Hugo

FOAF with Hugo +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-foaf" href="/tags/foaf">foaf</a>, and <a class="post-tag post-tag-hugo" href="/tags/hugo">hugo</a>

To publish a FOAF document with your Hugo site, configure a media type in your config.toml:

[mediaTypes."application/rdf+xml"]
+  suffixes = ["rdf"]
+[outputFormats.Foaf]
+  name = "FOAF"
+  mediaType = "application/rdf+xml"
+  baseName = "foaf"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.foaf.rdf with the following content:

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/">
+  <foaf:PersonalProfileDocument rdf:about="">
+    <foaf:maker rdf:resource="#me" />
+    <foaf:primaryTopic rdf:resource="{{ .Site.Title }}" />
+  </foaf:PersonalProfileDocument>
+
+  <foaf:Project rdf:ID="{{ .Site.Title }}">
+    <foaf:name>{{ .Site.Title }}</foaf:name>
+    <foaf:homepage rdf:resource="{{ .Site.BaseURL }}" />
+  </foaf:Project>
+
+  {{ range $.Site.Data.contributors }}
+  <foaf:Person rdf:ID="{{ .id }}">
+    <foaf:name>{{ .first_name }} {{ .last_name }}</foaf:name>
+    <foaf:title>{{ .title }}</foaf:title>
+    <foaf:givenname>{{ .first_name }}</foaf:givenname>
+    <foaf:family_name>{{ .last_name }}</foaf:family_name>
+    <foaf:mbox rdf:resource="mailto:{{ .email }}" />
+    <foaf:homepage rdf:resource="{{ .website }}" />
+  </foaf:Person>
+  {{ end }}
+</rdf:RDF>
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/hugo-foaf/jsonld.json b/posts/hugo-foaf/jsonld.json new file mode 100644 index 000000000..ba35dd75b --- /dev/null +++ b/posts/hugo-foaf/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet","website"],"name":"FOAF with Hugo","headline":"FOAF with Hugo","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/hugo-foaf/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-11-30","datePublished":"2020-11-30","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/hugo-foaf/","wordCount":"121","genre":["hugo","foaf"],"keywords":["hugo","foaf"]} \ No newline at end of file diff --git a/posts/hugo-humans/index.html b/posts/hugo-humans/index.html new file mode 100644 index 000000000..0ff1cff81 --- /dev/null +++ b/posts/hugo-humans/index.html @@ -0,0 +1,37 @@ +Sebastian Hoß – humans.txt with Hugo

humans.txt with Hugo +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-hugo" href="/tags/hugo">hugo</a>, and <a class="post-tag post-tag-humans.txt" href="/tags/humans.txt">humans.txt</a>

To publish a humans.txt document with your Hugo site, configure a media type in your config.toml:

[mediaTypes."text/plain"]
+  suffixes = ["txt"]
+[outputFormats.Humans]
+  name = "Humans"
+  mediaType = "text/plain"
+  baseName = "humans"
+  isPlainText = true
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.humans.txt with the following content:

/* TEAM */
+{{ range $.Site.Data.contributors }}
+{{ .title }}: {{ .first_name }} {{ .last_name }}
+Site: {{ .website }}
+{{ end }}
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/hugo-humans/jsonld.json b/posts/hugo-humans/jsonld.json new file mode 100644 index 000000000..820147159 --- /dev/null +++ b/posts/hugo-humans/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet","website"],"name":"humans.txt with Hugo","headline":"humans.txt with Hugo","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/hugo-humans/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-12-14","datePublished":"2020-12-14","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/hugo-humans/","wordCount":"78","genre":["hugo","humans.txt"],"keywords":["hugo","humans.txt"]} \ No newline at end of file diff --git a/posts/hugo-serviceworkers/index.html b/posts/hugo-serviceworkers/index.html new file mode 100644 index 000000000..e3d3a7c94 --- /dev/null +++ b/posts/hugo-serviceworkers/index.html @@ -0,0 +1,63 @@ +Sebastian Hoß – Service workers with Hugo

Service workers with Hugo +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-hugo" href="/tags/hugo">hugo</a>, and <a class="post-tag post-tag-service-worker" href="/tags/service-worker">service worker</a>

To use a serviceworker to cache a Hugo site, configure a media type in your config.toml:

[mediaTypes."application/javascript"]
+  suffixes = ["js"]
+[outputFormats.ServiceWorker]
+  name = "ServiceWorker"
+  mediaType = "application/javascript"
+  baseName = "serviceworker"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.serviceworker.js with the following content:

const CACHE = 'cache-and-update';
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(precache());
+});
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(fromCache(event.request));
+  event.waitUntil(update(event.request));
+});
+
+const precache = async () => {
+    const cache = await caches.open(CACHE);
+    return await cache.addAll([
+        {{ range $i, $e := .Site.RegularPages }}
+        '{{ $.RelPermalink }}'{{ if $i }}, {{ end }}
+        {{ end }}
+    ]);
+}
+
+const fromCache = async (request) => {
+    const cache = await caches.open(CACHE);
+    const match = await cache.match(request);
+    return match || Promise.reject('no-match');
+}
+
+const update = async (request) => {
+    const cache = await caches.open(CACHE);
+    const response = await fetch(request);
+    return await cache.put(request, response);
+}
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/hugo-serviceworkers/jsonld.json b/posts/hugo-serviceworkers/jsonld.json new file mode 100644 index 000000000..dbc151496 --- /dev/null +++ b/posts/hugo-serviceworkers/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet","website"],"name":"Service workers with Hugo","headline":"Service workers with Hugo","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/hugo-serviceworkers/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-01-25","datePublished":"2021-01-25","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/hugo-serviceworkers/","wordCount":"156","genre":["hugo","service worker"],"keywords":["hugo","service worker"]} \ No newline at end of file diff --git a/posts/hugo-webmanifest/index.html b/posts/hugo-webmanifest/index.html new file mode 100644 index 000000000..6531611ba --- /dev/null +++ b/posts/hugo-webmanifest/index.html @@ -0,0 +1,40 @@ +Sebastian Hoß – Web app manifests with Hugo

Web app manifests with Hugo +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-hugo" href="/tags/hugo">hugo</a>, <a class="post-tag post-tag-manifest" href="/tags/manifest">manifest</a>, and <a class="post-tag post-tag-web-app" href="/tags/web-app">web app</a>

To publish a web app manifest document with your Hugo site, configure a media type in your config.toml:

[mediaTypes."application/manifest+json"]
+  suffixes = ["webmanifest"]
+[outputFormats.Webmanifest]
+  name = "Web App Manifest"
+  mediaType = "application/manifest+json"
+  baseName = "manifest"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.manifest.json with the following content:

{
+  "name": "{{ .Site.Title }}",
+  "short_name": "{{ .Site.Title }}",
+  "start_url": ".",
+  "display": "minimal-ui",
+  "background_color": "#fff",
+  "description": "{{ .Site.Params.description }}"
+}
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/hugo-webmanifest/jsonld.json b/posts/hugo-webmanifest/jsonld.json new file mode 100644 index 000000000..d8acd1e41 --- /dev/null +++ b/posts/hugo-webmanifest/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet","website"],"name":"Web app manifests with Hugo","headline":"Web app manifests with Hugo","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/hugo-webmanifest/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-01-11","datePublished":"2021-01-11","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/hugo-webmanifest/","wordCount":"81","genre":["hugo","web app","manifest"],"keywords":["hugo","web app","manifest"]} \ No newline at end of file diff --git a/posts/index.html b/posts/index.html new file mode 100644 index 000000000..d44e5a0a8 --- /dev/null +++ b/posts/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Posts

Posts

Pages

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/index.xml b/posts/index.xml new file mode 100644 index 000000000..18dbfb8ca --- /dev/null +++ b/posts/index.xml @@ -0,0 +1,884 @@ +Posts on Sebastian Hoßhttps://seb.xn--ho-hia.de/posts/Recent content in Posts on Sebastian HoßHugoenMon, 09 Jan 2023 07:05:35 +0100jspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/Mon, 09 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/jspecify/<p>Every <a href="https://www.java.com/">Java</a> developer has probably encountered a <code>NullPointerException</code> at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="n">someName</span><span class="p">;</span><span class="w"> </span><span class="c1">// value is &#39;null&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">someName</span><span class="p">.</span><span class="na">toUpperCase</span><span class="p">();</span><span class="w"> </span><span class="c1">// throws NullPointerException</span><span class="w"> +</span></span></span></code></pre></div><p>Modern <a href="https://en.wikipedia.org/wiki/Integrated_development_environment">IDEs</a> have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is <code>null</code> and therefore a potential for a <code>NullPointerException</code> is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be <code>null</code>. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.</p>Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/Sun, 08 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-age/<p><a href="https://age-encryption.org/">age</a> is another tool supported by <a href="https://www.chezmoi.io/">chezmoi</a> to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. Compared to <code>gpg</code> it is much simpler by focusing on the encryption parts only.</p> +<p>Add the following snippet to your <code>.chezmoi.toml</code> to configure <code>chezmoi</code> to use <code>age</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">encryption</span> <span class="p">=</span> <span class="s2">&#34;age&#34;</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">age</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">identity</span> <span class="p">=</span> <span class="s2">&#34;path/to/age/private-key&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;age...public...key...&#34;</span> +</span></span></code></pre></div><p>Adding files to your <code>chezmoi</code> source directory remains the same as compared to using <code>gpg</code> - just call <code>chezmoi add --encrypt path/to/file</code>.</p>Multiple Git Configurationshttps://seb.xn--ho-hia.de/posts/multiple-git-configs/Thu, 05 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/multiple-git-configs/<p>To split yet re-use as much configuration for Git as possible, you can create one root configuration that looks similar to this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span> +</span></span><span class="line"><span class="cl"> <span class="na">name</span> <span class="o">=</span> <span class="s">Your Name Here</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[includeIf &#34;gitdir:~/git/personal/&#34;]</span> +</span></span><span class="line"><span class="cl"> <span class="na">path</span> <span class="o">=</span> <span class="s">~/.config/git/personal</span> +</span></span><span class="line"><span class="cl"><span class="k">[includeIf &#34;gitdir:~/git/work/&#34;]</span> +</span></span><span class="line"><span class="cl"> <span class="na">path</span> <span class="o">=</span> <span class="s">~/.config/git/work</span> +</span></span></code></pre></div><p>The <a href="https://git-scm.com/docs/git-config#_includes">includeIf</a> directive supports multiple keywords. In my case, work and personal projects have a different root directory, therefore I can filter based on the location using <code>gitdir</code>. The personal Git configuration simply looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span> +</span></span><span class="line"><span class="cl"> <span class="na">email</span> <span class="o">=</span> <span class="s">personal.email@example.com</span> +</span></span></code></pre></div><p>and the work related configuration like this using a different email address:</p>passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/passage-fuzzy-search/<p>To fuzzy search through passwords managed with <a href="https://github.com/FiloSottile/passage">passage</a>, I&rsquo;ve written the following script that is inspired by the upstream version which is using <code>fzf</code>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">fd --type<span class="o">=</span>file --base-directory<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PASSAGE_DIR</span><span class="k">:-</span><span class="si">${</span><span class="nv">HOME</span><span class="si">}</span><span class="p">/.passage/store</span><span class="si">}</span><span class="s2">&#34;</span> .age --exec <span class="nb">echo</span> <span class="s1">&#39;{.}&#39;</span> <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> sk --cycle --layout<span class="o">=</span>reverse --tiebreak<span class="o">=</span>score --no-multi <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> xargs --replace --max-args<span class="o">=</span><span class="m">1</span> --no-run-if-empty <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> passage show --clip<span class="o">=</span><span class="m">1</span> <span class="o">{}</span> +</span></span></code></pre></div><p>This version requires <a href="https://github.com/sharkdp/fd/">fd</a>, <a href="https://github.com/lotabout/skim">skim</a>, <a href="https://www.gnu.org/software/findutils/manual/html_node/find_html/Invoking-xargs.html">xargs</a>, and <a href="https://github.com/FiloSottile/passage">passage</a> itself of course. The detailed breakdown on how it works is as follows:</p> +<ol> +<li>Use <code>fd</code> to find all files within <code>${PASSAGE_DIR}</code> that end in <code>.age</code>. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.</li> +<li>Using both <code>--base-directory</code> and <code>--exec echo '{.}'</code> ensures that passwords are returned in such form that they can be passed back into <code>passage</code> again. The placeholder <code>'{.}'</code> is a feature provided by <code>fd</code> which strips the file extension from each returned value.</li> +<li>All passwords are then passed into <code>sk</code> to allow to fuzzy search across them all. Setting <code>--no-multi</code> ensures that only a single password can be selected.</li> +<li>Finally, <code>xargs</code> calls <code>passage</code> and replaces the curly braces with the selected password. Thanks to <code>--clip=1</code>, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.</li> +</ol> +<p>To call that script, I&rsquo;ve saved it as <code>passage-fuzzy-search.sh</code> in my <code>.local/bin</code> folder and added some checks into it to verify that every required software is actually installed.</p>chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/Mon, 26 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-update/<p>To automatically synchronize dotfiles across my computers, I&rsquo;ve written the following <code>systemd</code> unit:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-systemd" data-lang="systemd"><span class="line"><span class="cl"><span class="k">[Unit]</span> +</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Update chezmoi managed dotfiles</span> +</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"><span class="na">Wants</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Service]</span> +</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/chezmoi update --no-tty --force</span> +</span></span><span class="line"><span class="cl"><span class="na">RemainAfterExit</span><span class="o">=</span><span class="s">false</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Install]</span> +</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span> +</span></span></code></pre></div><p>This unit pulls changes from upstream first and then applies the changes to the current computer after I&rsquo;m logged in and a network connection is available. The <code>--no-tty</code> flag is required because there is no tty when systemd executes <code>chezmoi</code>. Likewise, the <code>--force</code> flag ensures that no interactive prompt will be displayed which we cannot answer since <code>systemd</code> is executing this unit without us being involved.</p>chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/Mon, 12 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/shell-init/<p>Many CLI applications offer initialization scripts to integrate into a shell, for example <code>starship init zsh</code> or <code>zoxide init zsh</code>. The documentation of these tools usually tell you to put something like <code>eval &quot;$(starship init zsh)&quot;</code> into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the <code>init</code> command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.</p>awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/Mon, 14 Feb 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/awsenv/<p>To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the <code>AWS_PROFILE</code> environment variable which is used by many tools that interact with the AWS API, like <code>awscli</code> or <code>terraform</code>. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses <code>aws sso login</code> to perform an MFA login into AWS. The AWS profiles must be set up in such a way that <code>aws configure list-profiles</code> can detect them, which is typically done by adding them in <code>${AWS_CONFIG_FILE:-$HOME/.aws/config}</code>.</p>Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/Sat, 29 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/clojure-java-interoperability/<p>Clojure has several <a href="https://clojure.org/java_interop">forms and macros</a> to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.</p> +<h2 id="using-gen-class">Using <code>gen-class</code></h2> +<p>Clojure code can be <a href="https://clojure.org/compilation">compiled</a> to standard JVM bytecode using <a href="https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/gen-class">gen-class</a>.</p> +<h3 id="adding-static-modifiers">Adding static modifiers</h3> +<p>Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class <code>com.example.Computation</code>.</p>Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/Sat, 15 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/<p>One feature that often surprises people while teaching them <a href="https://reactjs.org/">React</a> is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.</p> +<p>In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.</p>Proper hostnames in your local networkhttps://seb.xn--ho-hia.de/posts/home-network-hostnames/Sat, 08 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/home-network-hostnames/<p>Thanks to <a href="https://www.rfc-editor.org/rfc/rfc8375.html">RFC 8375</a>, we now have a proper domain to use for all our local devices. Simply move everything underneath <code>.home.arpa</code> to join the fun. In case you have <code>hostnamectl</code> available on your system run the following command to change the hostname of a device:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> <span class="nb">set</span> hostname +</span></span><span class="line"><span class="cl"><span class="gp">$</span> hostnamectl hostname some-device.home.arpa +</span></span><span class="line"><span class="cl"><span class="err"> +</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="gp">#</span> check hostname +</span></span><span class="line"><span class="cl"><span class="gp">$</span> hostnamectl status +</span></span></code></pre></div>Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/<p>Both <a href="https://www.vim.org/">Vim</a> and <a href="https://neovim.io/">Neovim</a> have a <a href="https://vimhelp.org/repeat.txt.html#packages">built-in</a> <a href="https://neovim.io/doc/user/usr_05.html#plugin">plugin mechanism</a> that loads plugins from <code>~/.vim/pack/*/{start,opt}/*</code> (Vim) or <code>~/.local/share/nvim/site/pack/*/{start,opt}/*</code> (Neovim). All you have to do to install new plugins, is to <code>git clone</code> their repository into those directories. To automatically update those clones, create the following script:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env zsh +</span></span></span><span class="line"><span class="cl"><span class="cp"></span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"><span class="c1"># This script git-pulls all installed nvim plugins which are using the built-in</span> +</span></span><span class="line"><span class="cl"><span class="c1"># nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack</span> +</span></span><span class="line"><span class="cl"><span class="c1">#</span> +</span></span><span class="line"><span class="cl"><span class="c1"># Required software that is not in GNU coreutils:</span> +</span></span><span class="line"><span class="cl"><span class="c1"># - &#39;git&#39; to fetch plugin updates from upstream</span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### User specific variables, adjust to your own needs</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># folder that contains all nvim plugins</span> +</span></span><span class="line"><span class="cl"><span class="nv">PLUGIN_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_DATA_HOME</span><span class="k">:-</span><span class="nv">$HOME</span><span class="p">/.local/share</span><span class="si">}</span><span class="s2">/nvim/site/pack&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### Script logic</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;updating all plugins in </span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"><span class="c1"># iterate through all directories and git pull em</span> +</span></span><span class="line"><span class="cl"><span class="k">for</span> directory in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span>/*/<span class="o">{</span>start,opt<span class="o">}</span>/*<span class="p">;</span> <span class="k">do</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> +</span></span><span class="line"><span class="cl"> <span class="nv">plugin</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;updating </span><span class="si">${</span><span class="nv">plugin</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"> git -C <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> pull --quiet +</span></span><span class="line"><span class="cl"> <span class="k">fi</span> +</span></span><span class="line"><span class="cl"><span class="k">done</span> +</span></span></code></pre></div><p>In case you are using Vim, adjust the <code>PLUGIN_DIR</code> variable to point to your Vim plugin directory instead and save the resulting shell script as a file called <code>update-nvim-plugins.sh</code> in some folder of your choice. Do not forget to set the executable bit with <code>chmod +x /path/to/your/folder/update-nvim-plugins.sh</code>. Since all good developers must be lazy, write the following <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html">systemd service</a> to execute the above script automatically:</p>Manage tmux sessions with tmuxphttps://seb.xn--ho-hia.de/posts/tmux-tmuxp/Mon, 20 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-tmuxp/<p>To manage <a href="https://github.com/tmux/tmux">tmux</a> sessions, I like to use <a href="https://github.com/tmux-python/tmuxp">tmuxp</a>. It works by having pre-defined sessions in <code>~/.config/tmuxp</code> which looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">session_name</span><span class="p">:</span><span class="w"> </span><span class="l">cool-app</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">~/projects/cool-app</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">windows</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">window_name</span><span class="p">:</span><span class="w"> </span><span class="l">backend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">backend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">window_name</span><span class="p">:</span><span class="w"> </span><span class="l">frontend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">frontend</span><span class="w"> +</span></span></span></code></pre></div><p>In case the name of the file is <code>cool-app.yaml</code>, you can open the sessions with <code>tmuxp load cool-app --yes</code>.</p>Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/Mon, 06 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-cd-versioning/<p>To automatically version <a href="https://maven.apache.org/">Maven</a> projects, I like to use the <a href="https://www.mojohaus.org/versions-maven-plugin/">m-versions-p</a> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ mvn versions:set -DnewVersion<span class="o">=</span>my.new.version -DgenerateBackupPoms<span class="o">=</span><span class="nb">false</span> +</span></span></code></pre></div><p>This will update the <code>version</code> property of every module in the reactor to prepare them for the next release. In case you are using <a href="https://github.com/features/actions">GitHub Actions</a>, consider using a <a href="../github-actions-create-timestamp">timestamp</a>.</p>GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/Mon, 22 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-maven-packages/<p><a href="https://github.com/features/packages">GitHub Packages</a> can be used to host <a href="https://maven.apache.org/">Maven</a> packages with the following configuration in your <code>~/.m2/settings.xml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>github<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub maven-build-process Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/maven-build-process<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub hcf4j Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/hcf4j<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;servers&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/servers&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>You will have to add another repository/server for each project you are fetching from GitHub.</p>Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/Mon, 08 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-google-central/<p>Some time ago, <a href="https://www.google.com/">Google</a> started hosting a <a href="https://storage-download.googleapis.com/maven-central/index.html">copy</a> of <a href="https://search.maven.org/">Maven Central</a>. Configure it in your <code>~/.m2/settings.xml</code> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrors&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (Asia)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-asia.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (EU)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-eu.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (US)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirrors&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Pick the mirror nearest to your location to get best speeds.</p>Backups with emacshttps://seb.xn--ho-hia.de/posts/emacs-backups/Mon, 25 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/emacs-backups/<p><a href="https://www.gnu.org/software/emacs/">emacs</a> will create backups of your files by default. Those backups are located right next to the original file and are called <code>&lt;file&gt;~</code>. Unfortunately, emacs will not clean those up by default, which annoys me from time to time. Therefore, I&rsquo;m now using the following configuration to keep those backups in a different folder:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-el" data-lang="el"><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">version-control</span> <span class="no">t</span> <span class="c1">;; Use version numbers for backups.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kept-new-versions</span> <span class="mi">10</span> <span class="c1">;; Number of newest versions to keep.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kept-old-versions</span> <span class="mi">0</span> <span class="c1">;; Number of oldest versions to keep.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">delete-old-versions</span> <span class="no">t</span> <span class="c1">;; Don&#39;t ask to delete excess backup versions.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">backup-by-copying</span> <span class="no">t</span><span class="p">)</span> <span class="c1">;; Copy all files, don&#39;t rename them.</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">vc-make-backup-files</span> <span class="no">t</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">;; Default and per-save backups go here:</span> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">backup-directory-alist</span> <span class="o">&#39;</span><span class="p">((</span><span class="s">&#34;&#34;</span> <span class="o">.</span> <span class="s">&#34;~/.emacs.d/backup/per-save&#34;</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">defun</span> <span class="nv">force-backup-of-buffer</span> <span class="p">()</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Make a special &#34;per session&#34; backup at the first save of each</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; emacs session.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">when</span> <span class="p">(</span><span class="nv">not</span> <span class="nv">buffer-backed-up</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Override the default parameters for per-session backups.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">backup-directory-alist</span> <span class="o">&#39;</span><span class="p">((</span><span class="s">&#34;&#34;</span> <span class="o">.</span> <span class="s">&#34;~/.emacs.d/backup/per-session&#34;</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">kept-new-versions</span> <span class="mi">3</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">backup-buffer</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Make a &#34;per save&#34; backup on each save. The first save results in</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; both a per-session and a per-save backup, to keep the numbering</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; of per-save backups consistent.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">buffer-backed-up</span> <span class="no">nil</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">backup-buffer</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">add-hook</span> <span class="ss">&#39;before-save-hook</span> <span class="ss">&#39;force-backup-of-buffer</span><span class="p">)</span> +</span></span></code></pre></div><p>Thanks to that configuration, backups per-save will be created in <code>~/.emacs.d/backup/per-save</code> and backups per-session in <code>~/.emacs.d/backup/per-session</code>.</p>Maintaining dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-maintenance/Mon, 11 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-maintenance/<p>To make it easier managing many dotfiles with <a href="https://www.chezmoi.io/">chezmoi</a>, a shell function similar to the one below can be used:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="k">function</span> m-dotfiles-ok <span class="o">{</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># public</span> +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/zsh --recursive +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/sway --recursive +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/tmux --recursive +</span></span><span class="line"><span class="cl"> chezmoi add .... +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># secrets</span> +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ~/.config/npm/npmrc +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ~/.ssh/id_rsa +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ... +</span></span><span class="line"><span class="cl"><span class="o">}</span> +</span></span></code></pre></div><p>Whenever you feel happy with your current setup, just call <code>m-dotfiles-ok</code> to push changes into the chezmoi source directory. Files will automatically be <a href="../chezmoi-gpg">encrypted</a> with <a href="https://www.gnupg.org/">gpg</a> and <a href="../chezmoi-auto-git">committed/pushed</a> into a <a href="https://git-scm.com/">Git</a> repository if you have done the necessary configuration beforehand.</p>Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/Mon, 20 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-gpg/<p><strong>RECOMMENDATION</strong>: Use <a href="../chezmoi-age">age</a> instead of <code>gpg</code>.</p> +<p><a href="https://www.chezmoi.io/">chezmoi</a> can use various external tools to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. <a href="https://www.gnupg.org/">gpg</a> is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure <code>gpg</code> with <code>chezmoi</code>, just set yourself as the recipient like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">gpg</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;your.name@example.com&#34;</span> +</span></span></code></pre></div><p>Calling <code>chezmoi add --encrypt /path/to/secret</code> will now create encrypt the file with your public key which allows you to decrypt them later with your private key.</p>Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/Mon, 06 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-git/<p><a href="https://www.chezmoi.io/">chezmoi</a> can automatically commit and push changes to your <a href="https://en.wikipedia.org/wiki/dotfile">dotfiles</a> into a (remote) <a href="https://git-scm.com/">Git</a> repository. Enable it with the following snippet in your <code>chezmoi.toml</code></p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">sourceVCS</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoCommit</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoPush</span> <span class="p">=</span> <span class="kc">true</span> +</span></span></code></pre></div><p>Every time you call <code>chezmoi add /path/to/file</code> will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.</p>Waybar on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-waybar/Mon, 23 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-waybar/<p><a href="https://github.com/Alexays/Waybar/">Waybar</a> can be used as a status bar for <a href="https://swaywm.org/">SwayWM</a>. You tell Sway to use it with the following snippet in your Sway configuration:</p> +<pre tabindex="0"><code>bar { + swaybar_command waybar +} +</code></pre><p>Configure Waybar itself in <code>~/.config/waybar/config</code>:</p> +<pre tabindex="0"><code>{ + &#34;layer&#34;: &#34;top&#34;, + &#34;modules-left&#34;: [&#34;sway/workspaces&#34;, &#34;sway/mode&#34;], + &#34;modules-center&#34;: [&#34;sway/window&#34;], + &#34;modules-right&#34;: [&#34;clock&#34;], + &#34;sway/window&#34;: { + &#34;max-length&#34;: 50 + }, + &#34;clock&#34;: { + &#34;format-alt&#34;: &#34;{:%a, %d. %b %H:%M}&#34; + } +} +</code></pre>tmux Status Barhttps://seb.xn--ho-hia.de/posts/tmux-status-bar/Mon, 09 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-status-bar/<p>To see the currently active <a href="https://github.com/tmux/tmux">tmux</a> status bar configuration, call:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ tmux show-options -g <span class="p">|</span> grep status +</span></span></code></pre></div><p>Change on of those values with in the current tmux session:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ tmux set-option status-right <span class="s2">&#34;&#34;</span> +</span></span></code></pre></div><p>Persist the change in your <code>tmux.conf</code> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># disable right side of status bar</span> +</span></span><span class="line"><span class="cl">set-option -g status-right <span class="s2">&#34;&#34;</span> +</span></span></code></pre></div>emacs and systemdhttps://seb.xn--ho-hia.de/posts/emacs-systemd/Mon, 26 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/emacs-systemd/<p>I like to use <a href="https://www.gnu.org/software/emacs/">emacs</a> to edit files in a terminal. It tends to start a little slow, therefore I&rsquo;ve created a <a href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</a> unit to automatically start the emacs daemon and use aliases to connect to the running daemon. The unit looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span> +</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Emacs text editor [%I]</span> +</span></span><span class="line"><span class="cl"><span class="na">Documentation</span><span class="o">=</span><span class="s">info:emacs man:emacs(1) https://gnu.org/software/emacs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Service]</span> +</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">forking</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/emacs --daemon=%i</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStop</span><span class="o">=</span><span class="s">/usr/bin/emacsclient --eval &#34;(kill-emacs)&#34;</span> +</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">SSH_AUTH_SOCK=%t/keyring/ssh</span> +</span></span><span class="line"><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">on-failure</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Install]</span> +</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span> +</span></span></code></pre></div><p>Enable it with <code>systemctl --user enable emacs@user</code> and define any number of aliases to make connecting to the emacs daemon easier:</p>Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/Mon, 12 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-push-only-mirror/<p>In case you want to have push-only mirrors for your <a href="https://git-scm.com/">Git</a> repository, consider adding a special mirror remote like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote add mirrors DISABLED +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git +</span></span></code></pre></div><p>The above will create a new remote called <code>mirrors</code> which has no <code>fetch</code> URL and therefore can only be pushed:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote -v +</span></span><span class="line"><span class="cl">mirrors DISABLED <span class="o">(</span>fetch<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@codeberg.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@gitlab.com:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@bitbucket.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span></code></pre></div><p>Calling <code>git push mirrors main:main</code> will push the local <code>main</code> branch into all defined mirrors.</p>Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/Mon, 28 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-mirror/<p>In case you want to make use of the decentralized nature of <a href="https://git-scm.com/">Git</a>, consider using multiple push targets like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote set-url origin --push --add git@example.com/project.git +</span></span><span class="line"><span class="cl">$ git remote set-url origin --push --add git@another.com/project.git +</span></span></code></pre></div><p>Note that the first call to <code>set-url</code> will overwrite an existing remote creating with <code>git clone</code>. Any additional call will actually recognize the <code>--add</code> option and add the new target to an existing remote.</p> +<h2 id="links">Links</h2> +<ul> +<li><a href="../git-push-only-mirror">push only mirrors</a></li> +<li><a href="../gitlab-distributor">gitlab-distributor</a></li> +</ul>Using multiple clusters with kubectlhttps://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/Mon, 14 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/<p>To connect to multiple <a href="https://kubernetes.io/">Kubernetes</a> clusters with <a href="https://kubernetes.io/docs/reference/kubectl/overview/">kubectl</a>, I like to define aliases like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">rancher</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/rancher.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">work</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/work.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">customer</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/customer.config&#34;</span> +</span></span></code></pre></div><p>Those aliases allow me to write things like <code>rancher get pods --namespace some-namespace</code> without worrying the wrong context is active. Using multiple configurations - one for each cluster - seems to be easier to manage since most clusters allow to download a ready-to-use configuration file. Instead of mangling them together manually, I just specify another alias whenever I get to work with another cluster.</p>Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-encoding/<p><a href="https://maven.apache.org/">Maven</a> projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.reporting.outputEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.reporting.outputEncoding&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +</ul>Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/Mon, 17 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-reproducible/<p>To create <a href="https://reproducible-builds.org/">reproducible builds</a> with <a href="https://maven.apache.org/">Maven</a> projects, it&rsquo;s enough to specify the <code>project.build.outputTimestamp</code> property like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.outputTimestamp&gt;</span>2020<span class="nt">&lt;/project.build.outputTimestamp&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +<li><a href="https://maven.apache.org/guides/mini/guide-reproducible-builds.html">https://maven.apache.org/guides/mini/guide-reproducible-builds.html</a></li> +<li><a href="https://github.com/rodiontsev/maven-build-info-plugin">https://github.com/rodiontsev/maven-build-info-plugin</a></li> +<li><a href="https://github.com/phax/ph-buildinfo-maven-plugin">https://github.com/phax/ph-buildinfo-maven-plugin</a></li> +<li><a href="https://github.com/Zlika/reproducible-build-maven-plugin">https://github.com/Zlika/reproducible-build-maven-plugin</a></li> +</ul>Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/Mon, 03 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/<p>To analyze <a href="https://maven.apache.org/">Maven</a> projects with <a href="https://sonarcloud.io">SonarCloud</a> using <a href="https://github.com/features/actions">GitHub Actions</a>, first create the following <code>settings.xml</code> file:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings</span> <span class="na">xmlns=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xmlns:xsi=</span><span class="s">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xsi:schemaLocation=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0 +</span></span></span><span class="line"><span class="cl"><span class="s"> http://maven.apache.org/xsd/settings-1.0.0.xsd&#34;</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroup&gt;</span>org.sonarsource.scanner.maven<span class="nt">&lt;/pluginGroup&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfile&gt;</span>sonar<span class="nt">&lt;/activeProfile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>sonar<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.host.url&gt;</span>https://sonarcloud.io<span class="nt">&lt;/sonar.host.url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.organization&gt;</span>YOUR_ORG<span class="nt">&lt;/sonar.organization&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.projectKey&gt;</span>YOUR_PROJECT<span class="nt">&lt;/sonar.projectKey&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.login&gt;</span>${env.SONAR_TOKEN}<span class="nt">&lt;/sonar.login&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Finally, add a step to your workflow:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Verify Project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">SONAR_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.SONAR_TOKEN }}</span><span class="w"> +</span></span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/settings.html">https://maven.apache.org/settings.html</a></li> +<li><a href="https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/">https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/</a></li> +</ul>Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/Mon, 19 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-login-shell/<p>To use <a href="https://github.com/tmux/tmux">tmux</a> as your login shell, use <code>chsh</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># list all available shells</span> +</span></span><span class="line"><span class="cl">$ chsh --list-shells +</span></span><span class="line"><span class="cl">/bin/sh +</span></span><span class="line"><span class="cl">/bin/bash +</span></span><span class="line"><span class="cl">/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/sh +</span></span><span class="line"><span class="cl">/usr/bin/bash +</span></span><span class="line"><span class="cl">/usr/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/zsh +</span></span><span class="line"><span class="cl">/bin/zsh +</span></span><span class="line"><span class="cl">/usr/bin/tmux +</span></span><span class="line"><span class="cl">/bin/tmux +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># select login shell</span> +</span></span><span class="line"><span class="cl">$ chsh --shell /usr/bin/tmux +</span></span></code></pre></div>Lock your screen on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenlock/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenlock/<p><a href="https://swaywm.org/">SwayWM</a> users can use <a href="https://github.com/swaywm/swaylock">swaylock</a> to lock their screen. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># lock your screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Ctrl+l <span class="nb">exec</span> swaylock --color <span class="m">000000</span> +</span></span></code></pre></div><p><code>$mod+Ctrl+l</code> will lock your screen and turn it to black. The <code>--color</code> flag allows any color in the form of <code>rrggbb[aa]</code>.</p>Peeking with tmuxhttps://seb.xn--ho-hia.de/posts/tmux-peek/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-peek/<p><a href="https://github.com/tmux/tmux">tmux</a> uses can use the following snippet to peek at files. Place it in your <code>.bashrc</code> or similar file.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">peek<span class="o">()</span> <span class="o">{</span> tmux split-window -p <span class="m">33</span> <span class="s2">&#34;</span><span class="nv">$EDITOR</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span> <span class="o">}</span> +</span></span></code></pre></div><p>Calling <code>peek &lt;file&gt;</code> will open <code>&lt;file&gt;</code> in lower third of tmux window.</p>Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/Mon, 22 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenshots/<p><a href="https://swaywm.org/">SwayWM</a> uses can use a mixture of <a href="https://github.com/emersion/grim">grim</a> and <a href="https://github.com/emersion/slurp">slurp</a> to take screenshots of their desktop. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># take screenshot of currently focused screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Print <span class="nb">exec</span> /usr/bin/grim -o <span class="k">$(</span>swaymsg -t get_outputs <span class="p">|</span> jq -r <span class="s1">&#39;.[] | select(.focused) | .name&#39;</span><span class="k">)</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># take screenshot of selection</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Shift+p <span class="nb">exec</span> /usr/bin/grim -g <span class="s2">&#34;</span><span class="k">$(</span>/usr/bin/slurp<span class="k">)</span><span class="s2">&#34;</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span></code></pre></div>Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/Mon, 08 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-readme/<p><code>README</code> file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a <code>Makefile</code> in the root of your project which contains the exact same instructions. Thanks to <code>make</code>, all your contributors can now use TAB-completion to run any of the pre-defined <code>make</code> targets.</p>Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/Mon, 22 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-schedule-build/<p>To delay the execution of an <a href="https://github.com/features/actions">GitHub Action</a>, use a mixture of the <code>on: schedule: ...</code> configuration, and a <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif">conditional build step</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">schedule</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&lt;CRON&gt;&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Count commits in last week</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">commits</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=count::$(git rev-list --count HEAD --since=&#39;&lt;DATE&gt;&#39;)&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">steps.commits.outputs.count &gt; 0</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">build-project</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;CRON&gt;</code>: cron expression - use <a href="https://crontab.guru/">https://crontab.guru/</a>.</li> +<li><code>&lt;DATE&gt;</code>: Git date expression that matches <code>&lt;CRON&gt;</code>.</li> +</ul>Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/Mon, 08 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-ignore/<p>In case you are using a <code>Makefile</code> to define a complex build step - for example start database, run tests, stop database - consider using the <code>-</code> qualifier in front of your actual build step like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">build</span> +</span></span><span class="line"><span class="cl"><span class="nf">build</span><span class="o">:</span> +</span></span><span class="line"><span class="cl"> start-database +</span></span><span class="line"><span class="cl"> -build-software +</span></span><span class="line"><span class="cl"> stop-database +</span></span></code></pre></div><p>Thanks to <code>-</code>, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.</p>Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/Mon, 25 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-serviceworkers/<p>To use a <a href="https://serviceworke.rs/">serviceworker</a> to cache a <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/javascript&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;js&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">ServiceWorker</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;ServiceWorker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/javascript&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;serviceworker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.serviceworker.js</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">const CACHE = &#39;cache-and-update&#39;; + +self.addEventListener(&#39;install&#39;, (event) =&gt; { + event.waitUntil(precache()); +}); + +self.addEventListener(&#39;fetch&#39;, (event) =&gt; { + event.respondWith(fromCache(event.request)); + event.waitUntil(update(event.request)); +}); + +const precache = async () =&gt; { + const cache = await caches.open(CACHE); + return await cache.addAll([ + {{ range $i, $e := .Site.RegularPages }} + &#39;{{ $.RelPermalink }}&#39;{{ if $i }}, {{ end }} + {{ end }} + ]); +} + +const fromCache = async (request) =&gt; { + const cache = await caches.open(CACHE); + const match = await cache.match(request); + return match || Promise.reject(&#39;no-match&#39;); +} + +const update = async (request) =&gt; { + const cache = await caches.open(CACHE); + const response = await fetch(request); + return await cache.put(request, response); +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://github.com/gohugoio/hugo/issues/5495">https://github.com/gohugoio/hugo/issues/5495</a></li> +<li><a href="https://github.com/wildhaber/offline-first-sw">https://github.com/wildhaber/offline-first-sw</a></li> +<li><a href="https://gohugohq.com/howto/go-offline-with-service-worker/">https://gohugohq.com/howto/go-offline-with-service-worker/</a></li> +</ul>Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/Mon, 11 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-webmanifest/<p>To publish a <a href="https://developer.mozilla.org/en-US/docs/Web/Manifest">web app manifest</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/manifest+json&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;webmanifest&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Webmanifest</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Web App Manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/manifest+json&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.manifest.json</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{ + &#34;name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;short_name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;start_url&#34;: &#34;.&#34;, + &#34;display&#34;: &#34;minimal-ui&#34;, + &#34;background_color&#34;: &#34;#fff&#34;, + &#34;description&#34;: &#34;{{ .Site.Params.description }}&#34; +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://web.dev/add-manifest/">https://web.dev/add-manifest/</a></li> +</ul>Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/Mon, 28 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-bundles/<p><a href="https://gohugo.io/">Hugo</a> allows <a href="https://gohugo.io/hugo-pipes/bundling/">bundling</a> of assets with several built-in functions:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ $normalize := resources.Get &#34;/css/normalize.css&#34; }} +{{ $font := resources.Get &#34;/css/font.css&#34; }} +{{ $header := resources.Get &#34;/css/header.css&#34; }} +{{ $footer := resources.Get &#34;/css/footer.css&#34; }} +{{ $navigation := resources.Get &#34;/css/navigation.css&#34; }} +{{ $navigation_mobile := resources.Get &#34;/css/navigation-mobile.css&#34; }} +{{ $layout := resources.Get &#34;/css/layout.css&#34; }} +{{ $layout_mobile := resources.Get &#34;/css/layout-mobile.css&#34; }} +{{ $syntax := resources.Get &#34;/css/syntax.css&#34; }} +{{ $darkmode := resources.Get &#34;/css/darkmode.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat &#34;css/base.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} +{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat &#34;css/mobile.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +&lt;link href=&#34;{{ $base.Permalink }}&#34; integrity=&#34;{{ $base.Data.Integrity }}&#34; media=&#34;screen&#34; rel=&#34;stylesheet&#34;&gt; +&lt;link href=&#34;{{ $mobile.Permalink }}&#34; integrity=&#34;{{ $mobile.Data.Integrity }}&#34; media=&#34;screen and (max-width: 800px)&#34; rel=&#34;stylesheet&#34;&gt; + +&lt;link href=&#34;{{ $darkmode.Permalink }}&#34; integrity=&#34;{{ $darkmode.Data.Integrity }}&#34; media=&#34;screen and (prefers-color-scheme: dark)&#34; rel=&#34;stylesheet&#34;&gt; +</code></pre>humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/Mon, 14 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-humans/<p>To publish a <a href="http://humanstxt.org/">humans.txt</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;text/plain&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;txt&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Humans</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;text/plain&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.humans.txt</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">/* TEAM */ +{{ range $.Site.Data.contributors }} +{{ .title }}: {{ .first_name }} {{ .last_name }} +Site: {{ .website }} +{{ end }} +</code></pre>FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/Mon, 30 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-foaf/<p>To publish a <a href="http://www.foaf-project.org/">FOAF</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/rdf+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;rdf&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Foaf</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;FOAF&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/rdf+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;foaf&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.foaf.rdf</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">&lt;rdf:RDF xmlns:rdf=&#34;http://www.w3.org/1999/02/22-rdf-syntax-ns#&#34; xmlns:rdfs=&#34;http://www.w3.org/2000/01/rdf-schema#&#34; xmlns:foaf=&#34;http://xmlns.com/foaf/0.1/&#34;&gt; + &lt;foaf:PersonalProfileDocument rdf:about=&#34;&#34;&gt; + &lt;foaf:maker rdf:resource=&#34;#me&#34; /&gt; + &lt;foaf:primaryTopic rdf:resource=&#34;{{ .Site.Title }}&#34; /&gt; + &lt;/foaf:PersonalProfileDocument&gt; + + &lt;foaf:Project rdf:ID=&#34;{{ .Site.Title }}&#34;&gt; + &lt;foaf:name&gt;{{ .Site.Title }}&lt;/foaf:name&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .Site.BaseURL }}&#34; /&gt; + &lt;/foaf:Project&gt; + + {{ range $.Site.Data.contributors }} + &lt;foaf:Person rdf:ID=&#34;{{ .id }}&#34;&gt; + &lt;foaf:name&gt;{{ .first_name }} {{ .last_name }}&lt;/foaf:name&gt; + &lt;foaf:title&gt;{{ .title }}&lt;/foaf:title&gt; + &lt;foaf:givenname&gt;{{ .first_name }}&lt;/foaf:givenname&gt; + &lt;foaf:family_name&gt;{{ .last_name }}&lt;/foaf:family_name&gt; + &lt;foaf:mbox rdf:resource=&#34;mailto:{{ .email }}&#34; /&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .website }}&#34; /&gt; + &lt;/foaf:Person&gt; + {{ end }} +&lt;/rdf:RDF&gt; +</code></pre>Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-atom/<p>To publish Atom feeds for your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/atom+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Atom</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/atom+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/list.atom.xml</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ printf `&lt;?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&gt;` | safeHTML }} +&lt;feed xmlns=&#34;http://www.w3.org/2005/Atom&#34;{{ with site.LanguageCode }} xml:lang=&#34;{{ . }}&#34;{{ end }}&gt; + &lt;generator uri=&#34;https://gohugo.io/&#34; version=&#34;{{ hugo.Version }}&#34;&gt;Hugo&lt;/generator&gt; + {{- $title := site.Title -}} + {{- with .Title -}} + {{- if (not (eq . site.Title)) -}} + {{- $title = printf `%s %s %s` . (i18n &#34;feed_title_on&#34; | default &#34;on&#34;) site.Title -}} + {{- end -}} + {{- end -}} + {{- if .IsTranslated -}} + {{ $title = printf &#34;%s (%s)&#34; $title (index site.Data.i18n.languages .Lang) }} + {{- end -}} + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` $title | safeHTML }} + {{ with (or (.Param &#34;subtitle&#34;) (.Param &#34;tagline&#34;)) }} + {{ printf `&lt;subtitle type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/subtitle&gt;` . | safeHTML }} + {{ end }} + {{ $output_formats := .OutputFormats }} + {{ range $output_formats -}} + {{- $rel := (or (and (eq &#34;atom&#34; (.Name | lower)) &#34;self&#34;) &#34;alternate&#34;) -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=%q type=%q title=%q /&gt;` .Permalink $rel .MediaType.Type .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- range .Translations }} + {{ $output_formats := .OutputFormats }} + {{- $lang := .Lang }} + {{- $langstr := index site.Data.i18n.languages .Lang }} + {{ range $output_formats -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=&#34;alternate&#34; type=%q hreflang=%q title=&#34;[%s] %s&#34; /&gt;` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- end }} + &lt;updated&gt;{{ now.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ with site.Copyright }} + {{- $copyright := replace . &#34;{year}&#34; now.Year -}} {{/* In case the site.copyright uses a special string &#34;{year}&#34; */}} + {{- $copyright = replace $copyright &#34;&amp;copy;&#34; &#34;©&#34; -}} + &lt;rights&gt;{{ $copyright | plainify }}&lt;/rights&gt; + {{- end }} + {{ with .Param &#34;feed&#34; }} + {{/* For this to work, the $icon file should be present in the assets/ directory */}} + {{- $icon := .icon | default &#34;icon.svg&#34; -}} + {{- with resources.Get $icon -}} + &lt;icon&gt;{{ (. | fingerprint).Permalink }}&lt;/icon&gt; + {{- end }} + + {{/* For this to work, the $logo file should be present in the assets/ directory */}} + {{- $logo := .logo | default &#34;logo.svg&#34; -}} + {{- with resources.Get $logo -}} + &lt;logo&gt;{{ (. | fingerprint).Permalink }}&lt;/logo&gt; + {{- end }} + {{ end }} + {{ with site.Author.name -}} + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + {{ with site.Author.email }} + &lt;email&gt;{{ . }}&lt;/email&gt; + {{ end -}} + &lt;/author&gt; + {{- end }} + {{ with site.Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }} + {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}} + {{/* Range through only the pages with a Type in $feed_sections. */}} + {{- $pages := where .RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- if (eq .Kind &#34;home&#34;) -}} + {{- $pages = where site.RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- end -}} + {{/* Remove the pages that have the disable_feed parameter set to true. */}} + {{- $pages = where $pages &#34;.Params.disable_feed&#34; &#34;!=&#34; true -}} + {{- range first $limit $pages }} + {{ $page := . }} + &lt;entry&gt; + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` .Title | safeHTML }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;alternate&#34; type=&#34;text/html&#34; /&gt; + {{- range .Translations }} + {{- $link := printf &#34;%s?utm_source=atom_feed&#34; .Permalink | safeHTML }} + {{- printf `&lt;link href=%q rel=&#34;alternate&#34; type=&#34;text/html&#34; hreflang=%q /&gt;` $link .Lang | safeHTML }} + {{- end }} + {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}} + {{- range first 5 (site.RegularPages.Related .) }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;related&#34; type=&#34;text/html&#34; title=&#34;{{ .Title }}&#34; /&gt; + {{- end }} + {{ with .Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{ with .Params.author -}} + {{- range . -}} &lt;!-- Assuming the author front-matter to be a list --&gt; + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + &lt;/author&gt; + {{- end -}} + {{- end }} + &lt;published&gt;{{ .Date.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/published&gt; + &lt;updated&gt;{{ .Lastmod.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ $description1 := .Description | default &#34;&#34; }} + {{ $description := (cond (eq &#34;&#34; $description1) &#34;&#34; (printf &#34;&lt;blockquote&gt;%s&lt;/blockquote&gt;&#34; ($description1 | markdownify))) }} + {{ printf `&lt;content type=&#34;html&#34;&gt;&lt;![CDATA[%s%s]]&gt;&lt;/content&gt;` $description .Content | safeHTML }} + {{ with site.Taxonomies }} + {{ range $taxo,$_ := . }} &lt;!-- Defaults taxos: &#34;tags&#34;, &#34;categories&#34; --&gt; + {{ with $page.Param $taxo }} + {{ $taxo_list := . }} &lt;!-- $taxo_list will be the tags/categories list --&gt; + {{ with site.GetPage (printf &#34;/%s&#34; $taxo) }} + {{ $taxonomy_page := . }} + {{ range $taxo_list }} &lt;!-- Below, assuming pretty URLs --&gt; + &lt;category scheme=&#34;{{ printf &#34;%s%s&#34; $taxonomy_page.Permalink (. | urlize) }}&#34; term=&#34;{{ (. | urlize) }}&#34; label=&#34;{{ . }}&#34; /&gt; + {{ end }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + &lt;/entry&gt; + {{ end }} +&lt;/feed&gt; +</code></pre>Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-toot/<p>The <a href="https://github.com/rzr/fediverse-action">rzr/fediverse-action</a> action allows to send a <a href="https://joinmastodon.org/">toot</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;NAME&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish Toot</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">rzr/fediverse-action@master</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">access-token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;MESSAGE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_SERVER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;MESSAGE&gt;</code>: Message for the toot.</li> +</ul>Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/Mon, 02 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-email/<p>The <a href="https://github.com/dawidd6/action-send-mail">dawidd6/action-send-mail</a> action allows to send an email in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Send mail</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">dawidd6/action-send-mail@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_address</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SERVER }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_port</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PORT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_USERNAME }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PASSWORD }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">subject</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;SUBJECT&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;BODY&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_RECIPIENT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SENDER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;SUBJECT&gt;</code>: Subject for the email.</li> +<li><code>&lt;BODY&gt;</code>: Body for the email.</li> +</ul> +<p>Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define <code>MAIL_RECIPIENT</code> for each project.</p>Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/<p>The <a href="https://github.com/actions/upload-release-asset">actions/upload-release-asset</a> action allows to upload a <a href="https://developer.github.com/v3/repos/releases/#upload-a-release-asset">release artifact</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload Release Asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">upload_release_asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-release-asset@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">upload_url</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.create_release.outputs.upload_url }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_path</span><span class="p">:</span><span class="w"> </span><span class="l">./some/path/to/file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_name</span><span class="p">:</span><span class="w"> </span><span class="l">public-name-for-file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_content_type</span><span class="p">:</span><span class="w"> </span><span class="l">application/zip</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/Mon, 05 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-release/<p>The <a href="https://github.com/actions/create-release">actions/create-release</a> action allows to create a new <a href="https://help.github.com/en/github/administering-a-repository/about-releases">GitHub releases</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create Release</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/create-release@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">tag_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;TAG&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">release_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RELEASE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">draft</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">prerelease</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Your release text here +</span></span></span><span class="line"><span class="cl"><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Some code block: +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> yaml: +</span></span></span><span class="line"><span class="cl"><span class="sd"> inside: +</span></span></span><span class="line"><span class="cl"><span class="sd"> of: +</span></span></span><span class="line"><span class="cl"><span class="sd"> another: yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;TAG&gt;</code>: The Git tag to create.</li> +<li><code>&lt;RELEASE&gt;</code>: The release name to use.</li> +</ul>Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/Mon, 21 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/<p>The <a href="https://github.com/peaceiris/actions-gh-pages">peaceiris/actions-gh-pages</a> action allows to publish a <a href="https://gohugo.io/">Hugo</a> site in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Website</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-gh-pages@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">github_token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">publish_dir</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PUBLISH_DIR&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">force_orphan</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">cname</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;CNAME&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;PUBLISH_DIR&gt;</code>: The file system location of the built site.</li> +<li><code>&lt;CNAME&gt;</code>: The <code>CNAME</code> of your custom domain.</li> +</ul>Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/Mon, 07 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions/cache</a> action allows to cache artifacts in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Maven artifacts</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">~/.m2/repository</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-maven-${{ hashFiles(&#39;**/pom.xml&#39;) }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> ${{ runner.os }}-maven-</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/Mon, 24 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions-hugo</a> action allows to use a specific <a href="https://gohugo.io/">Hugo</a> version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup hugo</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-hugo@v2</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hugo-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;HUGO_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;HUGO_VERSION&gt;</code>: The <a href="https://github.com/gohugoio/hugo/releases">released versions</a> or use <code>latest</code> to always use the latest version of Hugo.</li> +</ul>Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/Mon, 10 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/<p>The <a href="https://github.com/actions/setup-java">setup-java</a> action allows to use a specific Java version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up JDK &lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-java@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">java-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;JDK_VERSION&gt;</code>: The required Java version for your project.</li> +</ul>XDG Base Directory Specificationhttps://seb.xn--ho-hia.de/posts/xdg-dot-files/Mon, 27 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/xdg-dot-files/<p>The <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html">XDG Base Directory Specification</a> has been around for a while, yet not every software has adopted it yet. Here is an incomplete list of fixes:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># use existing env variables or define new</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CACHE_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CACHE_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.cache&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_DIRS</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CONFIG_DIRS</span><span class="o">=</span><span class="s2">&#34;/etc/xdg&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CONFIG_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_DATA_DIRS</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_DATA_DIRS</span><span class="o">=</span><span class="s2">&#34;/usr/local/share:/usr/share&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_DATA_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/share&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># gradle</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">GRADLE_USER_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">/gradle&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># httpie</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">HTTPIE_CONFIG_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">/httpie&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># npm</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">NPM_CONFIG_USERCONFIG</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">/npm/npmrc&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">npm_config_cache</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CACHE_HOME</span><span class="s2">/npm&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># password-store</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PASSWORD_STORE_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">/password-store&#34;</span> +</span></span></code></pre></div><p>To make your own software XDG-aware, consider using the <a href="https://github.com/dirs-dev">dirs-dev</a> or <a href="https://github.com/shibukawa/configdir">configdir</a> libraries.</p>GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/gitlab-distributor/<p><a href="https://git-scm.com/">Git</a> at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. <a href="https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows">Pro Git</a> rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.</p> +<p>While the central server approach is easy to use, it might not work in all scenarios:</p>Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/Mon, 29 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-help/<p>Use the following <a href="https://www.perl.org/">Perl</a> snippet to automatically generate help output for your <code>Makefile</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nv">GREEN</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 2<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">WHITE</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 7<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">YELLOW</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 3<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">RESET</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm sgr0<span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nv">HELP_FUN</span> <span class="o">=</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> %help<span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">while</span><span class="o">(</span>&lt;&gt;<span class="o">)</span> <span class="o">{</span> push @<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span><span class="m">2</span> // <span class="s1">&#39;targets&#39;</span><span class="o">}}</span>, <span class="o">[</span><span class="nv">$$</span>1, <span class="nv">$$</span>3<span class="o">]</span> <span class="k">if</span> /^<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">)</span><span class="se">\s</span>*:.*<span class="se">\#\#</span><span class="o">(</span>?:@<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">))</span>?<span class="se">\s</span><span class="o">(</span>.*<span class="o">)</span><span class="nv">$$</span>/ <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;usage: make [target]\n\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>sort keys %help<span class="o">)</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WHITE</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_:</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>@<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span>_<span class="o">}})</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="nv">$$sep</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span> x <span class="o">(</span><span class="m">32</span> - length <span class="nv">$$</span>_-&gt;<span class="o">[</span>0<span class="o">])</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34; </span><span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[0]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="nv">$$</span><span class="s2">sep</span><span class="si">${</span><span class="nv">GREEN</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[1]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;\n&#34;</span><span class="p">;</span> <span class="o">}</span> +</span></span></code></pre></div><p>To use <code>HELP_FUN</code>, add the following <code>help</code> target to the same <code>Makefile</code>:</p>Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/Mon, 15 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/short-git-clones/<p>In case you don&rsquo;t want to write <code>git clone git@github.com:orga/repo.git</code> all the time, consider using a custom SSH configuration (<code>~/.ssh/config</code>) like this:</p> +<pre tabindex="0"><code>Host github + HostName github.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITHUB&gt; + +Host gitlab + HostName gitlab.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITLAB&gt; + +Host bitbucket + HostName bitbucket.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-BITBUCKET&gt; + +Host codeberg + HostName codeberg.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-CODEBERG&gt; +</code></pre><p>Once configured, you can now write:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git clone github:orga/repo +</span></span><span class="line"><span class="cl">$ git clone gitlab:orga/repo +</span></span><span class="line"><span class="cl">$ git clone bitbucket:orga/repo +</span></span><span class="line"><span class="cl">$ git clone codeberg:orga/repo +</span></span></code></pre></div><p>In case you are working with many repositories inside a single organization, consider adding the following Git configuration (<code>$XDG_CONFIG_HOME/git/config</code> or <code>~/.gitconfig</code>):</p>Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/Thu, 11 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/<p>In case you are into <a href="https://calver.org/">calver</a> or have another reason to create a timestamp with <a href="https://github.com/features/actions">GitHub Actions</a>, do the following:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create release version</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;ID&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=&lt;NAME&gt;::$(date +&#39;%Y.%m.%d-%H%M%S&#39;)&#34;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;ID&gt;</code>: The unique ID of the timestamp step.</li> +<li><code>&lt;NAME&gt;</code>: The name of the created timestamp.</li> +</ul> +<p>The special syntax <code>::set-output name=&lt;NAME&gt;::</code> declares that the output of the command (<code>echo</code>) should be saved in a variable called <code>&lt;NAME&gt;</code>. Together with the <code>&lt;ID&gt;</code> of the pipeline step, this value can be referenced with the expression <code>${{ steps.&lt;ID&gt;.outputs.&lt;NAME&gt; }}</code> in the following steps of your pipeline.</p> \ No newline at end of file diff --git a/posts/jspecify/index.html b/posts/jspecify/index.html new file mode 100644 index 000000000..bb33165d6 --- /dev/null +++ b/posts/jspecify/index.html @@ -0,0 +1,36 @@ +Sebastian Hoß – jspecify

jspecify +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-java" href="/tags/java">java</a>, <a class="post-tag post-tag-jspecify" href="/tags/jspecify">jspecify</a>, and <a class="post-tag post-tag-nullness" href="/tags/nullness">nullness</a>

Every Java developer has probably encountered a NullPointerException at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:

String someName;         // value is 'null'
+
+someName.toUpperCase(); // throws NullPointerException
+

Modern IDEs have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is null and therefore a potential for a NullPointerException is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be null. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.

jspecify is the latest approach that tries to establish a standard. It has gained wide community support and recently celebrated their first public release (0.3.0).

The following snippet shows the dependency declaration for Maven projects:

<dependencies>
+    <dependency>
+        <groupId>org.jspecify</groupId>
+        <artifactId>jspecify</artifactId>
+        <version>0.3.0</version>
+    </dependency>
+</dependencies>
+

In case you want to declare that nothing in your module can ever be null, place the @NullMarked on your module-info.java like this:

@org.jspecify.annotations.NullMarked
+module your.module.here {
+
+    requires org.jspecify;
+
+    // ...
+
+}
+

The tooling support is not quiet clear yet, however if you are developing a library there is no harm in adding these annotations now and let your users enjoy their null-free life once tools have caught up.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/jspecify/jsonld.json b/posts/jspecify/jsonld.json new file mode 100644 index 000000000..da99c1e90 --- /dev/null +++ b/posts/jspecify/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"jspecify","headline":"jspecify","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/jspecify/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2023","dateCreated":"2023-01-09","datePublished":"2023-01-09","dateModified":"2023-01-09","url":"https://seb.xn--ho-hia.de/posts/jspecify/","wordCount":"257","genre":["java","nullness","jspecify"],"keywords":["java","nullness","jspecify"]} \ No newline at end of file diff --git a/posts/kubectl-multi-cluster/index.html b/posts/kubectl-multi-cluster/index.html new file mode 100644 index 000000000..6dfc017aa --- /dev/null +++ b/posts/kubectl-multi-cluster/index.html @@ -0,0 +1,21 @@ +Sebastian Hoß – Using multiple clusters with kubectl

Using multiple clusters with kubectl +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-kubectl" href="/tags/kubectl">kubectl</a>, and <a class="post-tag post-tag-kubernetes" href="/tags/kubernetes">kubernetes</a>

To connect to multiple Kubernetes clusters with kubectl, I like to define aliases like this:

alias rancher="kubectl --kubeconfig ~/.kube/rancher.config"
+alias work="kubectl --kubeconfig ~/.kube/work.config"
+alias customer="kubectl --kubeconfig ~/.kube/customer.config"
+

Those aliases allow me to write things like rancher get pods --namespace some-namespace without worrying the wrong context is active. Using multiple configurations - one for each cluster - seems to be easier to manage since most clusters allow to download a ready-to-use configuration file. Instead of mangling them together manually, I just specify another alias whenever I get to work with another cluster.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/kubectl-multi-cluster/jsonld.json b/posts/kubectl-multi-cluster/jsonld.json new file mode 100644 index 000000000..81bf20267 --- /dev/null +++ b/posts/kubectl-multi-cluster/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Using multiple clusters with kubectl","headline":"Using multiple clusters with kubectl","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-06-14","datePublished":"2021-06-14","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/","wordCount":"91","genre":["kubectl","kubernetes"],"keywords":["kubectl","kubernetes"]} \ No newline at end of file diff --git a/posts/makefile-help/index.html b/posts/makefile-help/index.html new file mode 100644 index 000000000..2d626ce1e --- /dev/null +++ b/posts/makefile-help/index.html @@ -0,0 +1,62 @@ +Sebastian Hoß – Makefile Help

Makefile Help +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-help" href="/tags/help">help</a>, <a class="post-tag post-tag-make" href="/tags/make">make</a>, <a class="post-tag post-tag-makefile" href="/tags/makefile">Makefile</a>, and <a class="post-tag post-tag-perl" href="/tags/perl">perl</a>

Use the following Perl snippet to automatically generate help output for your Makefile:

GREEN  := $(shell tput -Txterm setaf 2)
+WHITE  := $(shell tput -Txterm setaf 7)
+YELLOW := $(shell tput -Txterm setaf 3)
+RESET  := $(shell tput -Txterm sgr0)
+
+HELP_FUN = \
+    %help; \
+    while(<>) { push @{$$help{$$2 // 'targets'}}, [$$1, $$3] if /^([a-zA-Z\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
+    print "usage: make [target]\n\n"; \
+    for (sort keys %help) { \
+    print "${WHITE}$$_:${RESET}\n"; \
+    for (@{$$help{$$_}}) { \
+    $$sep = " " x (32 - length $$_->[0]); \
+    print "  ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
+    }; \
+    print "\n"; }
+

To use HELP_FUN, add the following help target to the same Makefile:

.DEFAULT_GOAL := help
+
+.PHONY: help
+help: ##@other Show this help
+	@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
+

Each target in the Makefile is marked as phony to signal that those targets are not actually files that are generated as part of your build process. The optional description of a target can be placed after the ##@ prefix. The first word represents the group of a target and everything that follows is the description of a target. All targets should be formatted just like the help target:

.PHONY: compile
+compile: ##@hacking Compile your code
+	<compile some code>
+
+.PHONY: test
+test: ##@hacking Test your code
+	<test some code>
+
+.PHONY: sign-cla
+sign-cla: ##@contrib Sign the contributor license agreement
+	<sign some file>
+

Once in place, you can either use make without any argument to call the help target or use make help to see the generated output:

$ make
+usage: make [target]
+
+contrib:
+  sign-cla            Sign the contributor license agreement
+
+hacking:
+  compile             Compile your code
+  test                Test your code
+
+other:
+  help                Show this help
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/makefile-help/jsonld.json b/posts/makefile-help/jsonld.json new file mode 100644 index 000000000..1cce21f9b --- /dev/null +++ b/posts/makefile-help/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Makefile Help","headline":"Makefile Help","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/makefile-help/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-06-29","datePublished":"2020-06-29","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/makefile-help/","wordCount":"272","genre":["make","Makefile","help","perl"],"keywords":["make","Makefile","help","perl"]} \ No newline at end of file diff --git a/posts/makefile-ignore/index.html b/posts/makefile-ignore/index.html new file mode 100644 index 000000000..180df8a10 --- /dev/null +++ b/posts/makefile-ignore/index.html @@ -0,0 +1,23 @@ +Sebastian Hoß – Ignore Exit Codes

Ignore Exit Codes +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-exit-code" href="/tags/exit-code">exit code</a>, <a class="post-tag post-tag-make" href="/tags/make">make</a>, and <a class="post-tag post-tag-makefile" href="/tags/makefile">Makefile</a>

In case you are using a Makefile to define a complex build step - for example start database, run tests, stop database - consider using the - qualifier in front of your actual build step like this:

.PHONY: build
+build:
+	start-database
+	-build-software
+	stop-database
+

Thanks to -, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/makefile-ignore/jsonld.json b/posts/makefile-ignore/jsonld.json new file mode 100644 index 000000000..e8a652558 --- /dev/null +++ b/posts/makefile-ignore/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Ignore Exit Codes","headline":"Ignore Exit Codes","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/makefile-ignore/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-02-08","datePublished":"2021-02-08","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/makefile-ignore/","wordCount":"73","genre":["make","Makefile","exit code"],"keywords":["make","Makefile","exit code"]} \ No newline at end of file diff --git a/posts/makefile-readme/index.html b/posts/makefile-readme/index.html new file mode 100644 index 000000000..3ae23f3e7 --- /dev/null +++ b/posts/makefile-readme/index.html @@ -0,0 +1,27 @@ +Sebastian Hoß – Executable READMEs

Executable READMEs +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-make" href="/tags/make">make</a>, <a class="post-tag post-tag-makefile" href="/tags/makefile">Makefile</a>, <a class="post-tag post-tag-readme" href="/tags/readme">README</a>, and <a class="post-tag post-tag-teamwork" href="/tags/teamwork">teamwork</a>

README file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a Makefile in the root of your project which contains the exact same instructions. Thanks to make, all your contributors can now use TAB-completion to run any of the pre-defined make targets.

The following example is part of one of my projects, and I certainly don’t want to type (or even copy) that all the time:

.PHONY: release-into-local-nexus
+release-into-local-nexus:
+	mvn versions:set \
+	   -DnewVersion=$(TIMESTAMPED_VERSION) \
+	   -DgenerateBackupPoms=false
+	-mvn clean deploy scm:tag \
+	   -DpushChanges=false \
+	   -DskipLocalStaging=true \
+	   -Drelease=local
+	mvn versions:set \
+	   -DnewVersion=9999.99.99-SNAPSHOT \
+	   -DgenerateBackupPoms=false
+

With the above target in place, everyone can now do make release-into-local-nexus instead of typing/copying the commands themselves. Thanks to TAB-completion you just have to do make r<TAB> and confirm with >ENTER> to perform a release.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/makefile-readme/jsonld.json b/posts/makefile-readme/jsonld.json new file mode 100644 index 000000000..d44b2d0a2 --- /dev/null +++ b/posts/makefile-readme/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Executable READMEs","headline":"Executable READMEs","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/makefile-readme/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-03-08","datePublished":"2021-03-08","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/makefile-readme/","wordCount":"160","genre":["make","Makefile","README","teamwork"],"keywords":["make","Makefile","README","teamwork"]} \ No newline at end of file diff --git a/posts/maven-cd-versioning/index.html b/posts/maven-cd-versioning/index.html new file mode 100644 index 000000000..738d5bc6c --- /dev/null +++ b/posts/maven-cd-versioning/index.html @@ -0,0 +1,19 @@ +Sebastian Hoß – Continuous Versioning with Maven

Continuous Versioning with Maven +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-maven" href="/tags/maven">maven</a>, and <a class="post-tag post-tag-versioning" href="/tags/versioning">versioning</a>

To automatically version Maven projects, I like to use the m-versions-p like this:

$ mvn versions:set -DnewVersion=my.new.version -DgenerateBackupPoms=false
+

This will update the version property of every module in the reactor to prepare them for the next release. In case you are using GitHub Actions, consider using a timestamp.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/maven-cd-versioning/jsonld.json b/posts/maven-cd-versioning/jsonld.json new file mode 100644 index 000000000..59495e3ac --- /dev/null +++ b/posts/maven-cd-versioning/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet","cd"],"name":"Continuous Versioning with Maven","headline":"Continuous Versioning with Maven","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/maven-cd-versioning/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-12-06","datePublished":"2021-12-06","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/maven-cd-versioning/","wordCount":"48","genre":["maven","versioning"],"keywords":["maven","versioning"]} \ No newline at end of file diff --git a/posts/maven-encoding/index.html b/posts/maven-encoding/index.html new file mode 100644 index 000000000..3ba9c9c58 --- /dev/null +++ b/posts/maven-encoding/index.html @@ -0,0 +1,22 @@ +Sebastian Hoß – Specify encoding for Maven projects

Specify encoding for Maven projects +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-encoding" href="/tags/encoding">encoding</a>, <a class="post-tag post-tag-linux" href="/tags/linux">linux</a>, <a class="post-tag post-tag-mac" href="/tags/mac">mac</a>, <a class="post-tag post-tag-maven" href="/tags/maven">maven</a>, and <a class="post-tag post-tag-windows" href="/tags/windows">windows</a>

Maven projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.

<properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+</properties>
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/maven-encoding/jsonld.json b/posts/maven-encoding/jsonld.json new file mode 100644 index 000000000..6fc778211 --- /dev/null +++ b/posts/maven-encoding/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Specify encoding for Maven projects","headline":"Specify encoding for Maven projects","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/maven-encoding/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-05-31","datePublished":"2021-05-31","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/maven-encoding/","wordCount":"56","genre":["maven","encoding","windows","linux","mac"],"keywords":["maven","encoding","windows","linux","mac"]} \ No newline at end of file diff --git a/posts/maven-github-sonarcloud/index.html b/posts/maven-github-sonarcloud/index.html new file mode 100644 index 000000000..6971f5953 --- /dev/null +++ b/posts/maven-github-sonarcloud/index.html @@ -0,0 +1,50 @@ +Sebastian Hoß – Analyze Maven projects with SonarCloud using GitHub Actions

Analyze Maven projects with SonarCloud using GitHub Actions +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-github" href="/tags/github">github</a>, <a class="post-tag post-tag-github-actions" href="/tags/github-actions">github actions</a>, <a class="post-tag post-tag-maven" href="/tags/maven">maven</a>, and <a class="post-tag post-tag-sonarqube" href="/tags/sonarqube">sonarqube</a>

To analyze Maven projects with SonarCloud using GitHub Actions, first create the following settings.xml file:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
+
+    <pluginGroups>
+        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
+    </pluginGroups>
+
+    <activeProfiles>
+        <activeProfile>sonar</activeProfile>
+    </activeProfiles>
+
+    <profiles>
+        <profile>
+            <id>sonar</id>
+            <properties>
+                <sonar.host.url>https://sonarcloud.io</sonar.host.url>
+                <sonar.organization>YOUR_ORG</sonar.organization>
+                <sonar.projectKey>YOUR_PROJECT</sonar.projectKey>
+                <sonar.login>${env.SONAR_TOKEN}</sonar.login>
+            </properties>
+        </profile>
+    </profiles>
+</settings>
+

Finally, add a step to your workflow:

- name: Verify Project
+  run: mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar
+  env:
+    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/maven-github-sonarcloud/jsonld.json b/posts/maven-github-sonarcloud/jsonld.json new file mode 100644 index 000000000..fabc0438d --- /dev/null +++ b/posts/maven-github-sonarcloud/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Analyze Maven projects with SonarCloud using GitHub Actions","headline":"Analyze Maven projects with SonarCloud using GitHub Actions","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-05-03","datePublished":"2021-05-03","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/","wordCount":"63","genre":["maven","github","sonarqube","github actions"],"keywords":["maven","github","sonarqube","github actions"]} \ No newline at end of file diff --git a/posts/maven-google-central/index.html b/posts/maven-google-central/index.html new file mode 100644 index 000000000..62b525fc2 --- /dev/null +++ b/posts/maven-google-central/index.html @@ -0,0 +1,40 @@ +Sebastian Hoß – Google Central

Google Central +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-google" href="/tags/google">google</a>, <a class="post-tag post-tag-maven" href="/tags/maven">maven</a>, and <a class="post-tag post-tag-repository" href="/tags/repository">repository</a>

Some time ago, Google started hosting a copy of Maven Central. Configure it in your ~/.m2/settings.xml like this:

<settings>
+  <mirrors>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (Asia)</name>
+      <url>https://maven-central-asia.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (EU)</name>
+      <url>https://maven-central-eu.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (US)</name>
+      <url>https://maven-central.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+  </mirrors>
+</settings>
+

Pick the mirror nearest to your location to get best speeds.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/maven-google-central/jsonld.json b/posts/maven-google-central/jsonld.json new file mode 100644 index 000000000..b072f36b3 --- /dev/null +++ b/posts/maven-google-central/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Google Central","headline":"Google Central","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/maven-google-central/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-11-08","datePublished":"2021-11-08","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/maven-google-central/","wordCount":"60","genre":["maven","google","repository"],"keywords":["maven","google","repository"]} \ No newline at end of file diff --git a/posts/maven-reproducible/index.html b/posts/maven-reproducible/index.html new file mode 100644 index 000000000..64c8cf3ec --- /dev/null +++ b/posts/maven-reproducible/index.html @@ -0,0 +1,21 @@ +Sebastian Hoß – Creating reproducible artifacts with Maven

Creating reproducible artifacts with Maven +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-maven" href="/tags/maven">maven</a>, and <a class="post-tag post-tag-reproducible" href="/tags/reproducible">reproducible</a>

To create reproducible builds with Maven projects, it’s enough to specify the project.build.outputTimestamp property like this:

<properties>
+    <project.build.outputTimestamp>2020</project.build.outputTimestamp>
+</properties>
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/maven-reproducible/jsonld.json b/posts/maven-reproducible/jsonld.json new file mode 100644 index 000000000..67aea7cb7 --- /dev/null +++ b/posts/maven-reproducible/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["devops","snippet"],"name":"Creating reproducible artifacts with Maven","headline":"Creating reproducible artifacts with Maven","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/maven-reproducible/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-05-17","datePublished":"2021-05-17","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/maven-reproducible/","wordCount":"25","genre":["maven","reproducible"],"keywords":["maven","reproducible"]} \ No newline at end of file diff --git a/posts/multiple-git-configs/index.html b/posts/multiple-git-configs/index.html new file mode 100644 index 000000000..4d32d9991 --- /dev/null +++ b/posts/multiple-git-configs/index.html @@ -0,0 +1,32 @@ +Sebastian Hoß – Multiple Git Configurations

Multiple Git Configurations +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-config" href="/tags/config">config</a>, and <a class="post-tag post-tag-git" href="/tags/git">git</a>

To split yet re-use as much configuration for Git as possible, you can create one root configuration that looks similar to this:

[user]
+  name = Your Name Here
+
+[includeIf "gitdir:~/git/personal/"]
+  path = ~/.config/git/personal
+[includeIf "gitdir:~/git/work/"]
+  path = ~/.config/git/work
+

The includeIf directive supports multiple keywords. In my case, work and personal projects have a different root directory, therefore I can filter based on the location using gitdir. The personal Git configuration simply looks like this:

[user]
+  email = personal.email@example.com
+

and the work related configuration like this using a different email address:

[user]
+  email = first.last@work.example
+

Additional settings that are different for personal/work accounts can be split the same way, for example to use a different signing key for work.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/multiple-git-configs/jsonld.json b/posts/multiple-git-configs/jsonld.json new file mode 100644 index 000000000..ed492a3d5 --- /dev/null +++ b/posts/multiple-git-configs/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Multiple Git Configurations","headline":"Multiple Git Configurations","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/multiple-git-configs/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2023","dateCreated":"2023-01-05","datePublished":"2023-01-05","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/multiple-git-configs/","wordCount":"118","genre":["git","config"],"keywords":["git","config"]} \ No newline at end of file diff --git a/posts/nvim-plugin-auto-updates/index.html b/posts/nvim-plugin-auto-updates/index.html new file mode 100644 index 000000000..ddb9bffde --- /dev/null +++ b/posts/nvim-plugin-auto-updates/index.html @@ -0,0 +1,70 @@ +Sebastian Hoß – Automatically update plugins for vim/nvim

Automatically update plugins for vim/nvim +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-git" href="/tags/git">git</a>, <a class="post-tag post-tag-neovim" href="/tags/neovim">neovim</a>, <a class="post-tag post-tag-plugins" href="/tags/plugins">plugins</a>, <a class="post-tag post-tag-shell" href="/tags/shell">shell</a>, <a class="post-tag post-tag-systemd" href="/tags/systemd">systemd</a>, <a class="post-tag post-tag-updates" href="/tags/updates">updates</a>, and <a class="post-tag post-tag-vim" href="/tags/vim">vim</a>

Both Vim and Neovim have a built-in plugin mechanism that loads plugins from ~/.vim/pack/*/{start,opt}/* (Vim) or ~/.local/share/nvim/site/pack/*/{start,opt}/* (Neovim). All you have to do to install new plugins, is to git clone their repository into those directories. To automatically update those clones, create the following script:

#!/usr/bin/env zsh
+
+###############################################################################
+# This script git-pulls all installed nvim plugins which are using the built-in
+# nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack
+#
+# Required software that is not in GNU coreutils:
+#   - 'git' to fetch plugin updates from upstream
+###############################################################################
+
+### User specific variables, adjust to your own needs
+
+# folder that contains all nvim plugins
+PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack"
+
+### Script logic
+
+echo "updating all plugins in ${PLUGIN_DIR}"
+# iterate through all directories and git pull em
+for directory in "${PLUGIN_DIR}"/*/{start,opt}/*; do
+    if [ -d "${directory}" ]; then
+        plugin=$(basename "${directory}")
+        echo "updating ${plugin}"
+        git -C "${directory}" pull --quiet
+    fi
+done
+

In case you are using Vim, adjust the PLUGIN_DIR variable to point to your Vim plugin directory instead and save the resulting shell script as a file called update-nvim-plugins.sh in some folder of your choice. Do not forget to set the executable bit with chmod +x /path/to/your/folder/update-nvim-plugins.sh. Since all good developers must be lazy, write the following systemd service to execute the above script automatically:

[Unit]
+Description=cron job that triggers an update of all nvim plugins
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/path/to/your/folder/update-nvim-plugins.sh
+RemainAfterExit=false
+

Adjust the ExecStart line to match the location where you saved the above script and place that service definition in a file called nvim-plugins-update.service into your local ~/.config/systemd/user directory. Add another file called nvim-plugins-update.timer next to it that defines a systemd timer with the following content:

[Unit]
+Description=Update nvim plugins every week
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+RandomizedDelaySec=5hours
+
+[Install]
+WantedBy=timers.target
+

Adjust how often you want to update the plugins you are using in the OnCalendar line. Enable this service/timer with:

$ systemctl --user enable nvim-plugins-update
+

Finally, add the following shell aliases to make it easier to interact with the created systemd units:

# trigger an update manually
+alias update-nvim-plugins='systemctl --user start nvim-plugins-update'
+# see status of last auto-update
+alias update-nvim-plugins-status='systemctl --user status nvim-plugins-update'
+# see logs of past auto-updates
+alias update-nvim-plugins-logs='journalctl --user --unit nvim-plugins-update'
+

With this setup in place, all your plugins will be automatically updated once per week or however often you have configured in the timer.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/nvim-plugin-auto-updates/jsonld.json b/posts/nvim-plugin-auto-updates/jsonld.json new file mode 100644 index 000000000..2f657f8a9 --- /dev/null +++ b/posts/nvim-plugin-auto-updates/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Automatically update plugins for vim/nvim","headline":"Automatically update plugins for vim/nvim","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2022","dateCreated":"2022-01-01","datePublished":"2022-01-01","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/","wordCount":"387","genre":["vim","neovim","plugins","updates","systemd","shell","git"],"keywords":["vim","neovim","plugins","updates","systemd","shell","git"]} \ No newline at end of file diff --git a/posts/passage-fuzzy-search/index.html b/posts/passage-fuzzy-search/index.html new file mode 100644 index 000000000..161c4e296 --- /dev/null +++ b/posts/passage-fuzzy-search/index.html @@ -0,0 +1,57 @@ +Sebastian Hoß – passage fuzzy search

passage fuzzy search +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-age" href="/tags/age">age</a>, <a class="post-tag post-tag-clipboard" href="/tags/clipboard">clipboard</a>, <a class="post-tag post-tag-fuzzy" href="/tags/fuzzy">fuzzy</a>, <a class="post-tag post-tag-passage" href="/tags/passage">passage</a>, and <a class="post-tag post-tag-search" href="/tags/search">search</a>

To fuzzy search through passwords managed with passage, I’ve written the following script that is inspired by the upstream version which is using fzf.

fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

This version requires fd, skim, xargs, and passage itself of course. The detailed breakdown on how it works is as follows:

  1. Use fd to find all files within ${PASSAGE_DIR} that end in .age. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.
  2. Using both --base-directory and --exec echo '{.}' ensures that passwords are returned in such form that they can be passed back into passage again. The placeholder '{.}' is a feature provided by fd which strips the file extension from each returned value.
  3. All passwords are then passed into sk to allow to fuzzy search across them all. Setting --no-multi ensures that only a single password can be selected.
  4. Finally, xargs calls passage and replaces the curly braces with the selected password. Thanks to --clip=1, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.

To call that script, I’ve saved it as passage-fuzzy-search.sh in my .local/bin folder and added some checks into it to verify that every required software is actually installed.

#!/usr/bin/env zsh
+
+###############################################################################
+# This shell script presents passwords saved with passage through skim
+#
+# Call it like this:
+#   passage-fuzzy-search.sh
+#
+# Required software that isn't in GNU coreutils:
+#   - 'passage' to read passwords
+#   - 'fd' to find passwords
+#   - 'sk' to filter passwords
+###############################################################################
+
+if ! (( ${+commands[passage]} )); then
+    echo 'passage not installed. Please install passage.'
+    exit
+fi
+if ! (( ${+commands[sk]} )); then
+    echo 'sk not installed. Please install skim.'
+    exit
+fi
+if ! (( ${+commands[fd]} )); then
+    echo 'fd not installed. Please install fd-find.'
+    exit
+fi
+
+fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

Since typing passage-fuzzy-search.sh is way too long, I have added an alias like this:

alias pp='passage-fuzzy-search.sh'
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/passage-fuzzy-search/jsonld.json b/posts/passage-fuzzy-search/jsonld.json new file mode 100644 index 000000000..57d10c1bd --- /dev/null +++ b/posts/passage-fuzzy-search/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"passage fuzzy search","headline":"passage fuzzy search","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/passage-fuzzy-search/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2022","dateCreated":"2022-12-27","datePublished":"2022-12-27","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/passage-fuzzy-search/","wordCount":"372","genre":["passage","age","fuzzy","search","clipboard"],"keywords":["passage","age","fuzzy","search","clipboard"]} \ No newline at end of file diff --git a/posts/shell-init/index.html b/posts/shell-init/index.html new file mode 100644 index 000000000..9da24c445 --- /dev/null +++ b/posts/shell-init/index.html @@ -0,0 +1,20 @@ +Sebastian Hoß – chezmoi & shell init scripts

chezmoi & shell init scripts +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-chezmoi" href="/tags/chezmoi">chezmoi</a>, <a class="post-tag post-tag-dotfiles" href="/tags/dotfiles">dotfiles</a>, <a class="post-tag post-tag-performance" href="/tags/performance">performance</a>, and <a class="post-tag post-tag-shell" href="/tags/shell">shell</a>

Many CLI applications offer initialization scripts to integrate into a shell, for example starship init zsh or zoxide init zsh. The documentation of these tools usually tell you to put something like eval "$(starship init zsh)" into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the init command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.

chezmoi provides a template function called output which replaces itself with the output of the command you specified. You can use that function this to integrate various tools into your shell as the following example shows while using zsh:

  1. Create a directory that holds all init scripts for every tool you want to use.
     $ mkdir --parents "${ZDOTDIR}"/tools.d
    +
  2. Let your shell load all available scripts in that directory. This snippet should be part of your .zshrc file:
    for init_script in "${ZDOTDIR}"/tools.d/*.sh; do
    +  source "${init_script}"
    +done
    +
  3. Create chezmoi .tmpl files for each tool and place them in the chezmoi source directory that matches the directory you created in step 1:
    {{ output "starship" "init" "zsh" "--print-full-init" }}
    +
  4. Call chezmoi apply to generate the init scripts.

The only downside here is that you have to re-run chezmoi apply after updating one of the tools because they change their init scripts sometimes. That problem can be solved with chezmoi auto-updates.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/shell-init/jsonld.json b/posts/shell-init/jsonld.json new file mode 100644 index 000000000..ea5d6efe7 --- /dev/null +++ b/posts/shell-init/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"chezmoi & shell init scripts","headline":"chezmoi & shell init scripts","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/shell-init/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2022","dateCreated":"2022-12-12","datePublished":"2022-12-12","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/shell-init/","wordCount":"259","genre":["chezmoi","dotfiles","shell","performance"],"keywords":["chezmoi","dotfiles","shell","performance"]} \ No newline at end of file diff --git a/posts/short-git-clones/index.html b/posts/short-git-clones/index.html new file mode 100644 index 000000000..27554a9b5 --- /dev/null +++ b/posts/short-git-clones/index.html @@ -0,0 +1,56 @@ +Sebastian Hoß – Short Git Clones

Short Git Clones +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-clone" href="/tags/clone">clone</a>, <a class="post-tag post-tag-git" href="/tags/git">git</a>, and <a class="post-tag post-tag-ssh" href="/tags/ssh">ssh</a>

In case you don’t want to write git clone git@github.com:orga/repo.git all the time, consider using a custom SSH configuration (~/.ssh/config) like this:

Host github
+    HostName github.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITHUB>
+
+Host gitlab
+    HostName gitlab.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITLAB>
+
+Host bitbucket
+    HostName bitbucket.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-BITBUCKET>
+
+Host codeberg
+    HostName codeberg.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-CODEBERG>
+

Once configured, you can now write:

$ git clone github:orga/repo
+$ git clone gitlab:orga/repo
+$ git clone bitbucket:orga/repo
+$ git clone codeberg:orga/repo
+

In case you are working with many repositories inside a single organization, consider adding the following Git configuration ($XDG_CONFIG_HOME/git/config or ~/.gitconfig):

[url "github:orga/"]
+  insteadOf = orga:
+[url "gitlab:orga/"]
+  insteadOf = orgl:
+[url "bitbucket:orga/"]
+  insteadOf = orgb:
+[url "codeberg:orga/"]
+  insteadOf = orgc:
+

Which allows you to just write:

$ git clone orga:repo
+$ git clone orgl:repo
+$ git clone orgb:repo
+$ git clone orgc:repo
+

Git will substitute the insteadOf values like orga: with the configured url (for example github:orga/). The actual clone URL is github:orga/repo at this point, which can be used by Git together with the SSH configuration mentioned above to clone repositories.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/short-git-clones/jsonld.json b/posts/short-git-clones/jsonld.json new file mode 100644 index 000000000..123b41574 --- /dev/null +++ b/posts/short-git-clones/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Short Git Clones","headline":"Short Git Clones","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/short-git-clones/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-06-15","datePublished":"2020-06-15","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/short-git-clones/","wordCount":"179","genre":["git","clone","ssh"],"keywords":["git","clone","ssh"]} \ No newline at end of file diff --git a/posts/sway-screenlock/index.html b/posts/sway-screenlock/index.html new file mode 100644 index 000000000..7e1a68605 --- /dev/null +++ b/posts/sway-screenlock/index.html @@ -0,0 +1,20 @@ +Sebastian Hoß – Lock your screen on SwayWM

Lock your screen on SwayWM +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-screenlock" href="/tags/screenlock">screenlock</a>, and <a class="post-tag post-tag-swaywm" href="/tags/swaywm">swaywm</a>

SwayWM users can use swaylock to lock their screen. Place the following key binding in your Sway configuration:

# lock your screen
+bindsym $mod+Ctrl+l exec swaylock --color 000000
+

$mod+Ctrl+l will lock your screen and turn it to black. The --color flag allows any color in the form of rrggbb[aa].

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/sway-screenlock/jsonld.json b/posts/sway-screenlock/jsonld.json new file mode 100644 index 000000000..f9728f214 --- /dev/null +++ b/posts/sway-screenlock/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Lock your screen on SwayWM","headline":"Lock your screen on SwayWM","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/sway-screenlock/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-04-05","datePublished":"2021-04-05","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/sway-screenlock/","wordCount":"49","genre":["swaywm","screenlock"],"keywords":["swaywm","screenlock"]} \ No newline at end of file diff --git a/posts/sway-screenshots/index.html b/posts/sway-screenshots/index.html new file mode 100644 index 000000000..210ebe8bb --- /dev/null +++ b/posts/sway-screenshots/index.html @@ -0,0 +1,23 @@ +Sebastian Hoß – Taken screenshots on SwayWM

Taken screenshots on SwayWM +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-grim" href="/tags/grim">grim</a>, <a class="post-tag post-tag-screenshot" href="/tags/screenshot">screenshot</a>, <a class="post-tag post-tag-slurp" href="/tags/slurp">slurp</a>, and <a class="post-tag post-tag-swaywm" href="/tags/swaywm">swaywm</a>

SwayWM uses can use a mixture of grim and slurp to take screenshots of their desktop. Place the following key binding in your Sway configuration:

# take screenshot of currently focused screen
+bindsym $mod+Print exec /usr/bin/grim -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
+# take screenshot of selection
+bindsym $mod+Shift+p exec /usr/bin/grim -g "$(/usr/bin/slurp)" $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/sway-screenshots/jsonld.json b/posts/sway-screenshots/jsonld.json new file mode 100644 index 000000000..1e1d091da --- /dev/null +++ b/posts/sway-screenshots/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Taken screenshots on SwayWM","headline":"Taken screenshots on SwayWM","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/sway-screenshots/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-03-22","datePublished":"2021-03-22","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/sway-screenshots/","wordCount":"65","genre":["swaywm","screenshot","slurp","grim"],"keywords":["swaywm","screenshot","slurp","grim"]} \ No newline at end of file diff --git a/posts/sway-waybar/index.html b/posts/sway-waybar/index.html new file mode 100644 index 000000000..cab67e0e0 --- /dev/null +++ b/posts/sway-waybar/index.html @@ -0,0 +1,36 @@ +Sebastian Hoß – Waybar on SwayWM

Waybar on SwayWM +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-swaywm" href="/tags/swaywm">swaywm</a>, and <a class="post-tag post-tag-waybar" href="/tags/waybar">waybar</a>

Waybar can be used as a status bar for SwayWM. You tell Sway to use it with the following snippet in your Sway configuration:

bar {
+    swaybar_command waybar
+}
+

Configure Waybar itself in ~/.config/waybar/config:

{
+    "layer": "top",
+    "modules-left": ["sway/workspaces", "sway/mode"],
+    "modules-center": ["sway/window"],
+    "modules-right": ["clock"],
+    "sway/window": {
+        "max-length": 50
+    },
+    "clock": {
+        "format-alt": "{:%a, %d. %b  %H:%M}"
+    }
+}
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/sway-waybar/jsonld.json b/posts/sway-waybar/jsonld.json new file mode 100644 index 000000000..3d1916460 --- /dev/null +++ b/posts/sway-waybar/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Waybar on SwayWM","headline":"Waybar on SwayWM","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/sway-waybar/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-08-23","datePublished":"2021-08-23","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/sway-waybar/","wordCount":"58","genre":["swaywm","waybar"],"keywords":["swaywm","waybar"]} \ No newline at end of file diff --git a/posts/tmux-login-shell/index.html b/posts/tmux-login-shell/index.html new file mode 100644 index 000000000..567735727 --- /dev/null +++ b/posts/tmux-login-shell/index.html @@ -0,0 +1,33 @@ +Sebastian Hoß – Use tmux as your login shell

Use tmux as your login shell +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-chsh" href="/tags/chsh">chsh</a>, <a class="post-tag post-tag-login-shell" href="/tags/login-shell">login shell</a>, and <a class="post-tag post-tag-tmux" href="/tags/tmux">tmux</a>

To use tmux as your login shell, use chsh:

# list all available shells
+$ chsh --list-shells
+/bin/sh
+/bin/bash
+/sbin/nologin
+/usr/bin/sh
+/usr/bin/bash
+/usr/sbin/nologin
+/usr/bin/zsh
+/bin/zsh
+/usr/bin/tmux
+/bin/tmux
+
+# select login shell
+$ chsh --shell /usr/bin/tmux
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/tmux-login-shell/jsonld.json b/posts/tmux-login-shell/jsonld.json new file mode 100644 index 000000000..203d288ca --- /dev/null +++ b/posts/tmux-login-shell/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Use tmux as your login shell","headline":"Use tmux as your login shell","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/tmux-login-shell/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-04-19","datePublished":"2021-04-19","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/tmux-login-shell/","wordCount":"35","genre":["tmux","login shell","chsh"],"keywords":["tmux","login shell","chsh"]} \ No newline at end of file diff --git a/posts/tmux-peek/index.html b/posts/tmux-peek/index.html new file mode 100644 index 000000000..3e19a18f3 --- /dev/null +++ b/posts/tmux-peek/index.html @@ -0,0 +1,19 @@ +Sebastian Hoß – Peeking with tmux

Peeking with tmux +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-peek" href="/tags/peek">peek</a>, and <a class="post-tag post-tag-tmux" href="/tags/tmux">tmux</a>

tmux uses can use the following snippet to peek at files. Place it in your .bashrc or similar file.

peek() { tmux split-window -p 33 "$EDITOR" "$@" }
+

Calling peek <file> will open <file> in lower third of tmux window.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/tmux-peek/jsonld.json b/posts/tmux-peek/jsonld.json new file mode 100644 index 000000000..54d940c83 --- /dev/null +++ b/posts/tmux-peek/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Peeking with tmux","headline":"Peeking with tmux","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/tmux-peek/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-04-05","datePublished":"2021-04-05","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/tmux-peek/","wordCount":"40","genre":["tmux","peek"],"keywords":["tmux","peek"]} \ No newline at end of file diff --git a/posts/tmux-status-bar/index.html b/posts/tmux-status-bar/index.html new file mode 100644 index 000000000..bd79e04d3 --- /dev/null +++ b/posts/tmux-status-bar/index.html @@ -0,0 +1,28 @@ +Sebastian Hoß – tmux Status Bar

tmux Status Bar +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-status-bar" href="/tags/status-bar">status bar</a>, and <a class="post-tag post-tag-tmux" href="/tags/tmux">tmux</a>

To see the currently active tmux status bar configuration, call:

$ tmux show-options -g | grep status
+

Change on of those values with in the current tmux session:

$ tmux set-option status-right ""
+

Persist the change in your tmux.conf like this:

# disable right side of status bar
+set-option -g status-right ""
+

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/tmux-status-bar/jsonld.json b/posts/tmux-status-bar/jsonld.json new file mode 100644 index 000000000..311a8d568 --- /dev/null +++ b/posts/tmux-status-bar/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"tmux Status Bar","headline":"tmux Status Bar","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/tmux-status-bar/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-08-09","datePublished":"2021-08-09","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/tmux-status-bar/","wordCount":"52","genre":["tmux","status bar"],"keywords":["tmux","status bar"]} \ No newline at end of file diff --git a/posts/tmux-tmuxp/index.html b/posts/tmux-tmuxp/index.html new file mode 100644 index 000000000..80c483f53 --- /dev/null +++ b/posts/tmux-tmuxp/index.html @@ -0,0 +1,25 @@ +Sebastian Hoß – Manage tmux sessions with tmuxp

Manage tmux sessions with tmuxp +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-tmux" href="/tags/tmux">tmux</a>, and <a class="post-tag post-tag-tmuxp" href="/tags/tmuxp">tmuxp</a>

To manage tmux sessions, I like to use tmuxp. It works by having pre-defined sessions in ~/.config/tmuxp which looks like this:

session_name: cool-app
+start_directory: ~/projects/cool-app
+windows:
+- window_name: backend
+  start_directory: backend
+- window_name: frontend
+  start_directory: frontend
+

In case the name of the file is cool-app.yaml, you can open the sessions with tmuxp load cool-app --yes.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/tmux-tmuxp/jsonld.json b/posts/tmux-tmuxp/jsonld.json new file mode 100644 index 000000000..ad410a028 --- /dev/null +++ b/posts/tmux-tmuxp/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"Manage tmux sessions with tmuxp","headline":"Manage tmux sessions with tmuxp","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/tmux-tmuxp/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2021","dateCreated":"2021-12-20","datePublished":"2021-12-20","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/tmux-tmuxp/","wordCount":"55","genre":["tmux","tmuxp"],"keywords":["tmux","tmuxp"]} \ No newline at end of file diff --git a/posts/xdg-dot-files/index.html b/posts/xdg-dot-files/index.html new file mode 100644 index 000000000..f4359297c --- /dev/null +++ b/posts/xdg-dot-files/index.html @@ -0,0 +1,37 @@ +Sebastian Hoß – XDG Base Directory Specification

XDG Base Directory Specification +View article history +Edit article

Published: +, Updated:
Talks about: +<a class="post-tag post-tag-dotfiles" href="/tags/dotfiles">dotfiles</a>, and <a class="post-tag post-tag-xdg" href="/tags/xdg">xdg</a>

The XDG Base Directory Specification has been around for a while, yet not every software has adopted it yet. Here is an incomplete list of fixes:

# use existing env variables or define new
+[ -z "$XDG_CACHE_HOME"  ] && export XDG_CACHE_HOME="$HOME/.cache"
+[ -z "$XDG_CONFIG_DIRS" ] && export XDG_CONFIG_DIRS="/etc/xdg"
+[ -z "$XDG_CONFIG_HOME" ] && export XDG_CONFIG_HOME="$HOME/.config"
+[ -z "$XDG_DATA_DIRS"   ] && export XDG_DATA_DIRS="/usr/local/share:/usr/share"
+[ -z "$XDG_DATA_HOME"   ] && export XDG_DATA_HOME="$HOME/.local/share"
+
+# gradle
+export GRADLE_USER_HOME="$XDG_DATA_HOME/gradle"
+
+# httpie
+export HTTPIE_CONFIG_DIR="$XDG_CONFIG_HOME/httpie"
+
+# npm
+export NPM_CONFIG_USERCONFIG="$XDG_CONFIG_HOME/npm/npmrc"
+export npm_config_cache="$XDG_CACHE_HOME/npm"
+
+# password-store
+export PASSWORD_STORE_DIR="$XDG_DATA_HOME/password-store"
+

To make your own software XDG-aware, consider using the dirs-dev or configdir libraries.

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/posts/xdg-dot-files/jsonld.json b/posts/xdg-dot-files/jsonld.json new file mode 100644 index 000000000..d75a08a94 --- /dev/null +++ b/posts/xdg-dot-files/jsonld.json @@ -0,0 +1 @@ +{"@context":"https://schema.org","@type":"BlogPosting","articleSection":["snippet"],"name":"XDG Base Directory Specification","headline":"XDG Base Directory Specification","inLanguage":"en","isFamilyFriendly":"true","mainEntityOfPage":{"@type":"WebPage","@id":"https://seb.xn--ho-hia.de/posts/xdg-dot-files/"},"author":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"creator":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"accountablePerson":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"publisher":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightHolder":{"@type":"Person","name":"Sebastian Hoß","url":"https://seb.xn--ho-hia.de/"},"copyrightYear":"2020","dateCreated":"2020-07-27","datePublished":"2020-07-27","dateModified":"2023-01-06","url":"https://seb.xn--ho-hia.de/posts/xdg-dot-files/","wordCount":"100","genre":["dotfiles","xdg"],"keywords":["dotfiles","xdg"]} \ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 000000000..b1ed0fe94 --- /dev/null +++ b/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Sitemap: https://seb.xn--ho-hia.de/sitemap.xml diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..7ce05f414 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1 @@ +https://seb.xn--ho-hia.de/categories/2023-01-09T07:05:35+01:00monthly0.5https://seb.xn--ho-hia.de/tags/java/2023-01-09T07:05:35+01:00monthly0.5https://seb.xn--ho-hia.de/posts/jspecify/2023-01-09T07:05:35+01:00monthly0.5https://seb.xn--ho-hia.de/tags/jspecify/2023-01-09T07:05:35+01:00monthly0.5https://seb.xn--ho-hia.de/tags/nullness/2023-01-09T07:05:35+01:00monthly0.5https://seb.xn--ho-hia.de/posts/2023-01-09T07:05:35+01:00monthly0.5https://seb.xn--ho-hia.de/categories/snippet/2023-01-09T07:05:35+01:00monthly0.5https://seb.xn--ho-hia.de/tags/2023-01-09T07:05:35+01:00monthly0.5https://seb.xn--ho-hia.de/tags/age/2023-01-08T09:25:44+01:00monthly0.5https://seb.xn--ho-hia.de/tags/chezmoi/2023-01-08T09:25:44+01:00monthly0.5https://seb.xn--ho-hia.de/tags/dotfiles/2023-01-08T09:25:44+01:00monthly0.5https://seb.xn--ho-hia.de/tags/encryption/2023-01-08T09:25:44+01:00monthly0.5https://seb.xn--ho-hia.de/posts/chezmoi-age/2023-01-08T09:25:44+01:00monthly0.5https://seb.xn--ho-hia.de/tags/config/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/git/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/multiple-git-configs/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/clipboard/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/fuzzy/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/passage/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/passage-fuzzy-search/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/search/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/automation/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/chezmoi-auto-update/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/shell-init/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/performance/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/shell/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/aws/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/awsenv/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/azure/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/fzf/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/clojure/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/clojure-java-interoperability/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/interoperability/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/breakpoints/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/categories/frontend/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/react/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/rendering/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/hostnames/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/categories/network/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/home-network-hostnames/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/rfc/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/neovim/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/plugins/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/systemd/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/updates/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/vim/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/tmux-tmuxp/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/tmux/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/tmuxp/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/categories/cd/2023-01-06T16:46:32+01:00monthly0.5https://seb.xn--ho-hia.de/posts/maven-cd-versioning/2023-01-06T16:46:32+01:00monthly0.5https://seb.xn--ho-hia.de/tags/maven/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/versioning/2023-01-06T16:46:32+01:00monthly0.5https://seb.xn--ho-hia.de/tags/github/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/github-packages/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/github-maven-packages/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/google/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/maven-google-central/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/repository/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/backup/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/emacs-backups/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/emacs/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/chezmoi-maintenance/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/chezmoi-gpg/2023-01-08T09:25:44+01:00monthly0.5https://seb.xn--ho-hia.de/tags/gpg/2023-01-08T09:25:44+01:00monthly0.5https://seb.xn--ho-hia.de/posts/chezmoi-auto-git/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/swaywm/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/waybar/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/sway-waybar/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/status-bar/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/tmux-status-bar/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/emacs-systemd/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/categories/decentralized/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/mirror/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/push/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/git-push-only-mirror/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/git-mirror/2023-01-06T16:46:32+01:00monthly0.5https://seb.xn--ho-hia.de/tags/kubectl/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/kubernetes/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/categories/devops/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/encoding/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/linux/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/mac/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/maven-encoding/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/windows/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/maven-reproducible/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/reproducible/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/github-actions/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/sonarqube/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/chsh/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/login-shell/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/tmux-login-shell/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/sway-screenlock/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/peek/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/tmux-peek/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/screenlock/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/grim/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/screenshot/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/slurp/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/sway-screenshots/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/makefile-readme/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/make/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/makefile/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/readme/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/teamwork/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/github-actions-schedule-build/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/schedule/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/exit-code/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/makefile-ignore/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/hugo/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/service-worker/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/hugo-serviceworkers/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/categories/website/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/manifest/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/web-app/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/hugo-webmanifest/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/assets/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/bundle/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/hugo-bundles/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/humans.txt/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/hugo-humans/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/foaf/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/hugo-foaf/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/atom/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/hugo-atom/2023-01-06T16:40:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/mastodon/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/github-actions-send-toot/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/toot/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/email/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/github-actions-send-email/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/asset/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/release/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/upload/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/github-actions-create-release/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/publish/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/cache/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/xdg/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/xdg-dot-files/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/bitbucket/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/codeberg/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/categories/configuration/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/gitlab/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/gitlab-distributor/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/repo.or.cz/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/help/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/posts/makefile-help/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/perl/2023-01-06T16:22:24+01:00monthly0.5https://seb.xn--ho-hia.de/tags/clone/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/short-git-clones/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/tags/ssh/2023-01-06T17:32:06+01:00monthly0.5https://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/tags/timestamp/2023-01-06T15:27:21+01:00monthly0.5https://seb.xn--ho-hia.de/sitemap/2020-06-12T21:39:42+02:00monthly0.5https://seb.xn--ho-hia.de/2023-01-08T09:39:49+01:00monthly0.5 \ No newline at end of file diff --git a/sitemap/atom.xml b/sitemap/atom.xml new file mode 100644 index 000000000..554163c8d --- /dev/null +++ b/sitemap/atom.xml @@ -0,0 +1 @@ +HugoSitemap on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/sitemap/ \ No newline at end of file diff --git a/sitemap/index.html b/sitemap/index.html new file mode 100644 index 000000000..e4b7ee69b --- /dev/null +++ b/sitemap/index.html @@ -0,0 +1,14 @@ +Sebastian Hoß – Sitemap

Sitemap

See all pages by category

See all pages by tag

Pages

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/sitemap/index.xml b/sitemap/index.xml new file mode 100644 index 000000000..fcd1e10f6 --- /dev/null +++ b/sitemap/index.xml @@ -0,0 +1 @@ +Sitemap on Sebastian Hoßhttps://seb.xn--ho-hia.de/sitemap/Recent content in Sitemap on Sebastian HoßHugoen \ No newline at end of file diff --git a/tags/age/atom.xml b/tags/age/atom.xml new file mode 100644 index 000000000..6e34e1620 --- /dev/null +++ b/tags/age/atom.xml @@ -0,0 +1,54 @@ +HugoAge on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/age/Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/2023-01-08T00:00:00+00:002023-01-08T09:25:44+01:00age is another tool supported by chezmoi to keep data private. Compared to gpg it is much simpler by focusing on the encryption parts only.

+

Add the following snippet to your .chezmoi.toml to configure chezmoi to use age:

+
encryption = "age"
+[age]
+  identity = "path/to/age/private-key"
+  recipient = "age...public...key..."
+

Adding files to your chezmoi source directory remains the same as compared to using gpg - just call chezmoi add --encrypt path/to/file.

+]]>
passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/2022-12-27T00:00:00+00:002023-01-06T16:40:24+01:00To fuzzy search through passwords managed with passage, I’ve written the following script that is inspired by the upstream version which is using fzf.

+
fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

This version requires fd, skim, xargs, and passage itself of course. The detailed breakdown on how it works is as follows:

+
    +
  1. Use fd to find all files within ${PASSAGE_DIR} that end in .age. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.
  2. +
  3. Using both --base-directory and --exec echo '{.}' ensures that passwords are returned in such form that they can be passed back into passage again. The placeholder '{.}' is a feature provided by fd which strips the file extension from each returned value.
  4. +
  5. All passwords are then passed into sk to allow to fuzzy search across them all. Setting --no-multi ensures that only a single password can be selected.
  6. +
  7. Finally, xargs calls passage and replaces the curly braces with the selected password. Thanks to --clip=1, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.
  8. +
+

To call that script, I’ve saved it as passage-fuzzy-search.sh in my .local/bin folder and added some checks into it to verify that every required software is actually installed.

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This shell script presents passwords saved with passage through skim
+#
+# Call it like this:
+#   passage-fuzzy-search.sh
+#
+# Required software that isn't in GNU coreutils:
+#   - 'passage' to read passwords
+#   - 'fd' to find passwords
+#   - 'sk' to filter passwords
+###############################################################################
+
+if ! (( ${+commands[passage]} )); then
+    echo 'passage not installed. Please install passage.'
+    exit
+fi
+if ! (( ${+commands[sk]} )); then
+    echo 'sk not installed. Please install skim.'
+    exit
+fi
+if ! (( ${+commands[fd]} )); then
+    echo 'fd not installed. Please install fd-find.'
+    exit
+fi
+
+fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

Since typing passage-fuzzy-search.sh is way too long, I have added an alias like this:

+
alias pp='passage-fuzzy-search.sh'
+
]]>
\ No newline at end of file diff --git a/tags/age/index.html b/tags/age/index.html new file mode 100644 index 000000000..6db0acff3 --- /dev/null +++ b/tags/age/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Age

Tag: Age

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/age/index.xml b/tags/age/index.xml new file mode 100644 index 000000000..4d997ef0a --- /dev/null +++ b/tags/age/index.xml @@ -0,0 +1,19 @@ +Age on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/age/Recent content in Age on Sebastian HoßHugoenSun, 08 Jan 2023 09:25:44 +0100Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/Sun, 08 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-age/<p><a href="https://age-encryption.org/">age</a> is another tool supported by <a href="https://www.chezmoi.io/">chezmoi</a> to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. Compared to <code>gpg</code> it is much simpler by focusing on the encryption parts only.</p> +<p>Add the following snippet to your <code>.chezmoi.toml</code> to configure <code>chezmoi</code> to use <code>age</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">encryption</span> <span class="p">=</span> <span class="s2">&#34;age&#34;</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">age</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">identity</span> <span class="p">=</span> <span class="s2">&#34;path/to/age/private-key&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;age...public...key...&#34;</span> +</span></span></code></pre></div><p>Adding files to your <code>chezmoi</code> source directory remains the same as compared to using <code>gpg</code> - just call <code>chezmoi add --encrypt path/to/file</code>.</p>passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/passage-fuzzy-search/<p>To fuzzy search through passwords managed with <a href="https://github.com/FiloSottile/passage">passage</a>, I&rsquo;ve written the following script that is inspired by the upstream version which is using <code>fzf</code>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">fd --type<span class="o">=</span>file --base-directory<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PASSAGE_DIR</span><span class="k">:-</span><span class="si">${</span><span class="nv">HOME</span><span class="si">}</span><span class="p">/.passage/store</span><span class="si">}</span><span class="s2">&#34;</span> .age --exec <span class="nb">echo</span> <span class="s1">&#39;{.}&#39;</span> <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> sk --cycle --layout<span class="o">=</span>reverse --tiebreak<span class="o">=</span>score --no-multi <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> xargs --replace --max-args<span class="o">=</span><span class="m">1</span> --no-run-if-empty <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> passage show --clip<span class="o">=</span><span class="m">1</span> <span class="o">{}</span> +</span></span></code></pre></div><p>This version requires <a href="https://github.com/sharkdp/fd/">fd</a>, <a href="https://github.com/lotabout/skim">skim</a>, <a href="https://www.gnu.org/software/findutils/manual/html_node/find_html/Invoking-xargs.html">xargs</a>, and <a href="https://github.com/FiloSottile/passage">passage</a> itself of course. The detailed breakdown on how it works is as follows:</p> +<ol> +<li>Use <code>fd</code> to find all files within <code>${PASSAGE_DIR}</code> that end in <code>.age</code>. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.</li> +<li>Using both <code>--base-directory</code> and <code>--exec echo '{.}'</code> ensures that passwords are returned in such form that they can be passed back into <code>passage</code> again. The placeholder <code>'{.}'</code> is a feature provided by <code>fd</code> which strips the file extension from each returned value.</li> +<li>All passwords are then passed into <code>sk</code> to allow to fuzzy search across them all. Setting <code>--no-multi</code> ensures that only a single password can be selected.</li> +<li>Finally, <code>xargs</code> calls <code>passage</code> and replaces the curly braces with the selected password. Thanks to <code>--clip=1</code>, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.</li> +</ol> +<p>To call that script, I&rsquo;ve saved it as <code>passage-fuzzy-search.sh</code> in my <code>.local/bin</code> folder and added some checks into it to verify that every required software is actually installed.</p> \ No newline at end of file diff --git a/tags/asset/atom.xml b/tags/asset/atom.xml new file mode 100644 index 000000000..7ec8b5dba --- /dev/null +++ b/tags/asset/atom.xml @@ -0,0 +1,21 @@ +HugoAsset on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/asset/Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/2020-10-19T00:00:00+00:002023-01-06T15:27:21+01:00The actions/upload-release-asset action allows to upload a release artifact in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Upload Release Asset
+        id: upload_release_asset
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./some/path/to/file.zip
+          asset_name: public-name-for-file.zip
+          asset_content_type: application/zip
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
\ No newline at end of file diff --git a/tags/asset/index.html b/tags/asset/index.html new file mode 100644 index 000000000..6815363a2 --- /dev/null +++ b/tags/asset/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Asset

Tag: Asset

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/asset/index.xml b/tags/asset/index.xml new file mode 100644 index 000000000..f2cb045c6 --- /dev/null +++ b/tags/asset/index.xml @@ -0,0 +1,20 @@ +Asset on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/asset/Recent content in Asset on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/<p>The <a href="https://github.com/actions/upload-release-asset">actions/upload-release-asset</a> action allows to upload a <a href="https://developer.github.com/v3/repos/releases/#upload-a-release-asset">release artifact</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload Release Asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">upload_release_asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-release-asset@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">upload_url</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.create_release.outputs.upload_url }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_path</span><span class="p">:</span><span class="w"> </span><span class="l">./some/path/to/file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_name</span><span class="p">:</span><span class="w"> </span><span class="l">public-name-for-file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_content_type</span><span class="p">:</span><span class="w"> </span><span class="l">application/zip</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul> \ No newline at end of file diff --git a/tags/assets/atom.xml b/tags/assets/atom.xml new file mode 100644 index 000000000..6b6a7036f --- /dev/null +++ b/tags/assets/atom.xml @@ -0,0 +1,20 @@ +HugoAssets on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/assets/Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/2020-12-28T00:00:00+00:002023-01-06T15:27:21+01:00Hugo allows bundling of assets with several built-in functions:

+
{{ $normalize := resources.Get "/css/normalize.css" }}
+{{ $font := resources.Get "/css/font.css" }}
+{{ $header := resources.Get "/css/header.css" }}
+{{ $footer := resources.Get "/css/footer.css" }}
+{{ $navigation := resources.Get "/css/navigation.css" }}
+{{ $navigation_mobile := resources.Get "/css/navigation-mobile.css" }}
+{{ $layout := resources.Get "/css/layout.css" }}
+{{ $layout_mobile := resources.Get "/css/layout-mobile.css" }}
+{{ $syntax := resources.Get "/css/syntax.css" }}
+{{ $darkmode := resources.Get "/css/darkmode.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat "css/base.css" | resources.Minify | resources.Fingerprint "sha512" }}
+{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat "css/mobile.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+<link href="{{ $base.Permalink }}" integrity="{{ $base.Data.Integrity }}" media="screen" rel="stylesheet">
+<link href="{{ $mobile.Permalink }}" integrity="{{ $mobile.Data.Integrity }}" media="screen and (max-width: 800px)" rel="stylesheet">
+
+<link href="{{ $darkmode.Permalink }}" integrity="{{ $darkmode.Data.Integrity }}" media="screen and (prefers-color-scheme: dark)" rel="stylesheet">
+
]]>
\ No newline at end of file diff --git a/tags/assets/index.html b/tags/assets/index.html new file mode 100644 index 000000000..15278445b --- /dev/null +++ b/tags/assets/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Assets

Tag: Assets

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/assets/index.xml b/tags/assets/index.xml new file mode 100644 index 000000000..0bc797e84 --- /dev/null +++ b/tags/assets/index.xml @@ -0,0 +1,20 @@ +Assets on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/assets/Recent content in Assets on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/Mon, 28 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-bundles/<p><a href="https://gohugo.io/">Hugo</a> allows <a href="https://gohugo.io/hugo-pipes/bundling/">bundling</a> of assets with several built-in functions:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ $normalize := resources.Get &#34;/css/normalize.css&#34; }} +{{ $font := resources.Get &#34;/css/font.css&#34; }} +{{ $header := resources.Get &#34;/css/header.css&#34; }} +{{ $footer := resources.Get &#34;/css/footer.css&#34; }} +{{ $navigation := resources.Get &#34;/css/navigation.css&#34; }} +{{ $navigation_mobile := resources.Get &#34;/css/navigation-mobile.css&#34; }} +{{ $layout := resources.Get &#34;/css/layout.css&#34; }} +{{ $layout_mobile := resources.Get &#34;/css/layout-mobile.css&#34; }} +{{ $syntax := resources.Get &#34;/css/syntax.css&#34; }} +{{ $darkmode := resources.Get &#34;/css/darkmode.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat &#34;css/base.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} +{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat &#34;css/mobile.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +&lt;link href=&#34;{{ $base.Permalink }}&#34; integrity=&#34;{{ $base.Data.Integrity }}&#34; media=&#34;screen&#34; rel=&#34;stylesheet&#34;&gt; +&lt;link href=&#34;{{ $mobile.Permalink }}&#34; integrity=&#34;{{ $mobile.Data.Integrity }}&#34; media=&#34;screen and (max-width: 800px)&#34; rel=&#34;stylesheet&#34;&gt; + +&lt;link href=&#34;{{ $darkmode.Permalink }}&#34; integrity=&#34;{{ $darkmode.Data.Integrity }}&#34; media=&#34;screen and (prefers-color-scheme: dark)&#34; rel=&#34;stylesheet&#34;&gt; +</code></pre> \ No newline at end of file diff --git a/tags/atom/atom.xml b/tags/atom/atom.xml new file mode 100644 index 000000000..d7d0731ce --- /dev/null +++ b/tags/atom/atom.xml @@ -0,0 +1,134 @@ +HugoAtom on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/atom/Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/2020-11-16T00:00:00+00:002023-01-06T16:40:24+01:00To publish Atom feeds for your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/atom+xml"]
+  suffixes = ["xml"]
+[outputFormats.Atom]
+  name = "Atom"
+  mediaType = "application/atom+xml"
+  baseName = "atom"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/list.atom.xml with the following content:

+
{{ printf `<?xml version="1.0" encoding="utf-8"?>` | safeHTML }}
+<feed xmlns="http://www.w3.org/2005/Atom"{{ with site.LanguageCode }} xml:lang="{{ . }}"{{ end }}>
+    <generator uri="https://gohugo.io/" version="{{ hugo.Version }}">Hugo</generator>
+    {{- $title := site.Title -}}
+    {{- with .Title -}}
+        {{- if (not (eq . site.Title)) -}}
+            {{- $title = printf `%s %s %s` . (i18n "feed_title_on" | default "on") site.Title -}}
+        {{- end -}}
+    {{- end -}}
+    {{- if .IsTranslated -}}
+        {{ $title = printf "%s (%s)" $title (index site.Data.i18n.languages .Lang) }}
+    {{- end -}}
+    {{ printf `<title type="html"><![CDATA[%s]]></title>` $title | safeHTML }}
+    {{ with (or (.Param "subtitle") (.Param "tagline")) }}
+        {{ printf `<subtitle type="html"><![CDATA[%s]]></subtitle>` . | safeHTML }}
+    {{ end }}
+    {{ $output_formats := .OutputFormats }}
+    {{ range $output_formats -}}
+        {{- $rel := (or (and (eq "atom" (.Name | lower)) "self") "alternate") -}}
+        {{ with $output_formats.Get .Name }}
+            {{ printf `<link href=%q rel=%q type=%q title=%q />` .Permalink $rel .MediaType.Type .Name | safeHTML }}
+        {{- end -}}
+    {{- end }}
+    {{- range .Translations }}
+        {{ $output_formats := .OutputFormats }}
+        {{- $lang := .Lang }}
+        {{- $langstr := index site.Data.i18n.languages .Lang }}
+        {{ range $output_formats -}}
+            {{ with $output_formats.Get .Name }}
+                {{ printf `<link href=%q rel="alternate" type=%q hreflang=%q title="[%s] %s" />` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }}
+            {{- end -}}
+        {{- end }}
+    {{- end }}
+    <updated>{{ now.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+    {{ with site.Copyright }}
+        {{- $copyright := replace . "{year}" now.Year -}} {{/* In case the site.copyright uses a special string "{year}" */}}
+        {{- $copyright = replace $copyright "&copy;" "©" -}}
+        <rights>{{ $copyright | plainify }}</rights>
+    {{- end }}
+    {{ with .Param "feed" }}
+        {{/* For this to work, the $icon file should be present in the assets/ directory */}}
+        {{- $icon := .icon | default "icon.svg" -}}
+        {{- with resources.Get $icon -}}
+            <icon>{{ (. | fingerprint).Permalink }}</icon>
+        {{- end }}
+
+        {{/* For this to work, the $logo file should be present in the assets/ directory */}}
+        {{- $logo := .logo | default "logo.svg" -}}
+        {{- with resources.Get $logo -}}
+            <logo>{{ (. | fingerprint).Permalink }}</logo>
+        {{- end }}
+    {{ end }}
+    {{ with site.Author.name -}}
+        <author>
+            <name>{{ . }}</name>
+            {{ with site.Author.email }}
+                <email>{{ . }}</email>
+            {{ end -}}
+        </author>
+    {{- end }}
+    {{ with site.Params.id }}
+        <id>{{ . | plainify }}</id>
+    {{ else }}
+        <id>{{ .Permalink }}</id>
+    {{ end }}
+    {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }}
+    {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}}
+    {{/* Range through only the pages with a Type in $feed_sections. */}}
+    {{- $pages := where .RegularPages "Type" "in" $feed_sections -}}
+    {{- if (eq .Kind "home") -}}
+        {{- $pages = where site.RegularPages "Type" "in" $feed_sections -}}
+    {{- end -}}
+    {{/* Remove the pages that have the disable_feed parameter set to true. */}}
+    {{- $pages = where $pages ".Params.disable_feed" "!=" true -}}
+    {{- range first $limit $pages }}
+        {{ $page := . }}
+        <entry>
+            {{ printf `<title type="html"><![CDATA[%s]]></title>` .Title | safeHTML }}
+            <link href="{{ .Permalink }}?utm_source=atom_feed" rel="alternate" type="text/html" />
+            {{- range .Translations }}
+                {{- $link := printf "%s?utm_source=atom_feed" .Permalink | safeHTML }}
+                {{- printf `<link href=%q rel="alternate" type="text/html" hreflang=%q />` $link .Lang | safeHTML }}
+            {{- end }}
+            {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}}
+            {{- range first 5 (site.RegularPages.Related .) }}
+                <link href="{{ .Permalink }}?utm_source=atom_feed" rel="related" type="text/html" title="{{ .Title }}" />
+            {{- end }}
+            {{ with .Params.id }}
+                <id>{{ . | plainify }}</id>
+            {{ else }}
+                <id>{{ .Permalink }}</id>
+            {{ end }}
+            {{ with .Params.author -}}
+                {{- range . -}} <!-- Assuming the author front-matter to be a list -->
+                    <author>
+                        <name>{{ . }}</name>
+                    </author>
+                {{- end -}}
+            {{- end }}
+            <published>{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</published>
+            <updated>{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+            {{ $description1 := .Description | default "" }}
+            {{ $description := (cond (eq "" $description1) "" (printf "<blockquote>%s</blockquote>" ($description1 | markdownify))) }}
+            {{ printf `<content type="html"><![CDATA[%s%s]]></content>` $description .Content | safeHTML }}
+            {{ with site.Taxonomies }}
+                {{ range $taxo,$_ := . }} <!-- Defaults taxos: "tags", "categories" -->
+                    {{ with $page.Param $taxo }}
+                        {{ $taxo_list := . }} <!-- $taxo_list will be the tags/categories list -->
+                        {{ with site.GetPage (printf "/%s" $taxo) }}
+                            {{ $taxonomy_page := . }}
+                            {{ range $taxo_list }} <!-- Below, assuming pretty URLs -->
+                                <category scheme="{{ printf "%s%s" $taxonomy_page.Permalink (. | urlize) }}" term="{{ (. | urlize) }}" label="{{ . }}" />
+                            {{ end }}
+                        {{ end }}
+                    {{ end }}
+                {{ end }}
+            {{ end }}
+        </entry>
+    {{ end }}
+</feed>
+
]]>
\ No newline at end of file diff --git a/tags/atom/index.html b/tags/atom/index.html new file mode 100644 index 000000000..67083b441 --- /dev/null +++ b/tags/atom/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Atom

Tag: Atom

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/atom/index.xml b/tags/atom/index.xml new file mode 100644 index 000000000..d438e878e --- /dev/null +++ b/tags/atom/index.xml @@ -0,0 +1,134 @@ +Atom on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/atom/Recent content in Atom on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-atom/<p>To publish Atom feeds for your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/atom+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Atom</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/atom+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/list.atom.xml</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ printf `&lt;?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&gt;` | safeHTML }} +&lt;feed xmlns=&#34;http://www.w3.org/2005/Atom&#34;{{ with site.LanguageCode }} xml:lang=&#34;{{ . }}&#34;{{ end }}&gt; + &lt;generator uri=&#34;https://gohugo.io/&#34; version=&#34;{{ hugo.Version }}&#34;&gt;Hugo&lt;/generator&gt; + {{- $title := site.Title -}} + {{- with .Title -}} + {{- if (not (eq . site.Title)) -}} + {{- $title = printf `%s %s %s` . (i18n &#34;feed_title_on&#34; | default &#34;on&#34;) site.Title -}} + {{- end -}} + {{- end -}} + {{- if .IsTranslated -}} + {{ $title = printf &#34;%s (%s)&#34; $title (index site.Data.i18n.languages .Lang) }} + {{- end -}} + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` $title | safeHTML }} + {{ with (or (.Param &#34;subtitle&#34;) (.Param &#34;tagline&#34;)) }} + {{ printf `&lt;subtitle type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/subtitle&gt;` . | safeHTML }} + {{ end }} + {{ $output_formats := .OutputFormats }} + {{ range $output_formats -}} + {{- $rel := (or (and (eq &#34;atom&#34; (.Name | lower)) &#34;self&#34;) &#34;alternate&#34;) -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=%q type=%q title=%q /&gt;` .Permalink $rel .MediaType.Type .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- range .Translations }} + {{ $output_formats := .OutputFormats }} + {{- $lang := .Lang }} + {{- $langstr := index site.Data.i18n.languages .Lang }} + {{ range $output_formats -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=&#34;alternate&#34; type=%q hreflang=%q title=&#34;[%s] %s&#34; /&gt;` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- end }} + &lt;updated&gt;{{ now.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ with site.Copyright }} + {{- $copyright := replace . &#34;{year}&#34; now.Year -}} {{/* In case the site.copyright uses a special string &#34;{year}&#34; */}} + {{- $copyright = replace $copyright &#34;&amp;copy;&#34; &#34;©&#34; -}} + &lt;rights&gt;{{ $copyright | plainify }}&lt;/rights&gt; + {{- end }} + {{ with .Param &#34;feed&#34; }} + {{/* For this to work, the $icon file should be present in the assets/ directory */}} + {{- $icon := .icon | default &#34;icon.svg&#34; -}} + {{- with resources.Get $icon -}} + &lt;icon&gt;{{ (. | fingerprint).Permalink }}&lt;/icon&gt; + {{- end }} + + {{/* For this to work, the $logo file should be present in the assets/ directory */}} + {{- $logo := .logo | default &#34;logo.svg&#34; -}} + {{- with resources.Get $logo -}} + &lt;logo&gt;{{ (. | fingerprint).Permalink }}&lt;/logo&gt; + {{- end }} + {{ end }} + {{ with site.Author.name -}} + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + {{ with site.Author.email }} + &lt;email&gt;{{ . }}&lt;/email&gt; + {{ end -}} + &lt;/author&gt; + {{- end }} + {{ with site.Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }} + {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}} + {{/* Range through only the pages with a Type in $feed_sections. */}} + {{- $pages := where .RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- if (eq .Kind &#34;home&#34;) -}} + {{- $pages = where site.RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- end -}} + {{/* Remove the pages that have the disable_feed parameter set to true. */}} + {{- $pages = where $pages &#34;.Params.disable_feed&#34; &#34;!=&#34; true -}} + {{- range first $limit $pages }} + {{ $page := . }} + &lt;entry&gt; + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` .Title | safeHTML }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;alternate&#34; type=&#34;text/html&#34; /&gt; + {{- range .Translations }} + {{- $link := printf &#34;%s?utm_source=atom_feed&#34; .Permalink | safeHTML }} + {{- printf `&lt;link href=%q rel=&#34;alternate&#34; type=&#34;text/html&#34; hreflang=%q /&gt;` $link .Lang | safeHTML }} + {{- end }} + {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}} + {{- range first 5 (site.RegularPages.Related .) }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;related&#34; type=&#34;text/html&#34; title=&#34;{{ .Title }}&#34; /&gt; + {{- end }} + {{ with .Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{ with .Params.author -}} + {{- range . -}} &lt;!-- Assuming the author front-matter to be a list --&gt; + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + &lt;/author&gt; + {{- end -}} + {{- end }} + &lt;published&gt;{{ .Date.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/published&gt; + &lt;updated&gt;{{ .Lastmod.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ $description1 := .Description | default &#34;&#34; }} + {{ $description := (cond (eq &#34;&#34; $description1) &#34;&#34; (printf &#34;&lt;blockquote&gt;%s&lt;/blockquote&gt;&#34; ($description1 | markdownify))) }} + {{ printf `&lt;content type=&#34;html&#34;&gt;&lt;![CDATA[%s%s]]&gt;&lt;/content&gt;` $description .Content | safeHTML }} + {{ with site.Taxonomies }} + {{ range $taxo,$_ := . }} &lt;!-- Defaults taxos: &#34;tags&#34;, &#34;categories&#34; --&gt; + {{ with $page.Param $taxo }} + {{ $taxo_list := . }} &lt;!-- $taxo_list will be the tags/categories list --&gt; + {{ with site.GetPage (printf &#34;/%s&#34; $taxo) }} + {{ $taxonomy_page := . }} + {{ range $taxo_list }} &lt;!-- Below, assuming pretty URLs --&gt; + &lt;category scheme=&#34;{{ printf &#34;%s%s&#34; $taxonomy_page.Permalink (. | urlize) }}&#34; term=&#34;{{ (. | urlize) }}&#34; label=&#34;{{ . }}&#34; /&gt; + {{ end }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + &lt;/entry&gt; + {{ end }} +&lt;/feed&gt; +</code></pre> \ No newline at end of file diff --git a/tags/automation/atom.xml b/tags/automation/atom.xml new file mode 100644 index 000000000..61e85b25e --- /dev/null +++ b/tags/automation/atom.xml @@ -0,0 +1,15 @@ +HugoAutomation on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/automation/chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/2022-12-26T00:00:00+00:002023-01-06T17:32:06+01:00To automatically synchronize dotfiles across my computers, I’ve written the following systemd unit:

+
[Unit]
+Description=Update chezmoi managed dotfiles
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/chezmoi update --no-tty --force
+RemainAfterExit=false
+
+[Install]
+WantedBy=default.target
+

This unit pulls changes from upstream first and then applies the changes to the current computer after I’m logged in and a network connection is available. The --no-tty flag is required because there is no tty when systemd executes chezmoi. Likewise, the --force flag ensures that no interactive prompt will be displayed which we cannot answer since systemd is executing this unit without us being involved.

+]]>
\ No newline at end of file diff --git a/tags/automation/index.html b/tags/automation/index.html new file mode 100644 index 000000000..e39be8b22 --- /dev/null +++ b/tags/automation/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Automation

Tag: Automation

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/automation/index.xml b/tags/automation/index.xml new file mode 100644 index 000000000..d8228cd79 --- /dev/null +++ b/tags/automation/index.xml @@ -0,0 +1,14 @@ +Automation on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/automation/Recent content in Automation on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/Mon, 26 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-update/<p>To automatically synchronize dotfiles across my computers, I&rsquo;ve written the following <code>systemd</code> unit:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-systemd" data-lang="systemd"><span class="line"><span class="cl"><span class="k">[Unit]</span> +</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Update chezmoi managed dotfiles</span> +</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"><span class="na">Wants</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Service]</span> +</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/chezmoi update --no-tty --force</span> +</span></span><span class="line"><span class="cl"><span class="na">RemainAfterExit</span><span class="o">=</span><span class="s">false</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Install]</span> +</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span> +</span></span></code></pre></div><p>This unit pulls changes from upstream first and then applies the changes to the current computer after I&rsquo;m logged in and a network connection is available. The <code>--no-tty</code> flag is required because there is no tty when systemd executes <code>chezmoi</code>. Likewise, the <code>--force</code> flag ensures that no interactive prompt will be displayed which we cannot answer since <code>systemd</code> is executing this unit without us being involved.</p> \ No newline at end of file diff --git a/tags/aws/atom.xml b/tags/aws/atom.xml new file mode 100644 index 000000000..8c93fa9ac --- /dev/null +++ b/tags/aws/atom.xml @@ -0,0 +1,36 @@ +HugoAws on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/aws/awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/2022-02-14T00:00:00+00:002023-01-06T16:40:24+01:00To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the AWS_PROFILE environment variable which is used by many tools that interact with the AWS API, like awscli or terraform. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses aws sso login to perform an MFA login into AWS. The AWS profiles must be set up in such a way that aws configure list-profiles can detect them, which is typically done by adding them in ${AWS_CONFIG_FILE:-$HOME/.aws/config}.

+
#!/usr/bin/env sh
+
+###############################################################################
+# This script performs an AWS SSO login for the user-selected AWS profile
+# and sets the AWS_PROFILE environment variable afterwards. To use
+# this, create an alias that sources this script like this:
+#
+#     alias awsenv='source path/to/this/script.sh'
+#
+# Required software that is not in GNU coreutils:
+#   - 'aws' to list profiles & get current caller identity
+#   - 'fzf' to list all available AWS profiles
+###############################################################################
+
+# prompt user to select one AWS profile
+profile=$(aws configure list-profiles | \
+  fzf --cycle --layout=reverse --tiebreak=index)
+
+# user can cancel switching profiles by pressing ESC
+if [ -n "${profile}" ]; then
+  # check is access token exists and is valid for selected profile
+  if ! aws --profile "${profile}" sts get-caller-identity >/dev/null 2>&1; then
+    # perform login into profile in case access token is invalid
+    if ! aws sso login --profile "${profile}"; then
+      # short circuit in case login failed
+      return
+    fi
+  fi
+  # AWS_PROFILE is used by many AWS-related tools
+  echo "Setting AWS_PROFILE to [${profile}]"
+  export AWS_PROFILE="${profile}"
+  # do not expose internal variables
+  unset profile
+fi
+
]]>
\ No newline at end of file diff --git a/tags/aws/index.html b/tags/aws/index.html new file mode 100644 index 000000000..e82ca4b72 --- /dev/null +++ b/tags/aws/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Aws

Tag: Aws

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/aws/index.xml b/tags/aws/index.xml new file mode 100644 index 000000000..a31d81efb --- /dev/null +++ b/tags/aws/index.xml @@ -0,0 +1 @@ +Aws on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/aws/Recent content in Aws on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/Mon, 14 Feb 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/awsenv/<p>To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the <code>AWS_PROFILE</code> environment variable which is used by many tools that interact with the AWS API, like <code>awscli</code> or <code>terraform</code>. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses <code>aws sso login</code> to perform an MFA login into AWS. The AWS profiles must be set up in such a way that <code>aws configure list-profiles</code> can detect them, which is typically done by adding them in <code>${AWS_CONFIG_FILE:-$HOME/.aws/config}</code>.</p> \ No newline at end of file diff --git a/tags/azure/atom.xml b/tags/azure/atom.xml new file mode 100644 index 000000000..fc761ce2a --- /dev/null +++ b/tags/azure/atom.xml @@ -0,0 +1,36 @@ +HugoAzure on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/azure/awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/2022-02-14T00:00:00+00:002023-01-06T16:40:24+01:00To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the AWS_PROFILE environment variable which is used by many tools that interact with the AWS API, like awscli or terraform. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses aws sso login to perform an MFA login into AWS. The AWS profiles must be set up in such a way that aws configure list-profiles can detect them, which is typically done by adding them in ${AWS_CONFIG_FILE:-$HOME/.aws/config}.

+
#!/usr/bin/env sh
+
+###############################################################################
+# This script performs an AWS SSO login for the user-selected AWS profile
+# and sets the AWS_PROFILE environment variable afterwards. To use
+# this, create an alias that sources this script like this:
+#
+#     alias awsenv='source path/to/this/script.sh'
+#
+# Required software that is not in GNU coreutils:
+#   - 'aws' to list profiles & get current caller identity
+#   - 'fzf' to list all available AWS profiles
+###############################################################################
+
+# prompt user to select one AWS profile
+profile=$(aws configure list-profiles | \
+  fzf --cycle --layout=reverse --tiebreak=index)
+
+# user can cancel switching profiles by pressing ESC
+if [ -n "${profile}" ]; then
+  # check is access token exists and is valid for selected profile
+  if ! aws --profile "${profile}" sts get-caller-identity >/dev/null 2>&1; then
+    # perform login into profile in case access token is invalid
+    if ! aws sso login --profile "${profile}"; then
+      # short circuit in case login failed
+      return
+    fi
+  fi
+  # AWS_PROFILE is used by many AWS-related tools
+  echo "Setting AWS_PROFILE to [${profile}]"
+  export AWS_PROFILE="${profile}"
+  # do not expose internal variables
+  unset profile
+fi
+
]]>
\ No newline at end of file diff --git a/tags/azure/index.html b/tags/azure/index.html new file mode 100644 index 000000000..5f7e5b04b --- /dev/null +++ b/tags/azure/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Azure

Tag: Azure

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/azure/index.xml b/tags/azure/index.xml new file mode 100644 index 000000000..6d6f21147 --- /dev/null +++ b/tags/azure/index.xml @@ -0,0 +1 @@ +Azure on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/azure/Recent content in Azure on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/Mon, 14 Feb 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/awsenv/<p>To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the <code>AWS_PROFILE</code> environment variable which is used by many tools that interact with the AWS API, like <code>awscli</code> or <code>terraform</code>. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses <code>aws sso login</code> to perform an MFA login into AWS. The AWS profiles must be set up in such a way that <code>aws configure list-profiles</code> can detect them, which is typically done by adding them in <code>${AWS_CONFIG_FILE:-$HOME/.aws/config}</code>.</p> \ No newline at end of file diff --git a/tags/backup/atom.xml b/tags/backup/atom.xml new file mode 100644 index 000000000..81dbbedb0 --- /dev/null +++ b/tags/backup/atom.xml @@ -0,0 +1,29 @@ +HugoBackup on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/backup/Backups with emacshttps://seb.xn--ho-hia.de/posts/emacs-backups/2021-10-25T00:00:00+00:002023-01-06T15:27:21+01:00emacs will create backups of your files by default. Those backups are located right next to the original file and are called <file>~. Unfortunately, emacs will not clean those up by default, which annoys me from time to time. Therefore, I’m now using the following configuration to keep those backups in a different folder:

+
(setq version-control t     ;; Use version numbers for backups.
+      kept-new-versions 10  ;; Number of newest versions to keep.
+      kept-old-versions 0   ;; Number of oldest versions to keep.
+      delete-old-versions t ;; Don't ask to delete excess backup versions.
+      backup-by-copying t)  ;; Copy all files, don't rename them.
+
+(setq vc-make-backup-files t)
+
+;; Default and per-save backups go here:
+(setq backup-directory-alist '(("" . "~/.emacs.d/backup/per-save")))
+
+(defun force-backup-of-buffer ()
+  ;; Make a special "per session" backup at the first save of each
+  ;; emacs session.
+  (when (not buffer-backed-up)
+    ;; Override the default parameters for per-session backups.
+    (let ((backup-directory-alist '(("" . "~/.emacs.d/backup/per-session")))
+          (kept-new-versions 3))
+      (backup-buffer)))
+  ;; Make a "per save" backup on each save.  The first save results in
+  ;; both a per-session and a per-save backup, to keep the numbering
+  ;; of per-save backups consistent.
+  (let ((buffer-backed-up nil))
+    (backup-buffer)))
+
+(add-hook 'before-save-hook  'force-backup-of-buffer)
+

Thanks to that configuration, backups per-save will be created in ~/.emacs.d/backup/per-save and backups per-session in ~/.emacs.d/backup/per-session.

+]]>
\ No newline at end of file diff --git a/tags/backup/index.html b/tags/backup/index.html new file mode 100644 index 000000000..f1ecc92b7 --- /dev/null +++ b/tags/backup/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Backup

Tag: Backup

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/backup/index.xml b/tags/backup/index.xml new file mode 100644 index 000000000..085125a95 --- /dev/null +++ b/tags/backup/index.xml @@ -0,0 +1,28 @@ +Backup on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/backup/Recent content in Backup on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Backups with emacshttps://seb.xn--ho-hia.de/posts/emacs-backups/Mon, 25 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/emacs-backups/<p><a href="https://www.gnu.org/software/emacs/">emacs</a> will create backups of your files by default. Those backups are located right next to the original file and are called <code>&lt;file&gt;~</code>. Unfortunately, emacs will not clean those up by default, which annoys me from time to time. Therefore, I&rsquo;m now using the following configuration to keep those backups in a different folder:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-el" data-lang="el"><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">version-control</span> <span class="no">t</span> <span class="c1">;; Use version numbers for backups.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kept-new-versions</span> <span class="mi">10</span> <span class="c1">;; Number of newest versions to keep.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kept-old-versions</span> <span class="mi">0</span> <span class="c1">;; Number of oldest versions to keep.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">delete-old-versions</span> <span class="no">t</span> <span class="c1">;; Don&#39;t ask to delete excess backup versions.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">backup-by-copying</span> <span class="no">t</span><span class="p">)</span> <span class="c1">;; Copy all files, don&#39;t rename them.</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">vc-make-backup-files</span> <span class="no">t</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">;; Default and per-save backups go here:</span> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">backup-directory-alist</span> <span class="o">&#39;</span><span class="p">((</span><span class="s">&#34;&#34;</span> <span class="o">.</span> <span class="s">&#34;~/.emacs.d/backup/per-save&#34;</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">defun</span> <span class="nv">force-backup-of-buffer</span> <span class="p">()</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Make a special &#34;per session&#34; backup at the first save of each</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; emacs session.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">when</span> <span class="p">(</span><span class="nv">not</span> <span class="nv">buffer-backed-up</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Override the default parameters for per-session backups.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">backup-directory-alist</span> <span class="o">&#39;</span><span class="p">((</span><span class="s">&#34;&#34;</span> <span class="o">.</span> <span class="s">&#34;~/.emacs.d/backup/per-session&#34;</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">kept-new-versions</span> <span class="mi">3</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">backup-buffer</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Make a &#34;per save&#34; backup on each save. The first save results in</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; both a per-session and a per-save backup, to keep the numbering</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; of per-save backups consistent.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">buffer-backed-up</span> <span class="no">nil</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">backup-buffer</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">add-hook</span> <span class="ss">&#39;before-save-hook</span> <span class="ss">&#39;force-backup-of-buffer</span><span class="p">)</span> +</span></span></code></pre></div><p>Thanks to that configuration, backups per-save will be created in <code>~/.emacs.d/backup/per-save</code> and backups per-session in <code>~/.emacs.d/backup/per-session</code>.</p> \ No newline at end of file diff --git a/tags/bitbucket/atom.xml b/tags/bitbucket/atom.xml new file mode 100644 index 000000000..a8cf778de --- /dev/null +++ b/tags/bitbucket/atom.xml @@ -0,0 +1,35 @@ +HugoBitbucket on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/bitbucket/GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/2020-07-13T00:00:00+00:002023-01-06T17:32:06+01:00Git at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. Pro Git rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.

+

While the central server approach is easy to use, it might not work in all scenarios:

+
    +
  1. An enterprise wants to share internal code as part of an open source project. All development is happening internally, and the public mirror gets an occasional update once in a while.
  2. +
  3. To protect against outages of the central server, mirrors should be created and be kept up-to-date.
  4. +
+

In case of the first scenario, tools like copybara, repoSpanner, or distributed-Git-forks offer a wide range of features to cover most details.

+

The second scenario can be solved manually with tools like gitomatic or automatically with GitLab’s mirror feature quite easy. GitLab allows to create a single pull-mirror and multiple push-mirrors. Therefore, it can be used to pull from your central server and push into all mirrors.

+

NOTE: This feature was previously available in the free tier but has now moved to GitLab Ultimate.

+
               +----------------+               
+               |     GitHub     |               
+               +----------------+               
+                        ^                       
+                        |                       
+                        |                       
+               +----------------+               
+         +-----|     GitLab     |------+        
+         |     +----------------+      |        
+         |                             |        
+         |                             |        
+         v                             v        
++----------------+            +----------------+
+|    Codeberg    |            |    BitBucket   |
++----------------+            +----------------+
+

To create such a setup, follow these steps:

+
    +
  1. Go to Settings > Repository and expand Mirroring repositories +Code Flow
  2. +
  3. Enter the URL of your central Git repository as the pull source, for example https://github.com/metio/ilo.git +Code Flow
  4. +
  5. Enter one push target for each mirror. Since pushing usually requires authentication, verify that the URL of the mirror contains a username, for example https://YOUR_USER@codeberg.org/metio.wtf/ilo.git. Add an access token for each mirror and select password as authentication method. +Code Flow
  6. +
+

In case you prefer SSH keys over HTTP access tokens, just select SSH public key as authentication method and verify that your key is both saved in GitLab and all mirrors.

+]]>
\ No newline at end of file diff --git a/tags/bitbucket/index.html b/tags/bitbucket/index.html new file mode 100644 index 000000000..4689d8021 --- /dev/null +++ b/tags/bitbucket/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Bitbucket

Tag: Bitbucket

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/bitbucket/index.xml b/tags/bitbucket/index.xml new file mode 100644 index 000000000..f11cce2aa --- /dev/null +++ b/tags/bitbucket/index.xml @@ -0,0 +1,2 @@ +Bitbucket on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/bitbucket/Recent content in Bitbucket on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/gitlab-distributor/<p><a href="https://git-scm.com/">Git</a> at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. <a href="https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows">Pro Git</a> rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.</p> +<p>While the central server approach is easy to use, it might not work in all scenarios:</p> \ No newline at end of file diff --git a/tags/breakpoints/atom.xml b/tags/breakpoints/atom.xml new file mode 100644 index 000000000..a89e41379 --- /dev/null +++ b/tags/breakpoints/atom.xml @@ -0,0 +1,58 @@ +HugoBreakpoints on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/breakpoints/Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/2022-01-15T00:00:00+00:002023-01-06T16:22:24+01:00One feature that often surprises people while teaching them React is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.

+

In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.

+
const RendersNothing = () => <></>
+

Now consider the following example, that adds some if-then-else logic to the same component:

+
const MightRenderSomething = () => {
+  if (someCondition) {
+    return <span>hello world!</span>
+  }
+  return <></>
+}
+

This component encapsulates the if-then-else logic of conditionally rendering a hello world message. Instead of cluttering your entire app with the same logic, you can now simply re-use that same component that contains this if condition. To see the full power of this technique, consider the following example. At first, we are going to define a hook that reads the current window width, then define components that conditionally render based on the current window width, and finally use those components in an example application.

+
const useWindowWidth = () => {
+  const [width, setWidth] = React.useState(0)
+
+  React.useEffect(() => {
+    const handleResize = () => {
+      setWidth(window.innerWidth)
+    }
+    window.addEventListener("resize", handleResize)
+    return () => {
+      window.removeEventListener("resize", handleResize)
+    }
+  }, [])
+
+  return width
+}
+

The following components use that hook to implement UI breakpoints for small (mobile) and large (desktop) screens. Note that the value 768 is just an example - replace it with whatever your design system tells you to.

+
const ForMobileDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+  
+  if (windowWidth < 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+
+const ForDesktopDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+
+  if (windowWidth >= 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+

Both of these components simply render nothing when the window width does not have an appropriate size. If the window width does have the right size, they render their children. We can use those components in our application like this:

+
const SomeActualComponent = () => (
+    <div>
+      <h1>common headline</h1>
+      <ForMobileDevicesOnly>
+        <span>only visible on mobile devices</span>
+      </ForMobileDevicesOnly>
+      <ForDesktopDevicesOnly>
+        <span>only visible on desktop devices</span>
+      </ForDesktopDevicesOnly>
+    </div>
+)
+

The above code snippet declares that some part of the UI can only be seen by mobile users, while others can only be seen by desktop users. Parts of the UI that are shared amongst all users are not wrapped by any of the components defined above.

+]]>
\ No newline at end of file diff --git a/tags/breakpoints/index.html b/tags/breakpoints/index.html new file mode 100644 index 000000000..5e76b20ef --- /dev/null +++ b/tags/breakpoints/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Breakpoints

Tag: Breakpoints

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/breakpoints/index.xml b/tags/breakpoints/index.xml new file mode 100644 index 000000000..a57ddf5f5 --- /dev/null +++ b/tags/breakpoints/index.xml @@ -0,0 +1,2 @@ +Breakpoints on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/breakpoints/Recent content in Breakpoints on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/Sat, 15 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/<p>One feature that often surprises people while teaching them <a href="https://reactjs.org/">React</a> is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.</p> +<p>In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.</p> \ No newline at end of file diff --git a/tags/bundle/atom.xml b/tags/bundle/atom.xml new file mode 100644 index 000000000..0ab215754 --- /dev/null +++ b/tags/bundle/atom.xml @@ -0,0 +1,20 @@ +HugoBundle on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/bundle/Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/2020-12-28T00:00:00+00:002023-01-06T15:27:21+01:00Hugo allows bundling of assets with several built-in functions:

+
{{ $normalize := resources.Get "/css/normalize.css" }}
+{{ $font := resources.Get "/css/font.css" }}
+{{ $header := resources.Get "/css/header.css" }}
+{{ $footer := resources.Get "/css/footer.css" }}
+{{ $navigation := resources.Get "/css/navigation.css" }}
+{{ $navigation_mobile := resources.Get "/css/navigation-mobile.css" }}
+{{ $layout := resources.Get "/css/layout.css" }}
+{{ $layout_mobile := resources.Get "/css/layout-mobile.css" }}
+{{ $syntax := resources.Get "/css/syntax.css" }}
+{{ $darkmode := resources.Get "/css/darkmode.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat "css/base.css" | resources.Minify | resources.Fingerprint "sha512" }}
+{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat "css/mobile.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+<link href="{{ $base.Permalink }}" integrity="{{ $base.Data.Integrity }}" media="screen" rel="stylesheet">
+<link href="{{ $mobile.Permalink }}" integrity="{{ $mobile.Data.Integrity }}" media="screen and (max-width: 800px)" rel="stylesheet">
+
+<link href="{{ $darkmode.Permalink }}" integrity="{{ $darkmode.Data.Integrity }}" media="screen and (prefers-color-scheme: dark)" rel="stylesheet">
+
]]>
\ No newline at end of file diff --git a/tags/bundle/index.html b/tags/bundle/index.html new file mode 100644 index 000000000..7e0bdebb0 --- /dev/null +++ b/tags/bundle/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Bundle

Tag: Bundle

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/bundle/index.xml b/tags/bundle/index.xml new file mode 100644 index 000000000..f5aa9fef0 --- /dev/null +++ b/tags/bundle/index.xml @@ -0,0 +1,20 @@ +Bundle on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/bundle/Recent content in Bundle on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/Mon, 28 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-bundles/<p><a href="https://gohugo.io/">Hugo</a> allows <a href="https://gohugo.io/hugo-pipes/bundling/">bundling</a> of assets with several built-in functions:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ $normalize := resources.Get &#34;/css/normalize.css&#34; }} +{{ $font := resources.Get &#34;/css/font.css&#34; }} +{{ $header := resources.Get &#34;/css/header.css&#34; }} +{{ $footer := resources.Get &#34;/css/footer.css&#34; }} +{{ $navigation := resources.Get &#34;/css/navigation.css&#34; }} +{{ $navigation_mobile := resources.Get &#34;/css/navigation-mobile.css&#34; }} +{{ $layout := resources.Get &#34;/css/layout.css&#34; }} +{{ $layout_mobile := resources.Get &#34;/css/layout-mobile.css&#34; }} +{{ $syntax := resources.Get &#34;/css/syntax.css&#34; }} +{{ $darkmode := resources.Get &#34;/css/darkmode.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat &#34;css/base.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} +{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat &#34;css/mobile.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +&lt;link href=&#34;{{ $base.Permalink }}&#34; integrity=&#34;{{ $base.Data.Integrity }}&#34; media=&#34;screen&#34; rel=&#34;stylesheet&#34;&gt; +&lt;link href=&#34;{{ $mobile.Permalink }}&#34; integrity=&#34;{{ $mobile.Data.Integrity }}&#34; media=&#34;screen and (max-width: 800px)&#34; rel=&#34;stylesheet&#34;&gt; + +&lt;link href=&#34;{{ $darkmode.Permalink }}&#34; integrity=&#34;{{ $darkmode.Data.Integrity }}&#34; media=&#34;screen and (prefers-color-scheme: dark)&#34; rel=&#34;stylesheet&#34;&gt; +</code></pre> \ No newline at end of file diff --git a/tags/cache/atom.xml b/tags/cache/atom.xml new file mode 100644 index 000000000..363806c32 --- /dev/null +++ b/tags/cache/atom.xml @@ -0,0 +1,18 @@ +HugoCache on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/cache/Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/2020-09-07T00:00:00+00:002023-01-06T15:27:21+01:00The actions/cache action allows to cache artifacts in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Cache Maven artifacts
+        uses: actions/cache@v1
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-            
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
\ No newline at end of file diff --git a/tags/cache/index.html b/tags/cache/index.html new file mode 100644 index 000000000..885c80d41 --- /dev/null +++ b/tags/cache/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Cache

Tag: Cache

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/cache/index.xml b/tags/cache/index.xml new file mode 100644 index 000000000..b5b0cac63 --- /dev/null +++ b/tags/cache/index.xml @@ -0,0 +1,17 @@ +Cache on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/cache/Recent content in Cache on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/Mon, 07 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions/cache</a> action allows to cache artifacts in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Maven artifacts</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">~/.m2/repository</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-maven-${{ hashFiles(&#39;**/pom.xml&#39;) }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> ${{ runner.os }}-maven-</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul> \ No newline at end of file diff --git a/tags/chezmoi/atom.xml b/tags/chezmoi/atom.xml new file mode 100644 index 000000000..9afb8030c --- /dev/null +++ b/tags/chezmoi/atom.xml @@ -0,0 +1,64 @@ +HugoChezmoi on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/chezmoi/Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/2023-01-08T00:00:00+00:002023-01-08T09:25:44+01:00age is another tool supported by chezmoi to keep data private. Compared to gpg it is much simpler by focusing on the encryption parts only.

+

Add the following snippet to your .chezmoi.toml to configure chezmoi to use age:

+
encryption = "age"
+[age]
+  identity = "path/to/age/private-key"
+  recipient = "age...public...key..."
+

Adding files to your chezmoi source directory remains the same as compared to using gpg - just call chezmoi add --encrypt path/to/file.

+]]>
chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/2022-12-26T00:00:00+00:002023-01-06T17:32:06+01:00To automatically synchronize dotfiles across my computers, I’ve written the following systemd unit:

+
[Unit]
+Description=Update chezmoi managed dotfiles
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/chezmoi update --no-tty --force
+RemainAfterExit=false
+
+[Install]
+WantedBy=default.target
+

This unit pulls changes from upstream first and then applies the changes to the current computer after I’m logged in and a network connection is available. The --no-tty flag is required because there is no tty when systemd executes chezmoi. Likewise, the --force flag ensures that no interactive prompt will be displayed which we cannot answer since systemd is executing this unit without us being involved.

+]]>
chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/2022-12-12T00:00:00+00:002023-01-06T16:40:24+01:00Many CLI applications offer initialization scripts to integrate into a shell, for example starship init zsh or zoxide init zsh. The documentation of these tools usually tell you to put something like eval "$(starship init zsh)" into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the init command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.

+

chezmoi provides a template function called output which replaces itself with the output of the command you specified. You can use that function this to integrate various tools into your shell as the following example shows while using zsh:

+
    +
  1. Create a directory that holds all init scripts for every tool you want to use. +
     $ mkdir --parents "${ZDOTDIR}"/tools.d
    +
  2. +
  3. Let your shell load all available scripts in that directory. This snippet should be part of your .zshrc file: +
    for init_script in "${ZDOTDIR}"/tools.d/*.sh; do
    +  source "${init_script}"
    +done
    +
  4. +
  5. Create chezmoi .tmpl files for each tool and place them in the chezmoi source directory that matches the directory you created in step 1: +
    {{ output "starship" "init" "zsh" "--print-full-init" }}
    +
  6. +
  7. Call chezmoi apply to generate the init scripts.
  8. +
+

The only downside here is that you have to re-run chezmoi apply after updating one of the tools because they change their init scripts sometimes. That problem can be solved with chezmoi auto-updates.

+]]>
Maintaining dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-maintenance/2021-10-11T00:00:00+00:002023-01-06T17:32:06+01:00To make it easier managing many dotfiles with chezmoi, a shell function similar to the one below can be used:

+
function m-dotfiles-ok {
+    # public
+    chezmoi add ~/.config/zsh --recursive
+    chezmoi add ~/.config/sway --recursive
+    chezmoi add ~/.config/tmux --recursive
+    chezmoi add ....
+
+    # secrets
+    chezmoi add --encrypt ~/.config/npm/npmrc
+    chezmoi add --encrypt ~/.ssh/id_rsa
+    chezmoi add --encrypt ...
+}
+

Whenever you feel happy with your current setup, just call m-dotfiles-ok to push changes into the chezmoi source directory. Files will automatically be encrypted with gpg and committed/pushed into a Git repository if you have done the necessary configuration beforehand.

+

In general, editing your dotfiles directly as explained in the second option of the FAQ seems easier though. Refactoring your dotfiles is especially easy when the exact_ prefix is used for directories. As explained in the documentation, all files that are not managed by chezmoi will be removed, therefore your configuration will always match what is in your source directory.

+]]>
Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/2021-09-20T00:00:00+00:002023-01-08T09:25:44+01:00RECOMMENDATION: Use age instead of gpg.

+

chezmoi can use various external tools to keep data private. gpg is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure gpg with chezmoi, just set yourself as the recipient like this:

+
[gpg]
+  recipient = "your.name@example.com"
+

Calling chezmoi add --encrypt /path/to/secret will now create encrypt the file with your public key which allows you to decrypt them later with your private key.

+]]>
Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/2021-09-06T00:00:00+00:002023-01-06T17:32:06+01:00chezmoi can automatically commit and push changes to your dotfiles into a (remote) Git repository. Enable it with the following snippet in your chezmoi.toml

+
[sourceVCS]
+    autoCommit = true
+    autoPush = true
+

Every time you call chezmoi add /path/to/file will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.

+]]>
\ No newline at end of file diff --git a/tags/chezmoi/index.html b/tags/chezmoi/index.html new file mode 100644 index 000000000..c2e26b979 --- /dev/null +++ b/tags/chezmoi/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Chezmoi

Tag: Chezmoi

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/chezmoi/index.xml b/tags/chezmoi/index.xml new file mode 100644 index 000000000..12e5de67b --- /dev/null +++ b/tags/chezmoi/index.xml @@ -0,0 +1,41 @@ +Chezmoi on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/chezmoi/Recent content in Chezmoi on Sebastian HoßHugoenSun, 08 Jan 2023 09:25:44 +0100Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/Sun, 08 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-age/<p><a href="https://age-encryption.org/">age</a> is another tool supported by <a href="https://www.chezmoi.io/">chezmoi</a> to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. Compared to <code>gpg</code> it is much simpler by focusing on the encryption parts only.</p> +<p>Add the following snippet to your <code>.chezmoi.toml</code> to configure <code>chezmoi</code> to use <code>age</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">encryption</span> <span class="p">=</span> <span class="s2">&#34;age&#34;</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">age</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">identity</span> <span class="p">=</span> <span class="s2">&#34;path/to/age/private-key&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;age...public...key...&#34;</span> +</span></span></code></pre></div><p>Adding files to your <code>chezmoi</code> source directory remains the same as compared to using <code>gpg</code> - just call <code>chezmoi add --encrypt path/to/file</code>.</p>chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/Mon, 26 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-update/<p>To automatically synchronize dotfiles across my computers, I&rsquo;ve written the following <code>systemd</code> unit:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-systemd" data-lang="systemd"><span class="line"><span class="cl"><span class="k">[Unit]</span> +</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Update chezmoi managed dotfiles</span> +</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"><span class="na">Wants</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Service]</span> +</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/chezmoi update --no-tty --force</span> +</span></span><span class="line"><span class="cl"><span class="na">RemainAfterExit</span><span class="o">=</span><span class="s">false</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Install]</span> +</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span> +</span></span></code></pre></div><p>This unit pulls changes from upstream first and then applies the changes to the current computer after I&rsquo;m logged in and a network connection is available. The <code>--no-tty</code> flag is required because there is no tty when systemd executes <code>chezmoi</code>. Likewise, the <code>--force</code> flag ensures that no interactive prompt will be displayed which we cannot answer since <code>systemd</code> is executing this unit without us being involved.</p>chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/Mon, 12 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/shell-init/<p>Many CLI applications offer initialization scripts to integrate into a shell, for example <code>starship init zsh</code> or <code>zoxide init zsh</code>. The documentation of these tools usually tell you to put something like <code>eval &quot;$(starship init zsh)&quot;</code> into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the <code>init</code> command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.</p>Maintaining dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-maintenance/Mon, 11 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-maintenance/<p>To make it easier managing many dotfiles with <a href="https://www.chezmoi.io/">chezmoi</a>, a shell function similar to the one below can be used:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="k">function</span> m-dotfiles-ok <span class="o">{</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># public</span> +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/zsh --recursive +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/sway --recursive +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/tmux --recursive +</span></span><span class="line"><span class="cl"> chezmoi add .... +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># secrets</span> +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ~/.config/npm/npmrc +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ~/.ssh/id_rsa +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ... +</span></span><span class="line"><span class="cl"><span class="o">}</span> +</span></span></code></pre></div><p>Whenever you feel happy with your current setup, just call <code>m-dotfiles-ok</code> to push changes into the chezmoi source directory. Files will automatically be <a href="../chezmoi-gpg">encrypted</a> with <a href="https://www.gnupg.org/">gpg</a> and <a href="../chezmoi-auto-git">committed/pushed</a> into a <a href="https://git-scm.com/">Git</a> repository if you have done the necessary configuration beforehand.</p>Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/Mon, 20 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-gpg/<p><strong>RECOMMENDATION</strong>: Use <a href="../chezmoi-age">age</a> instead of <code>gpg</code>.</p> +<p><a href="https://www.chezmoi.io/">chezmoi</a> can use various external tools to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. <a href="https://www.gnupg.org/">gpg</a> is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure <code>gpg</code> with <code>chezmoi</code>, just set yourself as the recipient like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">gpg</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;your.name@example.com&#34;</span> +</span></span></code></pre></div><p>Calling <code>chezmoi add --encrypt /path/to/secret</code> will now create encrypt the file with your public key which allows you to decrypt them later with your private key.</p>Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/Mon, 06 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-git/<p><a href="https://www.chezmoi.io/">chezmoi</a> can automatically commit and push changes to your <a href="https://en.wikipedia.org/wiki/dotfile">dotfiles</a> into a (remote) <a href="https://git-scm.com/">Git</a> repository. Enable it with the following snippet in your <code>chezmoi.toml</code></p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">sourceVCS</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoCommit</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoPush</span> <span class="p">=</span> <span class="kc">true</span> +</span></span></code></pre></div><p>Every time you call <code>chezmoi add /path/to/file</code> will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.</p> \ No newline at end of file diff --git a/tags/chsh/atom.xml b/tags/chsh/atom.xml new file mode 100644 index 000000000..b2238a046 --- /dev/null +++ b/tags/chsh/atom.xml @@ -0,0 +1,17 @@ +HugoChsh on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/chsh/Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/2021-04-19T00:00:00+00:002023-01-06T16:22:24+01:00To use tmux as your login shell, use chsh:

+
# list all available shells
+$ chsh --list-shells
+/bin/sh
+/bin/bash
+/sbin/nologin
+/usr/bin/sh
+/usr/bin/bash
+/usr/sbin/nologin
+/usr/bin/zsh
+/bin/zsh
+/usr/bin/tmux
+/bin/tmux
+
+# select login shell
+$ chsh --shell /usr/bin/tmux
+
]]>
\ No newline at end of file diff --git a/tags/chsh/index.html b/tags/chsh/index.html new file mode 100644 index 000000000..6a1eed895 --- /dev/null +++ b/tags/chsh/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Chsh

Tag: Chsh

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/chsh/index.xml b/tags/chsh/index.xml new file mode 100644 index 000000000..d5074618a --- /dev/null +++ b/tags/chsh/index.xml @@ -0,0 +1,17 @@ +Chsh on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/chsh/Recent content in Chsh on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/Mon, 19 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-login-shell/<p>To use <a href="https://github.com/tmux/tmux">tmux</a> as your login shell, use <code>chsh</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># list all available shells</span> +</span></span><span class="line"><span class="cl">$ chsh --list-shells +</span></span><span class="line"><span class="cl">/bin/sh +</span></span><span class="line"><span class="cl">/bin/bash +</span></span><span class="line"><span class="cl">/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/sh +</span></span><span class="line"><span class="cl">/usr/bin/bash +</span></span><span class="line"><span class="cl">/usr/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/zsh +</span></span><span class="line"><span class="cl">/bin/zsh +</span></span><span class="line"><span class="cl">/usr/bin/tmux +</span></span><span class="line"><span class="cl">/bin/tmux +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># select login shell</span> +</span></span><span class="line"><span class="cl">$ chsh --shell /usr/bin/tmux +</span></span></code></pre></div> \ No newline at end of file diff --git a/tags/clipboard/atom.xml b/tags/clipboard/atom.xml new file mode 100644 index 000000000..111f487ad --- /dev/null +++ b/tags/clipboard/atom.xml @@ -0,0 +1,47 @@ +HugoClipboard on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/clipboard/passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/2022-12-27T00:00:00+00:002023-01-06T16:40:24+01:00To fuzzy search through passwords managed with passage, I’ve written the following script that is inspired by the upstream version which is using fzf.

+
fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

This version requires fd, skim, xargs, and passage itself of course. The detailed breakdown on how it works is as follows:

+
    +
  1. Use fd to find all files within ${PASSAGE_DIR} that end in .age. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.
  2. +
  3. Using both --base-directory and --exec echo '{.}' ensures that passwords are returned in such form that they can be passed back into passage again. The placeholder '{.}' is a feature provided by fd which strips the file extension from each returned value.
  4. +
  5. All passwords are then passed into sk to allow to fuzzy search across them all. Setting --no-multi ensures that only a single password can be selected.
  6. +
  7. Finally, xargs calls passage and replaces the curly braces with the selected password. Thanks to --clip=1, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.
  8. +
+

To call that script, I’ve saved it as passage-fuzzy-search.sh in my .local/bin folder and added some checks into it to verify that every required software is actually installed.

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This shell script presents passwords saved with passage through skim
+#
+# Call it like this:
+#   passage-fuzzy-search.sh
+#
+# Required software that isn't in GNU coreutils:
+#   - 'passage' to read passwords
+#   - 'fd' to find passwords
+#   - 'sk' to filter passwords
+###############################################################################
+
+if ! (( ${+commands[passage]} )); then
+    echo 'passage not installed. Please install passage.'
+    exit
+fi
+if ! (( ${+commands[sk]} )); then
+    echo 'sk not installed. Please install skim.'
+    exit
+fi
+if ! (( ${+commands[fd]} )); then
+    echo 'fd not installed. Please install fd-find.'
+    exit
+fi
+
+fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

Since typing passage-fuzzy-search.sh is way too long, I have added an alias like this:

+
alias pp='passage-fuzzy-search.sh'
+
]]>
\ No newline at end of file diff --git a/tags/clipboard/index.html b/tags/clipboard/index.html new file mode 100644 index 000000000..f4d8d8e41 --- /dev/null +++ b/tags/clipboard/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Clipboard

Tag: Clipboard

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/clipboard/index.xml b/tags/clipboard/index.xml new file mode 100644 index 000000000..4c4aec540 --- /dev/null +++ b/tags/clipboard/index.xml @@ -0,0 +1,13 @@ +Clipboard on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/clipboard/Recent content in Clipboard on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/passage-fuzzy-search/<p>To fuzzy search through passwords managed with <a href="https://github.com/FiloSottile/passage">passage</a>, I&rsquo;ve written the following script that is inspired by the upstream version which is using <code>fzf</code>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">fd --type<span class="o">=</span>file --base-directory<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PASSAGE_DIR</span><span class="k">:-</span><span class="si">${</span><span class="nv">HOME</span><span class="si">}</span><span class="p">/.passage/store</span><span class="si">}</span><span class="s2">&#34;</span> .age --exec <span class="nb">echo</span> <span class="s1">&#39;{.}&#39;</span> <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> sk --cycle --layout<span class="o">=</span>reverse --tiebreak<span class="o">=</span>score --no-multi <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> xargs --replace --max-args<span class="o">=</span><span class="m">1</span> --no-run-if-empty <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> passage show --clip<span class="o">=</span><span class="m">1</span> <span class="o">{}</span> +</span></span></code></pre></div><p>This version requires <a href="https://github.com/sharkdp/fd/">fd</a>, <a href="https://github.com/lotabout/skim">skim</a>, <a href="https://www.gnu.org/software/findutils/manual/html_node/find_html/Invoking-xargs.html">xargs</a>, and <a href="https://github.com/FiloSottile/passage">passage</a> itself of course. The detailed breakdown on how it works is as follows:</p> +<ol> +<li>Use <code>fd</code> to find all files within <code>${PASSAGE_DIR}</code> that end in <code>.age</code>. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.</li> +<li>Using both <code>--base-directory</code> and <code>--exec echo '{.}'</code> ensures that passwords are returned in such form that they can be passed back into <code>passage</code> again. The placeholder <code>'{.}'</code> is a feature provided by <code>fd</code> which strips the file extension from each returned value.</li> +<li>All passwords are then passed into <code>sk</code> to allow to fuzzy search across them all. Setting <code>--no-multi</code> ensures that only a single password can be selected.</li> +<li>Finally, <code>xargs</code> calls <code>passage</code> and replaces the curly braces with the selected password. Thanks to <code>--clip=1</code>, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.</li> +</ol> +<p>To call that script, I&rsquo;ve saved it as <code>passage-fuzzy-search.sh</code> in my <code>.local/bin</code> folder and added some checks into it to verify that every required software is actually installed.</p> \ No newline at end of file diff --git a/tags/clojure/atom.xml b/tags/clojure/atom.xml new file mode 100644 index 000000000..c669832f3 --- /dev/null +++ b/tags/clojure/atom.xml @@ -0,0 +1,105 @@ +HugoClojure on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/clojure/Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/2022-01-29T00:00:00+00:002023-01-06T17:32:06+01:00Clojure has several forms and macros to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.

+

Using gen-class

+

Clojure code can be compiled to standard JVM bytecode using gen-class.

+

Adding static modifiers

+

Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class com.example.Computation.

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :methods [#^{:static true} [incrementRange [int] java.util.List]]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [max]
+  (increment-range max))
+

The Java wrapper has to follow the standard rules for method names. Therefore increment-range has to be renamed to incrementRange (or some similar name without the “-” in it). The “-” prefix for the Java wrapper can be configured inside the :gen-class form and will be removed once gen-class runs. The usage from Java looks like this:

+
package com.example
+
+public class ClojureJavaInteropStatic {
+
+    public static void main(String[] args) {
+        List incrementedRange = Computation.incrementRange(10);
+    }
+
+}
+

Adding generics

+

The returned list in the above code is raw because the method definition doesn’t use generics. To solve this problem declare that the generated class :implements a certain interface that exposes the desired method definition(s). You won’t be able to declare your methods as static anymore, but get a generified method for all your Java needs.

+

The Java interface:

+
package com.example
+
+public interface RangeIncrementer {
+  List<Long> incrementRange(int max);
+}
+

The changed Clojure namespace:

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :implements [com.example.RangeIncrementer]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [this max]
+  (increment-range max))
+

Finally, the generified usage from Java:

+
package com.example
+
+public class ClojureJavaInteropGenerics {
+
+    public static void main(String[] args) {
+        RangeIncrementer incrementer = new Computation();
+        List<Long> incrementedRange = incrementer.incrementRange(10);
+    }
+
+}
+

Couple of notes for this as well: First the generated class still only returns the raw type (List instead of List<Integer>). So instead of using the class, use the interface for the variable declaration (RangeIncrementer incrementer = .. instead of Computation comp = ..). The interface will return the non-raw List. Second the function definition for -incrementRange is now slightly different. It needs an additional parameter (this) which exposes the current instance to the generated class/method.

+

Returning an array of something is also possible with the following construct "[Ljava.lang.Object;". Need a 2-dim array? Just use "[[Ljava.lang.Object;" (notice the extra [) and so on. However, be aware that the method return types have to match, for example you can’t specify a return type of array if your Clojure function does not return an array. In the example above the call to map returns LazySeq which itself is a java.util.List. Therefore, the method declaration is valid, and you won’t get any ClassCastException when calling incrementRange from Java.

+

Make your life easier with macros

+

Instead of defining every Clojure function which should be exported twice (the real function + the Java wrapper), it is possible to use a macro to do that extra work automatically.

+
(require '[clojure.string :as string)
+
+(defn camel-case [input]
+  (let [words (string/split input #"[\s_-]+")]
+    (string/join (cons (string/lower-case (first words)) (map string/capitalize (rest words))))))
+
+(defn java-name [clojure-name]
+  (symbol (str "-" (camel-case (str clojure-name)))))
+
+(defmacro defn* [name & declarations]
+  (let [java-name (java-name name)]
+    `(do (defn ~name ~declarations)
+       (defn ~java-name ~declarations))))
+

The macro defn* replaces defn and automatically creates a second function with a valid camel-cased Java method name. The macro is available as a small library at Maven Central. The macro won’t add the extra parameter mentioned above to Java wrapper, so it is only useful for declaring static methods.

+

Using the Clojure Runtime

+

Using gen-class imposes certain limitations on calling Clojure code from Java. One of those are functions which make use of Clojure parameter destructuring. To invoke those functions you have to use the Clojure runtime.

+
// The Clojure 'require' function from the 'clojure.core' namespace.
+Var require = RT.var("clojure.core", "require");
+
+// Your namespace
+Symbol namespace = Symbol.intern("DESIRED.NAMESPACE.HERE");
+
+// Your function
+Var function = RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION");
+
+// The required keyword for the above function
+Keyword keyword = Keyword.intern("REQUIRED-KEYWORD");
+
+// Require/Import your namespace
+require.invoke(namespace);
+
+// Invoke your function with the given keyword and its value
+Object result = function.invoke(keyword, VALUE);
+

The desired namespace has to be on the classpath for this to work. Alternatively it is possible to load an entire Clojure script, as shown in the following example:

+
RT.loadResourceScript("DESIRED/NAMESPACE/HERE.clj");
+RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION").invoke(PARAMETER);
+

On a big project it is properly wise to move Java->Clojure interop code into helper classes/methods. Look here for an example.

+]]>
\ No newline at end of file diff --git a/tags/clojure/index.html b/tags/clojure/index.html new file mode 100644 index 000000000..f1d7eedf6 --- /dev/null +++ b/tags/clojure/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Clojure

Tag: Clojure

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/clojure/index.xml b/tags/clojure/index.xml new file mode 100644 index 000000000..f80255a4b --- /dev/null +++ b/tags/clojure/index.xml @@ -0,0 +1,5 @@ +Clojure on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/clojure/Recent content in Clojure on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/Sat, 29 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/clojure-java-interoperability/<p>Clojure has several <a href="https://clojure.org/java_interop">forms and macros</a> to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.</p> +<h2 id="using-gen-class">Using <code>gen-class</code></h2> +<p>Clojure code can be <a href="https://clojure.org/compilation">compiled</a> to standard JVM bytecode using <a href="https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/gen-class">gen-class</a>.</p> +<h3 id="adding-static-modifiers">Adding static modifiers</h3> +<p>Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class <code>com.example.Computation</code>.</p> \ No newline at end of file diff --git a/tags/clone/atom.xml b/tags/clone/atom.xml new file mode 100644 index 000000000..ba2412c4f --- /dev/null +++ b/tags/clone/atom.xml @@ -0,0 +1,41 @@ +HugoClone on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/clone/Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/2020-06-15T00:00:00+00:002023-01-06T17:32:06+01:00In case you don’t want to write git clone git@github.com:orga/repo.git all the time, consider using a custom SSH configuration (~/.ssh/config) like this:

+
Host github
+    HostName github.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITHUB>
+
+Host gitlab
+    HostName gitlab.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITLAB>
+
+Host bitbucket
+    HostName bitbucket.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-BITBUCKET>
+
+Host codeberg
+    HostName codeberg.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-CODEBERG>
+

Once configured, you can now write:

+
$ git clone github:orga/repo
+$ git clone gitlab:orga/repo
+$ git clone bitbucket:orga/repo
+$ git clone codeberg:orga/repo
+

In case you are working with many repositories inside a single organization, consider adding the following Git configuration ($XDG_CONFIG_HOME/git/config or ~/.gitconfig):

+
[url "github:orga/"]
+  insteadOf = orga:
+[url "gitlab:orga/"]
+  insteadOf = orgl:
+[url "bitbucket:orga/"]
+  insteadOf = orgb:
+[url "codeberg:orga/"]
+  insteadOf = orgc:
+

Which allows you to just write:

+
$ git clone orga:repo
+$ git clone orgl:repo
+$ git clone orgb:repo
+$ git clone orgc:repo
+

Git will substitute the insteadOf values like orga: with the configured url (for example github:orga/). The actual clone URL is github:orga/repo at this point, which can be used by Git together with the SSH configuration mentioned above to clone repositories.

+]]>
\ No newline at end of file diff --git a/tags/clone/index.html b/tags/clone/index.html new file mode 100644 index 000000000..0f8302cf2 --- /dev/null +++ b/tags/clone/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Clone

Tag: Clone

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/clone/index.xml b/tags/clone/index.xml new file mode 100644 index 000000000..c702c7b07 --- /dev/null +++ b/tags/clone/index.xml @@ -0,0 +1,26 @@ +Clone on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/clone/Recent content in Clone on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/Mon, 15 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/short-git-clones/<p>In case you don&rsquo;t want to write <code>git clone git@github.com:orga/repo.git</code> all the time, consider using a custom SSH configuration (<code>~/.ssh/config</code>) like this:</p> +<pre tabindex="0"><code>Host github + HostName github.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITHUB&gt; + +Host gitlab + HostName gitlab.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITLAB&gt; + +Host bitbucket + HostName bitbucket.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-BITBUCKET&gt; + +Host codeberg + HostName codeberg.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-CODEBERG&gt; +</code></pre><p>Once configured, you can now write:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git clone github:orga/repo +</span></span><span class="line"><span class="cl">$ git clone gitlab:orga/repo +</span></span><span class="line"><span class="cl">$ git clone bitbucket:orga/repo +</span></span><span class="line"><span class="cl">$ git clone codeberg:orga/repo +</span></span></code></pre></div><p>In case you are working with many repositories inside a single organization, consider adding the following Git configuration (<code>$XDG_CONFIG_HOME/git/config</code> or <code>~/.gitconfig</code>):</p> \ No newline at end of file diff --git a/tags/codeberg/atom.xml b/tags/codeberg/atom.xml new file mode 100644 index 000000000..02cd59c78 --- /dev/null +++ b/tags/codeberg/atom.xml @@ -0,0 +1,35 @@ +HugoCodeberg on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/codeberg/GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/2020-07-13T00:00:00+00:002023-01-06T17:32:06+01:00Git at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. Pro Git rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.

+

While the central server approach is easy to use, it might not work in all scenarios:

+
    +
  1. An enterprise wants to share internal code as part of an open source project. All development is happening internally, and the public mirror gets an occasional update once in a while.
  2. +
  3. To protect against outages of the central server, mirrors should be created and be kept up-to-date.
  4. +
+

In case of the first scenario, tools like copybara, repoSpanner, or distributed-Git-forks offer a wide range of features to cover most details.

+

The second scenario can be solved manually with tools like gitomatic or automatically with GitLab’s mirror feature quite easy. GitLab allows to create a single pull-mirror and multiple push-mirrors. Therefore, it can be used to pull from your central server and push into all mirrors.

+

NOTE: This feature was previously available in the free tier but has now moved to GitLab Ultimate.

+
               +----------------+               
+               |     GitHub     |               
+               +----------------+               
+                        ^                       
+                        |                       
+                        |                       
+               +----------------+               
+         +-----|     GitLab     |------+        
+         |     +----------------+      |        
+         |                             |        
+         |                             |        
+         v                             v        
++----------------+            +----------------+
+|    Codeberg    |            |    BitBucket   |
++----------------+            +----------------+
+

To create such a setup, follow these steps:

+
    +
  1. Go to Settings > Repository and expand Mirroring repositories +Code Flow
  2. +
  3. Enter the URL of your central Git repository as the pull source, for example https://github.com/metio/ilo.git +Code Flow
  4. +
  5. Enter one push target for each mirror. Since pushing usually requires authentication, verify that the URL of the mirror contains a username, for example https://YOUR_USER@codeberg.org/metio.wtf/ilo.git. Add an access token for each mirror and select password as authentication method. +Code Flow
  6. +
+

In case you prefer SSH keys over HTTP access tokens, just select SSH public key as authentication method and verify that your key is both saved in GitLab and all mirrors.

+]]>
\ No newline at end of file diff --git a/tags/codeberg/index.html b/tags/codeberg/index.html new file mode 100644 index 000000000..798d10cfd --- /dev/null +++ b/tags/codeberg/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Codeberg

Tag: Codeberg

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/codeberg/index.xml b/tags/codeberg/index.xml new file mode 100644 index 000000000..d0d8f70c6 --- /dev/null +++ b/tags/codeberg/index.xml @@ -0,0 +1,2 @@ +Codeberg on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/codeberg/Recent content in Codeberg on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/gitlab-distributor/<p><a href="https://git-scm.com/">Git</a> at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. <a href="https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows">Pro Git</a> rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.</p> +<p>While the central server approach is easy to use, it might not work in all scenarios:</p> \ No newline at end of file diff --git a/tags/config/atom.xml b/tags/config/atom.xml new file mode 100644 index 000000000..e0018e575 --- /dev/null +++ b/tags/config/atom.xml @@ -0,0 +1,16 @@ +HugoConfig on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/config/Multiple Git Configurationshttps://seb.xn--ho-hia.de/posts/multiple-git-configs/2023-01-05T00:00:00+00:002023-01-06T17:32:06+01:00To split yet re-use as much configuration for Git as possible, you can create one root configuration that looks similar to this:

+
[user]
+  name = Your Name Here
+
+[includeIf "gitdir:~/git/personal/"]
+  path = ~/.config/git/personal
+[includeIf "gitdir:~/git/work/"]
+  path = ~/.config/git/work
+

The includeIf directive supports multiple keywords. In my case, work and personal projects have a different root directory, therefore I can filter based on the location using gitdir. The personal Git configuration simply looks like this:

+
[user]
+  email = personal.email@example.com
+

and the work related configuration like this using a different email address:

+
[user]
+  email = first.last@work.example
+

Additional settings that are different for personal/work accounts can be split the same way, for example to use a different signing key for work.

+]]>
\ No newline at end of file diff --git a/tags/config/index.html b/tags/config/index.html new file mode 100644 index 000000000..7395ca165 --- /dev/null +++ b/tags/config/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Config

Tag: Config

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/config/index.xml b/tags/config/index.xml new file mode 100644 index 000000000..652b8aa32 --- /dev/null +++ b/tags/config/index.xml @@ -0,0 +1,12 @@ +Config on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/config/Recent content in Config on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Multiple Git Configurationshttps://seb.xn--ho-hia.de/posts/multiple-git-configs/Thu, 05 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/multiple-git-configs/<p>To split yet re-use as much configuration for Git as possible, you can create one root configuration that looks similar to this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span> +</span></span><span class="line"><span class="cl"> <span class="na">name</span> <span class="o">=</span> <span class="s">Your Name Here</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[includeIf &#34;gitdir:~/git/personal/&#34;]</span> +</span></span><span class="line"><span class="cl"> <span class="na">path</span> <span class="o">=</span> <span class="s">~/.config/git/personal</span> +</span></span><span class="line"><span class="cl"><span class="k">[includeIf &#34;gitdir:~/git/work/&#34;]</span> +</span></span><span class="line"><span class="cl"> <span class="na">path</span> <span class="o">=</span> <span class="s">~/.config/git/work</span> +</span></span></code></pre></div><p>The <a href="https://git-scm.com/docs/git-config#_includes">includeIf</a> directive supports multiple keywords. In my case, work and personal projects have a different root directory, therefore I can filter based on the location using <code>gitdir</code>. The personal Git configuration simply looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span> +</span></span><span class="line"><span class="cl"> <span class="na">email</span> <span class="o">=</span> <span class="s">personal.email@example.com</span> +</span></span></code></pre></div><p>and the work related configuration like this using a different email address:</p> \ No newline at end of file diff --git a/tags/dotfiles/atom.xml b/tags/dotfiles/atom.xml new file mode 100644 index 000000000..c4e01df3b --- /dev/null +++ b/tags/dotfiles/atom.xml @@ -0,0 +1,85 @@ +HugoDotfiles on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/dotfiles/Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/2023-01-08T00:00:00+00:002023-01-08T09:25:44+01:00age is another tool supported by chezmoi to keep data private. Compared to gpg it is much simpler by focusing on the encryption parts only.

+

Add the following snippet to your .chezmoi.toml to configure chezmoi to use age:

+
encryption = "age"
+[age]
+  identity = "path/to/age/private-key"
+  recipient = "age...public...key..."
+

Adding files to your chezmoi source directory remains the same as compared to using gpg - just call chezmoi add --encrypt path/to/file.

+]]>
chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/2022-12-26T00:00:00+00:002023-01-06T17:32:06+01:00To automatically synchronize dotfiles across my computers, I’ve written the following systemd unit:

+
[Unit]
+Description=Update chezmoi managed dotfiles
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/chezmoi update --no-tty --force
+RemainAfterExit=false
+
+[Install]
+WantedBy=default.target
+

This unit pulls changes from upstream first and then applies the changes to the current computer after I’m logged in and a network connection is available. The --no-tty flag is required because there is no tty when systemd executes chezmoi. Likewise, the --force flag ensures that no interactive prompt will be displayed which we cannot answer since systemd is executing this unit without us being involved.

+]]>
chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/2022-12-12T00:00:00+00:002023-01-06T16:40:24+01:00Many CLI applications offer initialization scripts to integrate into a shell, for example starship init zsh or zoxide init zsh. The documentation of these tools usually tell you to put something like eval "$(starship init zsh)" into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the init command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.

+

chezmoi provides a template function called output which replaces itself with the output of the command you specified. You can use that function this to integrate various tools into your shell as the following example shows while using zsh:

+
    +
  1. Create a directory that holds all init scripts for every tool you want to use. +
     $ mkdir --parents "${ZDOTDIR}"/tools.d
    +
  2. +
  3. Let your shell load all available scripts in that directory. This snippet should be part of your .zshrc file: +
    for init_script in "${ZDOTDIR}"/tools.d/*.sh; do
    +  source "${init_script}"
    +done
    +
  4. +
  5. Create chezmoi .tmpl files for each tool and place them in the chezmoi source directory that matches the directory you created in step 1: +
    {{ output "starship" "init" "zsh" "--print-full-init" }}
    +
  6. +
  7. Call chezmoi apply to generate the init scripts.
  8. +
+

The only downside here is that you have to re-run chezmoi apply after updating one of the tools because they change their init scripts sometimes. That problem can be solved with chezmoi auto-updates.

+]]>
Maintaining dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-maintenance/2021-10-11T00:00:00+00:002023-01-06T17:32:06+01:00To make it easier managing many dotfiles with chezmoi, a shell function similar to the one below can be used:

+
function m-dotfiles-ok {
+    # public
+    chezmoi add ~/.config/zsh --recursive
+    chezmoi add ~/.config/sway --recursive
+    chezmoi add ~/.config/tmux --recursive
+    chezmoi add ....
+
+    # secrets
+    chezmoi add --encrypt ~/.config/npm/npmrc
+    chezmoi add --encrypt ~/.ssh/id_rsa
+    chezmoi add --encrypt ...
+}
+

Whenever you feel happy with your current setup, just call m-dotfiles-ok to push changes into the chezmoi source directory. Files will automatically be encrypted with gpg and committed/pushed into a Git repository if you have done the necessary configuration beforehand.

+

In general, editing your dotfiles directly as explained in the second option of the FAQ seems easier though. Refactoring your dotfiles is especially easy when the exact_ prefix is used for directories. As explained in the documentation, all files that are not managed by chezmoi will be removed, therefore your configuration will always match what is in your source directory.

+]]>
Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/2021-09-20T00:00:00+00:002023-01-08T09:25:44+01:00RECOMMENDATION: Use age instead of gpg.

+

chezmoi can use various external tools to keep data private. gpg is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure gpg with chezmoi, just set yourself as the recipient like this:

+
[gpg]
+  recipient = "your.name@example.com"
+

Calling chezmoi add --encrypt /path/to/secret will now create encrypt the file with your public key which allows you to decrypt them later with your private key.

+]]>
Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/2021-09-06T00:00:00+00:002023-01-06T17:32:06+01:00chezmoi can automatically commit and push changes to your dotfiles into a (remote) Git repository. Enable it with the following snippet in your chezmoi.toml

+
[sourceVCS]
+    autoCommit = true
+    autoPush = true
+

Every time you call chezmoi add /path/to/file will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.

+]]>
XDG Base Directory Specificationhttps://seb.xn--ho-hia.de/posts/xdg-dot-files/2020-07-27T00:00:00+00:002023-01-06T16:22:24+01:00The XDG Base Directory Specification has been around for a while, yet not every software has adopted it yet. Here is an incomplete list of fixes:

+
# use existing env variables or define new
+[ -z "$XDG_CACHE_HOME"  ] && export XDG_CACHE_HOME="$HOME/.cache"
+[ -z "$XDG_CONFIG_DIRS" ] && export XDG_CONFIG_DIRS="/etc/xdg"
+[ -z "$XDG_CONFIG_HOME" ] && export XDG_CONFIG_HOME="$HOME/.config"
+[ -z "$XDG_DATA_DIRS"   ] && export XDG_DATA_DIRS="/usr/local/share:/usr/share"
+[ -z "$XDG_DATA_HOME"   ] && export XDG_DATA_HOME="$HOME/.local/share"
+
+# gradle
+export GRADLE_USER_HOME="$XDG_DATA_HOME/gradle"
+
+# httpie
+export HTTPIE_CONFIG_DIR="$XDG_CONFIG_HOME/httpie"
+
+# npm
+export NPM_CONFIG_USERCONFIG="$XDG_CONFIG_HOME/npm/npmrc"
+export npm_config_cache="$XDG_CACHE_HOME/npm"
+
+# password-store
+export PASSWORD_STORE_DIR="$XDG_DATA_HOME/password-store"
+

To make your own software XDG-aware, consider using the dirs-dev or configdir libraries.

+]]>
\ No newline at end of file diff --git a/tags/dotfiles/index.html b/tags/dotfiles/index.html new file mode 100644 index 000000000..d7ea1db03 --- /dev/null +++ b/tags/dotfiles/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Dotfiles

Tag: Dotfiles

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/dotfiles/index.xml b/tags/dotfiles/index.xml new file mode 100644 index 000000000..754d10fdb --- /dev/null +++ b/tags/dotfiles/index.xml @@ -0,0 +1,61 @@ +Dotfiles on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/dotfiles/Recent content in Dotfiles on Sebastian HoßHugoenSun, 08 Jan 2023 09:25:44 +0100Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/Sun, 08 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-age/<p><a href="https://age-encryption.org/">age</a> is another tool supported by <a href="https://www.chezmoi.io/">chezmoi</a> to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. Compared to <code>gpg</code> it is much simpler by focusing on the encryption parts only.</p> +<p>Add the following snippet to your <code>.chezmoi.toml</code> to configure <code>chezmoi</code> to use <code>age</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">encryption</span> <span class="p">=</span> <span class="s2">&#34;age&#34;</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">age</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">identity</span> <span class="p">=</span> <span class="s2">&#34;path/to/age/private-key&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;age...public...key...&#34;</span> +</span></span></code></pre></div><p>Adding files to your <code>chezmoi</code> source directory remains the same as compared to using <code>gpg</code> - just call <code>chezmoi add --encrypt path/to/file</code>.</p>chezmoi auto-updatehttps://seb.xn--ho-hia.de/posts/chezmoi-auto-update/Mon, 26 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-update/<p>To automatically synchronize dotfiles across my computers, I&rsquo;ve written the following <code>systemd</code> unit:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-systemd" data-lang="systemd"><span class="line"><span class="cl"><span class="k">[Unit]</span> +</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Update chezmoi managed dotfiles</span> +</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"><span class="na">Wants</span><span class="o">=</span><span class="s">network-online.target</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Service]</span> +</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/chezmoi update --no-tty --force</span> +</span></span><span class="line"><span class="cl"><span class="na">RemainAfterExit</span><span class="o">=</span><span class="s">false</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Install]</span> +</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span> +</span></span></code></pre></div><p>This unit pulls changes from upstream first and then applies the changes to the current computer after I&rsquo;m logged in and a network connection is available. The <code>--no-tty</code> flag is required because there is no tty when systemd executes <code>chezmoi</code>. Likewise, the <code>--force</code> flag ensures that no interactive prompt will be displayed which we cannot answer since <code>systemd</code> is executing this unit without us being involved.</p>chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/Mon, 12 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/shell-init/<p>Many CLI applications offer initialization scripts to integrate into a shell, for example <code>starship init zsh</code> or <code>zoxide init zsh</code>. The documentation of these tools usually tell you to put something like <code>eval &quot;$(starship init zsh)&quot;</code> into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the <code>init</code> command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.</p>Maintaining dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-maintenance/Mon, 11 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-maintenance/<p>To make it easier managing many dotfiles with <a href="https://www.chezmoi.io/">chezmoi</a>, a shell function similar to the one below can be used:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="k">function</span> m-dotfiles-ok <span class="o">{</span> +</span></span><span class="line"><span class="cl"> <span class="c1"># public</span> +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/zsh --recursive +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/sway --recursive +</span></span><span class="line"><span class="cl"> chezmoi add ~/.config/tmux --recursive +</span></span><span class="line"><span class="cl"> chezmoi add .... +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="c1"># secrets</span> +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ~/.config/npm/npmrc +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ~/.ssh/id_rsa +</span></span><span class="line"><span class="cl"> chezmoi add --encrypt ... +</span></span><span class="line"><span class="cl"><span class="o">}</span> +</span></span></code></pre></div><p>Whenever you feel happy with your current setup, just call <code>m-dotfiles-ok</code> to push changes into the chezmoi source directory. Files will automatically be <a href="../chezmoi-gpg">encrypted</a> with <a href="https://www.gnupg.org/">gpg</a> and <a href="../chezmoi-auto-git">committed/pushed</a> into a <a href="https://git-scm.com/">Git</a> repository if you have done the necessary configuration beforehand.</p>Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/Mon, 20 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-gpg/<p><strong>RECOMMENDATION</strong>: Use <a href="../chezmoi-age">age</a> instead of <code>gpg</code>.</p> +<p><a href="https://www.chezmoi.io/">chezmoi</a> can use various external tools to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. <a href="https://www.gnupg.org/">gpg</a> is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure <code>gpg</code> with <code>chezmoi</code>, just set yourself as the recipient like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">gpg</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;your.name@example.com&#34;</span> +</span></span></code></pre></div><p>Calling <code>chezmoi add --encrypt /path/to/secret</code> will now create encrypt the file with your public key which allows you to decrypt them later with your private key.</p>Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/Mon, 06 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-git/<p><a href="https://www.chezmoi.io/">chezmoi</a> can automatically commit and push changes to your <a href="https://en.wikipedia.org/wiki/dotfile">dotfiles</a> into a (remote) <a href="https://git-scm.com/">Git</a> repository. Enable it with the following snippet in your <code>chezmoi.toml</code></p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">sourceVCS</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoCommit</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoPush</span> <span class="p">=</span> <span class="kc">true</span> +</span></span></code></pre></div><p>Every time you call <code>chezmoi add /path/to/file</code> will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.</p>XDG Base Directory Specificationhttps://seb.xn--ho-hia.de/posts/xdg-dot-files/Mon, 27 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/xdg-dot-files/<p>The <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html">XDG Base Directory Specification</a> has been around for a while, yet not every software has adopted it yet. Here is an incomplete list of fixes:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># use existing env variables or define new</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CACHE_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CACHE_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.cache&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_DIRS</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CONFIG_DIRS</span><span class="o">=</span><span class="s2">&#34;/etc/xdg&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CONFIG_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_DATA_DIRS</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_DATA_DIRS</span><span class="o">=</span><span class="s2">&#34;/usr/local/share:/usr/share&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_DATA_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/share&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># gradle</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">GRADLE_USER_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">/gradle&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># httpie</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">HTTPIE_CONFIG_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">/httpie&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># npm</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">NPM_CONFIG_USERCONFIG</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">/npm/npmrc&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">npm_config_cache</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CACHE_HOME</span><span class="s2">/npm&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># password-store</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PASSWORD_STORE_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">/password-store&#34;</span> +</span></span></code></pre></div><p>To make your own software XDG-aware, consider using the <a href="https://github.com/dirs-dev">dirs-dev</a> or <a href="https://github.com/shibukawa/configdir">configdir</a> libraries.</p> \ No newline at end of file diff --git a/tags/emacs/atom.xml b/tags/emacs/atom.xml new file mode 100644 index 000000000..c7d3f058a --- /dev/null +++ b/tags/emacs/atom.xml @@ -0,0 +1,49 @@ +HugoEmacs on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/emacs/Backups with emacshttps://seb.xn--ho-hia.de/posts/emacs-backups/2021-10-25T00:00:00+00:002023-01-06T15:27:21+01:00emacs will create backups of your files by default. Those backups are located right next to the original file and are called <file>~. Unfortunately, emacs will not clean those up by default, which annoys me from time to time. Therefore, I’m now using the following configuration to keep those backups in a different folder:

+
(setq version-control t     ;; Use version numbers for backups.
+      kept-new-versions 10  ;; Number of newest versions to keep.
+      kept-old-versions 0   ;; Number of oldest versions to keep.
+      delete-old-versions t ;; Don't ask to delete excess backup versions.
+      backup-by-copying t)  ;; Copy all files, don't rename them.
+
+(setq vc-make-backup-files t)
+
+;; Default and per-save backups go here:
+(setq backup-directory-alist '(("" . "~/.emacs.d/backup/per-save")))
+
+(defun force-backup-of-buffer ()
+  ;; Make a special "per session" backup at the first save of each
+  ;; emacs session.
+  (when (not buffer-backed-up)
+    ;; Override the default parameters for per-session backups.
+    (let ((backup-directory-alist '(("" . "~/.emacs.d/backup/per-session")))
+          (kept-new-versions 3))
+      (backup-buffer)))
+  ;; Make a "per save" backup on each save.  The first save results in
+  ;; both a per-session and a per-save backup, to keep the numbering
+  ;; of per-save backups consistent.
+  (let ((buffer-backed-up nil))
+    (backup-buffer)))
+
+(add-hook 'before-save-hook  'force-backup-of-buffer)
+

Thanks to that configuration, backups per-save will be created in ~/.emacs.d/backup/per-save and backups per-session in ~/.emacs.d/backup/per-session.

+]]>
emacs and systemdhttps://seb.xn--ho-hia.de/posts/emacs-systemd/2021-07-26T00:00:00+00:002023-01-06T16:40:24+01:00I like to use emacs to edit files in a terminal. It tends to start a little slow, therefore I’ve created a systemd unit to automatically start the emacs daemon and use aliases to connect to the running daemon. The unit looks like this:

+
[Unit]
+Description=Emacs text editor [%I]
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+
+[Service]
+Type=forking
+ExecStart=/usr/bin/emacs --daemon=%i
+ExecStop=/usr/bin/emacsclient --eval "(kill-emacs)"
+Environment=SSH_AUTH_SOCK=%t/keyring/ssh
+Restart=on-failure
+
+[Install]
+WantedBy=default.target
+

Enable it with systemctl --user enable emacs@user and define any number of aliases to make connecting to the emacs daemon easier:

+
alias e='emacsclient --tty --socket-name=user'
+alias vim='emacsclient --tty --socket-name=user'
+alias vi='emacsclient --tty --socket-name=user'
+alias nano='emacsclient --tty --socket-name=user'
+alias ed='emacsclient --tty --socket-name=user'
+
]]>
\ No newline at end of file diff --git a/tags/emacs/index.html b/tags/emacs/index.html new file mode 100644 index 000000000..9d279857b --- /dev/null +++ b/tags/emacs/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Emacs

Tag: Emacs

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/emacs/index.xml b/tags/emacs/index.xml new file mode 100644 index 000000000..55ba742e6 --- /dev/null +++ b/tags/emacs/index.xml @@ -0,0 +1,42 @@ +Emacs on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/emacs/Recent content in Emacs on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Backups with emacshttps://seb.xn--ho-hia.de/posts/emacs-backups/Mon, 25 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/emacs-backups/<p><a href="https://www.gnu.org/software/emacs/">emacs</a> will create backups of your files by default. Those backups are located right next to the original file and are called <code>&lt;file&gt;~</code>. Unfortunately, emacs will not clean those up by default, which annoys me from time to time. Therefore, I&rsquo;m now using the following configuration to keep those backups in a different folder:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-el" data-lang="el"><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">version-control</span> <span class="no">t</span> <span class="c1">;; Use version numbers for backups.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kept-new-versions</span> <span class="mi">10</span> <span class="c1">;; Number of newest versions to keep.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">kept-old-versions</span> <span class="mi">0</span> <span class="c1">;; Number of oldest versions to keep.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">delete-old-versions</span> <span class="no">t</span> <span class="c1">;; Don&#39;t ask to delete excess backup versions.</span> +</span></span><span class="line"><span class="cl"> <span class="nv">backup-by-copying</span> <span class="no">t</span><span class="p">)</span> <span class="c1">;; Copy all files, don&#39;t rename them.</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">vc-make-backup-files</span> <span class="no">t</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">;; Default and per-save backups go here:</span> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">setq</span> <span class="nv">backup-directory-alist</span> <span class="o">&#39;</span><span class="p">((</span><span class="s">&#34;&#34;</span> <span class="o">.</span> <span class="s">&#34;~/.emacs.d/backup/per-save&#34;</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">defun</span> <span class="nv">force-backup-of-buffer</span> <span class="p">()</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Make a special &#34;per session&#34; backup at the first save of each</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; emacs session.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">when</span> <span class="p">(</span><span class="nv">not</span> <span class="nv">buffer-backed-up</span><span class="p">)</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Override the default parameters for per-session backups.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">backup-directory-alist</span> <span class="o">&#39;</span><span class="p">((</span><span class="s">&#34;&#34;</span> <span class="o">.</span> <span class="s">&#34;~/.emacs.d/backup/per-session&#34;</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">kept-new-versions</span> <span class="mi">3</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">backup-buffer</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; Make a &#34;per save&#34; backup on each save. The first save results in</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; both a per-session and a per-save backup, to keep the numbering</span> +</span></span><span class="line"><span class="cl"> <span class="c1">;; of per-save backups consistent.</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">buffer-backed-up</span> <span class="no">nil</span><span class="p">))</span> +</span></span><span class="line"><span class="cl"> <span class="p">(</span><span class="nv">backup-buffer</span><span class="p">)))</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">add-hook</span> <span class="ss">&#39;before-save-hook</span> <span class="ss">&#39;force-backup-of-buffer</span><span class="p">)</span> +</span></span></code></pre></div><p>Thanks to that configuration, backups per-save will be created in <code>~/.emacs.d/backup/per-save</code> and backups per-session in <code>~/.emacs.d/backup/per-session</code>.</p>emacs and systemdhttps://seb.xn--ho-hia.de/posts/emacs-systemd/Mon, 26 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/emacs-systemd/<p>I like to use <a href="https://www.gnu.org/software/emacs/">emacs</a> to edit files in a terminal. It tends to start a little slow, therefore I&rsquo;ve created a <a href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</a> unit to automatically start the emacs daemon and use aliases to connect to the running daemon. The unit looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span> +</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Emacs text editor [%I]</span> +</span></span><span class="line"><span class="cl"><span class="na">Documentation</span><span class="o">=</span><span class="s">info:emacs man:emacs(1) https://gnu.org/software/emacs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Service]</span> +</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">forking</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/emacs --daemon=%i</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStop</span><span class="o">=</span><span class="s">/usr/bin/emacsclient --eval &#34;(kill-emacs)&#34;</span> +</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">SSH_AUTH_SOCK=%t/keyring/ssh</span> +</span></span><span class="line"><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">on-failure</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Install]</span> +</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span> +</span></span></code></pre></div><p>Enable it with <code>systemctl --user enable emacs@user</code> and define any number of aliases to make connecting to the emacs daemon easier:</p> \ No newline at end of file diff --git a/tags/email/atom.xml b/tags/email/atom.xml new file mode 100644 index 000000000..cdf14262f --- /dev/null +++ b/tags/email/atom.xml @@ -0,0 +1,25 @@ +HugoEmail on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/email/Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/2020-11-02T00:00:00+00:002023-01-06T15:27:21+01:00The dawidd6/action-send-mail action allows to send an email in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Send mail
+        uses: dawidd6/action-send-mail@v3
+        with:
+          server_address: ${{ secrets.MAIL_SERVER }}
+          server_port: ${{ secrets.MAIL_PORT }}
+          username: ${{ secrets.MAIL_USERNAME }}
+          password: ${{ secrets.MAIL_PASSWORD }}
+          subject: <SUBJECT>
+          body: <BODY>
+          to: ${{ secrets.MAIL_RECIPIENT }}
+          from: ${{ secrets.MAIL_SENDER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <SUBJECT>: Subject for the email.
  • +
  • <BODY>: Body for the email.
  • +
+

Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define MAIL_RECIPIENT for each project.

+]]>
\ No newline at end of file diff --git a/tags/email/index.html b/tags/email/index.html new file mode 100644 index 000000000..4b3bc82f8 --- /dev/null +++ b/tags/email/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Email

Tag: Email

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/email/index.xml b/tags/email/index.xml new file mode 100644 index 000000000..ccf041ecb --- /dev/null +++ b/tags/email/index.xml @@ -0,0 +1,24 @@ +Email on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/email/Recent content in Email on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/Mon, 02 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-email/<p>The <a href="https://github.com/dawidd6/action-send-mail">dawidd6/action-send-mail</a> action allows to send an email in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Send mail</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">dawidd6/action-send-mail@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_address</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SERVER }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_port</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PORT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_USERNAME }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PASSWORD }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">subject</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;SUBJECT&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;BODY&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_RECIPIENT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SENDER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;SUBJECT&gt;</code>: Subject for the email.</li> +<li><code>&lt;BODY&gt;</code>: Body for the email.</li> +</ul> +<p>Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define <code>MAIL_RECIPIENT</code> for each project.</p> \ No newline at end of file diff --git a/tags/encoding/atom.xml b/tags/encoding/atom.xml new file mode 100644 index 000000000..a3375282c --- /dev/null +++ b/tags/encoding/atom.xml @@ -0,0 +1,10 @@ +HugoEncoding on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/encoding/Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/2021-05-31T00:00:00+00:002023-01-06T17:32:06+01:00Maven projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.

+
<properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+</properties>
+
+ +]]>
\ No newline at end of file diff --git a/tags/encoding/index.html b/tags/encoding/index.html new file mode 100644 index 000000000..e66228c59 --- /dev/null +++ b/tags/encoding/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Encoding

Tag: Encoding

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/encoding/index.xml b/tags/encoding/index.xml new file mode 100644 index 000000000..924bcb04f --- /dev/null +++ b/tags/encoding/index.xml @@ -0,0 +1,9 @@ +Encoding on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/encoding/Recent content in Encoding on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-encoding/<p><a href="https://maven.apache.org/">Maven</a> projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.reporting.outputEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.reporting.outputEncoding&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +</ul> \ No newline at end of file diff --git a/tags/encryption/atom.xml b/tags/encryption/atom.xml new file mode 100644 index 000000000..c761b0a6a --- /dev/null +++ b/tags/encryption/atom.xml @@ -0,0 +1,13 @@ +HugoEncryption on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/encryption/Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/2023-01-08T00:00:00+00:002023-01-08T09:25:44+01:00age is another tool supported by chezmoi to keep data private. Compared to gpg it is much simpler by focusing on the encryption parts only.

+

Add the following snippet to your .chezmoi.toml to configure chezmoi to use age:

+
encryption = "age"
+[age]
+  identity = "path/to/age/private-key"
+  recipient = "age...public...key..."
+

Adding files to your chezmoi source directory remains the same as compared to using gpg - just call chezmoi add --encrypt path/to/file.

+]]>
Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/2021-09-20T00:00:00+00:002023-01-08T09:25:44+01:00RECOMMENDATION: Use age instead of gpg.

+

chezmoi can use various external tools to keep data private. gpg is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure gpg with chezmoi, just set yourself as the recipient like this:

+
[gpg]
+  recipient = "your.name@example.com"
+

Calling chezmoi add --encrypt /path/to/secret will now create encrypt the file with your public key which allows you to decrypt them later with your private key.

+]]>
\ No newline at end of file diff --git a/tags/encryption/index.html b/tags/encryption/index.html new file mode 100644 index 000000000..5d02bf317 --- /dev/null +++ b/tags/encryption/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Encryption

Tag: Encryption

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/encryption/index.xml b/tags/encryption/index.xml new file mode 100644 index 000000000..70254e511 --- /dev/null +++ b/tags/encryption/index.xml @@ -0,0 +1,11 @@ +Encryption on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/encryption/Recent content in Encryption on Sebastian HoßHugoenSun, 08 Jan 2023 09:25:44 +0100Using chezmoi with agehttps://seb.xn--ho-hia.de/posts/chezmoi-age/Sun, 08 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-age/<p><a href="https://age-encryption.org/">age</a> is another tool supported by <a href="https://www.chezmoi.io/">chezmoi</a> to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. Compared to <code>gpg</code> it is much simpler by focusing on the encryption parts only.</p> +<p>Add the following snippet to your <code>.chezmoi.toml</code> to configure <code>chezmoi</code> to use <code>age</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">encryption</span> <span class="p">=</span> <span class="s2">&#34;age&#34;</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">age</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">identity</span> <span class="p">=</span> <span class="s2">&#34;path/to/age/private-key&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;age...public...key...&#34;</span> +</span></span></code></pre></div><p>Adding files to your <code>chezmoi</code> source directory remains the same as compared to using <code>gpg</code> - just call <code>chezmoi add --encrypt path/to/file</code>.</p>Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/Mon, 20 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-gpg/<p><strong>RECOMMENDATION</strong>: Use <a href="../chezmoi-age">age</a> instead of <code>gpg</code>.</p> +<p><a href="https://www.chezmoi.io/">chezmoi</a> can use various external tools to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. <a href="https://www.gnupg.org/">gpg</a> is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure <code>gpg</code> with <code>chezmoi</code>, just set yourself as the recipient like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">gpg</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;your.name@example.com&#34;</span> +</span></span></code></pre></div><p>Calling <code>chezmoi add --encrypt /path/to/secret</code> will now create encrypt the file with your public key which allows you to decrypt them later with your private key.</p> \ No newline at end of file diff --git a/tags/exit-code/atom.xml b/tags/exit-code/atom.xml new file mode 100644 index 000000000..b67342c5e --- /dev/null +++ b/tags/exit-code/atom.xml @@ -0,0 +1,14 @@ +HugoExit Code on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/exit-code/Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/2021-02-08T00:00:00+00:002023-01-06T16:40:24+01:00In case you are using a Makefile to define a complex build step - for example start database, run tests, stop database - consider using the - qualifier in front of your actual build step like this:

+
.PHONY: build
+build:
+	start-database
+	-build-software
+	stop-database
+

Thanks to -, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.

+ + +]]>
\ No newline at end of file diff --git a/tags/exit-code/index.html b/tags/exit-code/index.html new file mode 100644 index 000000000..4b62ff40d --- /dev/null +++ b/tags/exit-code/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Exit Code

Tag: Exit Code

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/exit-code/index.xml b/tags/exit-code/index.xml new file mode 100644 index 000000000..e1b3ec8dd --- /dev/null +++ b/tags/exit-code/index.xml @@ -0,0 +1,7 @@ +Exit Code on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/exit-code/Recent content in Exit Code on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/Mon, 08 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-ignore/<p>In case you are using a <code>Makefile</code> to define a complex build step - for example start database, run tests, stop database - consider using the <code>-</code> qualifier in front of your actual build step like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">build</span> +</span></span><span class="line"><span class="cl"><span class="nf">build</span><span class="o">:</span> +</span></span><span class="line"><span class="cl"> start-database +</span></span><span class="line"><span class="cl"> -build-software +</span></span><span class="line"><span class="cl"> stop-database +</span></span></code></pre></div><p>Thanks to <code>-</code>, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.</p> \ No newline at end of file diff --git a/tags/foaf/atom.xml b/tags/foaf/atom.xml new file mode 100644 index 000000000..8f43c0c3e --- /dev/null +++ b/tags/foaf/atom.xml @@ -0,0 +1,36 @@ +HugoFoaf on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/foaf/FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/2020-11-30T00:00:00+00:002023-01-06T16:40:24+01:00To publish a FOAF document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/rdf+xml"]
+  suffixes = ["rdf"]
+[outputFormats.Foaf]
+  name = "FOAF"
+  mediaType = "application/rdf+xml"
+  baseName = "foaf"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.foaf.rdf with the following content:

+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/">
+  <foaf:PersonalProfileDocument rdf:about="">
+    <foaf:maker rdf:resource="#me" />
+    <foaf:primaryTopic rdf:resource="{{ .Site.Title }}" />
+  </foaf:PersonalProfileDocument>
+
+  <foaf:Project rdf:ID="{{ .Site.Title }}">
+    <foaf:name>{{ .Site.Title }}</foaf:name>
+    <foaf:homepage rdf:resource="{{ .Site.BaseURL }}" />
+  </foaf:Project>
+
+  {{ range $.Site.Data.contributors }}
+  <foaf:Person rdf:ID="{{ .id }}">
+    <foaf:name>{{ .first_name }} {{ .last_name }}</foaf:name>
+    <foaf:title>{{ .title }}</foaf:title>
+    <foaf:givenname>{{ .first_name }}</foaf:givenname>
+    <foaf:family_name>{{ .last_name }}</foaf:family_name>
+    <foaf:mbox rdf:resource="mailto:{{ .email }}" />
+    <foaf:homepage rdf:resource="{{ .website }}" />
+  </foaf:Person>
+  {{ end }}
+</rdf:RDF>
+
]]>
\ No newline at end of file diff --git a/tags/foaf/index.html b/tags/foaf/index.html new file mode 100644 index 000000000..3f320e267 --- /dev/null +++ b/tags/foaf/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Foaf

Tag: Foaf

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/foaf/index.xml b/tags/foaf/index.xml new file mode 100644 index 000000000..d2a04f59f --- /dev/null +++ b/tags/foaf/index.xml @@ -0,0 +1,36 @@ +Foaf on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/foaf/Recent content in Foaf on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/Mon, 30 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-foaf/<p>To publish a <a href="http://www.foaf-project.org/">FOAF</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/rdf+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;rdf&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Foaf</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;FOAF&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/rdf+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;foaf&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.foaf.rdf</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">&lt;rdf:RDF xmlns:rdf=&#34;http://www.w3.org/1999/02/22-rdf-syntax-ns#&#34; xmlns:rdfs=&#34;http://www.w3.org/2000/01/rdf-schema#&#34; xmlns:foaf=&#34;http://xmlns.com/foaf/0.1/&#34;&gt; + &lt;foaf:PersonalProfileDocument rdf:about=&#34;&#34;&gt; + &lt;foaf:maker rdf:resource=&#34;#me&#34; /&gt; + &lt;foaf:primaryTopic rdf:resource=&#34;{{ .Site.Title }}&#34; /&gt; + &lt;/foaf:PersonalProfileDocument&gt; + + &lt;foaf:Project rdf:ID=&#34;{{ .Site.Title }}&#34;&gt; + &lt;foaf:name&gt;{{ .Site.Title }}&lt;/foaf:name&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .Site.BaseURL }}&#34; /&gt; + &lt;/foaf:Project&gt; + + {{ range $.Site.Data.contributors }} + &lt;foaf:Person rdf:ID=&#34;{{ .id }}&#34;&gt; + &lt;foaf:name&gt;{{ .first_name }} {{ .last_name }}&lt;/foaf:name&gt; + &lt;foaf:title&gt;{{ .title }}&lt;/foaf:title&gt; + &lt;foaf:givenname&gt;{{ .first_name }}&lt;/foaf:givenname&gt; + &lt;foaf:family_name&gt;{{ .last_name }}&lt;/foaf:family_name&gt; + &lt;foaf:mbox rdf:resource=&#34;mailto:{{ .email }}&#34; /&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .website }}&#34; /&gt; + &lt;/foaf:Person&gt; + {{ end }} +&lt;/rdf:RDF&gt; +</code></pre> \ No newline at end of file diff --git a/tags/fuzzy/atom.xml b/tags/fuzzy/atom.xml new file mode 100644 index 000000000..29ec747b3 --- /dev/null +++ b/tags/fuzzy/atom.xml @@ -0,0 +1,47 @@ +HugoFuzzy on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/fuzzy/passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/2022-12-27T00:00:00+00:002023-01-06T16:40:24+01:00To fuzzy search through passwords managed with passage, I’ve written the following script that is inspired by the upstream version which is using fzf.

+
fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

This version requires fd, skim, xargs, and passage itself of course. The detailed breakdown on how it works is as follows:

+
    +
  1. Use fd to find all files within ${PASSAGE_DIR} that end in .age. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.
  2. +
  3. Using both --base-directory and --exec echo '{.}' ensures that passwords are returned in such form that they can be passed back into passage again. The placeholder '{.}' is a feature provided by fd which strips the file extension from each returned value.
  4. +
  5. All passwords are then passed into sk to allow to fuzzy search across them all. Setting --no-multi ensures that only a single password can be selected.
  6. +
  7. Finally, xargs calls passage and replaces the curly braces with the selected password. Thanks to --clip=1, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.
  8. +
+

To call that script, I’ve saved it as passage-fuzzy-search.sh in my .local/bin folder and added some checks into it to verify that every required software is actually installed.

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This shell script presents passwords saved with passage through skim
+#
+# Call it like this:
+#   passage-fuzzy-search.sh
+#
+# Required software that isn't in GNU coreutils:
+#   - 'passage' to read passwords
+#   - 'fd' to find passwords
+#   - 'sk' to filter passwords
+###############################################################################
+
+if ! (( ${+commands[passage]} )); then
+    echo 'passage not installed. Please install passage.'
+    exit
+fi
+if ! (( ${+commands[sk]} )); then
+    echo 'sk not installed. Please install skim.'
+    exit
+fi
+if ! (( ${+commands[fd]} )); then
+    echo 'fd not installed. Please install fd-find.'
+    exit
+fi
+
+fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

Since typing passage-fuzzy-search.sh is way too long, I have added an alias like this:

+
alias pp='passage-fuzzy-search.sh'
+
]]>
\ No newline at end of file diff --git a/tags/fuzzy/index.html b/tags/fuzzy/index.html new file mode 100644 index 000000000..ef2c06d72 --- /dev/null +++ b/tags/fuzzy/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Fuzzy

Tag: Fuzzy

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/fuzzy/index.xml b/tags/fuzzy/index.xml new file mode 100644 index 000000000..80291ff53 --- /dev/null +++ b/tags/fuzzy/index.xml @@ -0,0 +1,13 @@ +Fuzzy on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/fuzzy/Recent content in Fuzzy on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/passage-fuzzy-search/<p>To fuzzy search through passwords managed with <a href="https://github.com/FiloSottile/passage">passage</a>, I&rsquo;ve written the following script that is inspired by the upstream version which is using <code>fzf</code>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">fd --type<span class="o">=</span>file --base-directory<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PASSAGE_DIR</span><span class="k">:-</span><span class="si">${</span><span class="nv">HOME</span><span class="si">}</span><span class="p">/.passage/store</span><span class="si">}</span><span class="s2">&#34;</span> .age --exec <span class="nb">echo</span> <span class="s1">&#39;{.}&#39;</span> <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> sk --cycle --layout<span class="o">=</span>reverse --tiebreak<span class="o">=</span>score --no-multi <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> xargs --replace --max-args<span class="o">=</span><span class="m">1</span> --no-run-if-empty <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> passage show --clip<span class="o">=</span><span class="m">1</span> <span class="o">{}</span> +</span></span></code></pre></div><p>This version requires <a href="https://github.com/sharkdp/fd/">fd</a>, <a href="https://github.com/lotabout/skim">skim</a>, <a href="https://www.gnu.org/software/findutils/manual/html_node/find_html/Invoking-xargs.html">xargs</a>, and <a href="https://github.com/FiloSottile/passage">passage</a> itself of course. The detailed breakdown on how it works is as follows:</p> +<ol> +<li>Use <code>fd</code> to find all files within <code>${PASSAGE_DIR}</code> that end in <code>.age</code>. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.</li> +<li>Using both <code>--base-directory</code> and <code>--exec echo '{.}'</code> ensures that passwords are returned in such form that they can be passed back into <code>passage</code> again. The placeholder <code>'{.}'</code> is a feature provided by <code>fd</code> which strips the file extension from each returned value.</li> +<li>All passwords are then passed into <code>sk</code> to allow to fuzzy search across them all. Setting <code>--no-multi</code> ensures that only a single password can be selected.</li> +<li>Finally, <code>xargs</code> calls <code>passage</code> and replaces the curly braces with the selected password. Thanks to <code>--clip=1</code>, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.</li> +</ol> +<p>To call that script, I&rsquo;ve saved it as <code>passage-fuzzy-search.sh</code> in my <code>.local/bin</code> folder and added some checks into it to verify that every required software is actually installed.</p> \ No newline at end of file diff --git a/tags/fzf/atom.xml b/tags/fzf/atom.xml new file mode 100644 index 000000000..eac4decf9 --- /dev/null +++ b/tags/fzf/atom.xml @@ -0,0 +1,36 @@ +HugoFzf on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/fzf/awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/2022-02-14T00:00:00+00:002023-01-06T16:40:24+01:00To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the AWS_PROFILE environment variable which is used by many tools that interact with the AWS API, like awscli or terraform. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses aws sso login to perform an MFA login into AWS. The AWS profiles must be set up in such a way that aws configure list-profiles can detect them, which is typically done by adding them in ${AWS_CONFIG_FILE:-$HOME/.aws/config}.

+
#!/usr/bin/env sh
+
+###############################################################################
+# This script performs an AWS SSO login for the user-selected AWS profile
+# and sets the AWS_PROFILE environment variable afterwards. To use
+# this, create an alias that sources this script like this:
+#
+#     alias awsenv='source path/to/this/script.sh'
+#
+# Required software that is not in GNU coreutils:
+#   - 'aws' to list profiles & get current caller identity
+#   - 'fzf' to list all available AWS profiles
+###############################################################################
+
+# prompt user to select one AWS profile
+profile=$(aws configure list-profiles | \
+  fzf --cycle --layout=reverse --tiebreak=index)
+
+# user can cancel switching profiles by pressing ESC
+if [ -n "${profile}" ]; then
+  # check is access token exists and is valid for selected profile
+  if ! aws --profile "${profile}" sts get-caller-identity >/dev/null 2>&1; then
+    # perform login into profile in case access token is invalid
+    if ! aws sso login --profile "${profile}"; then
+      # short circuit in case login failed
+      return
+    fi
+  fi
+  # AWS_PROFILE is used by many AWS-related tools
+  echo "Setting AWS_PROFILE to [${profile}]"
+  export AWS_PROFILE="${profile}"
+  # do not expose internal variables
+  unset profile
+fi
+
]]>
\ No newline at end of file diff --git a/tags/fzf/index.html b/tags/fzf/index.html new file mode 100644 index 000000000..e22a0c607 --- /dev/null +++ b/tags/fzf/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Fzf

Tag: Fzf

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/fzf/index.xml b/tags/fzf/index.xml new file mode 100644 index 000000000..5d78c92e4 --- /dev/null +++ b/tags/fzf/index.xml @@ -0,0 +1 @@ +Fzf on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/fzf/Recent content in Fzf on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100awsenvhttps://seb.xn--ho-hia.de/posts/awsenv/Mon, 14 Feb 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/awsenv/<p>To quickly log into and switch between AWS accounts in a terminal, I wrote the following script. It sets the <code>AWS_PROFILE</code> environment variable which is used by many tools that interact with the AWS API, like <code>awscli</code> or <code>terraform</code>. My current environment uses AzureAD as a single-sign-on provider, therefore this script uses <code>aws sso login</code> to perform an MFA login into AWS. The AWS profiles must be set up in such a way that <code>aws configure list-profiles</code> can detect them, which is typically done by adding them in <code>${AWS_CONFIG_FILE:-$HOME/.aws/config}</code>.</p> \ No newline at end of file diff --git a/tags/git/atom.xml b/tags/git/atom.xml new file mode 100644 index 000000000..80cbc7c72 --- /dev/null +++ b/tags/git/atom.xml @@ -0,0 +1,179 @@ +HugoGit on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/git/Multiple Git Configurationshttps://seb.xn--ho-hia.de/posts/multiple-git-configs/2023-01-05T00:00:00+00:002023-01-06T17:32:06+01:00To split yet re-use as much configuration for Git as possible, you can create one root configuration that looks similar to this:

+
[user]
+  name = Your Name Here
+
+[includeIf "gitdir:~/git/personal/"]
+  path = ~/.config/git/personal
+[includeIf "gitdir:~/git/work/"]
+  path = ~/.config/git/work
+

The includeIf directive supports multiple keywords. In my case, work and personal projects have a different root directory, therefore I can filter based on the location using gitdir. The personal Git configuration simply looks like this:

+
[user]
+  email = personal.email@example.com
+

and the work related configuration like this using a different email address:

+
[user]
+  email = first.last@work.example
+

Additional settings that are different for personal/work accounts can be split the same way, for example to use a different signing key for work.

+]]>
Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/2022-01-01T00:00:00+00:002023-01-06T16:22:24+01:00Both Vim and Neovim have a built-in plugin mechanism that loads plugins from ~/.vim/pack/*/{start,opt}/* (Vim) or ~/.local/share/nvim/site/pack/*/{start,opt}/* (Neovim). All you have to do to install new plugins, is to git clone their repository into those directories. To automatically update those clones, create the following script:

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This script git-pulls all installed nvim plugins which are using the built-in
+# nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack
+#
+# Required software that is not in GNU coreutils:
+#   - 'git' to fetch plugin updates from upstream
+###############################################################################
+
+### User specific variables, adjust to your own needs
+
+# folder that contains all nvim plugins
+PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack"
+
+### Script logic
+
+echo "updating all plugins in ${PLUGIN_DIR}"
+# iterate through all directories and git pull em
+for directory in "${PLUGIN_DIR}"/*/{start,opt}/*; do
+    if [ -d "${directory}" ]; then
+        plugin=$(basename "${directory}")
+        echo "updating ${plugin}"
+        git -C "${directory}" pull --quiet
+    fi
+done
+

In case you are using Vim, adjust the PLUGIN_DIR variable to point to your Vim plugin directory instead and save the resulting shell script as a file called update-nvim-plugins.sh in some folder of your choice. Do not forget to set the executable bit with chmod +x /path/to/your/folder/update-nvim-plugins.sh. Since all good developers must be lazy, write the following systemd service to execute the above script automatically:

+
[Unit]
+Description=cron job that triggers an update of all nvim plugins
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/path/to/your/folder/update-nvim-plugins.sh
+RemainAfterExit=false
+

Adjust the ExecStart line to match the location where you saved the above script and place that service definition in a file called nvim-plugins-update.service into your local ~/.config/systemd/user directory. Add another file called nvim-plugins-update.timer next to it that defines a systemd timer with the following content:

+
[Unit]
+Description=Update nvim plugins every week
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+RandomizedDelaySec=5hours
+
+[Install]
+WantedBy=timers.target
+

Adjust how often you want to update the plugins you are using in the OnCalendar line. Enable this service/timer with:

+
$ systemctl --user enable nvim-plugins-update
+

Finally, add the following shell aliases to make it easier to interact with the created systemd units:

+
# trigger an update manually
+alias update-nvim-plugins='systemctl --user start nvim-plugins-update'
+# see status of last auto-update
+alias update-nvim-plugins-status='systemctl --user status nvim-plugins-update'
+# see logs of past auto-updates
+alias update-nvim-plugins-logs='journalctl --user --unit nvim-plugins-update'
+

With this setup in place, all your plugins will be automatically updated once per week or however often you have configured in the timer.

+]]>
Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/2021-09-06T00:00:00+00:002023-01-06T17:32:06+01:00chezmoi can automatically commit and push changes to your dotfiles into a (remote) Git repository. Enable it with the following snippet in your chezmoi.toml

+
[sourceVCS]
+    autoCommit = true
+    autoPush = true
+

Every time you call chezmoi add /path/to/file will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.

+]]>
Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/2021-07-12T00:00:00+00:002023-01-06T17:32:06+01:00In case you want to have push-only mirrors for your Git repository, consider adding a special mirror remote like this:

+
$ git remote add mirrors DISABLED
+$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git
+$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git
+$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git
+

The above will create a new remote called mirrors which has no fetch URL and therefore can only be pushed:

+
$ git remote -v
+mirrors DISABLED (fetch)
+mirrors git@codeberg.org:org/repo.git (push)
+mirrors git@gitlab.com:org/repo.git (push)
+mirrors git@bitbucket.org:org/repo.git (push)
+

Calling git push mirrors main:main will push the local main branch into all defined mirrors.

+ + +]]>
Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/2021-06-28T00:00:00+00:002023-01-06T16:46:32+01:00In case you want to make use of the decentralized nature of Git, consider using multiple push targets like this:

+
$ git remote set-url origin --push --add git@example.com/project.git
+$ git remote set-url origin --push --add git@another.com/project.git
+

Note that the first call to set-url will overwrite an existing remote creating with git clone. Any additional call will actually recognize the --add option and add the new target to an existing remote.

+ + +]]>
GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/2020-07-13T00:00:00+00:002023-01-06T17:32:06+01:00Git at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. Pro Git rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.

+

While the central server approach is easy to use, it might not work in all scenarios:

+
    +
  1. An enterprise wants to share internal code as part of an open source project. All development is happening internally, and the public mirror gets an occasional update once in a while.
  2. +
  3. To protect against outages of the central server, mirrors should be created and be kept up-to-date.
  4. +
+

In case of the first scenario, tools like copybara, repoSpanner, or distributed-Git-forks offer a wide range of features to cover most details.

+

The second scenario can be solved manually with tools like gitomatic or automatically with GitLab’s mirror feature quite easy. GitLab allows to create a single pull-mirror and multiple push-mirrors. Therefore, it can be used to pull from your central server and push into all mirrors.

+

NOTE: This feature was previously available in the free tier but has now moved to GitLab Ultimate.

+
               +----------------+               
+               |     GitHub     |               
+               +----------------+               
+                        ^                       
+                        |                       
+                        |                       
+               +----------------+               
+         +-----|     GitLab     |------+        
+         |     +----------------+      |        
+         |                             |        
+         |                             |        
+         v                             v        
++----------------+            +----------------+
+|    Codeberg    |            |    BitBucket   |
++----------------+            +----------------+
+

To create such a setup, follow these steps:

+
    +
  1. Go to Settings > Repository and expand Mirroring repositories +Code Flow
  2. +
  3. Enter the URL of your central Git repository as the pull source, for example https://github.com/metio/ilo.git +Code Flow
  4. +
  5. Enter one push target for each mirror. Since pushing usually requires authentication, verify that the URL of the mirror contains a username, for example https://YOUR_USER@codeberg.org/metio.wtf/ilo.git. Add an access token for each mirror and select password as authentication method. +Code Flow
  6. +
+

In case you prefer SSH keys over HTTP access tokens, just select SSH public key as authentication method and verify that your key is both saved in GitLab and all mirrors.

+]]>
Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/2020-06-15T00:00:00+00:002023-01-06T17:32:06+01:00In case you don’t want to write git clone git@github.com:orga/repo.git all the time, consider using a custom SSH configuration (~/.ssh/config) like this:

+
Host github
+    HostName github.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITHUB>
+
+Host gitlab
+    HostName gitlab.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITLAB>
+
+Host bitbucket
+    HostName bitbucket.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-BITBUCKET>
+
+Host codeberg
+    HostName codeberg.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-CODEBERG>
+

Once configured, you can now write:

+
$ git clone github:orga/repo
+$ git clone gitlab:orga/repo
+$ git clone bitbucket:orga/repo
+$ git clone codeberg:orga/repo
+

In case you are working with many repositories inside a single organization, consider adding the following Git configuration ($XDG_CONFIG_HOME/git/config or ~/.gitconfig):

+
[url "github:orga/"]
+  insteadOf = orga:
+[url "gitlab:orga/"]
+  insteadOf = orgl:
+[url "bitbucket:orga/"]
+  insteadOf = orgb:
+[url "codeberg:orga/"]
+  insteadOf = orgc:
+

Which allows you to just write:

+
$ git clone orga:repo
+$ git clone orgl:repo
+$ git clone orgb:repo
+$ git clone orgc:repo
+

Git will substitute the insteadOf values like orga: with the configured url (for example github:orga/). The actual clone URL is github:orga/repo at this point, which can be used by Git together with the SSH configuration mentioned above to clone repositories.

+]]>
\ No newline at end of file diff --git a/tags/git/index.html b/tags/git/index.html new file mode 100644 index 000000000..1903d60f2 --- /dev/null +++ b/tags/git/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Git

Tag: Git

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/git/index.xml b/tags/git/index.xml new file mode 100644 index 000000000..566175d17 --- /dev/null +++ b/tags/git/index.xml @@ -0,0 +1,88 @@ +Git on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/git/Recent content in Git on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Multiple Git Configurationshttps://seb.xn--ho-hia.de/posts/multiple-git-configs/Thu, 05 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/multiple-git-configs/<p>To split yet re-use as much configuration for Git as possible, you can create one root configuration that looks similar to this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span> +</span></span><span class="line"><span class="cl"> <span class="na">name</span> <span class="o">=</span> <span class="s">Your Name Here</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[includeIf &#34;gitdir:~/git/personal/&#34;]</span> +</span></span><span class="line"><span class="cl"> <span class="na">path</span> <span class="o">=</span> <span class="s">~/.config/git/personal</span> +</span></span><span class="line"><span class="cl"><span class="k">[includeIf &#34;gitdir:~/git/work/&#34;]</span> +</span></span><span class="line"><span class="cl"> <span class="na">path</span> <span class="o">=</span> <span class="s">~/.config/git/work</span> +</span></span></code></pre></div><p>The <a href="https://git-scm.com/docs/git-config#_includes">includeIf</a> directive supports multiple keywords. In my case, work and personal projects have a different root directory, therefore I can filter based on the location using <code>gitdir</code>. The personal Git configuration simply looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span> +</span></span><span class="line"><span class="cl"> <span class="na">email</span> <span class="o">=</span> <span class="s">personal.email@example.com</span> +</span></span></code></pre></div><p>and the work related configuration like this using a different email address:</p>Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/<p>Both <a href="https://www.vim.org/">Vim</a> and <a href="https://neovim.io/">Neovim</a> have a <a href="https://vimhelp.org/repeat.txt.html#packages">built-in</a> <a href="https://neovim.io/doc/user/usr_05.html#plugin">plugin mechanism</a> that loads plugins from <code>~/.vim/pack/*/{start,opt}/*</code> (Vim) or <code>~/.local/share/nvim/site/pack/*/{start,opt}/*</code> (Neovim). All you have to do to install new plugins, is to <code>git clone</code> their repository into those directories. To automatically update those clones, create the following script:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env zsh +</span></span></span><span class="line"><span class="cl"><span class="cp"></span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"><span class="c1"># This script git-pulls all installed nvim plugins which are using the built-in</span> +</span></span><span class="line"><span class="cl"><span class="c1"># nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack</span> +</span></span><span class="line"><span class="cl"><span class="c1">#</span> +</span></span><span class="line"><span class="cl"><span class="c1"># Required software that is not in GNU coreutils:</span> +</span></span><span class="line"><span class="cl"><span class="c1"># - &#39;git&#39; to fetch plugin updates from upstream</span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### User specific variables, adjust to your own needs</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># folder that contains all nvim plugins</span> +</span></span><span class="line"><span class="cl"><span class="nv">PLUGIN_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_DATA_HOME</span><span class="k">:-</span><span class="nv">$HOME</span><span class="p">/.local/share</span><span class="si">}</span><span class="s2">/nvim/site/pack&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### Script logic</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;updating all plugins in </span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"><span class="c1"># iterate through all directories and git pull em</span> +</span></span><span class="line"><span class="cl"><span class="k">for</span> directory in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span>/*/<span class="o">{</span>start,opt<span class="o">}</span>/*<span class="p">;</span> <span class="k">do</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> +</span></span><span class="line"><span class="cl"> <span class="nv">plugin</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;updating </span><span class="si">${</span><span class="nv">plugin</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"> git -C <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> pull --quiet +</span></span><span class="line"><span class="cl"> <span class="k">fi</span> +</span></span><span class="line"><span class="cl"><span class="k">done</span> +</span></span></code></pre></div><p>In case you are using Vim, adjust the <code>PLUGIN_DIR</code> variable to point to your Vim plugin directory instead and save the resulting shell script as a file called <code>update-nvim-plugins.sh</code> in some folder of your choice. Do not forget to set the executable bit with <code>chmod +x /path/to/your/folder/update-nvim-plugins.sh</code>. Since all good developers must be lazy, write the following <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html">systemd service</a> to execute the above script automatically:</p>Manage dotfiles with chezmoi and githttps://seb.xn--ho-hia.de/posts/chezmoi-auto-git/Mon, 06 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-auto-git/<p><a href="https://www.chezmoi.io/">chezmoi</a> can automatically commit and push changes to your <a href="https://en.wikipedia.org/wiki/dotfile">dotfiles</a> into a (remote) <a href="https://git-scm.com/">Git</a> repository. Enable it with the following snippet in your <code>chezmoi.toml</code></p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">sourceVCS</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoCommit</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">autoPush</span> <span class="p">=</span> <span class="kc">true</span> +</span></span></code></pre></div><p>Every time you call <code>chezmoi add /path/to/file</code> will now create a new commit in your local chezmoi repository and push those changes into your configured remote repository.</p>Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/Mon, 12 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-push-only-mirror/<p>In case you want to have push-only mirrors for your <a href="https://git-scm.com/">Git</a> repository, consider adding a special mirror remote like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote add mirrors DISABLED +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git +</span></span></code></pre></div><p>The above will create a new remote called <code>mirrors</code> which has no <code>fetch</code> URL and therefore can only be pushed:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote -v +</span></span><span class="line"><span class="cl">mirrors DISABLED <span class="o">(</span>fetch<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@codeberg.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@gitlab.com:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@bitbucket.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span></code></pre></div><p>Calling <code>git push mirrors main:main</code> will push the local <code>main</code> branch into all defined mirrors.</p>Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/Mon, 28 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-mirror/<p>In case you want to make use of the decentralized nature of <a href="https://git-scm.com/">Git</a>, consider using multiple push targets like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote set-url origin --push --add git@example.com/project.git +</span></span><span class="line"><span class="cl">$ git remote set-url origin --push --add git@another.com/project.git +</span></span></code></pre></div><p>Note that the first call to <code>set-url</code> will overwrite an existing remote creating with <code>git clone</code>. Any additional call will actually recognize the <code>--add</code> option and add the new target to an existing remote.</p> +<h2 id="links">Links</h2> +<ul> +<li><a href="../git-push-only-mirror">push only mirrors</a></li> +<li><a href="../gitlab-distributor">gitlab-distributor</a></li> +</ul>GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/gitlab-distributor/<p><a href="https://git-scm.com/">Git</a> at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. <a href="https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows">Pro Git</a> rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.</p> +<p>While the central server approach is easy to use, it might not work in all scenarios:</p>Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/Mon, 15 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/short-git-clones/<p>In case you don&rsquo;t want to write <code>git clone git@github.com:orga/repo.git</code> all the time, consider using a custom SSH configuration (<code>~/.ssh/config</code>) like this:</p> +<pre tabindex="0"><code>Host github + HostName github.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITHUB&gt; + +Host gitlab + HostName gitlab.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITLAB&gt; + +Host bitbucket + HostName bitbucket.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-BITBUCKET&gt; + +Host codeberg + HostName codeberg.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-CODEBERG&gt; +</code></pre><p>Once configured, you can now write:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git clone github:orga/repo +</span></span><span class="line"><span class="cl">$ git clone gitlab:orga/repo +</span></span><span class="line"><span class="cl">$ git clone bitbucket:orga/repo +</span></span><span class="line"><span class="cl">$ git clone codeberg:orga/repo +</span></span></code></pre></div><p>In case you are working with many repositories inside a single organization, consider adding the following Git configuration (<code>$XDG_CONFIG_HOME/git/config</code> or <code>~/.gitconfig</code>):</p> \ No newline at end of file diff --git a/tags/github-actions/atom.xml b/tags/github-actions/atom.xml new file mode 100644 index 000000000..4f99f3061 --- /dev/null +++ b/tags/github-actions/atom.xml @@ -0,0 +1,232 @@ +HugoGithub Actions on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/github-actions/Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/2021-05-03T00:00:00+00:002023-01-06T17:32:06+01:00To analyze Maven projects with SonarCloud using GitHub Actions, first create the following settings.xml file:

+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
+
+    <pluginGroups>
+        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
+    </pluginGroups>
+
+    <activeProfiles>
+        <activeProfile>sonar</activeProfile>
+    </activeProfiles>
+
+    <profiles>
+        <profile>
+            <id>sonar</id>
+            <properties>
+                <sonar.host.url>https://sonarcloud.io</sonar.host.url>
+                <sonar.organization>YOUR_ORG</sonar.organization>
+                <sonar.projectKey>YOUR_PROJECT</sonar.projectKey>
+                <sonar.login>${env.SONAR_TOKEN}</sonar.login>
+            </properties>
+        </profile>
+    </profiles>
+</settings>
+

Finally, add a step to your workflow:

+
- name: Verify Project
+  run: mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar
+  env:
+    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+
+ +]]>
Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/2021-02-22T00:00:00+00:002023-01-06T16:40:24+01:00To delay the execution of an GitHub Action, use a mixture of the on: schedule: ... configuration, and a conditional build step.

+
name: <PIPELINE>
+on:
+  schedule:
+    - cron: '<CRON>'
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Count commits in last week
+        id: commits
+        run: echo "::set-output name=count::$(git rev-list --count HEAD --since='<DATE>')"
+      - name: Build project
+        if: steps.commits.outputs.count > 0
+        run: build-project
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <CRON>: cron expression - use https://crontab.guru/.
  • +
  • <DATE>: Git date expression that matches <CRON>.
  • +
+]]>
Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/2020-11-16T00:00:00+00:002023-01-06T15:27:21+01:00The rzr/fediverse-action action allows to send a toot in your GitHub Action.

+
name: <NAME>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Publish Toot
+        uses: rzr/fediverse-action@master
+        with:
+          access-token: ${{ secrets.MASTODON_TOKEN }}
+          message: <MESSAGE>
+          host: ${{ secrets.MASTODON_SERVER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <MESSAGE>: Message for the toot.
  • +
+]]>
Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/2020-11-02T00:00:00+00:002023-01-06T15:27:21+01:00The dawidd6/action-send-mail action allows to send an email in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Send mail
+        uses: dawidd6/action-send-mail@v3
+        with:
+          server_address: ${{ secrets.MAIL_SERVER }}
+          server_port: ${{ secrets.MAIL_PORT }}
+          username: ${{ secrets.MAIL_USERNAME }}
+          password: ${{ secrets.MAIL_PASSWORD }}
+          subject: <SUBJECT>
+          body: <BODY>
+          to: ${{ secrets.MAIL_RECIPIENT }}
+          from: ${{ secrets.MAIL_SENDER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <SUBJECT>: Subject for the email.
  • +
  • <BODY>: Body for the email.
  • +
+

Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define MAIL_RECIPIENT for each project.

+]]>
Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/2020-10-19T00:00:00+00:002023-01-06T15:27:21+01:00The actions/upload-release-asset action allows to upload a release artifact in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Upload Release Asset
+        id: upload_release_asset
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./some/path/to/file.zip
+          asset_name: public-name-for-file.zip
+          asset_content_type: application/zip
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/2020-10-05T00:00:00+00:002023-01-06T17:32:06+01:00The actions/create-release action allows to create a new GitHub releases in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+     - name: Create Release
+       uses: actions/create-release@v1
+       env:
+         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+       with:
+         tag_name: <TAG>
+         release_name: <RELEASE>
+         draft: false
+         prerelease: false
+         body: |
+           Your release text here
+
+           Some code block:
+           ```yaml
+           yaml:
+             inside:
+               of:
+                 another: yaml
+           ```           
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <TAG>: The Git tag to create.
  • +
  • <RELEASE>: The release name to use.
  • +
+]]>
Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/2020-09-21T00:00:00+00:002023-01-06T15:27:21+01:00The peaceiris/actions-gh-pages action allows to publish a Hugo site in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Deploy Website
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: <PUBLISH_DIR>
+          force_orphan: true
+          cname: <CNAME>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <PUBLISH_DIR>: The file system location of the built site.
  • +
  • <CNAME>: The CNAME of your custom domain.
  • +
+]]>
Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/2020-09-07T00:00:00+00:002023-01-06T15:27:21+01:00The actions/cache action allows to cache artifacts in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Cache Maven artifacts
+        uses: actions/cache@v1
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-            
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/2020-08-24T00:00:00+00:002023-01-06T15:27:21+01:00The actions-hugo action allows to use a specific Hugo version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Setup hugo
+        uses: peaceiris/actions-hugo@v2
+        with:
+          hugo-version: <HUGO_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <HUGO_VERSION>: The released versions or use latest to always use the latest version of Hugo.
  • +
+]]>
Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/2020-08-10T00:00:00+00:002023-01-06T15:27:21+01:00The setup-java action allows to use a specific Java version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Set up JDK <JDK_VERSION>
+        uses: actions/setup-java@v1
+        with:
+          java-version: <JDK_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <JDK_VERSION>: The required Java version for your project.
  • +
+]]>
Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/2020-06-11T00:00:00+00:002023-01-06T15:27:21+01:00In case you are into calver or have another reason to create a timestamp with GitHub Actions, do the following:

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Create release version
+        id: <ID>
+        run: echo "::set-output name=<NAME>::$(date +'%Y.%m.%d-%H%M%S')"
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <ID>: The unique ID of the timestamp step.
  • +
  • <NAME>: The name of the created timestamp.
  • +
+

The special syntax ::set-output name=<NAME>:: declares that the output of the command (echo) should be saved in a variable called <NAME>. Together with the <ID> of the pipeline step, this value can be referenced with the expression ${{ steps.<ID>.outputs.<NAME> }} in the following steps of your pipeline.

+]]>
\ No newline at end of file diff --git a/tags/github-actions/index.html b/tags/github-actions/index.html new file mode 100644 index 000000000..d826c67b7 --- /dev/null +++ b/tags/github-actions/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Github Actions

Tag: Github Actions

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/github-actions/index.xml b/tags/github-actions/index.xml new file mode 100644 index 000000000..6f0e4e140 --- /dev/null +++ b/tags/github-actions/index.xml @@ -0,0 +1,221 @@ +Github Actions on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/github-actions/Recent content in Github Actions on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/Mon, 03 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/<p>To analyze <a href="https://maven.apache.org/">Maven</a> projects with <a href="https://sonarcloud.io">SonarCloud</a> using <a href="https://github.com/features/actions">GitHub Actions</a>, first create the following <code>settings.xml</code> file:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings</span> <span class="na">xmlns=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xmlns:xsi=</span><span class="s">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xsi:schemaLocation=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0 +</span></span></span><span class="line"><span class="cl"><span class="s"> http://maven.apache.org/xsd/settings-1.0.0.xsd&#34;</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroup&gt;</span>org.sonarsource.scanner.maven<span class="nt">&lt;/pluginGroup&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfile&gt;</span>sonar<span class="nt">&lt;/activeProfile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>sonar<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.host.url&gt;</span>https://sonarcloud.io<span class="nt">&lt;/sonar.host.url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.organization&gt;</span>YOUR_ORG<span class="nt">&lt;/sonar.organization&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.projectKey&gt;</span>YOUR_PROJECT<span class="nt">&lt;/sonar.projectKey&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.login&gt;</span>${env.SONAR_TOKEN}<span class="nt">&lt;/sonar.login&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Finally, add a step to your workflow:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Verify Project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">SONAR_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.SONAR_TOKEN }}</span><span class="w"> +</span></span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/settings.html">https://maven.apache.org/settings.html</a></li> +<li><a href="https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/">https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/</a></li> +</ul>Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/Mon, 22 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-schedule-build/<p>To delay the execution of an <a href="https://github.com/features/actions">GitHub Action</a>, use a mixture of the <code>on: schedule: ...</code> configuration, and a <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif">conditional build step</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">schedule</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&lt;CRON&gt;&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Count commits in last week</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">commits</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=count::$(git rev-list --count HEAD --since=&#39;&lt;DATE&gt;&#39;)&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">steps.commits.outputs.count &gt; 0</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">build-project</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;CRON&gt;</code>: cron expression - use <a href="https://crontab.guru/">https://crontab.guru/</a>.</li> +<li><code>&lt;DATE&gt;</code>: Git date expression that matches <code>&lt;CRON&gt;</code>.</li> +</ul>Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-toot/<p>The <a href="https://github.com/rzr/fediverse-action">rzr/fediverse-action</a> action allows to send a <a href="https://joinmastodon.org/">toot</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;NAME&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish Toot</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">rzr/fediverse-action@master</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">access-token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;MESSAGE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_SERVER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;MESSAGE&gt;</code>: Message for the toot.</li> +</ul>Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/Mon, 02 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-email/<p>The <a href="https://github.com/dawidd6/action-send-mail">dawidd6/action-send-mail</a> action allows to send an email in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Send mail</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">dawidd6/action-send-mail@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_address</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SERVER }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_port</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PORT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_USERNAME }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PASSWORD }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">subject</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;SUBJECT&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;BODY&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_RECIPIENT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SENDER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;SUBJECT&gt;</code>: Subject for the email.</li> +<li><code>&lt;BODY&gt;</code>: Body for the email.</li> +</ul> +<p>Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define <code>MAIL_RECIPIENT</code> for each project.</p>Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/<p>The <a href="https://github.com/actions/upload-release-asset">actions/upload-release-asset</a> action allows to upload a <a href="https://developer.github.com/v3/repos/releases/#upload-a-release-asset">release artifact</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload Release Asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">upload_release_asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-release-asset@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">upload_url</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.create_release.outputs.upload_url }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_path</span><span class="p">:</span><span class="w"> </span><span class="l">./some/path/to/file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_name</span><span class="p">:</span><span class="w"> </span><span class="l">public-name-for-file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_content_type</span><span class="p">:</span><span class="w"> </span><span class="l">application/zip</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/Mon, 05 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-release/<p>The <a href="https://github.com/actions/create-release">actions/create-release</a> action allows to create a new <a href="https://help.github.com/en/github/administering-a-repository/about-releases">GitHub releases</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create Release</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/create-release@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">tag_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;TAG&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">release_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RELEASE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">draft</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">prerelease</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Your release text here +</span></span></span><span class="line"><span class="cl"><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Some code block: +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> yaml: +</span></span></span><span class="line"><span class="cl"><span class="sd"> inside: +</span></span></span><span class="line"><span class="cl"><span class="sd"> of: +</span></span></span><span class="line"><span class="cl"><span class="sd"> another: yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;TAG&gt;</code>: The Git tag to create.</li> +<li><code>&lt;RELEASE&gt;</code>: The release name to use.</li> +</ul>Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/Mon, 21 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/<p>The <a href="https://github.com/peaceiris/actions-gh-pages">peaceiris/actions-gh-pages</a> action allows to publish a <a href="https://gohugo.io/">Hugo</a> site in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Website</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-gh-pages@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">github_token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">publish_dir</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PUBLISH_DIR&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">force_orphan</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">cname</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;CNAME&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;PUBLISH_DIR&gt;</code>: The file system location of the built site.</li> +<li><code>&lt;CNAME&gt;</code>: The <code>CNAME</code> of your custom domain.</li> +</ul>Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/Mon, 07 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions/cache</a> action allows to cache artifacts in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Maven artifacts</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">~/.m2/repository</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-maven-${{ hashFiles(&#39;**/pom.xml&#39;) }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> ${{ runner.os }}-maven-</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/Mon, 24 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions-hugo</a> action allows to use a specific <a href="https://gohugo.io/">Hugo</a> version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup hugo</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-hugo@v2</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hugo-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;HUGO_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;HUGO_VERSION&gt;</code>: The <a href="https://github.com/gohugoio/hugo/releases">released versions</a> or use <code>latest</code> to always use the latest version of Hugo.</li> +</ul>Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/Mon, 10 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/<p>The <a href="https://github.com/actions/setup-java">setup-java</a> action allows to use a specific Java version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up JDK &lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-java@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">java-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;JDK_VERSION&gt;</code>: The required Java version for your project.</li> +</ul>Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/Thu, 11 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/<p>In case you are into <a href="https://calver.org/">calver</a> or have another reason to create a timestamp with <a href="https://github.com/features/actions">GitHub Actions</a>, do the following:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create release version</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;ID&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=&lt;NAME&gt;::$(date +&#39;%Y.%m.%d-%H%M%S&#39;)&#34;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;ID&gt;</code>: The unique ID of the timestamp step.</li> +<li><code>&lt;NAME&gt;</code>: The name of the created timestamp.</li> +</ul> +<p>The special syntax <code>::set-output name=&lt;NAME&gt;::</code> declares that the output of the command (<code>echo</code>) should be saved in a variable called <code>&lt;NAME&gt;</code>. Together with the <code>&lt;ID&gt;</code> of the pipeline step, this value can be referenced with the expression <code>${{ steps.&lt;ID&gt;.outputs.&lt;NAME&gt; }}</code> in the following steps of your pipeline.</p> \ No newline at end of file diff --git a/tags/github-packages/atom.xml b/tags/github-packages/atom.xml new file mode 100644 index 000000000..a6169c855 --- /dev/null +++ b/tags/github-packages/atom.xml @@ -0,0 +1,46 @@ +HugoGithub Packages on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/github-packages/GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/2021-11-22T00:00:00+00:002023-01-06T15:27:21+01:00GitHub Packages can be used to host Maven packages with the following configuration in your ~/.m2/settings.xml:

+
<settings>
+  <profiles>
+    <profile>
+      <id>github</id>
+      <repositories>
+        <repository>
+          <id>maven-build-process</id>
+          <name>GitHub maven-build-process Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/maven-build-process</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+        <repository>
+          <id>hcf4j</id>
+          <name>GitHub hcf4j Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/hcf4j</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+    </profile>
+  </profiles>
+  <servers>
+    <server>
+      <id>maven-build-process</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+    <server>
+      <id>hcf4j</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+  </servers>
+</settings>
+

You will have to add another repository/server for each project you are fetching from GitHub.

+]]>
\ No newline at end of file diff --git a/tags/github-packages/index.html b/tags/github-packages/index.html new file mode 100644 index 000000000..e3f061ea8 --- /dev/null +++ b/tags/github-packages/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Github Packages

Tag: Github Packages

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/github-packages/index.xml b/tags/github-packages/index.xml new file mode 100644 index 000000000..960cce6dc --- /dev/null +++ b/tags/github-packages/index.xml @@ -0,0 +1,45 @@ +Github Packages on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/github-packages/Recent content in Github Packages on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/Mon, 22 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-maven-packages/<p><a href="https://github.com/features/packages">GitHub Packages</a> can be used to host <a href="https://maven.apache.org/">Maven</a> packages with the following configuration in your <code>~/.m2/settings.xml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>github<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub maven-build-process Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/maven-build-process<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub hcf4j Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/hcf4j<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;servers&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/servers&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>You will have to add another repository/server for each project you are fetching from GitHub.</p> \ No newline at end of file diff --git a/tags/github/atom.xml b/tags/github/atom.xml new file mode 100644 index 000000000..cdab1a52e --- /dev/null +++ b/tags/github/atom.xml @@ -0,0 +1,311 @@ +HugoGithub on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/github/GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/2021-11-22T00:00:00+00:002023-01-06T15:27:21+01:00GitHub Packages can be used to host Maven packages with the following configuration in your ~/.m2/settings.xml:

+
<settings>
+  <profiles>
+    <profile>
+      <id>github</id>
+      <repositories>
+        <repository>
+          <id>maven-build-process</id>
+          <name>GitHub maven-build-process Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/maven-build-process</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+        <repository>
+          <id>hcf4j</id>
+          <name>GitHub hcf4j Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/hcf4j</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+    </profile>
+  </profiles>
+  <servers>
+    <server>
+      <id>maven-build-process</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+    <server>
+      <id>hcf4j</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+  </servers>
+</settings>
+

You will have to add another repository/server for each project you are fetching from GitHub.

+]]>
Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/2021-05-03T00:00:00+00:002023-01-06T17:32:06+01:00To analyze Maven projects with SonarCloud using GitHub Actions, first create the following settings.xml file:

+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
+
+    <pluginGroups>
+        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
+    </pluginGroups>
+
+    <activeProfiles>
+        <activeProfile>sonar</activeProfile>
+    </activeProfiles>
+
+    <profiles>
+        <profile>
+            <id>sonar</id>
+            <properties>
+                <sonar.host.url>https://sonarcloud.io</sonar.host.url>
+                <sonar.organization>YOUR_ORG</sonar.organization>
+                <sonar.projectKey>YOUR_PROJECT</sonar.projectKey>
+                <sonar.login>${env.SONAR_TOKEN}</sonar.login>
+            </properties>
+        </profile>
+    </profiles>
+</settings>
+

Finally, add a step to your workflow:

+
- name: Verify Project
+  run: mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar
+  env:
+    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+
+ +]]>
Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/2021-02-22T00:00:00+00:002023-01-06T16:40:24+01:00To delay the execution of an GitHub Action, use a mixture of the on: schedule: ... configuration, and a conditional build step.

+
name: <PIPELINE>
+on:
+  schedule:
+    - cron: '<CRON>'
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Count commits in last week
+        id: commits
+        run: echo "::set-output name=count::$(git rev-list --count HEAD --since='<DATE>')"
+      - name: Build project
+        if: steps.commits.outputs.count > 0
+        run: build-project
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <CRON>: cron expression - use https://crontab.guru/.
  • +
  • <DATE>: Git date expression that matches <CRON>.
  • +
+]]>
Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/2020-11-16T00:00:00+00:002023-01-06T15:27:21+01:00The rzr/fediverse-action action allows to send a toot in your GitHub Action.

+
name: <NAME>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Publish Toot
+        uses: rzr/fediverse-action@master
+        with:
+          access-token: ${{ secrets.MASTODON_TOKEN }}
+          message: <MESSAGE>
+          host: ${{ secrets.MASTODON_SERVER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <MESSAGE>: Message for the toot.
  • +
+]]>
Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/2020-11-02T00:00:00+00:002023-01-06T15:27:21+01:00The dawidd6/action-send-mail action allows to send an email in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Send mail
+        uses: dawidd6/action-send-mail@v3
+        with:
+          server_address: ${{ secrets.MAIL_SERVER }}
+          server_port: ${{ secrets.MAIL_PORT }}
+          username: ${{ secrets.MAIL_USERNAME }}
+          password: ${{ secrets.MAIL_PASSWORD }}
+          subject: <SUBJECT>
+          body: <BODY>
+          to: ${{ secrets.MAIL_RECIPIENT }}
+          from: ${{ secrets.MAIL_SENDER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <SUBJECT>: Subject for the email.
  • +
  • <BODY>: Body for the email.
  • +
+

Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define MAIL_RECIPIENT for each project.

+]]>
Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/2020-10-19T00:00:00+00:002023-01-06T15:27:21+01:00The actions/upload-release-asset action allows to upload a release artifact in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Upload Release Asset
+        id: upload_release_asset
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./some/path/to/file.zip
+          asset_name: public-name-for-file.zip
+          asset_content_type: application/zip
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/2020-10-05T00:00:00+00:002023-01-06T17:32:06+01:00The actions/create-release action allows to create a new GitHub releases in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+     - name: Create Release
+       uses: actions/create-release@v1
+       env:
+         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+       with:
+         tag_name: <TAG>
+         release_name: <RELEASE>
+         draft: false
+         prerelease: false
+         body: |
+           Your release text here
+
+           Some code block:
+           ```yaml
+           yaml:
+             inside:
+               of:
+                 another: yaml
+           ```           
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <TAG>: The Git tag to create.
  • +
  • <RELEASE>: The release name to use.
  • +
+]]>
Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/2020-09-21T00:00:00+00:002023-01-06T15:27:21+01:00The peaceiris/actions-gh-pages action allows to publish a Hugo site in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Deploy Website
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: <PUBLISH_DIR>
+          force_orphan: true
+          cname: <CNAME>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <PUBLISH_DIR>: The file system location of the built site.
  • +
  • <CNAME>: The CNAME of your custom domain.
  • +
+]]>
Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/2020-09-07T00:00:00+00:002023-01-06T15:27:21+01:00The actions/cache action allows to cache artifacts in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Cache Maven artifacts
+        uses: actions/cache@v1
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-            
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/2020-08-24T00:00:00+00:002023-01-06T15:27:21+01:00The actions-hugo action allows to use a specific Hugo version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Setup hugo
+        uses: peaceiris/actions-hugo@v2
+        with:
+          hugo-version: <HUGO_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <HUGO_VERSION>: The released versions or use latest to always use the latest version of Hugo.
  • +
+]]>
Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/2020-08-10T00:00:00+00:002023-01-06T15:27:21+01:00The setup-java action allows to use a specific Java version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Set up JDK <JDK_VERSION>
+        uses: actions/setup-java@v1
+        with:
+          java-version: <JDK_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <JDK_VERSION>: The required Java version for your project.
  • +
+]]>
GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/2020-07-13T00:00:00+00:002023-01-06T17:32:06+01:00Git at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. Pro Git rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.

+

While the central server approach is easy to use, it might not work in all scenarios:

+
    +
  1. An enterprise wants to share internal code as part of an open source project. All development is happening internally, and the public mirror gets an occasional update once in a while.
  2. +
  3. To protect against outages of the central server, mirrors should be created and be kept up-to-date.
  4. +
+

In case of the first scenario, tools like copybara, repoSpanner, or distributed-Git-forks offer a wide range of features to cover most details.

+

The second scenario can be solved manually with tools like gitomatic or automatically with GitLab’s mirror feature quite easy. GitLab allows to create a single pull-mirror and multiple push-mirrors. Therefore, it can be used to pull from your central server and push into all mirrors.

+

NOTE: This feature was previously available in the free tier but has now moved to GitLab Ultimate.

+
               +----------------+               
+               |     GitHub     |               
+               +----------------+               
+                        ^                       
+                        |                       
+                        |                       
+               +----------------+               
+         +-----|     GitLab     |------+        
+         |     +----------------+      |        
+         |                             |        
+         |                             |        
+         v                             v        
++----------------+            +----------------+
+|    Codeberg    |            |    BitBucket   |
++----------------+            +----------------+
+

To create such a setup, follow these steps:

+
    +
  1. Go to Settings > Repository and expand Mirroring repositories +Code Flow
  2. +
  3. Enter the URL of your central Git repository as the pull source, for example https://github.com/metio/ilo.git +Code Flow
  4. +
  5. Enter one push target for each mirror. Since pushing usually requires authentication, verify that the URL of the mirror contains a username, for example https://YOUR_USER@codeberg.org/metio.wtf/ilo.git. Add an access token for each mirror and select password as authentication method. +Code Flow
  6. +
+

In case you prefer SSH keys over HTTP access tokens, just select SSH public key as authentication method and verify that your key is both saved in GitLab and all mirrors.

+]]>
Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/2020-06-11T00:00:00+00:002023-01-06T15:27:21+01:00In case you are into calver or have another reason to create a timestamp with GitHub Actions, do the following:

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Create release version
+        id: <ID>
+        run: echo "::set-output name=<NAME>::$(date +'%Y.%m.%d-%H%M%S')"
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <ID>: The unique ID of the timestamp step.
  • +
  • <NAME>: The name of the created timestamp.
  • +
+

The special syntax ::set-output name=<NAME>:: declares that the output of the command (echo) should be saved in a variable called <NAME>. Together with the <ID> of the pipeline step, this value can be referenced with the expression ${{ steps.<ID>.outputs.<NAME> }} in the following steps of your pipeline.

+]]>
\ No newline at end of file diff --git a/tags/github/index.html b/tags/github/index.html new file mode 100644 index 000000000..9a258a928 --- /dev/null +++ b/tags/github/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Github

Tag: Github

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/github/index.xml b/tags/github/index.xml new file mode 100644 index 000000000..6f2ed4ab0 --- /dev/null +++ b/tags/github/index.xml @@ -0,0 +1,266 @@ +Github on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/github/Recent content in Github on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/Mon, 22 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-maven-packages/<p><a href="https://github.com/features/packages">GitHub Packages</a> can be used to host <a href="https://maven.apache.org/">Maven</a> packages with the following configuration in your <code>~/.m2/settings.xml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>github<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub maven-build-process Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/maven-build-process<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub hcf4j Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/hcf4j<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;servers&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/servers&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>You will have to add another repository/server for each project you are fetching from GitHub.</p>Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/Mon, 03 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/<p>To analyze <a href="https://maven.apache.org/">Maven</a> projects with <a href="https://sonarcloud.io">SonarCloud</a> using <a href="https://github.com/features/actions">GitHub Actions</a>, first create the following <code>settings.xml</code> file:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings</span> <span class="na">xmlns=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xmlns:xsi=</span><span class="s">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xsi:schemaLocation=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0 +</span></span></span><span class="line"><span class="cl"><span class="s"> http://maven.apache.org/xsd/settings-1.0.0.xsd&#34;</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroup&gt;</span>org.sonarsource.scanner.maven<span class="nt">&lt;/pluginGroup&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfile&gt;</span>sonar<span class="nt">&lt;/activeProfile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>sonar<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.host.url&gt;</span>https://sonarcloud.io<span class="nt">&lt;/sonar.host.url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.organization&gt;</span>YOUR_ORG<span class="nt">&lt;/sonar.organization&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.projectKey&gt;</span>YOUR_PROJECT<span class="nt">&lt;/sonar.projectKey&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.login&gt;</span>${env.SONAR_TOKEN}<span class="nt">&lt;/sonar.login&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Finally, add a step to your workflow:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Verify Project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">SONAR_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.SONAR_TOKEN }}</span><span class="w"> +</span></span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/settings.html">https://maven.apache.org/settings.html</a></li> +<li><a href="https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/">https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/</a></li> +</ul>Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/Mon, 22 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-schedule-build/<p>To delay the execution of an <a href="https://github.com/features/actions">GitHub Action</a>, use a mixture of the <code>on: schedule: ...</code> configuration, and a <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif">conditional build step</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">schedule</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&lt;CRON&gt;&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Count commits in last week</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">commits</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=count::$(git rev-list --count HEAD --since=&#39;&lt;DATE&gt;&#39;)&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">steps.commits.outputs.count &gt; 0</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">build-project</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;CRON&gt;</code>: cron expression - use <a href="https://crontab.guru/">https://crontab.guru/</a>.</li> +<li><code>&lt;DATE&gt;</code>: Git date expression that matches <code>&lt;CRON&gt;</code>.</li> +</ul>Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-toot/<p>The <a href="https://github.com/rzr/fediverse-action">rzr/fediverse-action</a> action allows to send a <a href="https://joinmastodon.org/">toot</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;NAME&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish Toot</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">rzr/fediverse-action@master</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">access-token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;MESSAGE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_SERVER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;MESSAGE&gt;</code>: Message for the toot.</li> +</ul>Send emails with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-email/Mon, 02 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-email/<p>The <a href="https://github.com/dawidd6/action-send-mail">dawidd6/action-send-mail</a> action allows to send an email in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Send mail</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">dawidd6/action-send-mail@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_address</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SERVER }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">server_port</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PORT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_USERNAME }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_PASSWORD }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">subject</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;SUBJECT&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;BODY&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_RECIPIENT }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">from</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MAIL_SENDER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;SUBJECT&gt;</code>: Subject for the email.</li> +<li><code>&lt;BODY&gt;</code>: Body for the email.</li> +</ul> +<p>Create appropriate secrets in your organization or project. In case you are using an organization, but different mailing lists, define <code>MAIL_RECIPIENT</code> for each project.</p>Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/<p>The <a href="https://github.com/actions/upload-release-asset">actions/upload-release-asset</a> action allows to upload a <a href="https://developer.github.com/v3/repos/releases/#upload-a-release-asset">release artifact</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload Release Asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">upload_release_asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-release-asset@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">upload_url</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.create_release.outputs.upload_url }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_path</span><span class="p">:</span><span class="w"> </span><span class="l">./some/path/to/file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_name</span><span class="p">:</span><span class="w"> </span><span class="l">public-name-for-file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_content_type</span><span class="p">:</span><span class="w"> </span><span class="l">application/zip</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/Mon, 05 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-release/<p>The <a href="https://github.com/actions/create-release">actions/create-release</a> action allows to create a new <a href="https://help.github.com/en/github/administering-a-repository/about-releases">GitHub releases</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create Release</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/create-release@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">tag_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;TAG&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">release_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RELEASE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">draft</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">prerelease</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Your release text here +</span></span></span><span class="line"><span class="cl"><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Some code block: +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> yaml: +</span></span></span><span class="line"><span class="cl"><span class="sd"> inside: +</span></span></span><span class="line"><span class="cl"><span class="sd"> of: +</span></span></span><span class="line"><span class="cl"><span class="sd"> another: yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;TAG&gt;</code>: The Git tag to create.</li> +<li><code>&lt;RELEASE&gt;</code>: The release name to use.</li> +</ul>Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/Mon, 21 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/<p>The <a href="https://github.com/peaceiris/actions-gh-pages">peaceiris/actions-gh-pages</a> action allows to publish a <a href="https://gohugo.io/">Hugo</a> site in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Website</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-gh-pages@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">github_token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">publish_dir</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PUBLISH_DIR&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">force_orphan</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">cname</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;CNAME&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;PUBLISH_DIR&gt;</code>: The file system location of the built site.</li> +<li><code>&lt;CNAME&gt;</code>: The <code>CNAME</code> of your custom domain.</li> +</ul>Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/Mon, 07 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions/cache</a> action allows to cache artifacts in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Maven artifacts</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">~/.m2/repository</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-maven-${{ hashFiles(&#39;**/pom.xml&#39;) }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> ${{ runner.os }}-maven-</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/Mon, 24 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions-hugo</a> action allows to use a specific <a href="https://gohugo.io/">Hugo</a> version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup hugo</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-hugo@v2</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hugo-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;HUGO_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;HUGO_VERSION&gt;</code>: The <a href="https://github.com/gohugoio/hugo/releases">released versions</a> or use <code>latest</code> to always use the latest version of Hugo.</li> +</ul>Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/Mon, 10 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/<p>The <a href="https://github.com/actions/setup-java">setup-java</a> action allows to use a specific Java version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up JDK &lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-java@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">java-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;JDK_VERSION&gt;</code>: The required Java version for your project.</li> +</ul>GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/gitlab-distributor/<p><a href="https://git-scm.com/">Git</a> at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. <a href="https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows">Pro Git</a> rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.</p> +<p>While the central server approach is easy to use, it might not work in all scenarios:</p>Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/Thu, 11 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/<p>In case you are into <a href="https://calver.org/">calver</a> or have another reason to create a timestamp with <a href="https://github.com/features/actions">GitHub Actions</a>, do the following:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create release version</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;ID&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=&lt;NAME&gt;::$(date +&#39;%Y.%m.%d-%H%M%S&#39;)&#34;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;ID&gt;</code>: The unique ID of the timestamp step.</li> +<li><code>&lt;NAME&gt;</code>: The name of the created timestamp.</li> +</ul> +<p>The special syntax <code>::set-output name=&lt;NAME&gt;::</code> declares that the output of the command (<code>echo</code>) should be saved in a variable called <code>&lt;NAME&gt;</code>. Together with the <code>&lt;ID&gt;</code> of the pipeline step, this value can be referenced with the expression <code>${{ steps.&lt;ID&gt;.outputs.&lt;NAME&gt; }}</code> in the following steps of your pipeline.</p> \ No newline at end of file diff --git a/tags/gitlab/atom.xml b/tags/gitlab/atom.xml new file mode 100644 index 000000000..a8bde5c8e --- /dev/null +++ b/tags/gitlab/atom.xml @@ -0,0 +1,35 @@ +HugoGitlab on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/gitlab/GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/2020-07-13T00:00:00+00:002023-01-06T17:32:06+01:00Git at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. Pro Git rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.

+

While the central server approach is easy to use, it might not work in all scenarios:

+
    +
  1. An enterprise wants to share internal code as part of an open source project. All development is happening internally, and the public mirror gets an occasional update once in a while.
  2. +
  3. To protect against outages of the central server, mirrors should be created and be kept up-to-date.
  4. +
+

In case of the first scenario, tools like copybara, repoSpanner, or distributed-Git-forks offer a wide range of features to cover most details.

+

The second scenario can be solved manually with tools like gitomatic or automatically with GitLab’s mirror feature quite easy. GitLab allows to create a single pull-mirror and multiple push-mirrors. Therefore, it can be used to pull from your central server and push into all mirrors.

+

NOTE: This feature was previously available in the free tier but has now moved to GitLab Ultimate.

+
               +----------------+               
+               |     GitHub     |               
+               +----------------+               
+                        ^                       
+                        |                       
+                        |                       
+               +----------------+               
+         +-----|     GitLab     |------+        
+         |     +----------------+      |        
+         |                             |        
+         |                             |        
+         v                             v        
++----------------+            +----------------+
+|    Codeberg    |            |    BitBucket   |
++----------------+            +----------------+
+

To create such a setup, follow these steps:

+
    +
  1. Go to Settings > Repository and expand Mirroring repositories +Code Flow
  2. +
  3. Enter the URL of your central Git repository as the pull source, for example https://github.com/metio/ilo.git +Code Flow
  4. +
  5. Enter one push target for each mirror. Since pushing usually requires authentication, verify that the URL of the mirror contains a username, for example https://YOUR_USER@codeberg.org/metio.wtf/ilo.git. Add an access token for each mirror and select password as authentication method. +Code Flow
  6. +
+

In case you prefer SSH keys over HTTP access tokens, just select SSH public key as authentication method and verify that your key is both saved in GitLab and all mirrors.

+]]>
\ No newline at end of file diff --git a/tags/gitlab/index.html b/tags/gitlab/index.html new file mode 100644 index 000000000..762cf08f0 --- /dev/null +++ b/tags/gitlab/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Gitlab

Tag: Gitlab

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/gitlab/index.xml b/tags/gitlab/index.xml new file mode 100644 index 000000000..7634e8fa9 --- /dev/null +++ b/tags/gitlab/index.xml @@ -0,0 +1,2 @@ +Gitlab on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/gitlab/Recent content in Gitlab on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/gitlab-distributor/<p><a href="https://git-scm.com/">Git</a> at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. <a href="https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows">Pro Git</a> rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.</p> +<p>While the central server approach is easy to use, it might not work in all scenarios:</p> \ No newline at end of file diff --git a/tags/google/atom.xml b/tags/google/atom.xml new file mode 100644 index 000000000..67e200de3 --- /dev/null +++ b/tags/google/atom.xml @@ -0,0 +1,25 @@ +HugoGoogle on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/google/Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/2021-11-08T00:00:00+00:002023-01-06T15:27:21+01:00Some time ago, Google started hosting a copy of Maven Central. Configure it in your ~/.m2/settings.xml like this:

+
<settings>
+  <mirrors>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (Asia)</name>
+      <url>https://maven-central-asia.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (EU)</name>
+      <url>https://maven-central-eu.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (US)</name>
+      <url>https://maven-central.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+  </mirrors>
+</settings>
+

Pick the mirror nearest to your location to get best speeds.

+]]>
\ No newline at end of file diff --git a/tags/google/index.html b/tags/google/index.html new file mode 100644 index 000000000..00c6c5f28 --- /dev/null +++ b/tags/google/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Google

Tag: Google

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/google/index.xml b/tags/google/index.xml new file mode 100644 index 000000000..c98383720 --- /dev/null +++ b/tags/google/index.xml @@ -0,0 +1,24 @@ +Google on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/google/Recent content in Google on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/Mon, 08 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-google-central/<p>Some time ago, <a href="https://www.google.com/">Google</a> started hosting a <a href="https://storage-download.googleapis.com/maven-central/index.html">copy</a> of <a href="https://search.maven.org/">Maven Central</a>. Configure it in your <code>~/.m2/settings.xml</code> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrors&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (Asia)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-asia.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (EU)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-eu.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (US)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirrors&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Pick the mirror nearest to your location to get best speeds.</p> \ No newline at end of file diff --git a/tags/gpg/atom.xml b/tags/gpg/atom.xml new file mode 100644 index 000000000..b5337e4c1 --- /dev/null +++ b/tags/gpg/atom.xml @@ -0,0 +1,6 @@ +HugoGpg on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/gpg/Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/2021-09-20T00:00:00+00:002023-01-08T09:25:44+01:00RECOMMENDATION: Use age instead of gpg.

+

chezmoi can use various external tools to keep data private. gpg is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure gpg with chezmoi, just set yourself as the recipient like this:

+
[gpg]
+  recipient = "your.name@example.com"
+

Calling chezmoi add --encrypt /path/to/secret will now create encrypt the file with your public key which allows you to decrypt them later with your private key.

+]]>
\ No newline at end of file diff --git a/tags/gpg/index.html b/tags/gpg/index.html new file mode 100644 index 000000000..7655fc796 --- /dev/null +++ b/tags/gpg/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Gpg

Tag: Gpg

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/gpg/index.xml b/tags/gpg/index.xml new file mode 100644 index 000000000..7a1ae0a32 --- /dev/null +++ b/tags/gpg/index.xml @@ -0,0 +1,5 @@ +Gpg on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/gpg/Recent content in Gpg on Sebastian HoßHugoenSun, 08 Jan 2023 09:25:44 +0100Encrypt dotfiles with chezmoihttps://seb.xn--ho-hia.de/posts/chezmoi-gpg/Mon, 20 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/chezmoi-gpg/<p><strong>RECOMMENDATION</strong>: Use <a href="../chezmoi-age">age</a> instead of <code>gpg</code>.</p> +<p><a href="https://www.chezmoi.io/">chezmoi</a> can use various external tools to <a href="https://www.chezmoi.io/docs/how-to/#keep-data-private">keep data private</a>. <a href="https://www.gnupg.org/">gpg</a> is used by various other tools as well, so chances are that you already have a functional setup on your system. To configure <code>gpg</code> with <code>chezmoi</code>, just set yourself as the recipient like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">gpg</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">recipient</span> <span class="p">=</span> <span class="s2">&#34;your.name@example.com&#34;</span> +</span></span></code></pre></div><p>Calling <code>chezmoi add --encrypt /path/to/secret</code> will now create encrypt the file with your public key which allows you to decrypt them later with your private key.</p> \ No newline at end of file diff --git a/tags/grim/atom.xml b/tags/grim/atom.xml new file mode 100644 index 000000000..6b5b44813 --- /dev/null +++ b/tags/grim/atom.xml @@ -0,0 +1,7 @@ +HugoGrim on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/grim/Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/2021-03-22T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM uses can use a mixture of grim and slurp to take screenshots of their desktop. Place the following key binding in your Sway configuration:

+
# take screenshot of currently focused screen
+bindsym $mod+Print exec /usr/bin/grim -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
+# take screenshot of selection
+bindsym $mod+Shift+p exec /usr/bin/grim -g "$(/usr/bin/slurp)" $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
]]>
\ No newline at end of file diff --git a/tags/grim/index.html b/tags/grim/index.html new file mode 100644 index 000000000..11443b026 --- /dev/null +++ b/tags/grim/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Grim

Tag: Grim

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/grim/index.xml b/tags/grim/index.xml new file mode 100644 index 000000000..4de7de176 --- /dev/null +++ b/tags/grim/index.xml @@ -0,0 +1,7 @@ +Grim on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/grim/Recent content in Grim on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/Mon, 22 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenshots/<p><a href="https://swaywm.org/">SwayWM</a> uses can use a mixture of <a href="https://github.com/emersion/grim">grim</a> and <a href="https://github.com/emersion/slurp">slurp</a> to take screenshots of their desktop. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># take screenshot of currently focused screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Print <span class="nb">exec</span> /usr/bin/grim -o <span class="k">$(</span>swaymsg -t get_outputs <span class="p">|</span> jq -r <span class="s1">&#39;.[] | select(.focused) | .name&#39;</span><span class="k">)</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># take screenshot of selection</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Shift+p <span class="nb">exec</span> /usr/bin/grim -g <span class="s2">&#34;</span><span class="k">$(</span>/usr/bin/slurp<span class="k">)</span><span class="s2">&#34;</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span></code></pre></div> \ No newline at end of file diff --git a/tags/help/atom.xml b/tags/help/atom.xml new file mode 100644 index 000000000..e3e150820 --- /dev/null +++ b/tags/help/atom.xml @@ -0,0 +1,49 @@ +HugoHelp on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/help/Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/2020-06-29T00:00:00+00:002023-01-06T16:22:24+01:00Use the following Perl snippet to automatically generate help output for your Makefile:

+
GREEN  := $(shell tput -Txterm setaf 2)
+WHITE  := $(shell tput -Txterm setaf 7)
+YELLOW := $(shell tput -Txterm setaf 3)
+RESET  := $(shell tput -Txterm sgr0)
+
+HELP_FUN = \
+    %help; \
+    while(<>) { push @{$$help{$$2 // 'targets'}}, [$$1, $$3] if /^([a-zA-Z\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
+    print "usage: make [target]\n\n"; \
+    for (sort keys %help) { \
+    print "${WHITE}$$_:${RESET}\n"; \
+    for (@{$$help{$$_}}) { \
+    $$sep = " " x (32 - length $$_->[0]); \
+    print "  ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
+    }; \
+    print "\n"; }
+

To use HELP_FUN, add the following help target to the same Makefile:

+
.DEFAULT_GOAL := help
+
+.PHONY: help
+help: ##@other Show this help
+	@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
+

Each target in the Makefile is marked as phony to signal that those targets are not actually files that are generated as part of your build process. The optional description of a target can be placed after the ##@ prefix. The first word represents the group of a target and everything that follows is the description of a target. All targets should be formatted just like the help target:

+
.PHONY: compile
+compile: ##@hacking Compile your code
+	<compile some code>
+
+.PHONY: test
+test: ##@hacking Test your code
+	<test some code>
+
+.PHONY: sign-cla
+sign-cla: ##@contrib Sign the contributor license agreement
+	<sign some file>
+

Once in place, you can either use make without any argument to call the help target or use make help to see the generated output:

+
$ make
+usage: make [target]
+
+contrib:
+  sign-cla            Sign the contributor license agreement
+
+hacking:
+  compile             Compile your code
+  test                Test your code
+
+other:
+  help                Show this help
+
]]>
\ No newline at end of file diff --git a/tags/help/index.html b/tags/help/index.html new file mode 100644 index 000000000..1b5b1b205 --- /dev/null +++ b/tags/help/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Help

Tag: Help

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/help/index.xml b/tags/help/index.xml new file mode 100644 index 000000000..04f45663d --- /dev/null +++ b/tags/help/index.xml @@ -0,0 +1,18 @@ +Help on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/help/Recent content in Help on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/Mon, 29 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-help/<p>Use the following <a href="https://www.perl.org/">Perl</a> snippet to automatically generate help output for your <code>Makefile</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nv">GREEN</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 2<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">WHITE</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 7<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">YELLOW</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 3<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">RESET</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm sgr0<span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nv">HELP_FUN</span> <span class="o">=</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> %help<span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">while</span><span class="o">(</span>&lt;&gt;<span class="o">)</span> <span class="o">{</span> push @<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span><span class="m">2</span> // <span class="s1">&#39;targets&#39;</span><span class="o">}}</span>, <span class="o">[</span><span class="nv">$$</span>1, <span class="nv">$$</span>3<span class="o">]</span> <span class="k">if</span> /^<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">)</span><span class="se">\s</span>*:.*<span class="se">\#\#</span><span class="o">(</span>?:@<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">))</span>?<span class="se">\s</span><span class="o">(</span>.*<span class="o">)</span><span class="nv">$$</span>/ <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;usage: make [target]\n\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>sort keys %help<span class="o">)</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WHITE</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_:</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>@<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span>_<span class="o">}})</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="nv">$$sep</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span> x <span class="o">(</span><span class="m">32</span> - length <span class="nv">$$</span>_-&gt;<span class="o">[</span>0<span class="o">])</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34; </span><span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[0]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="nv">$$</span><span class="s2">sep</span><span class="si">${</span><span class="nv">GREEN</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[1]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;\n&#34;</span><span class="p">;</span> <span class="o">}</span> +</span></span></code></pre></div><p>To use <code>HELP_FUN</code>, add the following <code>help</code> target to the same <code>Makefile</code>:</p> \ No newline at end of file diff --git a/tags/hostnames/atom.xml b/tags/hostnames/atom.xml new file mode 100644 index 000000000..8a287bd0a --- /dev/null +++ b/tags/hostnames/atom.xml @@ -0,0 +1,7 @@ +HugoHostnames on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/hostnames/Proper hostnames in your local networkhttps://seb.xn--ho-hia.de/posts/home-network-hostnames/2022-01-08T00:00:00+00:002023-01-06T16:22:24+01:00Thanks to RFC 8375, we now have a proper domain to use for all our local devices. Simply move everything underneath .home.arpa to join the fun. In case you have hostnamectl available on your system run the following command to change the hostname of a device:

+
# set hostname
+$ hostnamectl hostname some-device.home.arpa
+
+# check hostname
+$ hostnamectl status
+
]]>
\ No newline at end of file diff --git a/tags/hostnames/index.html b/tags/hostnames/index.html new file mode 100644 index 000000000..11610a49d --- /dev/null +++ b/tags/hostnames/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Hostnames

Tag: Hostnames

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/hostnames/index.xml b/tags/hostnames/index.xml new file mode 100644 index 000000000..1579eb934 --- /dev/null +++ b/tags/hostnames/index.xml @@ -0,0 +1,7 @@ +Hostnames on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/hostnames/Recent content in Hostnames on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Proper hostnames in your local networkhttps://seb.xn--ho-hia.de/posts/home-network-hostnames/Sat, 08 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/home-network-hostnames/<p>Thanks to <a href="https://www.rfc-editor.org/rfc/rfc8375.html">RFC 8375</a>, we now have a proper domain to use for all our local devices. Simply move everything underneath <code>.home.arpa</code> to join the fun. In case you have <code>hostnamectl</code> available on your system run the following command to change the hostname of a device:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> <span class="nb">set</span> hostname +</span></span><span class="line"><span class="cl"><span class="gp">$</span> hostnamectl hostname some-device.home.arpa +</span></span><span class="line"><span class="cl"><span class="err"> +</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="gp">#</span> check hostname +</span></span><span class="line"><span class="cl"><span class="gp">$</span> hostnamectl status +</span></span></code></pre></div> \ No newline at end of file diff --git a/tags/hugo/atom.xml b/tags/hugo/atom.xml new file mode 100644 index 000000000..5a580e82f --- /dev/null +++ b/tags/hugo/atom.xml @@ -0,0 +1,315 @@ +HugoHugo on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/hugo/Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/2021-01-25T00:00:00+00:002023-01-06T16:40:24+01:00To use a serviceworker to cache a Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/javascript"]
+  suffixes = ["js"]
+[outputFormats.ServiceWorker]
+  name = "ServiceWorker"
+  mediaType = "application/javascript"
+  baseName = "serviceworker"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.serviceworker.js with the following content:

+
const CACHE = 'cache-and-update';
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(precache());
+});
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(fromCache(event.request));
+  event.waitUntil(update(event.request));
+});
+
+const precache = async () => {
+    const cache = await caches.open(CACHE);
+    return await cache.addAll([
+        {{ range $i, $e := .Site.RegularPages }}
+        '{{ $.RelPermalink }}'{{ if $i }}, {{ end }}
+        {{ end }}
+    ]);
+}
+
+const fromCache = async (request) => {
+    const cache = await caches.open(CACHE);
+    const match = await cache.match(request);
+    return match || Promise.reject('no-match');
+}
+
+const update = async (request) => {
+    const cache = await caches.open(CACHE);
+    const response = await fetch(request);
+    return await cache.put(request, response);
+}
+
+ +]]>
Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/2021-01-11T00:00:00+00:002023-01-06T16:40:24+01:00To publish a web app manifest document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/manifest+json"]
+  suffixes = ["webmanifest"]
+[outputFormats.Webmanifest]
+  name = "Web App Manifest"
+  mediaType = "application/manifest+json"
+  baseName = "manifest"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.manifest.json with the following content:

+
{
+  "name": "{{ .Site.Title }}",
+  "short_name": "{{ .Site.Title }}",
+  "start_url": ".",
+  "display": "minimal-ui",
+  "background_color": "#fff",
+  "description": "{{ .Site.Params.description }}"
+}
+
+ +]]>
Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/2020-12-28T00:00:00+00:002023-01-06T15:27:21+01:00Hugo allows bundling of assets with several built-in functions:

+
{{ $normalize := resources.Get "/css/normalize.css" }}
+{{ $font := resources.Get "/css/font.css" }}
+{{ $header := resources.Get "/css/header.css" }}
+{{ $footer := resources.Get "/css/footer.css" }}
+{{ $navigation := resources.Get "/css/navigation.css" }}
+{{ $navigation_mobile := resources.Get "/css/navigation-mobile.css" }}
+{{ $layout := resources.Get "/css/layout.css" }}
+{{ $layout_mobile := resources.Get "/css/layout-mobile.css" }}
+{{ $syntax := resources.Get "/css/syntax.css" }}
+{{ $darkmode := resources.Get "/css/darkmode.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat "css/base.css" | resources.Minify | resources.Fingerprint "sha512" }}
+{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat "css/mobile.css" | resources.Minify | resources.Fingerprint "sha512" }}
+
+<link href="{{ $base.Permalink }}" integrity="{{ $base.Data.Integrity }}" media="screen" rel="stylesheet">
+<link href="{{ $mobile.Permalink }}" integrity="{{ $mobile.Data.Integrity }}" media="screen and (max-width: 800px)" rel="stylesheet">
+
+<link href="{{ $darkmode.Permalink }}" integrity="{{ $darkmode.Data.Integrity }}" media="screen and (prefers-color-scheme: dark)" rel="stylesheet">
+
]]>
humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/2020-12-14T00:00:00+00:002023-01-06T16:40:24+01:00To publish a humans.txt document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."text/plain"]
+  suffixes = ["txt"]
+[outputFormats.Humans]
+  name = "Humans"
+  mediaType = "text/plain"
+  baseName = "humans"
+  isPlainText = true
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.humans.txt with the following content:

+
/* TEAM */
+{{ range $.Site.Data.contributors }}
+{{ .title }}: {{ .first_name }} {{ .last_name }}
+Site: {{ .website }}
+{{ end }}
+
]]>
FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/2020-11-30T00:00:00+00:002023-01-06T16:40:24+01:00To publish a FOAF document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/rdf+xml"]
+  suffixes = ["rdf"]
+[outputFormats.Foaf]
+  name = "FOAF"
+  mediaType = "application/rdf+xml"
+  baseName = "foaf"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.foaf.rdf with the following content:

+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/">
+  <foaf:PersonalProfileDocument rdf:about="">
+    <foaf:maker rdf:resource="#me" />
+    <foaf:primaryTopic rdf:resource="{{ .Site.Title }}" />
+  </foaf:PersonalProfileDocument>
+
+  <foaf:Project rdf:ID="{{ .Site.Title }}">
+    <foaf:name>{{ .Site.Title }}</foaf:name>
+    <foaf:homepage rdf:resource="{{ .Site.BaseURL }}" />
+  </foaf:Project>
+
+  {{ range $.Site.Data.contributors }}
+  <foaf:Person rdf:ID="{{ .id }}">
+    <foaf:name>{{ .first_name }} {{ .last_name }}</foaf:name>
+    <foaf:title>{{ .title }}</foaf:title>
+    <foaf:givenname>{{ .first_name }}</foaf:givenname>
+    <foaf:family_name>{{ .last_name }}</foaf:family_name>
+    <foaf:mbox rdf:resource="mailto:{{ .email }}" />
+    <foaf:homepage rdf:resource="{{ .website }}" />
+  </foaf:Person>
+  {{ end }}
+</rdf:RDF>
+
]]>
Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/2020-11-16T00:00:00+00:002023-01-06T16:40:24+01:00To publish Atom feeds for your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/atom+xml"]
+  suffixes = ["xml"]
+[outputFormats.Atom]
+  name = "Atom"
+  mediaType = "application/atom+xml"
+  baseName = "atom"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/list.atom.xml with the following content:

+
{{ printf `<?xml version="1.0" encoding="utf-8"?>` | safeHTML }}
+<feed xmlns="http://www.w3.org/2005/Atom"{{ with site.LanguageCode }} xml:lang="{{ . }}"{{ end }}>
+    <generator uri="https://gohugo.io/" version="{{ hugo.Version }}">Hugo</generator>
+    {{- $title := site.Title -}}
+    {{- with .Title -}}
+        {{- if (not (eq . site.Title)) -}}
+            {{- $title = printf `%s %s %s` . (i18n "feed_title_on" | default "on") site.Title -}}
+        {{- end -}}
+    {{- end -}}
+    {{- if .IsTranslated -}}
+        {{ $title = printf "%s (%s)" $title (index site.Data.i18n.languages .Lang) }}
+    {{- end -}}
+    {{ printf `<title type="html"><![CDATA[%s]]></title>` $title | safeHTML }}
+    {{ with (or (.Param "subtitle") (.Param "tagline")) }}
+        {{ printf `<subtitle type="html"><![CDATA[%s]]></subtitle>` . | safeHTML }}
+    {{ end }}
+    {{ $output_formats := .OutputFormats }}
+    {{ range $output_formats -}}
+        {{- $rel := (or (and (eq "atom" (.Name | lower)) "self") "alternate") -}}
+        {{ with $output_formats.Get .Name }}
+            {{ printf `<link href=%q rel=%q type=%q title=%q />` .Permalink $rel .MediaType.Type .Name | safeHTML }}
+        {{- end -}}
+    {{- end }}
+    {{- range .Translations }}
+        {{ $output_formats := .OutputFormats }}
+        {{- $lang := .Lang }}
+        {{- $langstr := index site.Data.i18n.languages .Lang }}
+        {{ range $output_formats -}}
+            {{ with $output_formats.Get .Name }}
+                {{ printf `<link href=%q rel="alternate" type=%q hreflang=%q title="[%s] %s" />` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }}
+            {{- end -}}
+        {{- end }}
+    {{- end }}
+    <updated>{{ now.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+    {{ with site.Copyright }}
+        {{- $copyright := replace . "{year}" now.Year -}} {{/* In case the site.copyright uses a special string "{year}" */}}
+        {{- $copyright = replace $copyright "&copy;" "©" -}}
+        <rights>{{ $copyright | plainify }}</rights>
+    {{- end }}
+    {{ with .Param "feed" }}
+        {{/* For this to work, the $icon file should be present in the assets/ directory */}}
+        {{- $icon := .icon | default "icon.svg" -}}
+        {{- with resources.Get $icon -}}
+            <icon>{{ (. | fingerprint).Permalink }}</icon>
+        {{- end }}
+
+        {{/* For this to work, the $logo file should be present in the assets/ directory */}}
+        {{- $logo := .logo | default "logo.svg" -}}
+        {{- with resources.Get $logo -}}
+            <logo>{{ (. | fingerprint).Permalink }}</logo>
+        {{- end }}
+    {{ end }}
+    {{ with site.Author.name -}}
+        <author>
+            <name>{{ . }}</name>
+            {{ with site.Author.email }}
+                <email>{{ . }}</email>
+            {{ end -}}
+        </author>
+    {{- end }}
+    {{ with site.Params.id }}
+        <id>{{ . | plainify }}</id>
+    {{ else }}
+        <id>{{ .Permalink }}</id>
+    {{ end }}
+    {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }}
+    {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}}
+    {{/* Range through only the pages with a Type in $feed_sections. */}}
+    {{- $pages := where .RegularPages "Type" "in" $feed_sections -}}
+    {{- if (eq .Kind "home") -}}
+        {{- $pages = where site.RegularPages "Type" "in" $feed_sections -}}
+    {{- end -}}
+    {{/* Remove the pages that have the disable_feed parameter set to true. */}}
+    {{- $pages = where $pages ".Params.disable_feed" "!=" true -}}
+    {{- range first $limit $pages }}
+        {{ $page := . }}
+        <entry>
+            {{ printf `<title type="html"><![CDATA[%s]]></title>` .Title | safeHTML }}
+            <link href="{{ .Permalink }}?utm_source=atom_feed" rel="alternate" type="text/html" />
+            {{- range .Translations }}
+                {{- $link := printf "%s?utm_source=atom_feed" .Permalink | safeHTML }}
+                {{- printf `<link href=%q rel="alternate" type="text/html" hreflang=%q />` $link .Lang | safeHTML }}
+            {{- end }}
+            {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}}
+            {{- range first 5 (site.RegularPages.Related .) }}
+                <link href="{{ .Permalink }}?utm_source=atom_feed" rel="related" type="text/html" title="{{ .Title }}" />
+            {{- end }}
+            {{ with .Params.id }}
+                <id>{{ . | plainify }}</id>
+            {{ else }}
+                <id>{{ .Permalink }}</id>
+            {{ end }}
+            {{ with .Params.author -}}
+                {{- range . -}} <!-- Assuming the author front-matter to be a list -->
+                    <author>
+                        <name>{{ . }}</name>
+                    </author>
+                {{- end -}}
+            {{- end }}
+            <published>{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</published>
+            <updated>{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
+            {{ $description1 := .Description | default "" }}
+            {{ $description := (cond (eq "" $description1) "" (printf "<blockquote>%s</blockquote>" ($description1 | markdownify))) }}
+            {{ printf `<content type="html"><![CDATA[%s%s]]></content>` $description .Content | safeHTML }}
+            {{ with site.Taxonomies }}
+                {{ range $taxo,$_ := . }} <!-- Defaults taxos: "tags", "categories" -->
+                    {{ with $page.Param $taxo }}
+                        {{ $taxo_list := . }} <!-- $taxo_list will be the tags/categories list -->
+                        {{ with site.GetPage (printf "/%s" $taxo) }}
+                            {{ $taxonomy_page := . }}
+                            {{ range $taxo_list }} <!-- Below, assuming pretty URLs -->
+                                <category scheme="{{ printf "%s%s" $taxonomy_page.Permalink (. | urlize) }}" term="{{ (. | urlize) }}" label="{{ . }}" />
+                            {{ end }}
+                        {{ end }}
+                    {{ end }}
+                {{ end }}
+            {{ end }}
+        </entry>
+    {{ end }}
+</feed>
+
]]>
Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/2020-09-21T00:00:00+00:002023-01-06T15:27:21+01:00The peaceiris/actions-gh-pages action allows to publish a Hugo site in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Deploy Website
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: <PUBLISH_DIR>
+          force_orphan: true
+          cname: <CNAME>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <PUBLISH_DIR>: The file system location of the built site.
  • +
  • <CNAME>: The CNAME of your custom domain.
  • +
+]]>
Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/2020-08-24T00:00:00+00:002023-01-06T15:27:21+01:00The actions-hugo action allows to use a specific Hugo version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Setup hugo
+        uses: peaceiris/actions-hugo@v2
+        with:
+          hugo-version: <HUGO_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <HUGO_VERSION>: The released versions or use latest to always use the latest version of Hugo.
  • +
+]]>
\ No newline at end of file diff --git a/tags/hugo/index.html b/tags/hugo/index.html new file mode 100644 index 000000000..c5399eb04 --- /dev/null +++ b/tags/hugo/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Hugo

Tag: Hugo

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/hugo/index.xml b/tags/hugo/index.xml new file mode 100644 index 000000000..fc7e6c7f3 --- /dev/null +++ b/tags/hugo/index.xml @@ -0,0 +1,311 @@ +Hugo on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/hugo/Recent content in Hugo on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/Mon, 25 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-serviceworkers/<p>To use a <a href="https://serviceworke.rs/">serviceworker</a> to cache a <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/javascript&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;js&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">ServiceWorker</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;ServiceWorker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/javascript&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;serviceworker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.serviceworker.js</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">const CACHE = &#39;cache-and-update&#39;; + +self.addEventListener(&#39;install&#39;, (event) =&gt; { + event.waitUntil(precache()); +}); + +self.addEventListener(&#39;fetch&#39;, (event) =&gt; { + event.respondWith(fromCache(event.request)); + event.waitUntil(update(event.request)); +}); + +const precache = async () =&gt; { + const cache = await caches.open(CACHE); + return await cache.addAll([ + {{ range $i, $e := .Site.RegularPages }} + &#39;{{ $.RelPermalink }}&#39;{{ if $i }}, {{ end }} + {{ end }} + ]); +} + +const fromCache = async (request) =&gt; { + const cache = await caches.open(CACHE); + const match = await cache.match(request); + return match || Promise.reject(&#39;no-match&#39;); +} + +const update = async (request) =&gt; { + const cache = await caches.open(CACHE); + const response = await fetch(request); + return await cache.put(request, response); +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://github.com/gohugoio/hugo/issues/5495">https://github.com/gohugoio/hugo/issues/5495</a></li> +<li><a href="https://github.com/wildhaber/offline-first-sw">https://github.com/wildhaber/offline-first-sw</a></li> +<li><a href="https://gohugohq.com/howto/go-offline-with-service-worker/">https://gohugohq.com/howto/go-offline-with-service-worker/</a></li> +</ul>Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/Mon, 11 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-webmanifest/<p>To publish a <a href="https://developer.mozilla.org/en-US/docs/Web/Manifest">web app manifest</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/manifest+json&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;webmanifest&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Webmanifest</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Web App Manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/manifest+json&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.manifest.json</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{ + &#34;name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;short_name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;start_url&#34;: &#34;.&#34;, + &#34;display&#34;: &#34;minimal-ui&#34;, + &#34;background_color&#34;: &#34;#fff&#34;, + &#34;description&#34;: &#34;{{ .Site.Params.description }}&#34; +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://web.dev/add-manifest/">https://web.dev/add-manifest/</a></li> +</ul>Bundling with Hugohttps://seb.xn--ho-hia.de/posts/hugo-bundles/Mon, 28 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-bundles/<p><a href="https://gohugo.io/">Hugo</a> allows <a href="https://gohugo.io/hugo-pipes/bundling/">bundling</a> of assets with several built-in functions:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ $normalize := resources.Get &#34;/css/normalize.css&#34; }} +{{ $font := resources.Get &#34;/css/font.css&#34; }} +{{ $header := resources.Get &#34;/css/header.css&#34; }} +{{ $footer := resources.Get &#34;/css/footer.css&#34; }} +{{ $navigation := resources.Get &#34;/css/navigation.css&#34; }} +{{ $navigation_mobile := resources.Get &#34;/css/navigation-mobile.css&#34; }} +{{ $layout := resources.Get &#34;/css/layout.css&#34; }} +{{ $layout_mobile := resources.Get &#34;/css/layout-mobile.css&#34; }} +{{ $syntax := resources.Get &#34;/css/syntax.css&#34; }} +{{ $darkmode := resources.Get &#34;/css/darkmode.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +{{ $base := slice $normalize $font $header $footer $navigation $layout $syntax | resources.Concat &#34;css/base.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} +{{ $mobile := slice $navigation_mobile $layout_mobile | resources.Concat &#34;css/mobile.css&#34; | resources.Minify | resources.Fingerprint &#34;sha512&#34; }} + +&lt;link href=&#34;{{ $base.Permalink }}&#34; integrity=&#34;{{ $base.Data.Integrity }}&#34; media=&#34;screen&#34; rel=&#34;stylesheet&#34;&gt; +&lt;link href=&#34;{{ $mobile.Permalink }}&#34; integrity=&#34;{{ $mobile.Data.Integrity }}&#34; media=&#34;screen and (max-width: 800px)&#34; rel=&#34;stylesheet&#34;&gt; + +&lt;link href=&#34;{{ $darkmode.Permalink }}&#34; integrity=&#34;{{ $darkmode.Data.Integrity }}&#34; media=&#34;screen and (prefers-color-scheme: dark)&#34; rel=&#34;stylesheet&#34;&gt; +</code></pre>humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/Mon, 14 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-humans/<p>To publish a <a href="http://humanstxt.org/">humans.txt</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;text/plain&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;txt&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Humans</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;text/plain&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.humans.txt</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">/* TEAM */ +{{ range $.Site.Data.contributors }} +{{ .title }}: {{ .first_name }} {{ .last_name }} +Site: {{ .website }} +{{ end }} +</code></pre>FOAF with Hugohttps://seb.xn--ho-hia.de/posts/hugo-foaf/Mon, 30 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-foaf/<p>To publish a <a href="http://www.foaf-project.org/">FOAF</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/rdf+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;rdf&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Foaf</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;FOAF&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/rdf+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;foaf&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.foaf.rdf</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">&lt;rdf:RDF xmlns:rdf=&#34;http://www.w3.org/1999/02/22-rdf-syntax-ns#&#34; xmlns:rdfs=&#34;http://www.w3.org/2000/01/rdf-schema#&#34; xmlns:foaf=&#34;http://xmlns.com/foaf/0.1/&#34;&gt; + &lt;foaf:PersonalProfileDocument rdf:about=&#34;&#34;&gt; + &lt;foaf:maker rdf:resource=&#34;#me&#34; /&gt; + &lt;foaf:primaryTopic rdf:resource=&#34;{{ .Site.Title }}&#34; /&gt; + &lt;/foaf:PersonalProfileDocument&gt; + + &lt;foaf:Project rdf:ID=&#34;{{ .Site.Title }}&#34;&gt; + &lt;foaf:name&gt;{{ .Site.Title }}&lt;/foaf:name&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .Site.BaseURL }}&#34; /&gt; + &lt;/foaf:Project&gt; + + {{ range $.Site.Data.contributors }} + &lt;foaf:Person rdf:ID=&#34;{{ .id }}&#34;&gt; + &lt;foaf:name&gt;{{ .first_name }} {{ .last_name }}&lt;/foaf:name&gt; + &lt;foaf:title&gt;{{ .title }}&lt;/foaf:title&gt; + &lt;foaf:givenname&gt;{{ .first_name }}&lt;/foaf:givenname&gt; + &lt;foaf:family_name&gt;{{ .last_name }}&lt;/foaf:family_name&gt; + &lt;foaf:mbox rdf:resource=&#34;mailto:{{ .email }}&#34; /&gt; + &lt;foaf:homepage rdf:resource=&#34;{{ .website }}&#34; /&gt; + &lt;/foaf:Person&gt; + {{ end }} +&lt;/rdf:RDF&gt; +</code></pre>Atom Feed with Hugohttps://seb.xn--ho-hia.de/posts/hugo-atom/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-atom/<p>To publish Atom feeds for your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/atom+xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;xml&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Atom</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/atom+xml&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;atom&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/list.atom.xml</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{{ printf `&lt;?xml version=&#34;1.0&#34; encoding=&#34;utf-8&#34;?&gt;` | safeHTML }} +&lt;feed xmlns=&#34;http://www.w3.org/2005/Atom&#34;{{ with site.LanguageCode }} xml:lang=&#34;{{ . }}&#34;{{ end }}&gt; + &lt;generator uri=&#34;https://gohugo.io/&#34; version=&#34;{{ hugo.Version }}&#34;&gt;Hugo&lt;/generator&gt; + {{- $title := site.Title -}} + {{- with .Title -}} + {{- if (not (eq . site.Title)) -}} + {{- $title = printf `%s %s %s` . (i18n &#34;feed_title_on&#34; | default &#34;on&#34;) site.Title -}} + {{- end -}} + {{- end -}} + {{- if .IsTranslated -}} + {{ $title = printf &#34;%s (%s)&#34; $title (index site.Data.i18n.languages .Lang) }} + {{- end -}} + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` $title | safeHTML }} + {{ with (or (.Param &#34;subtitle&#34;) (.Param &#34;tagline&#34;)) }} + {{ printf `&lt;subtitle type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/subtitle&gt;` . | safeHTML }} + {{ end }} + {{ $output_formats := .OutputFormats }} + {{ range $output_formats -}} + {{- $rel := (or (and (eq &#34;atom&#34; (.Name | lower)) &#34;self&#34;) &#34;alternate&#34;) -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=%q type=%q title=%q /&gt;` .Permalink $rel .MediaType.Type .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- range .Translations }} + {{ $output_formats := .OutputFormats }} + {{- $lang := .Lang }} + {{- $langstr := index site.Data.i18n.languages .Lang }} + {{ range $output_formats -}} + {{ with $output_formats.Get .Name }} + {{ printf `&lt;link href=%q rel=&#34;alternate&#34; type=%q hreflang=%q title=&#34;[%s] %s&#34; /&gt;` .Permalink .MediaType.Type $lang $langstr .Name | safeHTML }} + {{- end -}} + {{- end }} + {{- end }} + &lt;updated&gt;{{ now.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ with site.Copyright }} + {{- $copyright := replace . &#34;{year}&#34; now.Year -}} {{/* In case the site.copyright uses a special string &#34;{year}&#34; */}} + {{- $copyright = replace $copyright &#34;&amp;copy;&#34; &#34;©&#34; -}} + &lt;rights&gt;{{ $copyright | plainify }}&lt;/rights&gt; + {{- end }} + {{ with .Param &#34;feed&#34; }} + {{/* For this to work, the $icon file should be present in the assets/ directory */}} + {{- $icon := .icon | default &#34;icon.svg&#34; -}} + {{- with resources.Get $icon -}} + &lt;icon&gt;{{ (. | fingerprint).Permalink }}&lt;/icon&gt; + {{- end }} + + {{/* For this to work, the $logo file should be present in the assets/ directory */}} + {{- $logo := .logo | default &#34;logo.svg&#34; -}} + {{- with resources.Get $logo -}} + &lt;logo&gt;{{ (. | fingerprint).Permalink }}&lt;/logo&gt; + {{- end }} + {{ end }} + {{ with site.Author.name -}} + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + {{ with site.Author.email }} + &lt;email&gt;{{ . }}&lt;/email&gt; + {{ end -}} + &lt;/author&gt; + {{- end }} + {{ with site.Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) }} + {{- $feed_sections := site.Params.feedSections | default site.Params.mainSections -}} + {{/* Range through only the pages with a Type in $feed_sections. */}} + {{- $pages := where .RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- if (eq .Kind &#34;home&#34;) -}} + {{- $pages = where site.RegularPages &#34;Type&#34; &#34;in&#34; $feed_sections -}} + {{- end -}} + {{/* Remove the pages that have the disable_feed parameter set to true. */}} + {{- $pages = where $pages &#34;.Params.disable_feed&#34; &#34;!=&#34; true -}} + {{- range first $limit $pages }} + {{ $page := . }} + &lt;entry&gt; + {{ printf `&lt;title type=&#34;html&#34;&gt;&lt;![CDATA[%s]]&gt;&lt;/title&gt;` .Title | safeHTML }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;alternate&#34; type=&#34;text/html&#34; /&gt; + {{- range .Translations }} + {{- $link := printf &#34;%s?utm_source=atom_feed&#34; .Permalink | safeHTML }} + {{- printf `&lt;link href=%q rel=&#34;alternate&#34; type=&#34;text/html&#34; hreflang=%q /&gt;` $link .Lang | safeHTML }} + {{- end }} + {{/* rel=related: See https://validator.w3.org/feed/docs/atom.html#link */}} + {{- range first 5 (site.RegularPages.Related .) }} + &lt;link href=&#34;{{ .Permalink }}?utm_source=atom_feed&#34; rel=&#34;related&#34; type=&#34;text/html&#34; title=&#34;{{ .Title }}&#34; /&gt; + {{- end }} + {{ with .Params.id }} + &lt;id&gt;{{ . | plainify }}&lt;/id&gt; + {{ else }} + &lt;id&gt;{{ .Permalink }}&lt;/id&gt; + {{ end }} + {{ with .Params.author -}} + {{- range . -}} &lt;!-- Assuming the author front-matter to be a list --&gt; + &lt;author&gt; + &lt;name&gt;{{ . }}&lt;/name&gt; + &lt;/author&gt; + {{- end -}} + {{- end }} + &lt;published&gt;{{ .Date.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/published&gt; + &lt;updated&gt;{{ .Lastmod.Format &#34;2006-01-02T15:04:05-07:00&#34; | safeHTML }}&lt;/updated&gt; + {{ $description1 := .Description | default &#34;&#34; }} + {{ $description := (cond (eq &#34;&#34; $description1) &#34;&#34; (printf &#34;&lt;blockquote&gt;%s&lt;/blockquote&gt;&#34; ($description1 | markdownify))) }} + {{ printf `&lt;content type=&#34;html&#34;&gt;&lt;![CDATA[%s%s]]&gt;&lt;/content&gt;` $description .Content | safeHTML }} + {{ with site.Taxonomies }} + {{ range $taxo,$_ := . }} &lt;!-- Defaults taxos: &#34;tags&#34;, &#34;categories&#34; --&gt; + {{ with $page.Param $taxo }} + {{ $taxo_list := . }} &lt;!-- $taxo_list will be the tags/categories list --&gt; + {{ with site.GetPage (printf &#34;/%s&#34; $taxo) }} + {{ $taxonomy_page := . }} + {{ range $taxo_list }} &lt;!-- Below, assuming pretty URLs --&gt; + &lt;category scheme=&#34;{{ printf &#34;%s%s&#34; $taxonomy_page.Permalink (. | urlize) }}&#34; term=&#34;{{ (. | urlize) }}&#34; label=&#34;{{ . }}&#34; /&gt; + {{ end }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + &lt;/entry&gt; + {{ end }} +&lt;/feed&gt; +</code></pre>Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/Mon, 21 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/<p>The <a href="https://github.com/peaceiris/actions-gh-pages">peaceiris/actions-gh-pages</a> action allows to publish a <a href="https://gohugo.io/">Hugo</a> site in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Website</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-gh-pages@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">github_token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">publish_dir</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PUBLISH_DIR&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">force_orphan</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">cname</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;CNAME&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;PUBLISH_DIR&gt;</code>: The file system location of the built site.</li> +<li><code>&lt;CNAME&gt;</code>: The <code>CNAME</code> of your custom domain.</li> +</ul>Use a specific Hugo version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/Mon, 24 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-hugo-version/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions-hugo</a> action allows to use a specific <a href="https://gohugo.io/">Hugo</a> version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup hugo</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-hugo@v2</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">hugo-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;HUGO_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;HUGO_VERSION&gt;</code>: The <a href="https://github.com/gohugoio/hugo/releases">released versions</a> or use <code>latest</code> to always use the latest version of Hugo.</li> +</ul> \ No newline at end of file diff --git a/tags/humans.txt/atom.xml b/tags/humans.txt/atom.xml new file mode 100644 index 000000000..cf4aec5d2 --- /dev/null +++ b/tags/humans.txt/atom.xml @@ -0,0 +1,19 @@ +HugoHumans.txt on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/humans.txt/humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/2020-12-14T00:00:00+00:002023-01-06T16:40:24+01:00To publish a humans.txt document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."text/plain"]
+  suffixes = ["txt"]
+[outputFormats.Humans]
+  name = "Humans"
+  mediaType = "text/plain"
+  baseName = "humans"
+  isPlainText = true
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.humans.txt with the following content:

+
/* TEAM */
+{{ range $.Site.Data.contributors }}
+{{ .title }}: {{ .first_name }} {{ .last_name }}
+Site: {{ .website }}
+{{ end }}
+
]]>
\ No newline at end of file diff --git a/tags/humans.txt/index.html b/tags/humans.txt/index.html new file mode 100644 index 000000000..afba6c5fe --- /dev/null +++ b/tags/humans.txt/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Humans.txt

Tag: Humans.txt

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/humans.txt/index.xml b/tags/humans.txt/index.xml new file mode 100644 index 000000000..1e172d089 --- /dev/null +++ b/tags/humans.txt/index.xml @@ -0,0 +1,19 @@ +Humans.txt on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/humans.txt/Recent content in Humans.txt on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100humans.txt with Hugohttps://seb.xn--ho-hia.de/posts/hugo-humans/Mon, 14 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-humans/<p>To publish a <a href="http://humanstxt.org/">humans.txt</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;text/plain&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;txt&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Humans</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;text/plain&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;humans&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.humans.txt</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">/* TEAM */ +{{ range $.Site.Data.contributors }} +{{ .title }}: {{ .first_name }} {{ .last_name }} +Site: {{ .website }} +{{ end }} +</code></pre> \ No newline at end of file diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 000000000..48f60f094 --- /dev/null +++ b/tags/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Tags

Tags

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/index.xml b/tags/index.xml new file mode 100644 index 000000000..d88da410f --- /dev/null +++ b/tags/index.xml @@ -0,0 +1 @@ +Tags on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/Recent content in Tags on Sebastian HoßHugoenMon, 09 Jan 2023 07:05:35 +0100Javahttps://seb.xn--ho-hia.de/tags/java/Mon, 09 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/tags/java/Jspecifyhttps://seb.xn--ho-hia.de/tags/jspecify/Mon, 09 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/tags/jspecify/Nullnesshttps://seb.xn--ho-hia.de/tags/nullness/Mon, 09 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/tags/nullness/Agehttps://seb.xn--ho-hia.de/tags/age/Sun, 08 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/tags/age/Chezmoihttps://seb.xn--ho-hia.de/tags/chezmoi/Sun, 08 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/tags/chezmoi/Dotfileshttps://seb.xn--ho-hia.de/tags/dotfiles/Sun, 08 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/tags/dotfiles/Encryptionhttps://seb.xn--ho-hia.de/tags/encryption/Sun, 08 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/tags/encryption/Confighttps://seb.xn--ho-hia.de/tags/config/Thu, 05 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/tags/config/Githttps://seb.xn--ho-hia.de/tags/git/Thu, 05 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/tags/git/Clipboardhttps://seb.xn--ho-hia.de/tags/clipboard/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/clipboard/Fuzzyhttps://seb.xn--ho-hia.de/tags/fuzzy/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/fuzzy/Passagehttps://seb.xn--ho-hia.de/tags/passage/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/passage/Searchhttps://seb.xn--ho-hia.de/tags/search/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/search/Automationhttps://seb.xn--ho-hia.de/tags/automation/Mon, 26 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/automation/Performancehttps://seb.xn--ho-hia.de/tags/performance/Mon, 12 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/performance/Shellhttps://seb.xn--ho-hia.de/tags/shell/Mon, 12 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/shell/Awshttps://seb.xn--ho-hia.de/tags/aws/Mon, 14 Feb 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/aws/Azurehttps://seb.xn--ho-hia.de/tags/azure/Mon, 14 Feb 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/azure/Fzfhttps://seb.xn--ho-hia.de/tags/fzf/Mon, 14 Feb 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/fzf/Clojurehttps://seb.xn--ho-hia.de/tags/clojure/Sat, 29 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/clojure/Interoperabilityhttps://seb.xn--ho-hia.de/tags/interoperability/Sat, 29 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/interoperability/Breakpointshttps://seb.xn--ho-hia.de/tags/breakpoints/Sat, 15 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/breakpoints/Reacthttps://seb.xn--ho-hia.de/tags/react/Sat, 15 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/react/Renderinghttps://seb.xn--ho-hia.de/tags/rendering/Sat, 15 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/rendering/Hostnameshttps://seb.xn--ho-hia.de/tags/hostnames/Sat, 08 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/hostnames/Rfchttps://seb.xn--ho-hia.de/tags/rfc/Sat, 08 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/rfc/Neovimhttps://seb.xn--ho-hia.de/tags/neovim/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/neovim/Pluginshttps://seb.xn--ho-hia.de/tags/plugins/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/plugins/Systemdhttps://seb.xn--ho-hia.de/tags/systemd/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/systemd/Updateshttps://seb.xn--ho-hia.de/tags/updates/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/updates/Vimhttps://seb.xn--ho-hia.de/tags/vim/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/tags/vim/Tmuxhttps://seb.xn--ho-hia.de/tags/tmux/Mon, 20 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/tmux/Tmuxphttps://seb.xn--ho-hia.de/tags/tmuxp/Mon, 20 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/tmuxp/Mavenhttps://seb.xn--ho-hia.de/tags/maven/Mon, 06 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/maven/Versioninghttps://seb.xn--ho-hia.de/tags/versioning/Mon, 06 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/versioning/Githubhttps://seb.xn--ho-hia.de/tags/github/Mon, 22 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/github/Github Packageshttps://seb.xn--ho-hia.de/tags/github-packages/Mon, 22 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/github-packages/Googlehttps://seb.xn--ho-hia.de/tags/google/Mon, 08 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/google/Repositoryhttps://seb.xn--ho-hia.de/tags/repository/Mon, 08 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/repository/Backuphttps://seb.xn--ho-hia.de/tags/backup/Mon, 25 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/backup/Emacshttps://seb.xn--ho-hia.de/tags/emacs/Mon, 25 Oct 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/emacs/Gpghttps://seb.xn--ho-hia.de/tags/gpg/Mon, 20 Sep 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/gpg/Swaywmhttps://seb.xn--ho-hia.de/tags/swaywm/Mon, 23 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/swaywm/Waybarhttps://seb.xn--ho-hia.de/tags/waybar/Mon, 23 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/waybar/Status Barhttps://seb.xn--ho-hia.de/tags/status-bar/Mon, 09 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/status-bar/Mirrorhttps://seb.xn--ho-hia.de/tags/mirror/Mon, 12 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/mirror/Pushhttps://seb.xn--ho-hia.de/tags/push/Mon, 12 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/push/Kubectlhttps://seb.xn--ho-hia.de/tags/kubectl/Mon, 14 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/kubectl/Kuberneteshttps://seb.xn--ho-hia.de/tags/kubernetes/Mon, 14 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/kubernetes/Encodinghttps://seb.xn--ho-hia.de/tags/encoding/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/encoding/Linuxhttps://seb.xn--ho-hia.de/tags/linux/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/linux/Machttps://seb.xn--ho-hia.de/tags/mac/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/mac/Windowshttps://seb.xn--ho-hia.de/tags/windows/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/windows/Reproduciblehttps://seb.xn--ho-hia.de/tags/reproducible/Mon, 17 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/reproducible/Github Actionshttps://seb.xn--ho-hia.de/tags/github-actions/Mon, 03 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/github-actions/Sonarqubehttps://seb.xn--ho-hia.de/tags/sonarqube/Mon, 03 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/sonarqube/Chshhttps://seb.xn--ho-hia.de/tags/chsh/Mon, 19 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/chsh/Login Shellhttps://seb.xn--ho-hia.de/tags/login-shell/Mon, 19 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/login-shell/Peekhttps://seb.xn--ho-hia.de/tags/peek/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/peek/Screenlockhttps://seb.xn--ho-hia.de/tags/screenlock/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/screenlock/Grimhttps://seb.xn--ho-hia.de/tags/grim/Mon, 22 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/grim/Screenshothttps://seb.xn--ho-hia.de/tags/screenshot/Mon, 22 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/screenshot/Slurphttps://seb.xn--ho-hia.de/tags/slurp/Mon, 22 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/slurp/Makehttps://seb.xn--ho-hia.de/tags/make/Mon, 08 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/make/Makefilehttps://seb.xn--ho-hia.de/tags/makefile/Mon, 08 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/makefile/READMEhttps://seb.xn--ho-hia.de/tags/readme/Mon, 08 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/readme/Teamworkhttps://seb.xn--ho-hia.de/tags/teamwork/Mon, 08 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/teamwork/Schedulehttps://seb.xn--ho-hia.de/tags/schedule/Mon, 22 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/schedule/Exit Codehttps://seb.xn--ho-hia.de/tags/exit-code/Mon, 08 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/exit-code/Hugohttps://seb.xn--ho-hia.de/tags/hugo/Mon, 25 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/hugo/Service Workerhttps://seb.xn--ho-hia.de/tags/service-worker/Mon, 25 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/service-worker/Manifesthttps://seb.xn--ho-hia.de/tags/manifest/Mon, 11 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/manifest/Web Apphttps://seb.xn--ho-hia.de/tags/web-app/Mon, 11 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/tags/web-app/Assetshttps://seb.xn--ho-hia.de/tags/assets/Mon, 28 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/assets/Bundlehttps://seb.xn--ho-hia.de/tags/bundle/Mon, 28 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/bundle/Humans.txthttps://seb.xn--ho-hia.de/tags/humans.txt/Mon, 14 Dec 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/humans.txt/Foafhttps://seb.xn--ho-hia.de/tags/foaf/Mon, 30 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/foaf/Atomhttps://seb.xn--ho-hia.de/tags/atom/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/atom/Mastodonhttps://seb.xn--ho-hia.de/tags/mastodon/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/mastodon/Toothttps://seb.xn--ho-hia.de/tags/toot/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/toot/Emailhttps://seb.xn--ho-hia.de/tags/email/Mon, 02 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/email/Assethttps://seb.xn--ho-hia.de/tags/asset/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/asset/Releasehttps://seb.xn--ho-hia.de/tags/release/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/release/Uploadhttps://seb.xn--ho-hia.de/tags/upload/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/upload/Publishhttps://seb.xn--ho-hia.de/tags/publish/Mon, 21 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/publish/Cachehttps://seb.xn--ho-hia.de/tags/cache/Mon, 07 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/cache/Xdghttps://seb.xn--ho-hia.de/tags/xdg/Mon, 27 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/xdg/Bitbuckethttps://seb.xn--ho-hia.de/tags/bitbucket/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/bitbucket/Codeberghttps://seb.xn--ho-hia.de/tags/codeberg/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/codeberg/Gitlabhttps://seb.xn--ho-hia.de/tags/gitlab/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/gitlab/Repo.or.czhttps://seb.xn--ho-hia.de/tags/repo.or.cz/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/repo.or.cz/Helphttps://seb.xn--ho-hia.de/tags/help/Mon, 29 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/help/Perlhttps://seb.xn--ho-hia.de/tags/perl/Mon, 29 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/perl/Clonehttps://seb.xn--ho-hia.de/tags/clone/Mon, 15 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/clone/Sshhttps://seb.xn--ho-hia.de/tags/ssh/Mon, 15 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/ssh/Timestamphttps://seb.xn--ho-hia.de/tags/timestamp/Thu, 11 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/tags/timestamp/ \ No newline at end of file diff --git a/tags/interoperability/atom.xml b/tags/interoperability/atom.xml new file mode 100644 index 000000000..f6626749a --- /dev/null +++ b/tags/interoperability/atom.xml @@ -0,0 +1,105 @@ +HugoInteroperability on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/interoperability/Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/2022-01-29T00:00:00+00:002023-01-06T17:32:06+01:00Clojure has several forms and macros to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.

+

Using gen-class

+

Clojure code can be compiled to standard JVM bytecode using gen-class.

+

Adding static modifiers

+

Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class com.example.Computation.

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :methods [#^{:static true} [incrementRange [int] java.util.List]]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [max]
+  (increment-range max))
+

The Java wrapper has to follow the standard rules for method names. Therefore increment-range has to be renamed to incrementRange (or some similar name without the “-” in it). The “-” prefix for the Java wrapper can be configured inside the :gen-class form and will be removed once gen-class runs. The usage from Java looks like this:

+
package com.example
+
+public class ClojureJavaInteropStatic {
+
+    public static void main(String[] args) {
+        List incrementedRange = Computation.incrementRange(10);
+    }
+
+}
+

Adding generics

+

The returned list in the above code is raw because the method definition doesn’t use generics. To solve this problem declare that the generated class :implements a certain interface that exposes the desired method definition(s). You won’t be able to declare your methods as static anymore, but get a generified method for all your Java needs.

+

The Java interface:

+
package com.example
+
+public interface RangeIncrementer {
+  List<Long> incrementRange(int max);
+}
+

The changed Clojure namespace:

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :implements [com.example.RangeIncrementer]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [this max]
+  (increment-range max))
+

Finally, the generified usage from Java:

+
package com.example
+
+public class ClojureJavaInteropGenerics {
+
+    public static void main(String[] args) {
+        RangeIncrementer incrementer = new Computation();
+        List<Long> incrementedRange = incrementer.incrementRange(10);
+    }
+
+}
+

Couple of notes for this as well: First the generated class still only returns the raw type (List instead of List<Integer>). So instead of using the class, use the interface for the variable declaration (RangeIncrementer incrementer = .. instead of Computation comp = ..). The interface will return the non-raw List. Second the function definition for -incrementRange is now slightly different. It needs an additional parameter (this) which exposes the current instance to the generated class/method.

+

Returning an array of something is also possible with the following construct "[Ljava.lang.Object;". Need a 2-dim array? Just use "[[Ljava.lang.Object;" (notice the extra [) and so on. However, be aware that the method return types have to match, for example you can’t specify a return type of array if your Clojure function does not return an array. In the example above the call to map returns LazySeq which itself is a java.util.List. Therefore, the method declaration is valid, and you won’t get any ClassCastException when calling incrementRange from Java.

+

Make your life easier with macros

+

Instead of defining every Clojure function which should be exported twice (the real function + the Java wrapper), it is possible to use a macro to do that extra work automatically.

+
(require '[clojure.string :as string)
+
+(defn camel-case [input]
+  (let [words (string/split input #"[\s_-]+")]
+    (string/join (cons (string/lower-case (first words)) (map string/capitalize (rest words))))))
+
+(defn java-name [clojure-name]
+  (symbol (str "-" (camel-case (str clojure-name)))))
+
+(defmacro defn* [name & declarations]
+  (let [java-name (java-name name)]
+    `(do (defn ~name ~declarations)
+       (defn ~java-name ~declarations))))
+

The macro defn* replaces defn and automatically creates a second function with a valid camel-cased Java method name. The macro is available as a small library at Maven Central. The macro won’t add the extra parameter mentioned above to Java wrapper, so it is only useful for declaring static methods.

+

Using the Clojure Runtime

+

Using gen-class imposes certain limitations on calling Clojure code from Java. One of those are functions which make use of Clojure parameter destructuring. To invoke those functions you have to use the Clojure runtime.

+
// The Clojure 'require' function from the 'clojure.core' namespace.
+Var require = RT.var("clojure.core", "require");
+
+// Your namespace
+Symbol namespace = Symbol.intern("DESIRED.NAMESPACE.HERE");
+
+// Your function
+Var function = RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION");
+
+// The required keyword for the above function
+Keyword keyword = Keyword.intern("REQUIRED-KEYWORD");
+
+// Require/Import your namespace
+require.invoke(namespace);
+
+// Invoke your function with the given keyword and its value
+Object result = function.invoke(keyword, VALUE);
+

The desired namespace has to be on the classpath for this to work. Alternatively it is possible to load an entire Clojure script, as shown in the following example:

+
RT.loadResourceScript("DESIRED/NAMESPACE/HERE.clj");
+RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION").invoke(PARAMETER);
+

On a big project it is properly wise to move Java->Clojure interop code into helper classes/methods. Look here for an example.

+]]>
\ No newline at end of file diff --git a/tags/interoperability/index.html b/tags/interoperability/index.html new file mode 100644 index 000000000..1129237c6 --- /dev/null +++ b/tags/interoperability/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Interoperability

Tag: Interoperability

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/interoperability/index.xml b/tags/interoperability/index.xml new file mode 100644 index 000000000..204f61032 --- /dev/null +++ b/tags/interoperability/index.xml @@ -0,0 +1,5 @@ +Interoperability on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/interoperability/Recent content in Interoperability on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/Sat, 29 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/clojure-java-interoperability/<p>Clojure has several <a href="https://clojure.org/java_interop">forms and macros</a> to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.</p> +<h2 id="using-gen-class">Using <code>gen-class</code></h2> +<p>Clojure code can be <a href="https://clojure.org/compilation">compiled</a> to standard JVM bytecode using <a href="https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/gen-class">gen-class</a>.</p> +<h3 id="adding-static-modifiers">Adding static modifiers</h3> +<p>Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class <code>com.example.Computation</code>.</p> \ No newline at end of file diff --git a/tags/java/atom.xml b/tags/java/atom.xml new file mode 100644 index 000000000..5db46cf7b --- /dev/null +++ b/tags/java/atom.xml @@ -0,0 +1,144 @@ +HugoJava on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/java/jspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/2023-01-09T00:00:00+00:002023-01-09T07:05:35+01:00Every Java developer has probably encountered a NullPointerException at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:

+
String someName;         // value is 'null'
+
+someName.toUpperCase(); // throws NullPointerException
+

Modern IDEs have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is null and therefore a potential for a NullPointerException is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be null. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.

+

jspecify is the latest approach that tries to establish a standard. It has gained wide community support and recently celebrated their first public release (0.3.0).

+

The following snippet shows the dependency declaration for Maven projects:

+
<dependencies>
+    <dependency>
+        <groupId>org.jspecify</groupId>
+        <artifactId>jspecify</artifactId>
+        <version>0.3.0</version>
+    </dependency>
+</dependencies>
+

In case you want to declare that nothing in your module can ever be null, place the @NullMarked on your module-info.java like this:

+
@org.jspecify.annotations.NullMarked
+module your.module.here {
+
+    requires org.jspecify;
+
+    // ...
+
+}
+

The tooling support is not quiet clear yet, however if you are developing a library there is no harm in adding these annotations now and let your users enjoy their null-free life once tools have caught up.

+]]>
Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/2022-01-29T00:00:00+00:002023-01-06T17:32:06+01:00Clojure has several forms and macros to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.

+

Using gen-class

+

Clojure code can be compiled to standard JVM bytecode using gen-class.

+

Adding static modifiers

+

Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class com.example.Computation.

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :methods [#^{:static true} [incrementRange [int] java.util.List]]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [max]
+  (increment-range max))
+

The Java wrapper has to follow the standard rules for method names. Therefore increment-range has to be renamed to incrementRange (or some similar name without the “-” in it). The “-” prefix for the Java wrapper can be configured inside the :gen-class form and will be removed once gen-class runs. The usage from Java looks like this:

+
package com.example
+
+public class ClojureJavaInteropStatic {
+
+    public static void main(String[] args) {
+        List incrementedRange = Computation.incrementRange(10);
+    }
+
+}
+

Adding generics

+

The returned list in the above code is raw because the method definition doesn’t use generics. To solve this problem declare that the generated class :implements a certain interface that exposes the desired method definition(s). You won’t be able to declare your methods as static anymore, but get a generified method for all your Java needs.

+

The Java interface:

+
package com.example
+
+public interface RangeIncrementer {
+  List<Long> incrementRange(int max);
+}
+

The changed Clojure namespace:

+
(ns com.example.computation
+  (:gen-class
+    :name com.example.Computation
+    :implements [com.example.RangeIncrementer]))
+
+(defn increment-range
+  "Creates a sequence of numbers up to max and then increments them."
+  [max]
+  (map inc (take max (range))))
+
+(defn -incrementRange
+  "A Java-callable wrapper around the 'increment-range' function."
+  [this max]
+  (increment-range max))
+

Finally, the generified usage from Java:

+
package com.example
+
+public class ClojureJavaInteropGenerics {
+
+    public static void main(String[] args) {
+        RangeIncrementer incrementer = new Computation();
+        List<Long> incrementedRange = incrementer.incrementRange(10);
+    }
+
+}
+

Couple of notes for this as well: First the generated class still only returns the raw type (List instead of List<Integer>). So instead of using the class, use the interface for the variable declaration (RangeIncrementer incrementer = .. instead of Computation comp = ..). The interface will return the non-raw List. Second the function definition for -incrementRange is now slightly different. It needs an additional parameter (this) which exposes the current instance to the generated class/method.

+

Returning an array of something is also possible with the following construct "[Ljava.lang.Object;". Need a 2-dim array? Just use "[[Ljava.lang.Object;" (notice the extra [) and so on. However, be aware that the method return types have to match, for example you can’t specify a return type of array if your Clojure function does not return an array. In the example above the call to map returns LazySeq which itself is a java.util.List. Therefore, the method declaration is valid, and you won’t get any ClassCastException when calling incrementRange from Java.

+

Make your life easier with macros

+

Instead of defining every Clojure function which should be exported twice (the real function + the Java wrapper), it is possible to use a macro to do that extra work automatically.

+
(require '[clojure.string :as string)
+
+(defn camel-case [input]
+  (let [words (string/split input #"[\s_-]+")]
+    (string/join (cons (string/lower-case (first words)) (map string/capitalize (rest words))))))
+
+(defn java-name [clojure-name]
+  (symbol (str "-" (camel-case (str clojure-name)))))
+
+(defmacro defn* [name & declarations]
+  (let [java-name (java-name name)]
+    `(do (defn ~name ~declarations)
+       (defn ~java-name ~declarations))))
+

The macro defn* replaces defn and automatically creates a second function with a valid camel-cased Java method name. The macro is available as a small library at Maven Central. The macro won’t add the extra parameter mentioned above to Java wrapper, so it is only useful for declaring static methods.

+

Using the Clojure Runtime

+

Using gen-class imposes certain limitations on calling Clojure code from Java. One of those are functions which make use of Clojure parameter destructuring. To invoke those functions you have to use the Clojure runtime.

+
// The Clojure 'require' function from the 'clojure.core' namespace.
+Var require = RT.var("clojure.core", "require");
+
+// Your namespace
+Symbol namespace = Symbol.intern("DESIRED.NAMESPACE.HERE");
+
+// Your function
+Var function = RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION");
+
+// The required keyword for the above function
+Keyword keyword = Keyword.intern("REQUIRED-KEYWORD");
+
+// Require/Import your namespace
+require.invoke(namespace);
+
+// Invoke your function with the given keyword and its value
+Object result = function.invoke(keyword, VALUE);
+

The desired namespace has to be on the classpath for this to work. Alternatively it is possible to load an entire Clojure script, as shown in the following example:

+
RT.loadResourceScript("DESIRED/NAMESPACE/HERE.clj");
+RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION").invoke(PARAMETER);
+

On a big project it is properly wise to move Java->Clojure interop code into helper classes/methods. Look here for an example.

+]]>
Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/2020-08-10T00:00:00+00:002023-01-06T15:27:21+01:00The setup-java action allows to use a specific Java version in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Set up JDK <JDK_VERSION>
+        uses: actions/setup-java@v1
+        with:
+          java-version: <JDK_VERSION>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <JDK_VERSION>: The required Java version for your project.
  • +
+]]>
\ No newline at end of file diff --git a/tags/java/index.html b/tags/java/index.html new file mode 100644 index 000000000..56d0e17bd --- /dev/null +++ b/tags/java/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Java

Tag: Java

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/java/index.xml b/tags/java/index.xml new file mode 100644 index 000000000..e91b2e378 --- /dev/null +++ b/tags/java/index.xml @@ -0,0 +1,23 @@ +Java on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/java/Recent content in Java on Sebastian HoßHugoenMon, 09 Jan 2023 07:05:35 +0100jspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/Mon, 09 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/jspecify/<p>Every <a href="https://www.java.com/">Java</a> developer has probably encountered a <code>NullPointerException</code> at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="n">someName</span><span class="p">;</span><span class="w"> </span><span class="c1">// value is &#39;null&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">someName</span><span class="p">.</span><span class="na">toUpperCase</span><span class="p">();</span><span class="w"> </span><span class="c1">// throws NullPointerException</span><span class="w"> +</span></span></span></code></pre></div><p>Modern <a href="https://en.wikipedia.org/wiki/Integrated_development_environment">IDEs</a> have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is <code>null</code> and therefore a potential for a <code>NullPointerException</code> is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be <code>null</code>. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.</p>Clojure Java Interoperabilityhttps://seb.xn--ho-hia.de/posts/clojure-java-interoperability/Sat, 29 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/clojure-java-interoperability/<p>Clojure has several <a href="https://clojure.org/java_interop">forms and macros</a> to call Java code. However, calling Clojure code from Java is not always so straightforward. The following post shows the different options currently available.</p> +<h2 id="using-gen-class">Using <code>gen-class</code></h2> +<p>Clojure code can be <a href="https://clojure.org/compilation">compiled</a> to standard JVM bytecode using <a href="https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/gen-class">gen-class</a>.</p> +<h3 id="adding-static-modifiers">Adding static modifiers</h3> +<p>Clojure imposes the concept of immutability. As such Clojure functions are/should be void of any state or side effects and only operate on the given input. Therefore, exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static method in the class <code>com.example.Computation</code>.</p>Use a specific Java version with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/Mon, 10 Aug 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-specify-java-version/<p>The <a href="https://github.com/actions/setup-java">setup-java</a> action allows to use a specific Java version in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Set up JDK &lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-java@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">java-version</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;JDK_VERSION&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;JDK_VERSION&gt;</code>: The required Java version for your project.</li> +</ul> \ No newline at end of file diff --git a/tags/jspecify/atom.xml b/tags/jspecify/atom.xml new file mode 100644 index 000000000..ddde705ca --- /dev/null +++ b/tags/jspecify/atom.xml @@ -0,0 +1,25 @@ +HugoJspecify on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/jspecify/jspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/2023-01-09T00:00:00+00:002023-01-09T07:05:35+01:00Every Java developer has probably encountered a NullPointerException at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:

+
String someName;         // value is 'null'
+
+someName.toUpperCase(); // throws NullPointerException
+

Modern IDEs have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is null and therefore a potential for a NullPointerException is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be null. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.

+

jspecify is the latest approach that tries to establish a standard. It has gained wide community support and recently celebrated their first public release (0.3.0).

+

The following snippet shows the dependency declaration for Maven projects:

+
<dependencies>
+    <dependency>
+        <groupId>org.jspecify</groupId>
+        <artifactId>jspecify</artifactId>
+        <version>0.3.0</version>
+    </dependency>
+</dependencies>
+

In case you want to declare that nothing in your module can ever be null, place the @NullMarked on your module-info.java like this:

+
@org.jspecify.annotations.NullMarked
+module your.module.here {
+
+    requires org.jspecify;
+
+    // ...
+
+}
+

The tooling support is not quiet clear yet, however if you are developing a library there is no harm in adding these annotations now and let your users enjoy their null-free life once tools have caught up.

+]]>
\ No newline at end of file diff --git a/tags/jspecify/index.html b/tags/jspecify/index.html new file mode 100644 index 000000000..6dad5d79a --- /dev/null +++ b/tags/jspecify/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Jspecify

Tag: Jspecify

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/jspecify/index.xml b/tags/jspecify/index.xml new file mode 100644 index 000000000..b4a4c37b8 --- /dev/null +++ b/tags/jspecify/index.xml @@ -0,0 +1,5 @@ +Jspecify on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/jspecify/Recent content in Jspecify on Sebastian HoßHugoenMon, 09 Jan 2023 07:05:35 +0100jspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/Mon, 09 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/jspecify/<p>Every <a href="https://www.java.com/">Java</a> developer has probably encountered a <code>NullPointerException</code> at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="n">someName</span><span class="p">;</span><span class="w"> </span><span class="c1">// value is &#39;null&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">someName</span><span class="p">.</span><span class="na">toUpperCase</span><span class="p">();</span><span class="w"> </span><span class="c1">// throws NullPointerException</span><span class="w"> +</span></span></span></code></pre></div><p>Modern <a href="https://en.wikipedia.org/wiki/Integrated_development_environment">IDEs</a> have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is <code>null</code> and therefore a potential for a <code>NullPointerException</code> is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be <code>null</code>. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.</p> \ No newline at end of file diff --git a/tags/kubectl/atom.xml b/tags/kubectl/atom.xml new file mode 100644 index 000000000..2f7869aa3 --- /dev/null +++ b/tags/kubectl/atom.xml @@ -0,0 +1,6 @@ +HugoKubectl on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/kubectl/Using multiple clusters with kubectlhttps://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/2021-06-14T00:00:00+00:002023-01-06T17:32:06+01:00To connect to multiple Kubernetes clusters with kubectl, I like to define aliases like this:

+
alias rancher="kubectl --kubeconfig ~/.kube/rancher.config"
+alias work="kubectl --kubeconfig ~/.kube/work.config"
+alias customer="kubectl --kubeconfig ~/.kube/customer.config"
+

Those aliases allow me to write things like rancher get pods --namespace some-namespace without worrying the wrong context is active. Using multiple configurations - one for each cluster - seems to be easier to manage since most clusters allow to download a ready-to-use configuration file. Instead of mangling them together manually, I just specify another alias whenever I get to work with another cluster.

+]]>
\ No newline at end of file diff --git a/tags/kubectl/index.html b/tags/kubectl/index.html new file mode 100644 index 000000000..f44a49e10 --- /dev/null +++ b/tags/kubectl/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Kubectl

Tag: Kubectl

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/kubectl/index.xml b/tags/kubectl/index.xml new file mode 100644 index 000000000..f92b2b15a --- /dev/null +++ b/tags/kubectl/index.xml @@ -0,0 +1,5 @@ +Kubectl on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/kubectl/Recent content in Kubectl on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Using multiple clusters with kubectlhttps://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/Mon, 14 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/<p>To connect to multiple <a href="https://kubernetes.io/">Kubernetes</a> clusters with <a href="https://kubernetes.io/docs/reference/kubectl/overview/">kubectl</a>, I like to define aliases like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">rancher</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/rancher.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">work</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/work.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">customer</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/customer.config&#34;</span> +</span></span></code></pre></div><p>Those aliases allow me to write things like <code>rancher get pods --namespace some-namespace</code> without worrying the wrong context is active. Using multiple configurations - one for each cluster - seems to be easier to manage since most clusters allow to download a ready-to-use configuration file. Instead of mangling them together manually, I just specify another alias whenever I get to work with another cluster.</p> \ No newline at end of file diff --git a/tags/kubernetes/atom.xml b/tags/kubernetes/atom.xml new file mode 100644 index 000000000..fa9b0bc0e --- /dev/null +++ b/tags/kubernetes/atom.xml @@ -0,0 +1,6 @@ +HugoKubernetes on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/kubernetes/Using multiple clusters with kubectlhttps://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/2021-06-14T00:00:00+00:002023-01-06T17:32:06+01:00To connect to multiple Kubernetes clusters with kubectl, I like to define aliases like this:

+
alias rancher="kubectl --kubeconfig ~/.kube/rancher.config"
+alias work="kubectl --kubeconfig ~/.kube/work.config"
+alias customer="kubectl --kubeconfig ~/.kube/customer.config"
+

Those aliases allow me to write things like rancher get pods --namespace some-namespace without worrying the wrong context is active. Using multiple configurations - one for each cluster - seems to be easier to manage since most clusters allow to download a ready-to-use configuration file. Instead of mangling them together manually, I just specify another alias whenever I get to work with another cluster.

+]]>
\ No newline at end of file diff --git a/tags/kubernetes/index.html b/tags/kubernetes/index.html new file mode 100644 index 000000000..4d8e8c060 --- /dev/null +++ b/tags/kubernetes/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Kubernetes

Tag: Kubernetes

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/kubernetes/index.xml b/tags/kubernetes/index.xml new file mode 100644 index 000000000..334736b16 --- /dev/null +++ b/tags/kubernetes/index.xml @@ -0,0 +1,5 @@ +Kubernetes on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/kubernetes/Recent content in Kubernetes on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Using multiple clusters with kubectlhttps://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/Mon, 14 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/kubectl-multi-cluster/<p>To connect to multiple <a href="https://kubernetes.io/">Kubernetes</a> clusters with <a href="https://kubernetes.io/docs/reference/kubectl/overview/">kubectl</a>, I like to define aliases like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">rancher</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/rancher.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">work</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/work.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">customer</span><span class="o">=</span><span class="s2">&#34;kubectl --kubeconfig ~/.kube/customer.config&#34;</span> +</span></span></code></pre></div><p>Those aliases allow me to write things like <code>rancher get pods --namespace some-namespace</code> without worrying the wrong context is active. Using multiple configurations - one for each cluster - seems to be easier to manage since most clusters allow to download a ready-to-use configuration file. Instead of mangling them together manually, I just specify another alias whenever I get to work with another cluster.</p> \ No newline at end of file diff --git a/tags/linux/atom.xml b/tags/linux/atom.xml new file mode 100644 index 000000000..ef8deb193 --- /dev/null +++ b/tags/linux/atom.xml @@ -0,0 +1,10 @@ +HugoLinux on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/linux/Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/2021-05-31T00:00:00+00:002023-01-06T17:32:06+01:00Maven projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.

+
<properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+</properties>
+
+ +]]>
\ No newline at end of file diff --git a/tags/linux/index.html b/tags/linux/index.html new file mode 100644 index 000000000..a80a53de9 --- /dev/null +++ b/tags/linux/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Linux

Tag: Linux

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/linux/index.xml b/tags/linux/index.xml new file mode 100644 index 000000000..df1ac8b16 --- /dev/null +++ b/tags/linux/index.xml @@ -0,0 +1,9 @@ +Linux on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/linux/Recent content in Linux on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-encoding/<p><a href="https://maven.apache.org/">Maven</a> projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.reporting.outputEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.reporting.outputEncoding&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +</ul> \ No newline at end of file diff --git a/tags/login-shell/atom.xml b/tags/login-shell/atom.xml new file mode 100644 index 000000000..146aac978 --- /dev/null +++ b/tags/login-shell/atom.xml @@ -0,0 +1,17 @@ +HugoLogin Shell on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/login-shell/Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/2021-04-19T00:00:00+00:002023-01-06T16:22:24+01:00To use tmux as your login shell, use chsh:

+
# list all available shells
+$ chsh --list-shells
+/bin/sh
+/bin/bash
+/sbin/nologin
+/usr/bin/sh
+/usr/bin/bash
+/usr/sbin/nologin
+/usr/bin/zsh
+/bin/zsh
+/usr/bin/tmux
+/bin/tmux
+
+# select login shell
+$ chsh --shell /usr/bin/tmux
+
]]>
\ No newline at end of file diff --git a/tags/login-shell/index.html b/tags/login-shell/index.html new file mode 100644 index 000000000..5bd972513 --- /dev/null +++ b/tags/login-shell/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Login Shell

Tag: Login Shell

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/login-shell/index.xml b/tags/login-shell/index.xml new file mode 100644 index 000000000..d7bf5f31f --- /dev/null +++ b/tags/login-shell/index.xml @@ -0,0 +1,17 @@ +Login Shell on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/login-shell/Recent content in Login Shell on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/Mon, 19 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-login-shell/<p>To use <a href="https://github.com/tmux/tmux">tmux</a> as your login shell, use <code>chsh</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># list all available shells</span> +</span></span><span class="line"><span class="cl">$ chsh --list-shells +</span></span><span class="line"><span class="cl">/bin/sh +</span></span><span class="line"><span class="cl">/bin/bash +</span></span><span class="line"><span class="cl">/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/sh +</span></span><span class="line"><span class="cl">/usr/bin/bash +</span></span><span class="line"><span class="cl">/usr/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/zsh +</span></span><span class="line"><span class="cl">/bin/zsh +</span></span><span class="line"><span class="cl">/usr/bin/tmux +</span></span><span class="line"><span class="cl">/bin/tmux +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># select login shell</span> +</span></span><span class="line"><span class="cl">$ chsh --shell /usr/bin/tmux +</span></span></code></pre></div> \ No newline at end of file diff --git a/tags/mac/atom.xml b/tags/mac/atom.xml new file mode 100644 index 000000000..7d3c03bea --- /dev/null +++ b/tags/mac/atom.xml @@ -0,0 +1,10 @@ +HugoMac on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/mac/Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/2021-05-31T00:00:00+00:002023-01-06T17:32:06+01:00Maven projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.

+
<properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+</properties>
+
+ +]]>
\ No newline at end of file diff --git a/tags/mac/index.html b/tags/mac/index.html new file mode 100644 index 000000000..e649c75a8 --- /dev/null +++ b/tags/mac/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Mac

Tag: Mac

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/mac/index.xml b/tags/mac/index.xml new file mode 100644 index 000000000..65cd4bd62 --- /dev/null +++ b/tags/mac/index.xml @@ -0,0 +1,9 @@ +Mac on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/mac/Recent content in Mac on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-encoding/<p><a href="https://maven.apache.org/">Maven</a> projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.reporting.outputEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.reporting.outputEncoding&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +</ul> \ No newline at end of file diff --git a/tags/make/atom.xml b/tags/make/atom.xml new file mode 100644 index 000000000..763ef5d53 --- /dev/null +++ b/tags/make/atom.xml @@ -0,0 +1,82 @@ +HugoMake on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/make/Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/2021-03-08T00:00:00+00:002023-01-06T16:40:24+01:00README file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a Makefile in the root of your project which contains the exact same instructions. Thanks to make, all your contributors can now use TAB-completion to run any of the pre-defined make targets.

+

The following example is part of one of my projects, and I certainly don’t want to type (or even copy) that all the time:

+
.PHONY: release-into-local-nexus
+release-into-local-nexus:
+	mvn versions:set \
+	   -DnewVersion=$(TIMESTAMPED_VERSION) \
+	   -DgenerateBackupPoms=false
+	-mvn clean deploy scm:tag \
+	   -DpushChanges=false \
+	   -DskipLocalStaging=true \
+	   -Drelease=local
+	mvn versions:set \
+	   -DnewVersion=9999.99.99-SNAPSHOT \
+	   -DgenerateBackupPoms=false
+

With the above target in place, everyone can now do make release-into-local-nexus instead of typing/copying the commands themselves. Thanks to TAB-completion you just have to do make r<TAB> and confirm with >ENTER> to perform a release.

+ + +]]>
Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/2021-02-08T00:00:00+00:002023-01-06T16:40:24+01:00In case you are using a Makefile to define a complex build step - for example start database, run tests, stop database - consider using the - qualifier in front of your actual build step like this:

+
.PHONY: build
+build:
+	start-database
+	-build-software
+	stop-database
+

Thanks to -, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.

+ + +]]>
Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/2020-06-29T00:00:00+00:002023-01-06T16:22:24+01:00Use the following Perl snippet to automatically generate help output for your Makefile:

+
GREEN  := $(shell tput -Txterm setaf 2)
+WHITE  := $(shell tput -Txterm setaf 7)
+YELLOW := $(shell tput -Txterm setaf 3)
+RESET  := $(shell tput -Txterm sgr0)
+
+HELP_FUN = \
+    %help; \
+    while(<>) { push @{$$help{$$2 // 'targets'}}, [$$1, $$3] if /^([a-zA-Z\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
+    print "usage: make [target]\n\n"; \
+    for (sort keys %help) { \
+    print "${WHITE}$$_:${RESET}\n"; \
+    for (@{$$help{$$_}}) { \
+    $$sep = " " x (32 - length $$_->[0]); \
+    print "  ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
+    }; \
+    print "\n"; }
+

To use HELP_FUN, add the following help target to the same Makefile:

+
.DEFAULT_GOAL := help
+
+.PHONY: help
+help: ##@other Show this help
+	@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
+

Each target in the Makefile is marked as phony to signal that those targets are not actually files that are generated as part of your build process. The optional description of a target can be placed after the ##@ prefix. The first word represents the group of a target and everything that follows is the description of a target. All targets should be formatted just like the help target:

+
.PHONY: compile
+compile: ##@hacking Compile your code
+	<compile some code>
+
+.PHONY: test
+test: ##@hacking Test your code
+	<test some code>
+
+.PHONY: sign-cla
+sign-cla: ##@contrib Sign the contributor license agreement
+	<sign some file>
+

Once in place, you can either use make without any argument to call the help target or use make help to see the generated output:

+
$ make
+usage: make [target]
+
+contrib:
+  sign-cla            Sign the contributor license agreement
+
+hacking:
+  compile             Compile your code
+  test                Test your code
+
+other:
+  help                Show this help
+
]]>
\ No newline at end of file diff --git a/tags/make/index.html b/tags/make/index.html new file mode 100644 index 000000000..cfc0b3db8 --- /dev/null +++ b/tags/make/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Make

Tag: Make

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/make/index.xml b/tags/make/index.xml new file mode 100644 index 000000000..3d323cacb --- /dev/null +++ b/tags/make/index.xml @@ -0,0 +1,24 @@ +Make on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/make/Recent content in Make on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/Mon, 08 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-readme/<p><code>README</code> file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a <code>Makefile</code> in the root of your project which contains the exact same instructions. Thanks to <code>make</code>, all your contributors can now use TAB-completion to run any of the pre-defined <code>make</code> targets.</p>Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/Mon, 08 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-ignore/<p>In case you are using a <code>Makefile</code> to define a complex build step - for example start database, run tests, stop database - consider using the <code>-</code> qualifier in front of your actual build step like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">build</span> +</span></span><span class="line"><span class="cl"><span class="nf">build</span><span class="o">:</span> +</span></span><span class="line"><span class="cl"> start-database +</span></span><span class="line"><span class="cl"> -build-software +</span></span><span class="line"><span class="cl"> stop-database +</span></span></code></pre></div><p>Thanks to <code>-</code>, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.</p>Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/Mon, 29 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-help/<p>Use the following <a href="https://www.perl.org/">Perl</a> snippet to automatically generate help output for your <code>Makefile</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nv">GREEN</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 2<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">WHITE</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 7<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">YELLOW</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 3<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">RESET</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm sgr0<span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nv">HELP_FUN</span> <span class="o">=</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> %help<span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">while</span><span class="o">(</span>&lt;&gt;<span class="o">)</span> <span class="o">{</span> push @<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span><span class="m">2</span> // <span class="s1">&#39;targets&#39;</span><span class="o">}}</span>, <span class="o">[</span><span class="nv">$$</span>1, <span class="nv">$$</span>3<span class="o">]</span> <span class="k">if</span> /^<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">)</span><span class="se">\s</span>*:.*<span class="se">\#\#</span><span class="o">(</span>?:@<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">))</span>?<span class="se">\s</span><span class="o">(</span>.*<span class="o">)</span><span class="nv">$$</span>/ <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;usage: make [target]\n\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>sort keys %help<span class="o">)</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WHITE</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_:</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>@<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span>_<span class="o">}})</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="nv">$$sep</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span> x <span class="o">(</span><span class="m">32</span> - length <span class="nv">$$</span>_-&gt;<span class="o">[</span>0<span class="o">])</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34; </span><span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[0]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="nv">$$</span><span class="s2">sep</span><span class="si">${</span><span class="nv">GREEN</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[1]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;\n&#34;</span><span class="p">;</span> <span class="o">}</span> +</span></span></code></pre></div><p>To use <code>HELP_FUN</code>, add the following <code>help</code> target to the same <code>Makefile</code>:</p> \ No newline at end of file diff --git a/tags/makefile/atom.xml b/tags/makefile/atom.xml new file mode 100644 index 000000000..938b5b0a2 --- /dev/null +++ b/tags/makefile/atom.xml @@ -0,0 +1,82 @@ +HugoMakefile on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/makefile/Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/2021-03-08T00:00:00+00:002023-01-06T16:40:24+01:00README file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a Makefile in the root of your project which contains the exact same instructions. Thanks to make, all your contributors can now use TAB-completion to run any of the pre-defined make targets.

+

The following example is part of one of my projects, and I certainly don’t want to type (or even copy) that all the time:

+
.PHONY: release-into-local-nexus
+release-into-local-nexus:
+	mvn versions:set \
+	   -DnewVersion=$(TIMESTAMPED_VERSION) \
+	   -DgenerateBackupPoms=false
+	-mvn clean deploy scm:tag \
+	   -DpushChanges=false \
+	   -DskipLocalStaging=true \
+	   -Drelease=local
+	mvn versions:set \
+	   -DnewVersion=9999.99.99-SNAPSHOT \
+	   -DgenerateBackupPoms=false
+

With the above target in place, everyone can now do make release-into-local-nexus instead of typing/copying the commands themselves. Thanks to TAB-completion you just have to do make r<TAB> and confirm with >ENTER> to perform a release.

+ + +]]>
Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/2021-02-08T00:00:00+00:002023-01-06T16:40:24+01:00In case you are using a Makefile to define a complex build step - for example start database, run tests, stop database - consider using the - qualifier in front of your actual build step like this:

+
.PHONY: build
+build:
+	start-database
+	-build-software
+	stop-database
+

Thanks to -, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.

+ + +]]>
Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/2020-06-29T00:00:00+00:002023-01-06T16:22:24+01:00Use the following Perl snippet to automatically generate help output for your Makefile:

+
GREEN  := $(shell tput -Txterm setaf 2)
+WHITE  := $(shell tput -Txterm setaf 7)
+YELLOW := $(shell tput -Txterm setaf 3)
+RESET  := $(shell tput -Txterm sgr0)
+
+HELP_FUN = \
+    %help; \
+    while(<>) { push @{$$help{$$2 // 'targets'}}, [$$1, $$3] if /^([a-zA-Z\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
+    print "usage: make [target]\n\n"; \
+    for (sort keys %help) { \
+    print "${WHITE}$$_:${RESET}\n"; \
+    for (@{$$help{$$_}}) { \
+    $$sep = " " x (32 - length $$_->[0]); \
+    print "  ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
+    }; \
+    print "\n"; }
+

To use HELP_FUN, add the following help target to the same Makefile:

+
.DEFAULT_GOAL := help
+
+.PHONY: help
+help: ##@other Show this help
+	@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
+

Each target in the Makefile is marked as phony to signal that those targets are not actually files that are generated as part of your build process. The optional description of a target can be placed after the ##@ prefix. The first word represents the group of a target and everything that follows is the description of a target. All targets should be formatted just like the help target:

+
.PHONY: compile
+compile: ##@hacking Compile your code
+	<compile some code>
+
+.PHONY: test
+test: ##@hacking Test your code
+	<test some code>
+
+.PHONY: sign-cla
+sign-cla: ##@contrib Sign the contributor license agreement
+	<sign some file>
+

Once in place, you can either use make without any argument to call the help target or use make help to see the generated output:

+
$ make
+usage: make [target]
+
+contrib:
+  sign-cla            Sign the contributor license agreement
+
+hacking:
+  compile             Compile your code
+  test                Test your code
+
+other:
+  help                Show this help
+
]]>
\ No newline at end of file diff --git a/tags/makefile/index.html b/tags/makefile/index.html new file mode 100644 index 000000000..b982ce5f0 --- /dev/null +++ b/tags/makefile/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Makefile

Tag: Makefile

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/makefile/index.xml b/tags/makefile/index.xml new file mode 100644 index 000000000..160e1ff45 --- /dev/null +++ b/tags/makefile/index.xml @@ -0,0 +1,24 @@ +Makefile on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/makefile/Recent content in Makefile on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/Mon, 08 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-readme/<p><code>README</code> file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a <code>Makefile</code> in the root of your project which contains the exact same instructions. Thanks to <code>make</code>, all your contributors can now use TAB-completion to run any of the pre-defined <code>make</code> targets.</p>Ignore Exit Codeshttps://seb.xn--ho-hia.de/posts/makefile-ignore/Mon, 08 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-ignore/<p>In case you are using a <code>Makefile</code> to define a complex build step - for example start database, run tests, stop database - consider using the <code>-</code> qualifier in front of your actual build step like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">build</span> +</span></span><span class="line"><span class="cl"><span class="nf">build</span><span class="o">:</span> +</span></span><span class="line"><span class="cl"> start-database +</span></span><span class="line"><span class="cl"> -build-software +</span></span><span class="line"><span class="cl"> stop-database +</span></span></code></pre></div><p>Thanks to <code>-</code>, the database will be stopped even if building your software fails, therefore making sure to clean up after ourselves once the build finishes.</p>Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/Mon, 29 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-help/<p>Use the following <a href="https://www.perl.org/">Perl</a> snippet to automatically generate help output for your <code>Makefile</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nv">GREEN</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 2<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">WHITE</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 7<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">YELLOW</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 3<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">RESET</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm sgr0<span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nv">HELP_FUN</span> <span class="o">=</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> %help<span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">while</span><span class="o">(</span>&lt;&gt;<span class="o">)</span> <span class="o">{</span> push @<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span><span class="m">2</span> // <span class="s1">&#39;targets&#39;</span><span class="o">}}</span>, <span class="o">[</span><span class="nv">$$</span>1, <span class="nv">$$</span>3<span class="o">]</span> <span class="k">if</span> /^<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">)</span><span class="se">\s</span>*:.*<span class="se">\#\#</span><span class="o">(</span>?:@<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">))</span>?<span class="se">\s</span><span class="o">(</span>.*<span class="o">)</span><span class="nv">$$</span>/ <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;usage: make [target]\n\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>sort keys %help<span class="o">)</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WHITE</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_:</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>@<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span>_<span class="o">}})</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="nv">$$sep</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span> x <span class="o">(</span><span class="m">32</span> - length <span class="nv">$$</span>_-&gt;<span class="o">[</span>0<span class="o">])</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34; </span><span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[0]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="nv">$$</span><span class="s2">sep</span><span class="si">${</span><span class="nv">GREEN</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[1]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;\n&#34;</span><span class="p">;</span> <span class="o">}</span> +</span></span></code></pre></div><p>To use <code>HELP_FUN</code>, add the following <code>help</code> target to the same <code>Makefile</code>:</p> \ No newline at end of file diff --git a/tags/manifest/atom.xml b/tags/manifest/atom.xml new file mode 100644 index 000000000..9b6ab20fd --- /dev/null +++ b/tags/manifest/atom.xml @@ -0,0 +1,26 @@ +HugoManifest on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/manifest/Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/2021-01-11T00:00:00+00:002023-01-06T16:40:24+01:00To publish a web app manifest document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/manifest+json"]
+  suffixes = ["webmanifest"]
+[outputFormats.Webmanifest]
+  name = "Web App Manifest"
+  mediaType = "application/manifest+json"
+  baseName = "manifest"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.manifest.json with the following content:

+
{
+  "name": "{{ .Site.Title }}",
+  "short_name": "{{ .Site.Title }}",
+  "start_url": ".",
+  "display": "minimal-ui",
+  "background_color": "#fff",
+  "description": "{{ .Site.Params.description }}"
+}
+
+ +]]>
\ No newline at end of file diff --git a/tags/manifest/index.html b/tags/manifest/index.html new file mode 100644 index 000000000..1a3b29499 --- /dev/null +++ b/tags/manifest/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Manifest

Tag: Manifest

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/manifest/index.xml b/tags/manifest/index.xml new file mode 100644 index 000000000..dff4960b1 --- /dev/null +++ b/tags/manifest/index.xml @@ -0,0 +1,25 @@ +Manifest on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/manifest/Recent content in Manifest on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/Mon, 11 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-webmanifest/<p>To publish a <a href="https://developer.mozilla.org/en-US/docs/Web/Manifest">web app manifest</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/manifest+json&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;webmanifest&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Webmanifest</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Web App Manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/manifest+json&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.manifest.json</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{ + &#34;name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;short_name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;start_url&#34;: &#34;.&#34;, + &#34;display&#34;: &#34;minimal-ui&#34;, + &#34;background_color&#34;: &#34;#fff&#34;, + &#34;description&#34;: &#34;{{ .Site.Params.description }}&#34; +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://web.dev/add-manifest/">https://web.dev/add-manifest/</a></li> +</ul> \ No newline at end of file diff --git a/tags/mastodon/atom.xml b/tags/mastodon/atom.xml new file mode 100644 index 000000000..48d8561c4 --- /dev/null +++ b/tags/mastodon/atom.xml @@ -0,0 +1,18 @@ +HugoMastodon on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/mastodon/Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/2020-11-16T00:00:00+00:002023-01-06T15:27:21+01:00The rzr/fediverse-action action allows to send a toot in your GitHub Action.

+
name: <NAME>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Publish Toot
+        uses: rzr/fediverse-action@master
+        with:
+          access-token: ${{ secrets.MASTODON_TOKEN }}
+          message: <MESSAGE>
+          host: ${{ secrets.MASTODON_SERVER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <MESSAGE>: Message for the toot.
  • +
+]]>
\ No newline at end of file diff --git a/tags/mastodon/index.html b/tags/mastodon/index.html new file mode 100644 index 000000000..de08b9ea6 --- /dev/null +++ b/tags/mastodon/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Mastodon

Tag: Mastodon

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/mastodon/index.xml b/tags/mastodon/index.xml new file mode 100644 index 000000000..2d1d1f6c4 --- /dev/null +++ b/tags/mastodon/index.xml @@ -0,0 +1,17 @@ +Mastodon on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/mastodon/Recent content in Mastodon on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-toot/<p>The <a href="https://github.com/rzr/fediverse-action">rzr/fediverse-action</a> action allows to send a <a href="https://joinmastodon.org/">toot</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;NAME&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish Toot</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">rzr/fediverse-action@master</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">access-token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;MESSAGE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_SERVER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;MESSAGE&gt;</code>: Message for the toot.</li> +</ul> \ No newline at end of file diff --git a/tags/maven/atom.xml b/tags/maven/atom.xml new file mode 100644 index 000000000..4ac7f3ba9 --- /dev/null +++ b/tags/maven/atom.xml @@ -0,0 +1,147 @@ +HugoMaven on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/maven/Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/2021-12-06T00:00:00+00:002023-01-06T16:46:32+01:00To automatically version Maven projects, I like to use the m-versions-p like this:

+
$ mvn versions:set -DnewVersion=my.new.version -DgenerateBackupPoms=false
+

This will update the version property of every module in the reactor to prepare them for the next release. In case you are using GitHub Actions, consider using a timestamp.

+]]>
GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/2021-11-22T00:00:00+00:002023-01-06T15:27:21+01:00GitHub Packages can be used to host Maven packages with the following configuration in your ~/.m2/settings.xml:

+
<settings>
+  <profiles>
+    <profile>
+      <id>github</id>
+      <repositories>
+        <repository>
+          <id>maven-build-process</id>
+          <name>GitHub maven-build-process Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/maven-build-process</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+        <repository>
+          <id>hcf4j</id>
+          <name>GitHub hcf4j Apache Maven Packages</name>
+          <url>https://maven.pkg.github.com/metio/hcf4j</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+    </profile>
+  </profiles>
+  <servers>
+    <server>
+      <id>maven-build-process</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+    <server>
+      <id>hcf4j</id>
+      <username>USERNAME</username>
+      <password>GITHUB_TOKEN</password>
+    </server>
+  </servers>
+</settings>
+

You will have to add another repository/server for each project you are fetching from GitHub.

+]]>
Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/2021-11-08T00:00:00+00:002023-01-06T15:27:21+01:00Some time ago, Google started hosting a copy of Maven Central. Configure it in your ~/.m2/settings.xml like this:

+
<settings>
+  <mirrors>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (Asia)</name>
+      <url>https://maven-central-asia.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (EU)</name>
+      <url>https://maven-central-eu.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (US)</name>
+      <url>https://maven-central.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+  </mirrors>
+</settings>
+

Pick the mirror nearest to your location to get best speeds.

+]]>
Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/2021-05-31T00:00:00+00:002023-01-06T17:32:06+01:00Maven projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.

+
<properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+</properties>
+
+ +]]>
Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/2021-05-17T00:00:00+00:002023-01-06T16:22:24+01:00To create reproducible builds with Maven projects, it’s enough to specify the project.build.outputTimestamp property like this:

+
<properties>
+    <project.build.outputTimestamp>2020</project.build.outputTimestamp>
+</properties>
+
+ +]]>
Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/2021-05-03T00:00:00+00:002023-01-06T17:32:06+01:00To analyze Maven projects with SonarCloud using GitHub Actions, first create the following settings.xml file:

+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
+
+    <pluginGroups>
+        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
+    </pluginGroups>
+
+    <activeProfiles>
+        <activeProfile>sonar</activeProfile>
+    </activeProfiles>
+
+    <profiles>
+        <profile>
+            <id>sonar</id>
+            <properties>
+                <sonar.host.url>https://sonarcloud.io</sonar.host.url>
+                <sonar.organization>YOUR_ORG</sonar.organization>
+                <sonar.projectKey>YOUR_PROJECT</sonar.projectKey>
+                <sonar.login>${env.SONAR_TOKEN}</sonar.login>
+            </properties>
+        </profile>
+    </profiles>
+</settings>
+

Finally, add a step to your workflow:

+
- name: Verify Project
+  run: mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar
+  env:
+    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+
+ +]]>
Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/2020-09-07T00:00:00+00:002023-01-06T15:27:21+01:00The actions/cache action allows to cache artifacts in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Cache Maven artifacts
+        uses: actions/cache@v1
+        with:
+          path: ~/.m2/repository
+          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            ${{ runner.os }}-maven-            
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
\ No newline at end of file diff --git a/tags/maven/index.html b/tags/maven/index.html new file mode 100644 index 000000000..730f1283c --- /dev/null +++ b/tags/maven/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Maven

Tag: Maven

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/maven/index.xml b/tags/maven/index.xml new file mode 100644 index 000000000..27dff241d --- /dev/null +++ b/tags/maven/index.xml @@ -0,0 +1,140 @@ +Maven on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/maven/Recent content in Maven on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/Mon, 06 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-cd-versioning/<p>To automatically version <a href="https://maven.apache.org/">Maven</a> projects, I like to use the <a href="https://www.mojohaus.org/versions-maven-plugin/">m-versions-p</a> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ mvn versions:set -DnewVersion<span class="o">=</span>my.new.version -DgenerateBackupPoms<span class="o">=</span><span class="nb">false</span> +</span></span></code></pre></div><p>This will update the <code>version</code> property of every module in the reactor to prepare them for the next release. In case you are using <a href="https://github.com/features/actions">GitHub Actions</a>, consider using a <a href="../github-actions-create-timestamp">timestamp</a>.</p>GitHub Packages with Mavenhttps://seb.xn--ho-hia.de/posts/github-maven-packages/Mon, 22 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-maven-packages/<p><a href="https://github.com/features/packages">GitHub Packages</a> can be used to host <a href="https://maven.apache.org/">Maven</a> packages with the following configuration in your <code>~/.m2/settings.xml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>github<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub maven-build-process Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/maven-build-process<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>GitHub hcf4j Apache Maven Packages<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven.pkg.github.com/metio/hcf4j<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/releases&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/snapshots&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repositories&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;servers&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>maven-build-process<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>hcf4j<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;username&gt;</span>USERNAME<span class="nt">&lt;/username&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;password&gt;</span>GITHUB_TOKEN<span class="nt">&lt;/password&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/server&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/servers&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>You will have to add another repository/server for each project you are fetching from GitHub.</p>Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/Mon, 08 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-google-central/<p>Some time ago, <a href="https://www.google.com/">Google</a> started hosting a <a href="https://storage-download.googleapis.com/maven-central/index.html">copy</a> of <a href="https://search.maven.org/">Maven Central</a>. Configure it in your <code>~/.m2/settings.xml</code> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrors&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (Asia)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-asia.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (EU)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-eu.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (US)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirrors&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Pick the mirror nearest to your location to get best speeds.</p>Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-encoding/<p><a href="https://maven.apache.org/">Maven</a> projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.reporting.outputEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.reporting.outputEncoding&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +</ul>Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/Mon, 17 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-reproducible/<p>To create <a href="https://reproducible-builds.org/">reproducible builds</a> with <a href="https://maven.apache.org/">Maven</a> projects, it&rsquo;s enough to specify the <code>project.build.outputTimestamp</code> property like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.outputTimestamp&gt;</span>2020<span class="nt">&lt;/project.build.outputTimestamp&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +<li><a href="https://maven.apache.org/guides/mini/guide-reproducible-builds.html">https://maven.apache.org/guides/mini/guide-reproducible-builds.html</a></li> +<li><a href="https://github.com/rodiontsev/maven-build-info-plugin">https://github.com/rodiontsev/maven-build-info-plugin</a></li> +<li><a href="https://github.com/phax/ph-buildinfo-maven-plugin">https://github.com/phax/ph-buildinfo-maven-plugin</a></li> +<li><a href="https://github.com/Zlika/reproducible-build-maven-plugin">https://github.com/Zlika/reproducible-build-maven-plugin</a></li> +</ul>Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/Mon, 03 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/<p>To analyze <a href="https://maven.apache.org/">Maven</a> projects with <a href="https://sonarcloud.io">SonarCloud</a> using <a href="https://github.com/features/actions">GitHub Actions</a>, first create the following <code>settings.xml</code> file:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings</span> <span class="na">xmlns=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xmlns:xsi=</span><span class="s">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xsi:schemaLocation=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0 +</span></span></span><span class="line"><span class="cl"><span class="s"> http://maven.apache.org/xsd/settings-1.0.0.xsd&#34;</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroup&gt;</span>org.sonarsource.scanner.maven<span class="nt">&lt;/pluginGroup&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfile&gt;</span>sonar<span class="nt">&lt;/activeProfile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>sonar<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.host.url&gt;</span>https://sonarcloud.io<span class="nt">&lt;/sonar.host.url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.organization&gt;</span>YOUR_ORG<span class="nt">&lt;/sonar.organization&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.projectKey&gt;</span>YOUR_PROJECT<span class="nt">&lt;/sonar.projectKey&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.login&gt;</span>${env.SONAR_TOKEN}<span class="nt">&lt;/sonar.login&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Finally, add a step to your workflow:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Verify Project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">SONAR_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.SONAR_TOKEN }}</span><span class="w"> +</span></span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/settings.html">https://maven.apache.org/settings.html</a></li> +<li><a href="https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/">https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/</a></li> +</ul>Cache Maven artifacts with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/Mon, 07 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-cache-maven-artifacts/<p>The <a href="https://github.com/peaceiris/actions-hugo">actions/cache</a> action allows to cache artifacts in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Cache Maven artifacts</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/cache@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">~/.m2/repository</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ runner.os }}-maven-${{ hashFiles(&#39;**/pom.xml&#39;) }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">restore-keys</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> ${{ runner.os }}-maven-</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul> \ No newline at end of file diff --git a/tags/mirror/atom.xml b/tags/mirror/atom.xml new file mode 100644 index 000000000..b2c95a4db --- /dev/null +++ b/tags/mirror/atom.xml @@ -0,0 +1,27 @@ +HugoMirror on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/mirror/Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/2021-07-12T00:00:00+00:002023-01-06T17:32:06+01:00In case you want to have push-only mirrors for your Git repository, consider adding a special mirror remote like this:

+
$ git remote add mirrors DISABLED
+$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git
+$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git
+$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git
+

The above will create a new remote called mirrors which has no fetch URL and therefore can only be pushed:

+
$ git remote -v
+mirrors DISABLED (fetch)
+mirrors git@codeberg.org:org/repo.git (push)
+mirrors git@gitlab.com:org/repo.git (push)
+mirrors git@bitbucket.org:org/repo.git (push)
+

Calling git push mirrors main:main will push the local main branch into all defined mirrors.

+ + +]]>
Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/2021-06-28T00:00:00+00:002023-01-06T16:46:32+01:00In case you want to make use of the decentralized nature of Git, consider using multiple push targets like this:

+
$ git remote set-url origin --push --add git@example.com/project.git
+$ git remote set-url origin --push --add git@another.com/project.git
+

Note that the first call to set-url will overwrite an existing remote creating with git clone. Any additional call will actually recognize the --add option and add the new target to an existing remote.

+ + +]]>
\ No newline at end of file diff --git a/tags/mirror/index.html b/tags/mirror/index.html new file mode 100644 index 000000000..47072ea89 --- /dev/null +++ b/tags/mirror/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Mirror

Tag: Mirror

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/mirror/index.xml b/tags/mirror/index.xml new file mode 100644 index 000000000..6597eca59 --- /dev/null +++ b/tags/mirror/index.xml @@ -0,0 +1,20 @@ +Mirror on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/mirror/Recent content in Mirror on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/Mon, 12 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-push-only-mirror/<p>In case you want to have push-only mirrors for your <a href="https://git-scm.com/">Git</a> repository, consider adding a special mirror remote like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote add mirrors DISABLED +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git +</span></span></code></pre></div><p>The above will create a new remote called <code>mirrors</code> which has no <code>fetch</code> URL and therefore can only be pushed:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote -v +</span></span><span class="line"><span class="cl">mirrors DISABLED <span class="o">(</span>fetch<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@codeberg.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@gitlab.com:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@bitbucket.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span></code></pre></div><p>Calling <code>git push mirrors main:main</code> will push the local <code>main</code> branch into all defined mirrors.</p>Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/Mon, 28 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-mirror/<p>In case you want to make use of the decentralized nature of <a href="https://git-scm.com/">Git</a>, consider using multiple push targets like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote set-url origin --push --add git@example.com/project.git +</span></span><span class="line"><span class="cl">$ git remote set-url origin --push --add git@another.com/project.git +</span></span></code></pre></div><p>Note that the first call to <code>set-url</code> will overwrite an existing remote creating with <code>git clone</code>. Any additional call will actually recognize the <code>--add</code> option and add the new target to an existing remote.</p> +<h2 id="links">Links</h2> +<ul> +<li><a href="../git-push-only-mirror">push only mirrors</a></li> +<li><a href="../gitlab-distributor">gitlab-distributor</a></li> +</ul> \ No newline at end of file diff --git a/tags/neovim/atom.xml b/tags/neovim/atom.xml new file mode 100644 index 000000000..21fd37a12 --- /dev/null +++ b/tags/neovim/atom.xml @@ -0,0 +1,59 @@ +HugoNeovim on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/neovim/Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/2022-01-01T00:00:00+00:002023-01-06T16:22:24+01:00Both Vim and Neovim have a built-in plugin mechanism that loads plugins from ~/.vim/pack/*/{start,opt}/* (Vim) or ~/.local/share/nvim/site/pack/*/{start,opt}/* (Neovim). All you have to do to install new plugins, is to git clone their repository into those directories. To automatically update those clones, create the following script:

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This script git-pulls all installed nvim plugins which are using the built-in
+# nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack
+#
+# Required software that is not in GNU coreutils:
+#   - 'git' to fetch plugin updates from upstream
+###############################################################################
+
+### User specific variables, adjust to your own needs
+
+# folder that contains all nvim plugins
+PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack"
+
+### Script logic
+
+echo "updating all plugins in ${PLUGIN_DIR}"
+# iterate through all directories and git pull em
+for directory in "${PLUGIN_DIR}"/*/{start,opt}/*; do
+    if [ -d "${directory}" ]; then
+        plugin=$(basename "${directory}")
+        echo "updating ${plugin}"
+        git -C "${directory}" pull --quiet
+    fi
+done
+

In case you are using Vim, adjust the PLUGIN_DIR variable to point to your Vim plugin directory instead and save the resulting shell script as a file called update-nvim-plugins.sh in some folder of your choice. Do not forget to set the executable bit with chmod +x /path/to/your/folder/update-nvim-plugins.sh. Since all good developers must be lazy, write the following systemd service to execute the above script automatically:

+
[Unit]
+Description=cron job that triggers an update of all nvim plugins
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/path/to/your/folder/update-nvim-plugins.sh
+RemainAfterExit=false
+

Adjust the ExecStart line to match the location where you saved the above script and place that service definition in a file called nvim-plugins-update.service into your local ~/.config/systemd/user directory. Add another file called nvim-plugins-update.timer next to it that defines a systemd timer with the following content:

+
[Unit]
+Description=Update nvim plugins every week
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+RandomizedDelaySec=5hours
+
+[Install]
+WantedBy=timers.target
+

Adjust how often you want to update the plugins you are using in the OnCalendar line. Enable this service/timer with:

+
$ systemctl --user enable nvim-plugins-update
+

Finally, add the following shell aliases to make it easier to interact with the created systemd units:

+
# trigger an update manually
+alias update-nvim-plugins='systemctl --user start nvim-plugins-update'
+# see status of last auto-update
+alias update-nvim-plugins-status='systemctl --user status nvim-plugins-update'
+# see logs of past auto-updates
+alias update-nvim-plugins-logs='journalctl --user --unit nvim-plugins-update'
+

With this setup in place, all your plugins will be automatically updated once per week or however often you have configured in the timer.

+]]>
\ No newline at end of file diff --git a/tags/neovim/index.html b/tags/neovim/index.html new file mode 100644 index 000000000..e777a91e1 --- /dev/null +++ b/tags/neovim/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Neovim

Tag: Neovim

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/neovim/index.xml b/tags/neovim/index.xml new file mode 100644 index 000000000..5cb3dd470 --- /dev/null +++ b/tags/neovim/index.xml @@ -0,0 +1,28 @@ +Neovim on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/neovim/Recent content in Neovim on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/<p>Both <a href="https://www.vim.org/">Vim</a> and <a href="https://neovim.io/">Neovim</a> have a <a href="https://vimhelp.org/repeat.txt.html#packages">built-in</a> <a href="https://neovim.io/doc/user/usr_05.html#plugin">plugin mechanism</a> that loads plugins from <code>~/.vim/pack/*/{start,opt}/*</code> (Vim) or <code>~/.local/share/nvim/site/pack/*/{start,opt}/*</code> (Neovim). All you have to do to install new plugins, is to <code>git clone</code> their repository into those directories. To automatically update those clones, create the following script:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env zsh +</span></span></span><span class="line"><span class="cl"><span class="cp"></span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"><span class="c1"># This script git-pulls all installed nvim plugins which are using the built-in</span> +</span></span><span class="line"><span class="cl"><span class="c1"># nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack</span> +</span></span><span class="line"><span class="cl"><span class="c1">#</span> +</span></span><span class="line"><span class="cl"><span class="c1"># Required software that is not in GNU coreutils:</span> +</span></span><span class="line"><span class="cl"><span class="c1"># - &#39;git&#39; to fetch plugin updates from upstream</span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### User specific variables, adjust to your own needs</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># folder that contains all nvim plugins</span> +</span></span><span class="line"><span class="cl"><span class="nv">PLUGIN_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_DATA_HOME</span><span class="k">:-</span><span class="nv">$HOME</span><span class="p">/.local/share</span><span class="si">}</span><span class="s2">/nvim/site/pack&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### Script logic</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;updating all plugins in </span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"><span class="c1"># iterate through all directories and git pull em</span> +</span></span><span class="line"><span class="cl"><span class="k">for</span> directory in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span>/*/<span class="o">{</span>start,opt<span class="o">}</span>/*<span class="p">;</span> <span class="k">do</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> +</span></span><span class="line"><span class="cl"> <span class="nv">plugin</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;updating </span><span class="si">${</span><span class="nv">plugin</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"> git -C <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> pull --quiet +</span></span><span class="line"><span class="cl"> <span class="k">fi</span> +</span></span><span class="line"><span class="cl"><span class="k">done</span> +</span></span></code></pre></div><p>In case you are using Vim, adjust the <code>PLUGIN_DIR</code> variable to point to your Vim plugin directory instead and save the resulting shell script as a file called <code>update-nvim-plugins.sh</code> in some folder of your choice. Do not forget to set the executable bit with <code>chmod +x /path/to/your/folder/update-nvim-plugins.sh</code>. Since all good developers must be lazy, write the following <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html">systemd service</a> to execute the above script automatically:</p> \ No newline at end of file diff --git a/tags/nullness/atom.xml b/tags/nullness/atom.xml new file mode 100644 index 000000000..8016cdcad --- /dev/null +++ b/tags/nullness/atom.xml @@ -0,0 +1,25 @@ +HugoNullness on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/nullness/jspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/2023-01-09T00:00:00+00:002023-01-09T07:05:35+01:00Every Java developer has probably encountered a NullPointerException at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:

+
String someName;         // value is 'null'
+
+someName.toUpperCase(); // throws NullPointerException
+

Modern IDEs have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is null and therefore a potential for a NullPointerException is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be null. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.

+

jspecify is the latest approach that tries to establish a standard. It has gained wide community support and recently celebrated their first public release (0.3.0).

+

The following snippet shows the dependency declaration for Maven projects:

+
<dependencies>
+    <dependency>
+        <groupId>org.jspecify</groupId>
+        <artifactId>jspecify</artifactId>
+        <version>0.3.0</version>
+    </dependency>
+</dependencies>
+

In case you want to declare that nothing in your module can ever be null, place the @NullMarked on your module-info.java like this:

+
@org.jspecify.annotations.NullMarked
+module your.module.here {
+
+    requires org.jspecify;
+
+    // ...
+
+}
+

The tooling support is not quiet clear yet, however if you are developing a library there is no harm in adding these annotations now and let your users enjoy their null-free life once tools have caught up.

+]]>
\ No newline at end of file diff --git a/tags/nullness/index.html b/tags/nullness/index.html new file mode 100644 index 000000000..e388f910d --- /dev/null +++ b/tags/nullness/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Nullness

Tag: Nullness

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/nullness/index.xml b/tags/nullness/index.xml new file mode 100644 index 000000000..ed4c8703e --- /dev/null +++ b/tags/nullness/index.xml @@ -0,0 +1,5 @@ +Nullness on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/nullness/Recent content in Nullness on Sebastian HoßHugoenMon, 09 Jan 2023 07:05:35 +0100jspecifyhttps://seb.xn--ho-hia.de/posts/jspecify/Mon, 09 Jan 2023 00:00:00 +0000https://seb.xn--ho-hia.de/posts/jspecify/<p>Every <a href="https://www.java.com/">Java</a> developer has probably encountered a <code>NullPointerException</code> at least once in their life. The exception is thrown every time you try to dereference and use some object before initializing it. The following snippet shows a simple example:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"> </span><span class="n">someName</span><span class="p">;</span><span class="w"> </span><span class="c1">// value is &#39;null&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">someName</span><span class="p">.</span><span class="na">toUpperCase</span><span class="p">();</span><span class="w"> </span><span class="c1">// throws NullPointerException</span><span class="w"> +</span></span></span></code></pre></div><p>Modern <a href="https://en.wikipedia.org/wiki/Integrated_development_environment">IDEs</a> have some sort of detection for this kind of problem and warn developers while they are writing code like this. Those IDEs typically rely on static code analysis to determine if a value is <code>null</code> and therefore a potential for a <code>NullPointerException</code> is present in your code. To improve the result of such an analysis, annotations can be placed on your code which signal that a parameter can or can not be <code>null</code>. Multiple approaches have existed in the past to define a standard set of annotations for such a task, however none of them succeeded.</p> \ No newline at end of file diff --git a/tags/passage/atom.xml b/tags/passage/atom.xml new file mode 100644 index 000000000..68c3e7352 --- /dev/null +++ b/tags/passage/atom.xml @@ -0,0 +1,47 @@ +HugoPassage on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/passage/passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/2022-12-27T00:00:00+00:002023-01-06T16:40:24+01:00To fuzzy search through passwords managed with passage, I’ve written the following script that is inspired by the upstream version which is using fzf.

+
fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

This version requires fd, skim, xargs, and passage itself of course. The detailed breakdown on how it works is as follows:

+
    +
  1. Use fd to find all files within ${PASSAGE_DIR} that end in .age. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.
  2. +
  3. Using both --base-directory and --exec echo '{.}' ensures that passwords are returned in such form that they can be passed back into passage again. The placeholder '{.}' is a feature provided by fd which strips the file extension from each returned value.
  4. +
  5. All passwords are then passed into sk to allow to fuzzy search across them all. Setting --no-multi ensures that only a single password can be selected.
  6. +
  7. Finally, xargs calls passage and replaces the curly braces with the selected password. Thanks to --clip=1, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.
  8. +
+

To call that script, I’ve saved it as passage-fuzzy-search.sh in my .local/bin folder and added some checks into it to verify that every required software is actually installed.

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This shell script presents passwords saved with passage through skim
+#
+# Call it like this:
+#   passage-fuzzy-search.sh
+#
+# Required software that isn't in GNU coreutils:
+#   - 'passage' to read passwords
+#   - 'fd' to find passwords
+#   - 'sk' to filter passwords
+###############################################################################
+
+if ! (( ${+commands[passage]} )); then
+    echo 'passage not installed. Please install passage.'
+    exit
+fi
+if ! (( ${+commands[sk]} )); then
+    echo 'sk not installed. Please install skim.'
+    exit
+fi
+if ! (( ${+commands[fd]} )); then
+    echo 'fd not installed. Please install fd-find.'
+    exit
+fi
+
+fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

Since typing passage-fuzzy-search.sh is way too long, I have added an alias like this:

+
alias pp='passage-fuzzy-search.sh'
+
]]>
\ No newline at end of file diff --git a/tags/passage/index.html b/tags/passage/index.html new file mode 100644 index 000000000..36ce3f19f --- /dev/null +++ b/tags/passage/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Passage

Tag: Passage

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/passage/index.xml b/tags/passage/index.xml new file mode 100644 index 000000000..c51d6c6f0 --- /dev/null +++ b/tags/passage/index.xml @@ -0,0 +1,13 @@ +Passage on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/passage/Recent content in Passage on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/passage-fuzzy-search/<p>To fuzzy search through passwords managed with <a href="https://github.com/FiloSottile/passage">passage</a>, I&rsquo;ve written the following script that is inspired by the upstream version which is using <code>fzf</code>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">fd --type<span class="o">=</span>file --base-directory<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PASSAGE_DIR</span><span class="k">:-</span><span class="si">${</span><span class="nv">HOME</span><span class="si">}</span><span class="p">/.passage/store</span><span class="si">}</span><span class="s2">&#34;</span> .age --exec <span class="nb">echo</span> <span class="s1">&#39;{.}&#39;</span> <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> sk --cycle --layout<span class="o">=</span>reverse --tiebreak<span class="o">=</span>score --no-multi <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> xargs --replace --max-args<span class="o">=</span><span class="m">1</span> --no-run-if-empty <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> passage show --clip<span class="o">=</span><span class="m">1</span> <span class="o">{}</span> +</span></span></code></pre></div><p>This version requires <a href="https://github.com/sharkdp/fd/">fd</a>, <a href="https://github.com/lotabout/skim">skim</a>, <a href="https://www.gnu.org/software/findutils/manual/html_node/find_html/Invoking-xargs.html">xargs</a>, and <a href="https://github.com/FiloSottile/passage">passage</a> itself of course. The detailed breakdown on how it works is as follows:</p> +<ol> +<li>Use <code>fd</code> to find all files within <code>${PASSAGE_DIR}</code> that end in <code>.age</code>. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.</li> +<li>Using both <code>--base-directory</code> and <code>--exec echo '{.}'</code> ensures that passwords are returned in such form that they can be passed back into <code>passage</code> again. The placeholder <code>'{.}'</code> is a feature provided by <code>fd</code> which strips the file extension from each returned value.</li> +<li>All passwords are then passed into <code>sk</code> to allow to fuzzy search across them all. Setting <code>--no-multi</code> ensures that only a single password can be selected.</li> +<li>Finally, <code>xargs</code> calls <code>passage</code> and replaces the curly braces with the selected password. Thanks to <code>--clip=1</code>, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.</li> +</ol> +<p>To call that script, I&rsquo;ve saved it as <code>passage-fuzzy-search.sh</code> in my <code>.local/bin</code> folder and added some checks into it to verify that every required software is actually installed.</p> \ No newline at end of file diff --git a/tags/peek/atom.xml b/tags/peek/atom.xml new file mode 100644 index 000000000..6b4773263 --- /dev/null +++ b/tags/peek/atom.xml @@ -0,0 +1,4 @@ +HugoPeek on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/peek/Peeking with tmuxhttps://seb.xn--ho-hia.de/posts/tmux-peek/2021-04-05T00:00:00+00:002023-01-06T15:27:21+01:00tmux uses can use the following snippet to peek at files. Place it in your .bashrc or similar file.

+
peek() { tmux split-window -p 33 "$EDITOR" "$@" }
+

Calling peek <file> will open <file> in lower third of tmux window.

+]]>
\ No newline at end of file diff --git a/tags/peek/index.html b/tags/peek/index.html new file mode 100644 index 000000000..6326c5018 --- /dev/null +++ b/tags/peek/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Peek

Tag: Peek

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/peek/index.xml b/tags/peek/index.xml new file mode 100644 index 000000000..dcc43518b --- /dev/null +++ b/tags/peek/index.xml @@ -0,0 +1,3 @@ +Peek on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/peek/Recent content in Peek on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Peeking with tmuxhttps://seb.xn--ho-hia.de/posts/tmux-peek/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-peek/<p><a href="https://github.com/tmux/tmux">tmux</a> uses can use the following snippet to peek at files. Place it in your <code>.bashrc</code> or similar file.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">peek<span class="o">()</span> <span class="o">{</span> tmux split-window -p <span class="m">33</span> <span class="s2">&#34;</span><span class="nv">$EDITOR</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span> <span class="o">}</span> +</span></span></code></pre></div><p>Calling <code>peek &lt;file&gt;</code> will open <code>&lt;file&gt;</code> in lower third of tmux window.</p> \ No newline at end of file diff --git a/tags/performance/atom.xml b/tags/performance/atom.xml new file mode 100644 index 000000000..deff4ae69 --- /dev/null +++ b/tags/performance/atom.xml @@ -0,0 +1,18 @@ +HugoPerformance on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/performance/chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/2022-12-12T00:00:00+00:002023-01-06T16:40:24+01:00Many CLI applications offer initialization scripts to integrate into a shell, for example starship init zsh or zoxide init zsh. The documentation of these tools usually tell you to put something like eval "$(starship init zsh)" into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the init command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.

+

chezmoi provides a template function called output which replaces itself with the output of the command you specified. You can use that function this to integrate various tools into your shell as the following example shows while using zsh:

+
    +
  1. Create a directory that holds all init scripts for every tool you want to use. +
     $ mkdir --parents "${ZDOTDIR}"/tools.d
    +
  2. +
  3. Let your shell load all available scripts in that directory. This snippet should be part of your .zshrc file: +
    for init_script in "${ZDOTDIR}"/tools.d/*.sh; do
    +  source "${init_script}"
    +done
    +
  4. +
  5. Create chezmoi .tmpl files for each tool and place them in the chezmoi source directory that matches the directory you created in step 1: +
    {{ output "starship" "init" "zsh" "--print-full-init" }}
    +
  6. +
  7. Call chezmoi apply to generate the init scripts.
  8. +
+

The only downside here is that you have to re-run chezmoi apply after updating one of the tools because they change their init scripts sometimes. That problem can be solved with chezmoi auto-updates.

+]]>
\ No newline at end of file diff --git a/tags/performance/index.html b/tags/performance/index.html new file mode 100644 index 000000000..3df0375db --- /dev/null +++ b/tags/performance/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Performance

Tag: Performance

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/performance/index.xml b/tags/performance/index.xml new file mode 100644 index 000000000..88a1163a5 --- /dev/null +++ b/tags/performance/index.xml @@ -0,0 +1 @@ +Performance on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/performance/Recent content in Performance on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/Mon, 12 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/shell-init/<p>Many CLI applications offer initialization scripts to integrate into a shell, for example <code>starship init zsh</code> or <code>zoxide init zsh</code>. The documentation of these tools usually tell you to put something like <code>eval &quot;$(starship init zsh)&quot;</code> into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the <code>init</code> command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.</p> \ No newline at end of file diff --git a/tags/perl/atom.xml b/tags/perl/atom.xml new file mode 100644 index 000000000..1719102d7 --- /dev/null +++ b/tags/perl/atom.xml @@ -0,0 +1,49 @@ +HugoPerl on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/perl/Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/2020-06-29T00:00:00+00:002023-01-06T16:22:24+01:00Use the following Perl snippet to automatically generate help output for your Makefile:

+
GREEN  := $(shell tput -Txterm setaf 2)
+WHITE  := $(shell tput -Txterm setaf 7)
+YELLOW := $(shell tput -Txterm setaf 3)
+RESET  := $(shell tput -Txterm sgr0)
+
+HELP_FUN = \
+    %help; \
+    while(<>) { push @{$$help{$$2 // 'targets'}}, [$$1, $$3] if /^([a-zA-Z\-]+)\s*:.*\#\#(?:@([a-zA-Z\-]+))?\s(.*)$$/ }; \
+    print "usage: make [target]\n\n"; \
+    for (sort keys %help) { \
+    print "${WHITE}$$_:${RESET}\n"; \
+    for (@{$$help{$$_}}) { \
+    $$sep = " " x (32 - length $$_->[0]); \
+    print "  ${YELLOW}$$_->[0]${RESET}$$sep${GREEN}$$_->[1]${RESET}\n"; \
+    }; \
+    print "\n"; }
+

To use HELP_FUN, add the following help target to the same Makefile:

+
.DEFAULT_GOAL := help
+
+.PHONY: help
+help: ##@other Show this help
+	@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)
+

Each target in the Makefile is marked as phony to signal that those targets are not actually files that are generated as part of your build process. The optional description of a target can be placed after the ##@ prefix. The first word represents the group of a target and everything that follows is the description of a target. All targets should be formatted just like the help target:

+
.PHONY: compile
+compile: ##@hacking Compile your code
+	<compile some code>
+
+.PHONY: test
+test: ##@hacking Test your code
+	<test some code>
+
+.PHONY: sign-cla
+sign-cla: ##@contrib Sign the contributor license agreement
+	<sign some file>
+

Once in place, you can either use make without any argument to call the help target or use make help to see the generated output:

+
$ make
+usage: make [target]
+
+contrib:
+  sign-cla            Sign the contributor license agreement
+
+hacking:
+  compile             Compile your code
+  test                Test your code
+
+other:
+  help                Show this help
+
]]>
\ No newline at end of file diff --git a/tags/perl/index.html b/tags/perl/index.html new file mode 100644 index 000000000..91699e82f --- /dev/null +++ b/tags/perl/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Perl

Tag: Perl

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/perl/index.xml b/tags/perl/index.xml new file mode 100644 index 000000000..9c1196c8b --- /dev/null +++ b/tags/perl/index.xml @@ -0,0 +1,18 @@ +Perl on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/perl/Recent content in Perl on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Makefile Helphttps://seb.xn--ho-hia.de/posts/makefile-help/Mon, 29 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-help/<p>Use the following <a href="https://www.perl.org/">Perl</a> snippet to automatically generate help output for your <code>Makefile</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nv">GREEN</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 2<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">WHITE</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 7<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">YELLOW</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm setaf 3<span class="k">)</span> +</span></span><span class="line"><span class="cl"><span class="nv">RESET</span> <span class="o">:=</span> <span class="k">$(</span>shell tput -Txterm sgr0<span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nv">HELP_FUN</span> <span class="o">=</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> %help<span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">while</span><span class="o">(</span>&lt;&gt;<span class="o">)</span> <span class="o">{</span> push @<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span><span class="m">2</span> // <span class="s1">&#39;targets&#39;</span><span class="o">}}</span>, <span class="o">[</span><span class="nv">$$</span>1, <span class="nv">$$</span>3<span class="o">]</span> <span class="k">if</span> /^<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">)</span><span class="se">\s</span>*:.*<span class="se">\#\#</span><span class="o">(</span>?:@<span class="o">([</span>a-zA-Z<span class="se">\-</span><span class="o">]</span>+<span class="o">))</span>?<span class="se">\s</span><span class="o">(</span>.*<span class="o">)</span><span class="nv">$$</span>/ <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;usage: make [target]\n\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>sort keys %help<span class="o">)</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WHITE</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_:</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="k">for</span> <span class="o">(</span>@<span class="o">{</span><span class="nv">$$</span>help<span class="o">{</span><span class="nv">$$</span>_<span class="o">}})</span> <span class="o">{</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="nv">$$sep</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span> x <span class="o">(</span><span class="m">32</span> - length <span class="nv">$$</span>_-&gt;<span class="o">[</span>0<span class="o">])</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34; </span><span class="si">${</span><span class="nv">YELLOW</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[0]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="nv">$$</span><span class="s2">sep</span><span class="si">${</span><span class="nv">GREEN</span><span class="si">}</span><span class="nv">$$</span><span class="s2">_-&gt;[1]</span><span class="si">${</span><span class="nv">RESET</span><span class="si">}</span><span class="s2">\n&#34;</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> <span class="o">}</span><span class="p">;</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> print <span class="s2">&#34;\n&#34;</span><span class="p">;</span> <span class="o">}</span> +</span></span></code></pre></div><p>To use <code>HELP_FUN</code>, add the following <code>help</code> target to the same <code>Makefile</code>:</p> \ No newline at end of file diff --git a/tags/plugins/atom.xml b/tags/plugins/atom.xml new file mode 100644 index 000000000..daa16012a --- /dev/null +++ b/tags/plugins/atom.xml @@ -0,0 +1,59 @@ +HugoPlugins on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/plugins/Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/2022-01-01T00:00:00+00:002023-01-06T16:22:24+01:00Both Vim and Neovim have a built-in plugin mechanism that loads plugins from ~/.vim/pack/*/{start,opt}/* (Vim) or ~/.local/share/nvim/site/pack/*/{start,opt}/* (Neovim). All you have to do to install new plugins, is to git clone their repository into those directories. To automatically update those clones, create the following script:

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This script git-pulls all installed nvim plugins which are using the built-in
+# nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack
+#
+# Required software that is not in GNU coreutils:
+#   - 'git' to fetch plugin updates from upstream
+###############################################################################
+
+### User specific variables, adjust to your own needs
+
+# folder that contains all nvim plugins
+PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack"
+
+### Script logic
+
+echo "updating all plugins in ${PLUGIN_DIR}"
+# iterate through all directories and git pull em
+for directory in "${PLUGIN_DIR}"/*/{start,opt}/*; do
+    if [ -d "${directory}" ]; then
+        plugin=$(basename "${directory}")
+        echo "updating ${plugin}"
+        git -C "${directory}" pull --quiet
+    fi
+done
+

In case you are using Vim, adjust the PLUGIN_DIR variable to point to your Vim plugin directory instead and save the resulting shell script as a file called update-nvim-plugins.sh in some folder of your choice. Do not forget to set the executable bit with chmod +x /path/to/your/folder/update-nvim-plugins.sh. Since all good developers must be lazy, write the following systemd service to execute the above script automatically:

+
[Unit]
+Description=cron job that triggers an update of all nvim plugins
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/path/to/your/folder/update-nvim-plugins.sh
+RemainAfterExit=false
+

Adjust the ExecStart line to match the location where you saved the above script and place that service definition in a file called nvim-plugins-update.service into your local ~/.config/systemd/user directory. Add another file called nvim-plugins-update.timer next to it that defines a systemd timer with the following content:

+
[Unit]
+Description=Update nvim plugins every week
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+RandomizedDelaySec=5hours
+
+[Install]
+WantedBy=timers.target
+

Adjust how often you want to update the plugins you are using in the OnCalendar line. Enable this service/timer with:

+
$ systemctl --user enable nvim-plugins-update
+

Finally, add the following shell aliases to make it easier to interact with the created systemd units:

+
# trigger an update manually
+alias update-nvim-plugins='systemctl --user start nvim-plugins-update'
+# see status of last auto-update
+alias update-nvim-plugins-status='systemctl --user status nvim-plugins-update'
+# see logs of past auto-updates
+alias update-nvim-plugins-logs='journalctl --user --unit nvim-plugins-update'
+

With this setup in place, all your plugins will be automatically updated once per week or however often you have configured in the timer.

+]]>
\ No newline at end of file diff --git a/tags/plugins/index.html b/tags/plugins/index.html new file mode 100644 index 000000000..a25827a6d --- /dev/null +++ b/tags/plugins/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Plugins

Tag: Plugins

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/plugins/index.xml b/tags/plugins/index.xml new file mode 100644 index 000000000..5f9bdcf4f --- /dev/null +++ b/tags/plugins/index.xml @@ -0,0 +1,28 @@ +Plugins on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/plugins/Recent content in Plugins on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/<p>Both <a href="https://www.vim.org/">Vim</a> and <a href="https://neovim.io/">Neovim</a> have a <a href="https://vimhelp.org/repeat.txt.html#packages">built-in</a> <a href="https://neovim.io/doc/user/usr_05.html#plugin">plugin mechanism</a> that loads plugins from <code>~/.vim/pack/*/{start,opt}/*</code> (Vim) or <code>~/.local/share/nvim/site/pack/*/{start,opt}/*</code> (Neovim). All you have to do to install new plugins, is to <code>git clone</code> their repository into those directories. To automatically update those clones, create the following script:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env zsh +</span></span></span><span class="line"><span class="cl"><span class="cp"></span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"><span class="c1"># This script git-pulls all installed nvim plugins which are using the built-in</span> +</span></span><span class="line"><span class="cl"><span class="c1"># nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack</span> +</span></span><span class="line"><span class="cl"><span class="c1">#</span> +</span></span><span class="line"><span class="cl"><span class="c1"># Required software that is not in GNU coreutils:</span> +</span></span><span class="line"><span class="cl"><span class="c1"># - &#39;git&#39; to fetch plugin updates from upstream</span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### User specific variables, adjust to your own needs</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># folder that contains all nvim plugins</span> +</span></span><span class="line"><span class="cl"><span class="nv">PLUGIN_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_DATA_HOME</span><span class="k">:-</span><span class="nv">$HOME</span><span class="p">/.local/share</span><span class="si">}</span><span class="s2">/nvim/site/pack&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### Script logic</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;updating all plugins in </span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"><span class="c1"># iterate through all directories and git pull em</span> +</span></span><span class="line"><span class="cl"><span class="k">for</span> directory in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span>/*/<span class="o">{</span>start,opt<span class="o">}</span>/*<span class="p">;</span> <span class="k">do</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> +</span></span><span class="line"><span class="cl"> <span class="nv">plugin</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;updating </span><span class="si">${</span><span class="nv">plugin</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"> git -C <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> pull --quiet +</span></span><span class="line"><span class="cl"> <span class="k">fi</span> +</span></span><span class="line"><span class="cl"><span class="k">done</span> +</span></span></code></pre></div><p>In case you are using Vim, adjust the <code>PLUGIN_DIR</code> variable to point to your Vim plugin directory instead and save the resulting shell script as a file called <code>update-nvim-plugins.sh</code> in some folder of your choice. Do not forget to set the executable bit with <code>chmod +x /path/to/your/folder/update-nvim-plugins.sh</code>. Since all good developers must be lazy, write the following <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html">systemd service</a> to execute the above script automatically:</p> \ No newline at end of file diff --git a/tags/publish/atom.xml b/tags/publish/atom.xml new file mode 100644 index 000000000..a0f2c63af --- /dev/null +++ b/tags/publish/atom.xml @@ -0,0 +1,20 @@ +HugoPublish on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/publish/Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/2020-09-21T00:00:00+00:002023-01-06T15:27:21+01:00The peaceiris/actions-gh-pages action allows to publish a Hugo site in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Deploy Website
+        uses: peaceiris/actions-gh-pages@v3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: <PUBLISH_DIR>
+          force_orphan: true
+          cname: <CNAME>
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <PUBLISH_DIR>: The file system location of the built site.
  • +
  • <CNAME>: The CNAME of your custom domain.
  • +
+]]>
\ No newline at end of file diff --git a/tags/publish/index.html b/tags/publish/index.html new file mode 100644 index 000000000..639ad9445 --- /dev/null +++ b/tags/publish/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Publish

Tag: Publish

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/publish/index.xml b/tags/publish/index.xml new file mode 100644 index 000000000..aeef0e215 --- /dev/null +++ b/tags/publish/index.xml @@ -0,0 +1,19 @@ +Publish on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/publish/Recent content in Publish on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Publish Hugo site with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/Mon, 21 Sep 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-publish-hugo-site/<p>The <a href="https://github.com/peaceiris/actions-gh-pages">peaceiris/actions-gh-pages</a> action allows to publish a <a href="https://gohugo.io/">Hugo</a> site in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Website</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-gh-pages@v3</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">github_token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">publish_dir</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PUBLISH_DIR&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">force_orphan</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">cname</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;CNAME&gt;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;PUBLISH_DIR&gt;</code>: The file system location of the built site.</li> +<li><code>&lt;CNAME&gt;</code>: The <code>CNAME</code> of your custom domain.</li> +</ul> \ No newline at end of file diff --git a/tags/push/atom.xml b/tags/push/atom.xml new file mode 100644 index 000000000..81b777514 --- /dev/null +++ b/tags/push/atom.xml @@ -0,0 +1,27 @@ +HugoPush on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/push/Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/2021-07-12T00:00:00+00:002023-01-06T17:32:06+01:00In case you want to have push-only mirrors for your Git repository, consider adding a special mirror remote like this:

+
$ git remote add mirrors DISABLED
+$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git
+$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git
+$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git
+

The above will create a new remote called mirrors which has no fetch URL and therefore can only be pushed:

+
$ git remote -v
+mirrors DISABLED (fetch)
+mirrors git@codeberg.org:org/repo.git (push)
+mirrors git@gitlab.com:org/repo.git (push)
+mirrors git@bitbucket.org:org/repo.git (push)
+

Calling git push mirrors main:main will push the local main branch into all defined mirrors.

+ + +]]>
Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/2021-06-28T00:00:00+00:002023-01-06T16:46:32+01:00In case you want to make use of the decentralized nature of Git, consider using multiple push targets like this:

+
$ git remote set-url origin --push --add git@example.com/project.git
+$ git remote set-url origin --push --add git@another.com/project.git
+

Note that the first call to set-url will overwrite an existing remote creating with git clone. Any additional call will actually recognize the --add option and add the new target to an existing remote.

+ + +]]>
\ No newline at end of file diff --git a/tags/push/index.html b/tags/push/index.html new file mode 100644 index 000000000..45d354728 --- /dev/null +++ b/tags/push/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Push

Tag: Push

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/push/index.xml b/tags/push/index.xml new file mode 100644 index 000000000..c3e5abe3c --- /dev/null +++ b/tags/push/index.xml @@ -0,0 +1,20 @@ +Push on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/push/Recent content in Push on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Push-only mirrors for Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-push-only-mirror/Mon, 12 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-push-only-mirror/<p>In case you want to have push-only mirrors for your <a href="https://git-scm.com/">Git</a> repository, consider adding a special mirror remote like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote add mirrors DISABLED +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@codeberg.org:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@gitlab.com:org/repo.git +</span></span><span class="line"><span class="cl">$ git remote set-url --add --push mirrors git@bitbucket.org:org/repo.git +</span></span></code></pre></div><p>The above will create a new remote called <code>mirrors</code> which has no <code>fetch</code> URL and therefore can only be pushed:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote -v +</span></span><span class="line"><span class="cl">mirrors DISABLED <span class="o">(</span>fetch<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@codeberg.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@gitlab.com:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span><span class="line"><span class="cl">mirrors git@bitbucket.org:org/repo.git <span class="o">(</span>push<span class="o">)</span> +</span></span></code></pre></div><p>Calling <code>git push mirrors main:main</code> will push the local <code>main</code> branch into all defined mirrors.</p>Mirror Git Repositorieshttps://seb.xn--ho-hia.de/posts/git-mirror/Mon, 28 Jun 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/git-mirror/<p>In case you want to make use of the decentralized nature of <a href="https://git-scm.com/">Git</a>, consider using multiple push targets like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git remote set-url origin --push --add git@example.com/project.git +</span></span><span class="line"><span class="cl">$ git remote set-url origin --push --add git@another.com/project.git +</span></span></code></pre></div><p>Note that the first call to <code>set-url</code> will overwrite an existing remote creating with <code>git clone</code>. Any additional call will actually recognize the <code>--add</code> option and add the new target to an existing remote.</p> +<h2 id="links">Links</h2> +<ul> +<li><a href="../git-push-only-mirror">push only mirrors</a></li> +<li><a href="../gitlab-distributor">gitlab-distributor</a></li> +</ul> \ No newline at end of file diff --git a/tags/react/atom.xml b/tags/react/atom.xml new file mode 100644 index 000000000..c4954c47f --- /dev/null +++ b/tags/react/atom.xml @@ -0,0 +1,58 @@ +HugoReact on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/react/Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/2022-01-15T00:00:00+00:002023-01-06T16:22:24+01:00One feature that often surprises people while teaching them React is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.

+

In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.

+
const RendersNothing = () => <></>
+

Now consider the following example, that adds some if-then-else logic to the same component:

+
const MightRenderSomething = () => {
+  if (someCondition) {
+    return <span>hello world!</span>
+  }
+  return <></>
+}
+

This component encapsulates the if-then-else logic of conditionally rendering a hello world message. Instead of cluttering your entire app with the same logic, you can now simply re-use that same component that contains this if condition. To see the full power of this technique, consider the following example. At first, we are going to define a hook that reads the current window width, then define components that conditionally render based on the current window width, and finally use those components in an example application.

+
const useWindowWidth = () => {
+  const [width, setWidth] = React.useState(0)
+
+  React.useEffect(() => {
+    const handleResize = () => {
+      setWidth(window.innerWidth)
+    }
+    window.addEventListener("resize", handleResize)
+    return () => {
+      window.removeEventListener("resize", handleResize)
+    }
+  }, [])
+
+  return width
+}
+

The following components use that hook to implement UI breakpoints for small (mobile) and large (desktop) screens. Note that the value 768 is just an example - replace it with whatever your design system tells you to.

+
const ForMobileDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+  
+  if (windowWidth < 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+
+const ForDesktopDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+
+  if (windowWidth >= 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+

Both of these components simply render nothing when the window width does not have an appropriate size. If the window width does have the right size, they render their children. We can use those components in our application like this:

+
const SomeActualComponent = () => (
+    <div>
+      <h1>common headline</h1>
+      <ForMobileDevicesOnly>
+        <span>only visible on mobile devices</span>
+      </ForMobileDevicesOnly>
+      <ForDesktopDevicesOnly>
+        <span>only visible on desktop devices</span>
+      </ForDesktopDevicesOnly>
+    </div>
+)
+

The above code snippet declares that some part of the UI can only be seen by mobile users, while others can only be seen by desktop users. Parts of the UI that are shared amongst all users are not wrapped by any of the components defined above.

+]]>
\ No newline at end of file diff --git a/tags/react/index.html b/tags/react/index.html new file mode 100644 index 000000000..7ee811697 --- /dev/null +++ b/tags/react/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – React

Tag: React

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/react/index.xml b/tags/react/index.xml new file mode 100644 index 000000000..2a3bd533c --- /dev/null +++ b/tags/react/index.xml @@ -0,0 +1,2 @@ +React on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/react/Recent content in React on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/Sat, 15 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/<p>One feature that often surprises people while teaching them <a href="https://reactjs.org/">React</a> is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.</p> +<p>In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.</p> \ No newline at end of file diff --git a/tags/readme/atom.xml b/tags/readme/atom.xml new file mode 100644 index 000000000..6aa9e7702 --- /dev/null +++ b/tags/readme/atom.xml @@ -0,0 +1,21 @@ +HugoREADME on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/readme/Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/2021-03-08T00:00:00+00:002023-01-06T16:40:24+01:00README file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a Makefile in the root of your project which contains the exact same instructions. Thanks to make, all your contributors can now use TAB-completion to run any of the pre-defined make targets.

+

The following example is part of one of my projects, and I certainly don’t want to type (or even copy) that all the time:

+
.PHONY: release-into-local-nexus
+release-into-local-nexus:
+	mvn versions:set \
+	   -DnewVersion=$(TIMESTAMPED_VERSION) \
+	   -DgenerateBackupPoms=false
+	-mvn clean deploy scm:tag \
+	   -DpushChanges=false \
+	   -DskipLocalStaging=true \
+	   -Drelease=local
+	mvn versions:set \
+	   -DnewVersion=9999.99.99-SNAPSHOT \
+	   -DgenerateBackupPoms=false
+

With the above target in place, everyone can now do make release-into-local-nexus instead of typing/copying the commands themselves. Thanks to TAB-completion you just have to do make r<TAB> and confirm with >ENTER> to perform a release.

+ + +]]>
\ No newline at end of file diff --git a/tags/readme/index.html b/tags/readme/index.html new file mode 100644 index 000000000..f8fc9d112 --- /dev/null +++ b/tags/readme/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – README

Tag: README

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/readme/index.xml b/tags/readme/index.xml new file mode 100644 index 000000000..20a378b33 --- /dev/null +++ b/tags/readme/index.xml @@ -0,0 +1 @@ +README on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/readme/Recent content in README on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/Mon, 08 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-readme/<p><code>README</code> file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a <code>Makefile</code> in the root of your project which contains the exact same instructions. Thanks to <code>make</code>, all your contributors can now use TAB-completion to run any of the pre-defined <code>make</code> targets.</p> \ No newline at end of file diff --git a/tags/release/atom.xml b/tags/release/atom.xml new file mode 100644 index 000000000..89353fae7 --- /dev/null +++ b/tags/release/atom.xml @@ -0,0 +1,52 @@ +HugoRelease on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/release/Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/2020-10-19T00:00:00+00:002023-01-06T15:27:21+01:00The actions/upload-release-asset action allows to upload a release artifact in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Upload Release Asset
+        id: upload_release_asset
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./some/path/to/file.zip
+          asset_name: public-name-for-file.zip
+          asset_content_type: application/zip
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/2020-10-05T00:00:00+00:002023-01-06T17:32:06+01:00The actions/create-release action allows to create a new GitHub releases in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+     - name: Create Release
+       uses: actions/create-release@v1
+       env:
+         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+       with:
+         tag_name: <TAG>
+         release_name: <RELEASE>
+         draft: false
+         prerelease: false
+         body: |
+           Your release text here
+
+           Some code block:
+           ```yaml
+           yaml:
+             inside:
+               of:
+                 another: yaml
+           ```           
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <TAG>: The Git tag to create.
  • +
  • <RELEASE>: The release name to use.
  • +
+]]>
\ No newline at end of file diff --git a/tags/release/index.html b/tags/release/index.html new file mode 100644 index 000000000..eae118893 --- /dev/null +++ b/tags/release/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Release

Tag: Release

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/release/index.xml b/tags/release/index.xml new file mode 100644 index 000000000..888629812 --- /dev/null +++ b/tags/release/index.xml @@ -0,0 +1,50 @@ +Release on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/release/Recent content in Release on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/<p>The <a href="https://github.com/actions/upload-release-asset">actions/upload-release-asset</a> action allows to upload a <a href="https://developer.github.com/v3/repos/releases/#upload-a-release-asset">release artifact</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload Release Asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">upload_release_asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-release-asset@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">upload_url</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.create_release.outputs.upload_url }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_path</span><span class="p">:</span><span class="w"> </span><span class="l">./some/path/to/file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_name</span><span class="p">:</span><span class="w"> </span><span class="l">public-name-for-file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_content_type</span><span class="p">:</span><span class="w"> </span><span class="l">application/zip</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul>Create GitHub releases with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-release/Mon, 05 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-release/<p>The <a href="https://github.com/actions/create-release">actions/create-release</a> action allows to create a new <a href="https://help.github.com/en/github/administering-a-repository/about-releases">GitHub releases</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create Release</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/create-release@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">tag_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;TAG&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">release_name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RELEASE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">draft</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">prerelease</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">body</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Your release text here +</span></span></span><span class="line"><span class="cl"><span class="sd"> +</span></span></span><span class="line"><span class="cl"><span class="sd"> Some code block: +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> yaml: +</span></span></span><span class="line"><span class="cl"><span class="sd"> inside: +</span></span></span><span class="line"><span class="cl"><span class="sd"> of: +</span></span></span><span class="line"><span class="cl"><span class="sd"> another: yaml +</span></span></span><span class="line"><span class="cl"><span class="sd"> ```</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;TAG&gt;</code>: The Git tag to create.</li> +<li><code>&lt;RELEASE&gt;</code>: The release name to use.</li> +</ul> \ No newline at end of file diff --git a/tags/rendering/atom.xml b/tags/rendering/atom.xml new file mode 100644 index 000000000..b5fa4af4c --- /dev/null +++ b/tags/rendering/atom.xml @@ -0,0 +1,58 @@ +HugoRendering on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/rendering/Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/2022-01-15T00:00:00+00:002023-01-06T16:22:24+01:00One feature that often surprises people while teaching them React is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.

+

In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.

+
const RendersNothing = () => <></>
+

Now consider the following example, that adds some if-then-else logic to the same component:

+
const MightRenderSomething = () => {
+  if (someCondition) {
+    return <span>hello world!</span>
+  }
+  return <></>
+}
+

This component encapsulates the if-then-else logic of conditionally rendering a hello world message. Instead of cluttering your entire app with the same logic, you can now simply re-use that same component that contains this if condition. To see the full power of this technique, consider the following example. At first, we are going to define a hook that reads the current window width, then define components that conditionally render based on the current window width, and finally use those components in an example application.

+
const useWindowWidth = () => {
+  const [width, setWidth] = React.useState(0)
+
+  React.useEffect(() => {
+    const handleResize = () => {
+      setWidth(window.innerWidth)
+    }
+    window.addEventListener("resize", handleResize)
+    return () => {
+      window.removeEventListener("resize", handleResize)
+    }
+  }, [])
+
+  return width
+}
+

The following components use that hook to implement UI breakpoints for small (mobile) and large (desktop) screens. Note that the value 768 is just an example - replace it with whatever your design system tells you to.

+
const ForMobileDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+  
+  if (windowWidth < 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+
+const ForDesktopDevicesOnly = (props) => {
+  const windowWidth = useWindowWidth()
+
+  if (windowWidth >= 768) {
+    return <>{props.children}</>
+  }
+  return <></>
+}
+

Both of these components simply render nothing when the window width does not have an appropriate size. If the window width does have the right size, they render their children. We can use those components in our application like this:

+
const SomeActualComponent = () => (
+    <div>
+      <h1>common headline</h1>
+      <ForMobileDevicesOnly>
+        <span>only visible on mobile devices</span>
+      </ForMobileDevicesOnly>
+      <ForDesktopDevicesOnly>
+        <span>only visible on desktop devices</span>
+      </ForDesktopDevicesOnly>
+    </div>
+)
+

The above code snippet declares that some part of the UI can only be seen by mobile users, while others can only be seen by desktop users. Parts of the UI that are shared amongst all users are not wrapped by any of the components defined above.

+]]>
\ No newline at end of file diff --git a/tags/rendering/index.html b/tags/rendering/index.html new file mode 100644 index 000000000..472b57a6c --- /dev/null +++ b/tags/rendering/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Rendering

Tag: Rendering

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/rendering/index.xml b/tags/rendering/index.xml new file mode 100644 index 000000000..6aad83bdf --- /dev/null +++ b/tags/rendering/index.xml @@ -0,0 +1,2 @@ +Rendering on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/rendering/Recent content in Rendering on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Declarative conditional rendering in Reacthttps://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/Sat, 15 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/declarative-conditional-rendering-in-react/<p>One feature that often surprises people while teaching them <a href="https://reactjs.org/">React</a> is that a component does not have to render anything. It seems trivial at first, however it quickly shows that a render-nothing components can reduce boilerplate code and improve code-reuse.</p> +<p>In its simplest (shortest) form a render-nothing component looks like the following snippet. It does not actually do anything and is not particularly helpful for anything. You could add it to every other component in your application without breaking or influencing anything.</p> \ No newline at end of file diff --git a/tags/repo.or.cz/atom.xml b/tags/repo.or.cz/atom.xml new file mode 100644 index 000000000..f2e8c1ffc --- /dev/null +++ b/tags/repo.or.cz/atom.xml @@ -0,0 +1,35 @@ +HugoRepo.or.cz on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/repo.or.cz/GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/2020-07-13T00:00:00+00:002023-01-06T17:32:06+01:00Git at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. Pro Git rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.

+

While the central server approach is easy to use, it might not work in all scenarios:

+
    +
  1. An enterprise wants to share internal code as part of an open source project. All development is happening internally, and the public mirror gets an occasional update once in a while.
  2. +
  3. To protect against outages of the central server, mirrors should be created and be kept up-to-date.
  4. +
+

In case of the first scenario, tools like copybara, repoSpanner, or distributed-Git-forks offer a wide range of features to cover most details.

+

The second scenario can be solved manually with tools like gitomatic or automatically with GitLab’s mirror feature quite easy. GitLab allows to create a single pull-mirror and multiple push-mirrors. Therefore, it can be used to pull from your central server and push into all mirrors.

+

NOTE: This feature was previously available in the free tier but has now moved to GitLab Ultimate.

+
               +----------------+               
+               |     GitHub     |               
+               +----------------+               
+                        ^                       
+                        |                       
+                        |                       
+               +----------------+               
+         +-----|     GitLab     |------+        
+         |     +----------------+      |        
+         |                             |        
+         |                             |        
+         v                             v        
++----------------+            +----------------+
+|    Codeberg    |            |    BitBucket   |
++----------------+            +----------------+
+

To create such a setup, follow these steps:

+
    +
  1. Go to Settings > Repository and expand Mirroring repositories +Code Flow
  2. +
  3. Enter the URL of your central Git repository as the pull source, for example https://github.com/metio/ilo.git +Code Flow
  4. +
  5. Enter one push target for each mirror. Since pushing usually requires authentication, verify that the URL of the mirror contains a username, for example https://YOUR_USER@codeberg.org/metio.wtf/ilo.git. Add an access token for each mirror and select password as authentication method. +Code Flow
  6. +
+

In case you prefer SSH keys over HTTP access tokens, just select SSH public key as authentication method and verify that your key is both saved in GitLab and all mirrors.

+]]>
\ No newline at end of file diff --git a/tags/repo.or.cz/index.html b/tags/repo.or.cz/index.html new file mode 100644 index 000000000..09486c828 --- /dev/null +++ b/tags/repo.or.cz/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Repo.or.cz

Tag: Repo.or.cz

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/repo.or.cz/index.xml b/tags/repo.or.cz/index.xml new file mode 100644 index 000000000..3e3b84895 --- /dev/null +++ b/tags/repo.or.cz/index.xml @@ -0,0 +1,2 @@ +Repo.or.cz on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/repo.or.cz/Recent content in Repo.or.cz on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100GitLab the Git Distributorhttps://seb.xn--ho-hia.de/posts/gitlab-distributor/Mon, 13 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/gitlab-distributor/<p><a href="https://git-scm.com/">Git</a> at its core is a decentralized version control system. Yet many people are relying on a single central server (github.com at the time of this writing) to share their work with others. <a href="https://git-scm.com/book/en/v2/Distributed-Git-Distributed-Workflows">Pro Git</a> rightfully mentions it at first position in its chapter about distributed workflows because using a central server is usually the simplest approach to sharing code.</p> +<p>While the central server approach is easy to use, it might not work in all scenarios:</p> \ No newline at end of file diff --git a/tags/repository/atom.xml b/tags/repository/atom.xml new file mode 100644 index 000000000..5e3298625 --- /dev/null +++ b/tags/repository/atom.xml @@ -0,0 +1,25 @@ +HugoRepository on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/repository/Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/2021-11-08T00:00:00+00:002023-01-06T15:27:21+01:00Some time ago, Google started hosting a copy of Maven Central. Configure it in your ~/.m2/settings.xml like this:

+
<settings>
+  <mirrors>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (Asia)</name>
+      <url>https://maven-central-asia.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (EU)</name>
+      <url>https://maven-central-eu.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+    <mirror>
+      <id>google-maven-central</id>
+      <name>Google Maven Central (US)</name>
+      <url>https://maven-central.storage-download.googleapis.com/maven2/</url>
+      <mirrorOf>central</mirrorOf>
+    </mirror>
+  </mirrors>
+</settings>
+

Pick the mirror nearest to your location to get best speeds.

+]]>
\ No newline at end of file diff --git a/tags/repository/index.html b/tags/repository/index.html new file mode 100644 index 000000000..80c653cb3 --- /dev/null +++ b/tags/repository/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Repository

Tag: Repository

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/repository/index.xml b/tags/repository/index.xml new file mode 100644 index 000000000..dbd68e500 --- /dev/null +++ b/tags/repository/index.xml @@ -0,0 +1,24 @@ +Repository on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/repository/Recent content in Repository on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Google Centralhttps://seb.xn--ho-hia.de/posts/maven-google-central/Mon, 08 Nov 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-google-central/<p>Some time ago, <a href="https://www.google.com/">Google</a> started hosting a <a href="https://storage-download.googleapis.com/maven-central/index.html">copy</a> of <a href="https://search.maven.org/">Maven Central</a>. Configure it in your <code>~/.m2/settings.xml</code> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrors&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (Asia)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-asia.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (EU)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central-eu.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>google-maven-central<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;name&gt;</span>Google Maven Central (US)<span class="nt">&lt;/name&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;url&gt;</span>https://maven-central.storage-download.googleapis.com/maven2/<span class="nt">&lt;/url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirror&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/mirrors&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Pick the mirror nearest to your location to get best speeds.</p> \ No newline at end of file diff --git a/tags/reproducible/atom.xml b/tags/reproducible/atom.xml new file mode 100644 index 000000000..e7b3c2f6a --- /dev/null +++ b/tags/reproducible/atom.xml @@ -0,0 +1,13 @@ +HugoReproducible on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/reproducible/Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/2021-05-17T00:00:00+00:002023-01-06T16:22:24+01:00To create reproducible builds with Maven projects, it’s enough to specify the project.build.outputTimestamp property like this:

+
<properties>
+    <project.build.outputTimestamp>2020</project.build.outputTimestamp>
+</properties>
+
+ +]]>
\ No newline at end of file diff --git a/tags/reproducible/index.html b/tags/reproducible/index.html new file mode 100644 index 000000000..88e64eded --- /dev/null +++ b/tags/reproducible/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Reproducible

Tag: Reproducible

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/reproducible/index.xml b/tags/reproducible/index.xml new file mode 100644 index 000000000..4fada449b --- /dev/null +++ b/tags/reproducible/index.xml @@ -0,0 +1,12 @@ +Reproducible on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/reproducible/Recent content in Reproducible on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Creating reproducible artifacts with Mavenhttps://seb.xn--ho-hia.de/posts/maven-reproducible/Mon, 17 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-reproducible/<p>To create <a href="https://reproducible-builds.org/">reproducible builds</a> with <a href="https://maven.apache.org/">Maven</a> projects, it&rsquo;s enough to specify the <code>project.build.outputTimestamp</code> property like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.outputTimestamp&gt;</span>2020<span class="nt">&lt;/project.build.outputTimestamp&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +<li><a href="https://maven.apache.org/guides/mini/guide-reproducible-builds.html">https://maven.apache.org/guides/mini/guide-reproducible-builds.html</a></li> +<li><a href="https://github.com/rodiontsev/maven-build-info-plugin">https://github.com/rodiontsev/maven-build-info-plugin</a></li> +<li><a href="https://github.com/phax/ph-buildinfo-maven-plugin">https://github.com/phax/ph-buildinfo-maven-plugin</a></li> +<li><a href="https://github.com/Zlika/reproducible-build-maven-plugin">https://github.com/Zlika/reproducible-build-maven-plugin</a></li> +</ul> \ No newline at end of file diff --git a/tags/rfc/atom.xml b/tags/rfc/atom.xml new file mode 100644 index 000000000..125344fc8 --- /dev/null +++ b/tags/rfc/atom.xml @@ -0,0 +1,7 @@ +HugoRfc on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/rfc/Proper hostnames in your local networkhttps://seb.xn--ho-hia.de/posts/home-network-hostnames/2022-01-08T00:00:00+00:002023-01-06T16:22:24+01:00Thanks to RFC 8375, we now have a proper domain to use for all our local devices. Simply move everything underneath .home.arpa to join the fun. In case you have hostnamectl available on your system run the following command to change the hostname of a device:

+
# set hostname
+$ hostnamectl hostname some-device.home.arpa
+
+# check hostname
+$ hostnamectl status
+
]]>
\ No newline at end of file diff --git a/tags/rfc/index.html b/tags/rfc/index.html new file mode 100644 index 000000000..7275480ff --- /dev/null +++ b/tags/rfc/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Rfc

Tag: Rfc

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/rfc/index.xml b/tags/rfc/index.xml new file mode 100644 index 000000000..bfaecfabb --- /dev/null +++ b/tags/rfc/index.xml @@ -0,0 +1,7 @@ +Rfc on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/rfc/Recent content in Rfc on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Proper hostnames in your local networkhttps://seb.xn--ho-hia.de/posts/home-network-hostnames/Sat, 08 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/home-network-hostnames/<p>Thanks to <a href="https://www.rfc-editor.org/rfc/rfc8375.html">RFC 8375</a>, we now have a proper domain to use for all our local devices. Simply move everything underneath <code>.home.arpa</code> to join the fun. In case you have <code>hostnamectl</code> available on your system run the following command to change the hostname of a device:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> <span class="nb">set</span> hostname +</span></span><span class="line"><span class="cl"><span class="gp">$</span> hostnamectl hostname some-device.home.arpa +</span></span><span class="line"><span class="cl"><span class="err"> +</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="gp">#</span> check hostname +</span></span><span class="line"><span class="cl"><span class="gp">$</span> hostnamectl status +</span></span></code></pre></div> \ No newline at end of file diff --git a/tags/schedule/atom.xml b/tags/schedule/atom.xml new file mode 100644 index 000000000..b0f1ccea6 --- /dev/null +++ b/tags/schedule/atom.xml @@ -0,0 +1,22 @@ +HugoSchedule on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/schedule/Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/2021-02-22T00:00:00+00:002023-01-06T16:40:24+01:00To delay the execution of an GitHub Action, use a mixture of the on: schedule: ... configuration, and a conditional build step.

+
name: <PIPELINE>
+on:
+  schedule:
+    - cron: '<CRON>'
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Count commits in last week
+        id: commits
+        run: echo "::set-output name=count::$(git rev-list --count HEAD --since='<DATE>')"
+      - name: Build project
+        if: steps.commits.outputs.count > 0
+        run: build-project
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <CRON>: cron expression - use https://crontab.guru/.
  • +
  • <DATE>: Git date expression that matches <CRON>.
  • +
+]]>
\ No newline at end of file diff --git a/tags/schedule/index.html b/tags/schedule/index.html new file mode 100644 index 000000000..6a8d1474b --- /dev/null +++ b/tags/schedule/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Schedule

Tag: Schedule

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/schedule/index.xml b/tags/schedule/index.xml new file mode 100644 index 000000000..6cba513b9 --- /dev/null +++ b/tags/schedule/index.xml @@ -0,0 +1,21 @@ +Schedule on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/schedule/Recent content in Schedule on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Delay GitHub Actions buildshttps://seb.xn--ho-hia.de/posts/github-actions-schedule-build/Mon, 22 Feb 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-schedule-build/<p>To delay the execution of an <a href="https://github.com/features/actions">GitHub Action</a>, use a mixture of the <code>on: schedule: ...</code> configuration, and a <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif">conditional build step</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">schedule</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">cron</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&lt;CRON&gt;&#39;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Count commits in last week</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">commits</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=count::$(git rev-list --count HEAD --since=&#39;&lt;DATE&gt;&#39;)&#34;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">steps.commits.outputs.count &gt; 0</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">build-project</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;CRON&gt;</code>: cron expression - use <a href="https://crontab.guru/">https://crontab.guru/</a>.</li> +<li><code>&lt;DATE&gt;</code>: Git date expression that matches <code>&lt;CRON&gt;</code>.</li> +</ul> \ No newline at end of file diff --git a/tags/screenlock/atom.xml b/tags/screenlock/atom.xml new file mode 100644 index 000000000..a69264a11 --- /dev/null +++ b/tags/screenlock/atom.xml @@ -0,0 +1,5 @@ +HugoScreenlock on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/screenlock/Lock your screen on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenlock/2021-04-05T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM users can use swaylock to lock their screen. Place the following key binding in your Sway configuration:

+
# lock your screen
+bindsym $mod+Ctrl+l exec swaylock --color 000000
+

$mod+Ctrl+l will lock your screen and turn it to black. The --color flag allows any color in the form of rrggbb[aa].

+]]>
\ No newline at end of file diff --git a/tags/screenlock/index.html b/tags/screenlock/index.html new file mode 100644 index 000000000..96b2ef713 --- /dev/null +++ b/tags/screenlock/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Screenlock

Tag: Screenlock

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/screenlock/index.xml b/tags/screenlock/index.xml new file mode 100644 index 000000000..a9b53b44d --- /dev/null +++ b/tags/screenlock/index.xml @@ -0,0 +1,4 @@ +Screenlock on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/screenlock/Recent content in Screenlock on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Lock your screen on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenlock/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenlock/<p><a href="https://swaywm.org/">SwayWM</a> users can use <a href="https://github.com/swaywm/swaylock">swaylock</a> to lock their screen. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># lock your screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Ctrl+l <span class="nb">exec</span> swaylock --color <span class="m">000000</span> +</span></span></code></pre></div><p><code>$mod+Ctrl+l</code> will lock your screen and turn it to black. The <code>--color</code> flag allows any color in the form of <code>rrggbb[aa]</code>.</p> \ No newline at end of file diff --git a/tags/screenshot/atom.xml b/tags/screenshot/atom.xml new file mode 100644 index 000000000..538d80053 --- /dev/null +++ b/tags/screenshot/atom.xml @@ -0,0 +1,7 @@ +HugoScreenshot on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/screenshot/Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/2021-03-22T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM uses can use a mixture of grim and slurp to take screenshots of their desktop. Place the following key binding in your Sway configuration:

+
# take screenshot of currently focused screen
+bindsym $mod+Print exec /usr/bin/grim -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
+# take screenshot of selection
+bindsym $mod+Shift+p exec /usr/bin/grim -g "$(/usr/bin/slurp)" $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
]]>
\ No newline at end of file diff --git a/tags/screenshot/index.html b/tags/screenshot/index.html new file mode 100644 index 000000000..bc02a2bb3 --- /dev/null +++ b/tags/screenshot/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Screenshot

Tag: Screenshot

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/screenshot/index.xml b/tags/screenshot/index.xml new file mode 100644 index 000000000..f91342e89 --- /dev/null +++ b/tags/screenshot/index.xml @@ -0,0 +1,7 @@ +Screenshot on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/screenshot/Recent content in Screenshot on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/Mon, 22 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenshots/<p><a href="https://swaywm.org/">SwayWM</a> uses can use a mixture of <a href="https://github.com/emersion/grim">grim</a> and <a href="https://github.com/emersion/slurp">slurp</a> to take screenshots of their desktop. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># take screenshot of currently focused screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Print <span class="nb">exec</span> /usr/bin/grim -o <span class="k">$(</span>swaymsg -t get_outputs <span class="p">|</span> jq -r <span class="s1">&#39;.[] | select(.focused) | .name&#39;</span><span class="k">)</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># take screenshot of selection</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Shift+p <span class="nb">exec</span> /usr/bin/grim -g <span class="s2">&#34;</span><span class="k">$(</span>/usr/bin/slurp<span class="k">)</span><span class="s2">&#34;</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span></code></pre></div> \ No newline at end of file diff --git a/tags/search/atom.xml b/tags/search/atom.xml new file mode 100644 index 000000000..6194a4e43 --- /dev/null +++ b/tags/search/atom.xml @@ -0,0 +1,47 @@ +HugoSearch on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/search/passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/2022-12-27T00:00:00+00:002023-01-06T16:40:24+01:00To fuzzy search through passwords managed with passage, I’ve written the following script that is inspired by the upstream version which is using fzf.

+
fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

This version requires fd, skim, xargs, and passage itself of course. The detailed breakdown on how it works is as follows:

+
    +
  1. Use fd to find all files within ${PASSAGE_DIR} that end in .age. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.
  2. +
  3. Using both --base-directory and --exec echo '{.}' ensures that passwords are returned in such form that they can be passed back into passage again. The placeholder '{.}' is a feature provided by fd which strips the file extension from each returned value.
  4. +
  5. All passwords are then passed into sk to allow to fuzzy search across them all. Setting --no-multi ensures that only a single password can be selected.
  6. +
  7. Finally, xargs calls passage and replaces the curly braces with the selected password. Thanks to --clip=1, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.
  8. +
+

To call that script, I’ve saved it as passage-fuzzy-search.sh in my .local/bin folder and added some checks into it to verify that every required software is actually installed.

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This shell script presents passwords saved with passage through skim
+#
+# Call it like this:
+#   passage-fuzzy-search.sh
+#
+# Required software that isn't in GNU coreutils:
+#   - 'passage' to read passwords
+#   - 'fd' to find passwords
+#   - 'sk' to filter passwords
+###############################################################################
+
+if ! (( ${+commands[passage]} )); then
+    echo 'passage not installed. Please install passage.'
+    exit
+fi
+if ! (( ${+commands[sk]} )); then
+    echo 'sk not installed. Please install skim.'
+    exit
+fi
+if ! (( ${+commands[fd]} )); then
+    echo 'fd not installed. Please install fd-find.'
+    exit
+fi
+
+fd --type=file --base-directory="${PASSAGE_DIR:-${HOME}/.passage/store}" .age --exec echo '{.}' | \
+  sk --cycle --layout=reverse --tiebreak=score --no-multi | \
+  xargs --replace --max-args=1 --no-run-if-empty \
+    passage show --clip=1 {}
+

Since typing passage-fuzzy-search.sh is way too long, I have added an alias like this:

+
alias pp='passage-fuzzy-search.sh'
+
]]>
\ No newline at end of file diff --git a/tags/search/index.html b/tags/search/index.html new file mode 100644 index 000000000..b3939ab91 --- /dev/null +++ b/tags/search/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Search

Tag: Search

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/search/index.xml b/tags/search/index.xml new file mode 100644 index 000000000..5fea50d46 --- /dev/null +++ b/tags/search/index.xml @@ -0,0 +1,13 @@ +Search on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/search/Recent content in Search on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100passage fuzzy searchhttps://seb.xn--ho-hia.de/posts/passage-fuzzy-search/Tue, 27 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/passage-fuzzy-search/<p>To fuzzy search through passwords managed with <a href="https://github.com/FiloSottile/passage">passage</a>, I&rsquo;ve written the following script that is inspired by the upstream version which is using <code>fzf</code>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">fd --type<span class="o">=</span>file --base-directory<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PASSAGE_DIR</span><span class="k">:-</span><span class="si">${</span><span class="nv">HOME</span><span class="si">}</span><span class="p">/.passage/store</span><span class="si">}</span><span class="s2">&#34;</span> .age --exec <span class="nb">echo</span> <span class="s1">&#39;{.}&#39;</span> <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> sk --cycle --layout<span class="o">=</span>reverse --tiebreak<span class="o">=</span>score --no-multi <span class="p">|</span> <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> xargs --replace --max-args<span class="o">=</span><span class="m">1</span> --no-run-if-empty <span class="se">\ +</span></span></span><span class="line"><span class="cl"><span class="se"></span> passage show --clip<span class="o">=</span><span class="m">1</span> <span class="o">{}</span> +</span></span></code></pre></div><p>This version requires <a href="https://github.com/sharkdp/fd/">fd</a>, <a href="https://github.com/lotabout/skim">skim</a>, <a href="https://www.gnu.org/software/findutils/manual/html_node/find_html/Invoking-xargs.html">xargs</a>, and <a href="https://github.com/FiloSottile/passage">passage</a> itself of course. The detailed breakdown on how it works is as follows:</p> +<ol> +<li>Use <code>fd</code> to find all files within <code>${PASSAGE_DIR}</code> that end in <code>.age</code>. Each password in passage is inside that folder and has such a file extensions, therefore we are selecting every password we have.</li> +<li>Using both <code>--base-directory</code> and <code>--exec echo '{.}'</code> ensures that passwords are returned in such form that they can be passed back into <code>passage</code> again. The placeholder <code>'{.}'</code> is a feature provided by <code>fd</code> which strips the file extension from each returned value.</li> +<li>All passwords are then passed into <code>sk</code> to allow to fuzzy search across them all. Setting <code>--no-multi</code> ensures that only a single password can be selected.</li> +<li>Finally, <code>xargs</code> calls <code>passage</code> and replaces the curly braces with the selected password. Thanks to <code>--clip=1</code>, the first line in the selected password entry will be copied to the clipboard and automatically cleared after 45 seconds.</li> +</ol> +<p>To call that script, I&rsquo;ve saved it as <code>passage-fuzzy-search.sh</code> in my <code>.local/bin</code> folder and added some checks into it to verify that every required software is actually installed.</p> \ No newline at end of file diff --git a/tags/service-worker/atom.xml b/tags/service-worker/atom.xml new file mode 100644 index 000000000..40eee9531 --- /dev/null +++ b/tags/service-worker/atom.xml @@ -0,0 +1,51 @@ +HugoService Worker on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/service-worker/Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/2021-01-25T00:00:00+00:002023-01-06T16:40:24+01:00To use a serviceworker to cache a Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/javascript"]
+  suffixes = ["js"]
+[outputFormats.ServiceWorker]
+  name = "ServiceWorker"
+  mediaType = "application/javascript"
+  baseName = "serviceworker"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.serviceworker.js with the following content:

+
const CACHE = 'cache-and-update';
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(precache());
+});
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(fromCache(event.request));
+  event.waitUntil(update(event.request));
+});
+
+const precache = async () => {
+    const cache = await caches.open(CACHE);
+    return await cache.addAll([
+        {{ range $i, $e := .Site.RegularPages }}
+        '{{ $.RelPermalink }}'{{ if $i }}, {{ end }}
+        {{ end }}
+    ]);
+}
+
+const fromCache = async (request) => {
+    const cache = await caches.open(CACHE);
+    const match = await cache.match(request);
+    return match || Promise.reject('no-match');
+}
+
+const update = async (request) => {
+    const cache = await caches.open(CACHE);
+    const response = await fetch(request);
+    return await cache.put(request, response);
+}
+
+ +]]>
\ No newline at end of file diff --git a/tags/service-worker/index.html b/tags/service-worker/index.html new file mode 100644 index 000000000..3ef48857d --- /dev/null +++ b/tags/service-worker/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Service Worker

Tag: Service Worker

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/service-worker/index.xml b/tags/service-worker/index.xml new file mode 100644 index 000000000..29334b5ca --- /dev/null +++ b/tags/service-worker/index.xml @@ -0,0 +1,50 @@ +Service Worker on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/service-worker/Recent content in Service Worker on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Service workers with Hugohttps://seb.xn--ho-hia.de/posts/hugo-serviceworkers/Mon, 25 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-serviceworkers/<p>To use a <a href="https://serviceworke.rs/">serviceworker</a> to cache a <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/javascript&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;js&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">ServiceWorker</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;ServiceWorker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/javascript&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;serviceworker&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.serviceworker.js</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">const CACHE = &#39;cache-and-update&#39;; + +self.addEventListener(&#39;install&#39;, (event) =&gt; { + event.waitUntil(precache()); +}); + +self.addEventListener(&#39;fetch&#39;, (event) =&gt; { + event.respondWith(fromCache(event.request)); + event.waitUntil(update(event.request)); +}); + +const precache = async () =&gt; { + const cache = await caches.open(CACHE); + return await cache.addAll([ + {{ range $i, $e := .Site.RegularPages }} + &#39;{{ $.RelPermalink }}&#39;{{ if $i }}, {{ end }} + {{ end }} + ]); +} + +const fromCache = async (request) =&gt; { + const cache = await caches.open(CACHE); + const match = await cache.match(request); + return match || Promise.reject(&#39;no-match&#39;); +} + +const update = async (request) =&gt; { + const cache = await caches.open(CACHE); + const response = await fetch(request); + return await cache.put(request, response); +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://github.com/gohugoio/hugo/issues/5495">https://github.com/gohugoio/hugo/issues/5495</a></li> +<li><a href="https://github.com/wildhaber/offline-first-sw">https://github.com/wildhaber/offline-first-sw</a></li> +<li><a href="https://gohugohq.com/howto/go-offline-with-service-worker/">https://gohugohq.com/howto/go-offline-with-service-worker/</a></li> +</ul> \ No newline at end of file diff --git a/tags/shell/atom.xml b/tags/shell/atom.xml new file mode 100644 index 000000000..3969fe794 --- /dev/null +++ b/tags/shell/atom.xml @@ -0,0 +1,76 @@ +HugoShell on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/shell/chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/2022-12-12T00:00:00+00:002023-01-06T16:40:24+01:00Many CLI applications offer initialization scripts to integrate into a shell, for example starship init zsh or zoxide init zsh. The documentation of these tools usually tell you to put something like eval "$(starship init zsh)" into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the init command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.

+

chezmoi provides a template function called output which replaces itself with the output of the command you specified. You can use that function this to integrate various tools into your shell as the following example shows while using zsh:

+
    +
  1. Create a directory that holds all init scripts for every tool you want to use. +
     $ mkdir --parents "${ZDOTDIR}"/tools.d
    +
  2. +
  3. Let your shell load all available scripts in that directory. This snippet should be part of your .zshrc file: +
    for init_script in "${ZDOTDIR}"/tools.d/*.sh; do
    +  source "${init_script}"
    +done
    +
  4. +
  5. Create chezmoi .tmpl files for each tool and place them in the chezmoi source directory that matches the directory you created in step 1: +
    {{ output "starship" "init" "zsh" "--print-full-init" }}
    +
  6. +
  7. Call chezmoi apply to generate the init scripts.
  8. +
+

The only downside here is that you have to re-run chezmoi apply after updating one of the tools because they change their init scripts sometimes. That problem can be solved with chezmoi auto-updates.

+]]>
Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/2022-01-01T00:00:00+00:002023-01-06T16:22:24+01:00Both Vim and Neovim have a built-in plugin mechanism that loads plugins from ~/.vim/pack/*/{start,opt}/* (Vim) or ~/.local/share/nvim/site/pack/*/{start,opt}/* (Neovim). All you have to do to install new plugins, is to git clone their repository into those directories. To automatically update those clones, create the following script:

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This script git-pulls all installed nvim plugins which are using the built-in
+# nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack
+#
+# Required software that is not in GNU coreutils:
+#   - 'git' to fetch plugin updates from upstream
+###############################################################################
+
+### User specific variables, adjust to your own needs
+
+# folder that contains all nvim plugins
+PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack"
+
+### Script logic
+
+echo "updating all plugins in ${PLUGIN_DIR}"
+# iterate through all directories and git pull em
+for directory in "${PLUGIN_DIR}"/*/{start,opt}/*; do
+    if [ -d "${directory}" ]; then
+        plugin=$(basename "${directory}")
+        echo "updating ${plugin}"
+        git -C "${directory}" pull --quiet
+    fi
+done
+

In case you are using Vim, adjust the PLUGIN_DIR variable to point to your Vim plugin directory instead and save the resulting shell script as a file called update-nvim-plugins.sh in some folder of your choice. Do not forget to set the executable bit with chmod +x /path/to/your/folder/update-nvim-plugins.sh. Since all good developers must be lazy, write the following systemd service to execute the above script automatically:

+
[Unit]
+Description=cron job that triggers an update of all nvim plugins
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/path/to/your/folder/update-nvim-plugins.sh
+RemainAfterExit=false
+

Adjust the ExecStart line to match the location where you saved the above script and place that service definition in a file called nvim-plugins-update.service into your local ~/.config/systemd/user directory. Add another file called nvim-plugins-update.timer next to it that defines a systemd timer with the following content:

+
[Unit]
+Description=Update nvim plugins every week
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+RandomizedDelaySec=5hours
+
+[Install]
+WantedBy=timers.target
+

Adjust how often you want to update the plugins you are using in the OnCalendar line. Enable this service/timer with:

+
$ systemctl --user enable nvim-plugins-update
+

Finally, add the following shell aliases to make it easier to interact with the created systemd units:

+
# trigger an update manually
+alias update-nvim-plugins='systemctl --user start nvim-plugins-update'
+# see status of last auto-update
+alias update-nvim-plugins-status='systemctl --user status nvim-plugins-update'
+# see logs of past auto-updates
+alias update-nvim-plugins-logs='journalctl --user --unit nvim-plugins-update'
+

With this setup in place, all your plugins will be automatically updated once per week or however often you have configured in the timer.

+]]>
\ No newline at end of file diff --git a/tags/shell/index.html b/tags/shell/index.html new file mode 100644 index 000000000..2f9b23f2c --- /dev/null +++ b/tags/shell/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Shell

Tag: Shell

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/shell/index.xml b/tags/shell/index.xml new file mode 100644 index 000000000..2b5d211a1 --- /dev/null +++ b/tags/shell/index.xml @@ -0,0 +1,28 @@ +Shell on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/shell/Recent content in Shell on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100chezmoi & shell init scriptshttps://seb.xn--ho-hia.de/posts/shell-init/Mon, 12 Dec 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/shell-init/<p>Many CLI applications offer initialization scripts to integrate into a shell, for example <code>starship init zsh</code> or <code>zoxide init zsh</code>. The documentation of these tools usually tell you to put something like <code>eval &quot;$(starship init zsh)&quot;</code> into your shell RC file. While this approach works fine, it does decrease startup speed of your shell because it needs to run the <code>init</code> command every time you open a new shell. Given that you open shells much more often than new versions of these tools are released and installed, you can cache the output of these commands to get a bit of speed back.</p>Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/<p>Both <a href="https://www.vim.org/">Vim</a> and <a href="https://neovim.io/">Neovim</a> have a <a href="https://vimhelp.org/repeat.txt.html#packages">built-in</a> <a href="https://neovim.io/doc/user/usr_05.html#plugin">plugin mechanism</a> that loads plugins from <code>~/.vim/pack/*/{start,opt}/*</code> (Vim) or <code>~/.local/share/nvim/site/pack/*/{start,opt}/*</code> (Neovim). All you have to do to install new plugins, is to <code>git clone</code> their repository into those directories. To automatically update those clones, create the following script:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env zsh +</span></span></span><span class="line"><span class="cl"><span class="cp"></span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"><span class="c1"># This script git-pulls all installed nvim plugins which are using the built-in</span> +</span></span><span class="line"><span class="cl"><span class="c1"># nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack</span> +</span></span><span class="line"><span class="cl"><span class="c1">#</span> +</span></span><span class="line"><span class="cl"><span class="c1"># Required software that is not in GNU coreutils:</span> +</span></span><span class="line"><span class="cl"><span class="c1"># - &#39;git&#39; to fetch plugin updates from upstream</span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### User specific variables, adjust to your own needs</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># folder that contains all nvim plugins</span> +</span></span><span class="line"><span class="cl"><span class="nv">PLUGIN_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_DATA_HOME</span><span class="k">:-</span><span class="nv">$HOME</span><span class="p">/.local/share</span><span class="si">}</span><span class="s2">/nvim/site/pack&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### Script logic</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;updating all plugins in </span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"><span class="c1"># iterate through all directories and git pull em</span> +</span></span><span class="line"><span class="cl"><span class="k">for</span> directory in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span>/*/<span class="o">{</span>start,opt<span class="o">}</span>/*<span class="p">;</span> <span class="k">do</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> +</span></span><span class="line"><span class="cl"> <span class="nv">plugin</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;updating </span><span class="si">${</span><span class="nv">plugin</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"> git -C <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> pull --quiet +</span></span><span class="line"><span class="cl"> <span class="k">fi</span> +</span></span><span class="line"><span class="cl"><span class="k">done</span> +</span></span></code></pre></div><p>In case you are using Vim, adjust the <code>PLUGIN_DIR</code> variable to point to your Vim plugin directory instead and save the resulting shell script as a file called <code>update-nvim-plugins.sh</code> in some folder of your choice. Do not forget to set the executable bit with <code>chmod +x /path/to/your/folder/update-nvim-plugins.sh</code>. Since all good developers must be lazy, write the following <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html">systemd service</a> to execute the above script automatically:</p> \ No newline at end of file diff --git a/tags/slurp/atom.xml b/tags/slurp/atom.xml new file mode 100644 index 000000000..dc0e88d44 --- /dev/null +++ b/tags/slurp/atom.xml @@ -0,0 +1,7 @@ +HugoSlurp on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/slurp/Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/2021-03-22T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM uses can use a mixture of grim and slurp to take screenshots of their desktop. Place the following key binding in your Sway configuration:

+
# take screenshot of currently focused screen
+bindsym $mod+Print exec /usr/bin/grim -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
+# take screenshot of selection
+bindsym $mod+Shift+p exec /usr/bin/grim -g "$(/usr/bin/slurp)" $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
]]>
\ No newline at end of file diff --git a/tags/slurp/index.html b/tags/slurp/index.html new file mode 100644 index 000000000..b39501dca --- /dev/null +++ b/tags/slurp/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Slurp

Tag: Slurp

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/slurp/index.xml b/tags/slurp/index.xml new file mode 100644 index 000000000..254c8e5ea --- /dev/null +++ b/tags/slurp/index.xml @@ -0,0 +1,7 @@ +Slurp on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/slurp/Recent content in Slurp on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/Mon, 22 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenshots/<p><a href="https://swaywm.org/">SwayWM</a> uses can use a mixture of <a href="https://github.com/emersion/grim">grim</a> and <a href="https://github.com/emersion/slurp">slurp</a> to take screenshots of their desktop. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># take screenshot of currently focused screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Print <span class="nb">exec</span> /usr/bin/grim -o <span class="k">$(</span>swaymsg -t get_outputs <span class="p">|</span> jq -r <span class="s1">&#39;.[] | select(.focused) | .name&#39;</span><span class="k">)</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># take screenshot of selection</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Shift+p <span class="nb">exec</span> /usr/bin/grim -g <span class="s2">&#34;</span><span class="k">$(</span>/usr/bin/slurp<span class="k">)</span><span class="s2">&#34;</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span></code></pre></div> \ No newline at end of file diff --git a/tags/sonarqube/atom.xml b/tags/sonarqube/atom.xml new file mode 100644 index 000000000..eb2c13efb --- /dev/null +++ b/tags/sonarqube/atom.xml @@ -0,0 +1,37 @@ +HugoSonarqube on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/sonarqube/Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/2021-05-03T00:00:00+00:002023-01-06T17:32:06+01:00To analyze Maven projects with SonarCloud using GitHub Actions, first create the following settings.xml file:

+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
+
+    <pluginGroups>
+        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
+    </pluginGroups>
+
+    <activeProfiles>
+        <activeProfile>sonar</activeProfile>
+    </activeProfiles>
+
+    <profiles>
+        <profile>
+            <id>sonar</id>
+            <properties>
+                <sonar.host.url>https://sonarcloud.io</sonar.host.url>
+                <sonar.organization>YOUR_ORG</sonar.organization>
+                <sonar.projectKey>YOUR_PROJECT</sonar.projectKey>
+                <sonar.login>${env.SONAR_TOKEN}</sonar.login>
+            </properties>
+        </profile>
+    </profiles>
+</settings>
+

Finally, add a step to your workflow:

+
- name: Verify Project
+  run: mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar
+  env:
+    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+
+ +]]>
\ No newline at end of file diff --git a/tags/sonarqube/index.html b/tags/sonarqube/index.html new file mode 100644 index 000000000..d4e4335f6 --- /dev/null +++ b/tags/sonarqube/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Sonarqube

Tag: Sonarqube

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/sonarqube/index.xml b/tags/sonarqube/index.xml new file mode 100644 index 000000000..0886a75c8 --- /dev/null +++ b/tags/sonarqube/index.xml @@ -0,0 +1,36 @@ +Sonarqube on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/sonarqube/Recent content in Sonarqube on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Analyze Maven projects with SonarCloud using GitHub Actionshttps://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/Mon, 03 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-github-sonarcloud/<p>To analyze <a href="https://maven.apache.org/">Maven</a> projects with <a href="https://sonarcloud.io">SonarCloud</a> using <a href="https://github.com/features/actions">GitHub Actions</a>, first create the following <code>settings.xml</code> file:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;settings</span> <span class="na">xmlns=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xmlns:xsi=</span><span class="s">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="na">xsi:schemaLocation=</span><span class="s">&#34;http://maven.apache.org/SETTINGS/1.0.0 +</span></span></span><span class="line"><span class="cl"><span class="s"> http://maven.apache.org/xsd/settings-1.0.0.xsd&#34;</span><span class="nt">&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;pluginGroup&gt;</span>org.sonarsource.scanner.maven<span class="nt">&lt;/pluginGroup&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/pluginGroups&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;activeProfile&gt;</span>sonar<span class="nt">&lt;/activeProfile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/activeProfiles&gt;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profiles&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;id&gt;</span>sonar<span class="nt">&lt;/id&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.host.url&gt;</span>https://sonarcloud.io<span class="nt">&lt;/sonar.host.url&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.organization&gt;</span>YOUR_ORG<span class="nt">&lt;/sonar.organization&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.projectKey&gt;</span>YOUR_PROJECT<span class="nt">&lt;/sonar.projectKey&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;sonar.login&gt;</span>${env.SONAR_TOKEN}<span class="nt">&lt;/sonar.login&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profile&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;/profiles&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/settings&gt;</span> +</span></span></code></pre></div><p>Finally, add a step to your workflow:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Verify Project</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">mvn --settings $GITHUB_WORKSPACE/settings.xml verify sonar:sonar</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">SONAR_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.SONAR_TOKEN }}</span><span class="w"> +</span></span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/settings.html">https://maven.apache.org/settings.html</a></li> +<li><a href="https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/">https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-maven/</a></li> +</ul> \ No newline at end of file diff --git a/tags/ssh/atom.xml b/tags/ssh/atom.xml new file mode 100644 index 000000000..6db5eefed --- /dev/null +++ b/tags/ssh/atom.xml @@ -0,0 +1,41 @@ +HugoSsh on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/ssh/Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/2020-06-15T00:00:00+00:002023-01-06T17:32:06+01:00In case you don’t want to write git clone git@github.com:orga/repo.git all the time, consider using a custom SSH configuration (~/.ssh/config) like this:

+
Host github
+    HostName github.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITHUB>
+
+Host gitlab
+    HostName gitlab.com
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-GITLAB>
+
+Host bitbucket
+    HostName bitbucket.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-BITBUCKET>
+
+Host codeberg
+    HostName codeberg.org
+    User git
+    IdentityFile ~/.ssh/<KEY-FOR-CODEBERG>
+

Once configured, you can now write:

+
$ git clone github:orga/repo
+$ git clone gitlab:orga/repo
+$ git clone bitbucket:orga/repo
+$ git clone codeberg:orga/repo
+

In case you are working with many repositories inside a single organization, consider adding the following Git configuration ($XDG_CONFIG_HOME/git/config or ~/.gitconfig):

+
[url "github:orga/"]
+  insteadOf = orga:
+[url "gitlab:orga/"]
+  insteadOf = orgl:
+[url "bitbucket:orga/"]
+  insteadOf = orgb:
+[url "codeberg:orga/"]
+  insteadOf = orgc:
+

Which allows you to just write:

+
$ git clone orga:repo
+$ git clone orgl:repo
+$ git clone orgb:repo
+$ git clone orgc:repo
+

Git will substitute the insteadOf values like orga: with the configured url (for example github:orga/). The actual clone URL is github:orga/repo at this point, which can be used by Git together with the SSH configuration mentioned above to clone repositories.

+]]>
\ No newline at end of file diff --git a/tags/ssh/index.html b/tags/ssh/index.html new file mode 100644 index 000000000..4b6083882 --- /dev/null +++ b/tags/ssh/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Ssh

Tag: Ssh

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/ssh/index.xml b/tags/ssh/index.xml new file mode 100644 index 000000000..408a41f3f --- /dev/null +++ b/tags/ssh/index.xml @@ -0,0 +1,26 @@ +Ssh on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/ssh/Recent content in Ssh on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Short Git Cloneshttps://seb.xn--ho-hia.de/posts/short-git-clones/Mon, 15 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/short-git-clones/<p>In case you don&rsquo;t want to write <code>git clone git@github.com:orga/repo.git</code> all the time, consider using a custom SSH configuration (<code>~/.ssh/config</code>) like this:</p> +<pre tabindex="0"><code>Host github + HostName github.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITHUB&gt; + +Host gitlab + HostName gitlab.com + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-GITLAB&gt; + +Host bitbucket + HostName bitbucket.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-BITBUCKET&gt; + +Host codeberg + HostName codeberg.org + User git + IdentityFile ~/.ssh/&lt;KEY-FOR-CODEBERG&gt; +</code></pre><p>Once configured, you can now write:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git clone github:orga/repo +</span></span><span class="line"><span class="cl">$ git clone gitlab:orga/repo +</span></span><span class="line"><span class="cl">$ git clone bitbucket:orga/repo +</span></span><span class="line"><span class="cl">$ git clone codeberg:orga/repo +</span></span></code></pre></div><p>In case you are working with many repositories inside a single organization, consider adding the following Git configuration (<code>$XDG_CONFIG_HOME/git/config</code> or <code>~/.gitconfig</code>):</p> \ No newline at end of file diff --git a/tags/status-bar/atom.xml b/tags/status-bar/atom.xml new file mode 100644 index 000000000..8b9b17732 --- /dev/null +++ b/tags/status-bar/atom.xml @@ -0,0 +1,8 @@ +HugoStatus Bar on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/status-bar/tmux Status Barhttps://seb.xn--ho-hia.de/posts/tmux-status-bar/2021-08-09T00:00:00+00:002023-01-06T16:22:24+01:00To see the currently active tmux status bar configuration, call:

+
$ tmux show-options -g | grep status
+

Change on of those values with in the current tmux session:

+
$ tmux set-option status-right ""
+

Persist the change in your tmux.conf like this:

+
# disable right side of status bar
+set-option -g status-right ""
+
]]>
\ No newline at end of file diff --git a/tags/status-bar/index.html b/tags/status-bar/index.html new file mode 100644 index 000000000..a7bcf8d37 --- /dev/null +++ b/tags/status-bar/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Status Bar

Tag: Status Bar

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/status-bar/index.xml b/tags/status-bar/index.xml new file mode 100644 index 000000000..ac3eca37b --- /dev/null +++ b/tags/status-bar/index.xml @@ -0,0 +1,8 @@ +Status Bar on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/status-bar/Recent content in Status Bar on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100tmux Status Barhttps://seb.xn--ho-hia.de/posts/tmux-status-bar/Mon, 09 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-status-bar/<p>To see the currently active <a href="https://github.com/tmux/tmux">tmux</a> status bar configuration, call:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ tmux show-options -g <span class="p">|</span> grep status +</span></span></code></pre></div><p>Change on of those values with in the current tmux session:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ tmux set-option status-right <span class="s2">&#34;&#34;</span> +</span></span></code></pre></div><p>Persist the change in your <code>tmux.conf</code> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># disable right side of status bar</span> +</span></span><span class="line"><span class="cl">set-option -g status-right <span class="s2">&#34;&#34;</span> +</span></span></code></pre></div> \ No newline at end of file diff --git a/tags/swaywm/atom.xml b/tags/swaywm/atom.xml new file mode 100644 index 000000000..921fbbdb5 --- /dev/null +++ b/tags/swaywm/atom.xml @@ -0,0 +1,28 @@ +HugoSwaywm on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/swaywm/Waybar on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-waybar/2021-08-23T00:00:00+00:002023-01-06T16:40:24+01:00Waybar can be used as a status bar for SwayWM. You tell Sway to use it with the following snippet in your Sway configuration:

+
bar {
+    swaybar_command waybar
+}
+

Configure Waybar itself in ~/.config/waybar/config:

+
{
+    "layer": "top",
+    "modules-left": ["sway/workspaces", "sway/mode"],
+    "modules-center": ["sway/window"],
+    "modules-right": ["clock"],
+    "sway/window": {
+        "max-length": 50
+    },
+    "clock": {
+        "format-alt": "{:%a, %d. %b  %H:%M}"
+    }
+}
+
]]>
Lock your screen on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenlock/2021-04-05T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM users can use swaylock to lock their screen. Place the following key binding in your Sway configuration:

+
# lock your screen
+bindsym $mod+Ctrl+l exec swaylock --color 000000
+

$mod+Ctrl+l will lock your screen and turn it to black. The --color flag allows any color in the form of rrggbb[aa].

+]]>
Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/2021-03-22T00:00:00+00:002023-01-06T16:40:24+01:00SwayWM uses can use a mixture of grim and slurp to take screenshots of their desktop. Place the following key binding in your Sway configuration:

+
# take screenshot of currently focused screen
+bindsym $mod+Print exec /usr/bin/grim -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
+# take screenshot of selection
+bindsym $mod+Shift+p exec /usr/bin/grim -g "$(/usr/bin/slurp)" $(xdg-user-dir PICTURES)/$(date +'%Y-%m-%d-%H%M%S.png')
+
]]>
\ No newline at end of file diff --git a/tags/swaywm/index.html b/tags/swaywm/index.html new file mode 100644 index 000000000..250da83e6 --- /dev/null +++ b/tags/swaywm/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Swaywm

Tag: Swaywm

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/swaywm/index.xml b/tags/swaywm/index.xml new file mode 100644 index 000000000..7638e7860 --- /dev/null +++ b/tags/swaywm/index.xml @@ -0,0 +1,27 @@ +Swaywm on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/swaywm/Recent content in Swaywm on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Waybar on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-waybar/Mon, 23 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-waybar/<p><a href="https://github.com/Alexays/Waybar/">Waybar</a> can be used as a status bar for <a href="https://swaywm.org/">SwayWM</a>. You tell Sway to use it with the following snippet in your Sway configuration:</p> +<pre tabindex="0"><code>bar { + swaybar_command waybar +} +</code></pre><p>Configure Waybar itself in <code>~/.config/waybar/config</code>:</p> +<pre tabindex="0"><code>{ + &#34;layer&#34;: &#34;top&#34;, + &#34;modules-left&#34;: [&#34;sway/workspaces&#34;, &#34;sway/mode&#34;], + &#34;modules-center&#34;: [&#34;sway/window&#34;], + &#34;modules-right&#34;: [&#34;clock&#34;], + &#34;sway/window&#34;: { + &#34;max-length&#34;: 50 + }, + &#34;clock&#34;: { + &#34;format-alt&#34;: &#34;{:%a, %d. %b %H:%M}&#34; + } +} +</code></pre>Lock your screen on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenlock/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenlock/<p><a href="https://swaywm.org/">SwayWM</a> users can use <a href="https://github.com/swaywm/swaylock">swaylock</a> to lock their screen. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># lock your screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Ctrl+l <span class="nb">exec</span> swaylock --color <span class="m">000000</span> +</span></span></code></pre></div><p><code>$mod+Ctrl+l</code> will lock your screen and turn it to black. The <code>--color</code> flag allows any color in the form of <code>rrggbb[aa]</code>.</p>Taken screenshots on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-screenshots/Mon, 22 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-screenshots/<p><a href="https://swaywm.org/">SwayWM</a> uses can use a mixture of <a href="https://github.com/emersion/grim">grim</a> and <a href="https://github.com/emersion/slurp">slurp</a> to take screenshots of their desktop. Place the following key binding in your Sway configuration:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># take screenshot of currently focused screen</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Print <span class="nb">exec</span> /usr/bin/grim -o <span class="k">$(</span>swaymsg -t get_outputs <span class="p">|</span> jq -r <span class="s1">&#39;.[] | select(.focused) | .name&#39;</span><span class="k">)</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># take screenshot of selection</span> +</span></span><span class="line"><span class="cl">bindsym <span class="nv">$mod</span>+Shift+p <span class="nb">exec</span> /usr/bin/grim -g <span class="s2">&#34;</span><span class="k">$(</span>/usr/bin/slurp<span class="k">)</span><span class="s2">&#34;</span> <span class="k">$(</span>xdg-user-dir PICTURES<span class="k">)</span>/<span class="k">$(</span>date +<span class="s1">&#39;%Y-%m-%d-%H%M%S.png&#39;</span><span class="k">)</span> +</span></span></code></pre></div> \ No newline at end of file diff --git a/tags/systemd/atom.xml b/tags/systemd/atom.xml new file mode 100644 index 000000000..1625b37ea --- /dev/null +++ b/tags/systemd/atom.xml @@ -0,0 +1,79 @@ +HugoSystemd on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/systemd/Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/2022-01-01T00:00:00+00:002023-01-06T16:22:24+01:00Both Vim and Neovim have a built-in plugin mechanism that loads plugins from ~/.vim/pack/*/{start,opt}/* (Vim) or ~/.local/share/nvim/site/pack/*/{start,opt}/* (Neovim). All you have to do to install new plugins, is to git clone their repository into those directories. To automatically update those clones, create the following script:

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This script git-pulls all installed nvim plugins which are using the built-in
+# nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack
+#
+# Required software that is not in GNU coreutils:
+#   - 'git' to fetch plugin updates from upstream
+###############################################################################
+
+### User specific variables, adjust to your own needs
+
+# folder that contains all nvim plugins
+PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack"
+
+### Script logic
+
+echo "updating all plugins in ${PLUGIN_DIR}"
+# iterate through all directories and git pull em
+for directory in "${PLUGIN_DIR}"/*/{start,opt}/*; do
+    if [ -d "${directory}" ]; then
+        plugin=$(basename "${directory}")
+        echo "updating ${plugin}"
+        git -C "${directory}" pull --quiet
+    fi
+done
+

In case you are using Vim, adjust the PLUGIN_DIR variable to point to your Vim plugin directory instead and save the resulting shell script as a file called update-nvim-plugins.sh in some folder of your choice. Do not forget to set the executable bit with chmod +x /path/to/your/folder/update-nvim-plugins.sh. Since all good developers must be lazy, write the following systemd service to execute the above script automatically:

+
[Unit]
+Description=cron job that triggers an update of all nvim plugins
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/path/to/your/folder/update-nvim-plugins.sh
+RemainAfterExit=false
+

Adjust the ExecStart line to match the location where you saved the above script and place that service definition in a file called nvim-plugins-update.service into your local ~/.config/systemd/user directory. Add another file called nvim-plugins-update.timer next to it that defines a systemd timer with the following content:

+
[Unit]
+Description=Update nvim plugins every week
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+RandomizedDelaySec=5hours
+
+[Install]
+WantedBy=timers.target
+

Adjust how often you want to update the plugins you are using in the OnCalendar line. Enable this service/timer with:

+
$ systemctl --user enable nvim-plugins-update
+

Finally, add the following shell aliases to make it easier to interact with the created systemd units:

+
# trigger an update manually
+alias update-nvim-plugins='systemctl --user start nvim-plugins-update'
+# see status of last auto-update
+alias update-nvim-plugins-status='systemctl --user status nvim-plugins-update'
+# see logs of past auto-updates
+alias update-nvim-plugins-logs='journalctl --user --unit nvim-plugins-update'
+

With this setup in place, all your plugins will be automatically updated once per week or however often you have configured in the timer.

+]]>
emacs and systemdhttps://seb.xn--ho-hia.de/posts/emacs-systemd/2021-07-26T00:00:00+00:002023-01-06T16:40:24+01:00I like to use emacs to edit files in a terminal. It tends to start a little slow, therefore I’ve created a systemd unit to automatically start the emacs daemon and use aliases to connect to the running daemon. The unit looks like this:

+
[Unit]
+Description=Emacs text editor [%I]
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+
+[Service]
+Type=forking
+ExecStart=/usr/bin/emacs --daemon=%i
+ExecStop=/usr/bin/emacsclient --eval "(kill-emacs)"
+Environment=SSH_AUTH_SOCK=%t/keyring/ssh
+Restart=on-failure
+
+[Install]
+WantedBy=default.target
+

Enable it with systemctl --user enable emacs@user and define any number of aliases to make connecting to the emacs daemon easier:

+
alias e='emacsclient --tty --socket-name=user'
+alias vim='emacsclient --tty --socket-name=user'
+alias vi='emacsclient --tty --socket-name=user'
+alias nano='emacsclient --tty --socket-name=user'
+alias ed='emacsclient --tty --socket-name=user'
+
]]>
\ No newline at end of file diff --git a/tags/systemd/index.html b/tags/systemd/index.html new file mode 100644 index 000000000..68fb8d2a4 --- /dev/null +++ b/tags/systemd/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Systemd

Tag: Systemd

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/systemd/index.xml b/tags/systemd/index.xml new file mode 100644 index 000000000..2191ba03b --- /dev/null +++ b/tags/systemd/index.xml @@ -0,0 +1,42 @@ +Systemd on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/systemd/Recent content in Systemd on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/<p>Both <a href="https://www.vim.org/">Vim</a> and <a href="https://neovim.io/">Neovim</a> have a <a href="https://vimhelp.org/repeat.txt.html#packages">built-in</a> <a href="https://neovim.io/doc/user/usr_05.html#plugin">plugin mechanism</a> that loads plugins from <code>~/.vim/pack/*/{start,opt}/*</code> (Vim) or <code>~/.local/share/nvim/site/pack/*/{start,opt}/*</code> (Neovim). All you have to do to install new plugins, is to <code>git clone</code> their repository into those directories. To automatically update those clones, create the following script:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env zsh +</span></span></span><span class="line"><span class="cl"><span class="cp"></span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"><span class="c1"># This script git-pulls all installed nvim plugins which are using the built-in</span> +</span></span><span class="line"><span class="cl"><span class="c1"># nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack</span> +</span></span><span class="line"><span class="cl"><span class="c1">#</span> +</span></span><span class="line"><span class="cl"><span class="c1"># Required software that is not in GNU coreutils:</span> +</span></span><span class="line"><span class="cl"><span class="c1"># - &#39;git&#39; to fetch plugin updates from upstream</span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### User specific variables, adjust to your own needs</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># folder that contains all nvim plugins</span> +</span></span><span class="line"><span class="cl"><span class="nv">PLUGIN_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_DATA_HOME</span><span class="k">:-</span><span class="nv">$HOME</span><span class="p">/.local/share</span><span class="si">}</span><span class="s2">/nvim/site/pack&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### Script logic</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;updating all plugins in </span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"><span class="c1"># iterate through all directories and git pull em</span> +</span></span><span class="line"><span class="cl"><span class="k">for</span> directory in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span>/*/<span class="o">{</span>start,opt<span class="o">}</span>/*<span class="p">;</span> <span class="k">do</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> +</span></span><span class="line"><span class="cl"> <span class="nv">plugin</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;updating </span><span class="si">${</span><span class="nv">plugin</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"> git -C <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> pull --quiet +</span></span><span class="line"><span class="cl"> <span class="k">fi</span> +</span></span><span class="line"><span class="cl"><span class="k">done</span> +</span></span></code></pre></div><p>In case you are using Vim, adjust the <code>PLUGIN_DIR</code> variable to point to your Vim plugin directory instead and save the resulting shell script as a file called <code>update-nvim-plugins.sh</code> in some folder of your choice. Do not forget to set the executable bit with <code>chmod +x /path/to/your/folder/update-nvim-plugins.sh</code>. Since all good developers must be lazy, write the following <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html">systemd service</a> to execute the above script automatically:</p>emacs and systemdhttps://seb.xn--ho-hia.de/posts/emacs-systemd/Mon, 26 Jul 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/emacs-systemd/<p>I like to use <a href="https://www.gnu.org/software/emacs/">emacs</a> to edit files in a terminal. It tends to start a little slow, therefore I&rsquo;ve created a <a href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</a> unit to automatically start the emacs daemon and use aliases to connect to the running daemon. The unit looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span> +</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Emacs text editor [%I]</span> +</span></span><span class="line"><span class="cl"><span class="na">Documentation</span><span class="o">=</span><span class="s">info:emacs man:emacs(1) https://gnu.org/software/emacs/</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Service]</span> +</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">forking</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/emacs --daemon=%i</span> +</span></span><span class="line"><span class="cl"><span class="na">ExecStop</span><span class="o">=</span><span class="s">/usr/bin/emacsclient --eval &#34;(kill-emacs)&#34;</span> +</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">SSH_AUTH_SOCK=%t/keyring/ssh</span> +</span></span><span class="line"><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">on-failure</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="k">[Install]</span> +</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span> +</span></span></code></pre></div><p>Enable it with <code>systemctl --user enable emacs@user</code> and define any number of aliases to make connecting to the emacs daemon easier:</p> \ No newline at end of file diff --git a/tags/teamwork/atom.xml b/tags/teamwork/atom.xml new file mode 100644 index 000000000..a3311d23c --- /dev/null +++ b/tags/teamwork/atom.xml @@ -0,0 +1,21 @@ +HugoTeamwork on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/teamwork/Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/2021-03-08T00:00:00+00:002023-01-06T16:40:24+01:00README file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a Makefile in the root of your project which contains the exact same instructions. Thanks to make, all your contributors can now use TAB-completion to run any of the pre-defined make targets.

+

The following example is part of one of my projects, and I certainly don’t want to type (or even copy) that all the time:

+
.PHONY: release-into-local-nexus
+release-into-local-nexus:
+	mvn versions:set \
+	   -DnewVersion=$(TIMESTAMPED_VERSION) \
+	   -DgenerateBackupPoms=false
+	-mvn clean deploy scm:tag \
+	   -DpushChanges=false \
+	   -DskipLocalStaging=true \
+	   -Drelease=local
+	mvn versions:set \
+	   -DnewVersion=9999.99.99-SNAPSHOT \
+	   -DgenerateBackupPoms=false
+

With the above target in place, everyone can now do make release-into-local-nexus instead of typing/copying the commands themselves. Thanks to TAB-completion you just have to do make r<TAB> and confirm with >ENTER> to perform a release.

+ + +]]>
\ No newline at end of file diff --git a/tags/teamwork/index.html b/tags/teamwork/index.html new file mode 100644 index 000000000..0e66bdac6 --- /dev/null +++ b/tags/teamwork/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Teamwork

Tag: Teamwork

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/teamwork/index.xml b/tags/teamwork/index.xml new file mode 100644 index 000000000..ff316d80b --- /dev/null +++ b/tags/teamwork/index.xml @@ -0,0 +1 @@ +Teamwork on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/teamwork/Recent content in Teamwork on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Executable READMEshttps://seb.xn--ho-hia.de/posts/makefile-readme/Mon, 08 Mar 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/makefile-readme/<p><code>README</code> file typically contain information about the project itself, for example how it can be installed/used/build. Most of the time these files contains command line instructions that users/contributors copy and paste into their terminal. Instead of doing that, consider placing a <code>Makefile</code> in the root of your project which contains the exact same instructions. Thanks to <code>make</code>, all your contributors can now use TAB-completion to run any of the pre-defined <code>make</code> targets.</p> \ No newline at end of file diff --git a/tags/timestamp/atom.xml b/tags/timestamp/atom.xml new file mode 100644 index 000000000..b7f8c5b83 --- /dev/null +++ b/tags/timestamp/atom.xml @@ -0,0 +1,17 @@ +HugoTimestamp on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/timestamp/Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/2020-06-11T00:00:00+00:002023-01-06T15:27:21+01:00In case you are into calver or have another reason to create a timestamp with GitHub Actions, do the following:

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Create release version
+        id: <ID>
+        run: echo "::set-output name=<NAME>::$(date +'%Y.%m.%d-%H%M%S')"
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <ID>: The unique ID of the timestamp step.
  • +
  • <NAME>: The name of the created timestamp.
  • +
+

The special syntax ::set-output name=<NAME>:: declares that the output of the command (echo) should be saved in a variable called <NAME>. Together with the <ID> of the pipeline step, this value can be referenced with the expression ${{ steps.<ID>.outputs.<NAME> }} in the following steps of your pipeline.

+]]>
\ No newline at end of file diff --git a/tags/timestamp/index.html b/tags/timestamp/index.html new file mode 100644 index 000000000..cec7fccea --- /dev/null +++ b/tags/timestamp/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Timestamp

Tag: Timestamp

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/timestamp/index.xml b/tags/timestamp/index.xml new file mode 100644 index 000000000..394b2ff13 --- /dev/null +++ b/tags/timestamp/index.xml @@ -0,0 +1,16 @@ +Timestamp on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/timestamp/Recent content in Timestamp on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Create Timestamp with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/Thu, 11 Jun 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-create-timestamp/<p>In case you are into <a href="https://calver.org/">calver</a> or have another reason to create a timestamp with <a href="https://github.com/features/actions">GitHub Actions</a>, do the following:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create release version</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;ID&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;::set-output name=&lt;NAME&gt;::$(date +&#39;%Y.%m.%d-%H%M%S&#39;)&#34;</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;ID&gt;</code>: The unique ID of the timestamp step.</li> +<li><code>&lt;NAME&gt;</code>: The name of the created timestamp.</li> +</ul> +<p>The special syntax <code>::set-output name=&lt;NAME&gt;::</code> declares that the output of the command (<code>echo</code>) should be saved in a variable called <code>&lt;NAME&gt;</code>. Together with the <code>&lt;ID&gt;</code> of the pipeline step, this value can be referenced with the expression <code>${{ steps.&lt;ID&gt;.outputs.&lt;NAME&gt; }}</code> in the following steps of your pipeline.</p> \ No newline at end of file diff --git a/tags/tmux/atom.xml b/tags/tmux/atom.xml new file mode 100644 index 000000000..bd2bb15bb --- /dev/null +++ b/tags/tmux/atom.xml @@ -0,0 +1,36 @@ +HugoTmux on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/tmux/Manage tmux sessions with tmuxphttps://seb.xn--ho-hia.de/posts/tmux-tmuxp/2021-12-20T00:00:00+00:002023-01-06T16:22:24+01:00To manage tmux sessions, I like to use tmuxp. It works by having pre-defined sessions in ~/.config/tmuxp which looks like this:

+
session_name: cool-app
+start_directory: ~/projects/cool-app
+windows:
+- window_name: backend
+  start_directory: backend
+- window_name: frontend
+  start_directory: frontend
+

In case the name of the file is cool-app.yaml, you can open the sessions with tmuxp load cool-app --yes.

+]]>
tmux Status Barhttps://seb.xn--ho-hia.de/posts/tmux-status-bar/2021-08-09T00:00:00+00:002023-01-06T16:22:24+01:00To see the currently active tmux status bar configuration, call:

+
$ tmux show-options -g | grep status
+

Change on of those values with in the current tmux session:

+
$ tmux set-option status-right ""
+

Persist the change in your tmux.conf like this:

+
# disable right side of status bar
+set-option -g status-right ""
+
]]>
Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/2021-04-19T00:00:00+00:002023-01-06T16:22:24+01:00To use tmux as your login shell, use chsh:

+
# list all available shells
+$ chsh --list-shells
+/bin/sh
+/bin/bash
+/sbin/nologin
+/usr/bin/sh
+/usr/bin/bash
+/usr/sbin/nologin
+/usr/bin/zsh
+/bin/zsh
+/usr/bin/tmux
+/bin/tmux
+
+# select login shell
+$ chsh --shell /usr/bin/tmux
+
]]>
Peeking with tmuxhttps://seb.xn--ho-hia.de/posts/tmux-peek/2021-04-05T00:00:00+00:002023-01-06T15:27:21+01:00tmux uses can use the following snippet to peek at files. Place it in your .bashrc or similar file.

+
peek() { tmux split-window -p 33 "$EDITOR" "$@" }
+

Calling peek <file> will open <file> in lower third of tmux window.

+]]>
\ No newline at end of file diff --git a/tags/tmux/index.html b/tags/tmux/index.html new file mode 100644 index 000000000..e6878d8c6 --- /dev/null +++ b/tags/tmux/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Tmux

Tag: Tmux

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/tmux/index.xml b/tags/tmux/index.xml new file mode 100644 index 000000000..a2d63220b --- /dev/null +++ b/tags/tmux/index.xml @@ -0,0 +1,34 @@ +Tmux on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/tmux/Recent content in Tmux on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Manage tmux sessions with tmuxphttps://seb.xn--ho-hia.de/posts/tmux-tmuxp/Mon, 20 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-tmuxp/<p>To manage <a href="https://github.com/tmux/tmux">tmux</a> sessions, I like to use <a href="https://github.com/tmux-python/tmuxp">tmuxp</a>. It works by having pre-defined sessions in <code>~/.config/tmuxp</code> which looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">session_name</span><span class="p">:</span><span class="w"> </span><span class="l">cool-app</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">~/projects/cool-app</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">windows</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">window_name</span><span class="p">:</span><span class="w"> </span><span class="l">backend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">backend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">window_name</span><span class="p">:</span><span class="w"> </span><span class="l">frontend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">frontend</span><span class="w"> +</span></span></span></code></pre></div><p>In case the name of the file is <code>cool-app.yaml</code>, you can open the sessions with <code>tmuxp load cool-app --yes</code>.</p>tmux Status Barhttps://seb.xn--ho-hia.de/posts/tmux-status-bar/Mon, 09 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-status-bar/<p>To see the currently active <a href="https://github.com/tmux/tmux">tmux</a> status bar configuration, call:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ tmux show-options -g <span class="p">|</span> grep status +</span></span></code></pre></div><p>Change on of those values with in the current tmux session:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ tmux set-option status-right <span class="s2">&#34;&#34;</span> +</span></span></code></pre></div><p>Persist the change in your <code>tmux.conf</code> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># disable right side of status bar</span> +</span></span><span class="line"><span class="cl">set-option -g status-right <span class="s2">&#34;&#34;</span> +</span></span></code></pre></div>Use tmux as your login shellhttps://seb.xn--ho-hia.de/posts/tmux-login-shell/Mon, 19 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-login-shell/<p>To use <a href="https://github.com/tmux/tmux">tmux</a> as your login shell, use <code>chsh</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># list all available shells</span> +</span></span><span class="line"><span class="cl">$ chsh --list-shells +</span></span><span class="line"><span class="cl">/bin/sh +</span></span><span class="line"><span class="cl">/bin/bash +</span></span><span class="line"><span class="cl">/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/sh +</span></span><span class="line"><span class="cl">/usr/bin/bash +</span></span><span class="line"><span class="cl">/usr/sbin/nologin +</span></span><span class="line"><span class="cl">/usr/bin/zsh +</span></span><span class="line"><span class="cl">/bin/zsh +</span></span><span class="line"><span class="cl">/usr/bin/tmux +</span></span><span class="line"><span class="cl">/bin/tmux +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># select login shell</span> +</span></span><span class="line"><span class="cl">$ chsh --shell /usr/bin/tmux +</span></span></code></pre></div>Peeking with tmuxhttps://seb.xn--ho-hia.de/posts/tmux-peek/Mon, 05 Apr 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-peek/<p><a href="https://github.com/tmux/tmux">tmux</a> uses can use the following snippet to peek at files. Place it in your <code>.bashrc</code> or similar file.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">peek<span class="o">()</span> <span class="o">{</span> tmux split-window -p <span class="m">33</span> <span class="s2">&#34;</span><span class="nv">$EDITOR</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span> <span class="o">}</span> +</span></span></code></pre></div><p>Calling <code>peek &lt;file&gt;</code> will open <code>&lt;file&gt;</code> in lower third of tmux window.</p> \ No newline at end of file diff --git a/tags/tmuxp/atom.xml b/tags/tmuxp/atom.xml new file mode 100644 index 000000000..356088484 --- /dev/null +++ b/tags/tmuxp/atom.xml @@ -0,0 +1,10 @@ +HugoTmuxp on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/tmuxp/Manage tmux sessions with tmuxphttps://seb.xn--ho-hia.de/posts/tmux-tmuxp/2021-12-20T00:00:00+00:002023-01-06T16:22:24+01:00To manage tmux sessions, I like to use tmuxp. It works by having pre-defined sessions in ~/.config/tmuxp which looks like this:

+
session_name: cool-app
+start_directory: ~/projects/cool-app
+windows:
+- window_name: backend
+  start_directory: backend
+- window_name: frontend
+  start_directory: frontend
+

In case the name of the file is cool-app.yaml, you can open the sessions with tmuxp load cool-app --yes.

+]]>
\ No newline at end of file diff --git a/tags/tmuxp/index.html b/tags/tmuxp/index.html new file mode 100644 index 000000000..c2e5a98c0 --- /dev/null +++ b/tags/tmuxp/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Tmuxp

Tag: Tmuxp

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/tmuxp/index.xml b/tags/tmuxp/index.xml new file mode 100644 index 000000000..061b46c72 --- /dev/null +++ b/tags/tmuxp/index.xml @@ -0,0 +1,9 @@ +Tmuxp on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/tmuxp/Recent content in Tmuxp on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Manage tmux sessions with tmuxphttps://seb.xn--ho-hia.de/posts/tmux-tmuxp/Mon, 20 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/tmux-tmuxp/<p>To manage <a href="https://github.com/tmux/tmux">tmux</a> sessions, I like to use <a href="https://github.com/tmux-python/tmuxp">tmuxp</a>. It works by having pre-defined sessions in <code>~/.config/tmuxp</code> which looks like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">session_name</span><span class="p">:</span><span class="w"> </span><span class="l">cool-app</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">~/projects/cool-app</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">windows</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">window_name</span><span class="p">:</span><span class="w"> </span><span class="l">backend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">backend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">window_name</span><span class="p">:</span><span class="w"> </span><span class="l">frontend</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">start_directory</span><span class="p">:</span><span class="w"> </span><span class="l">frontend</span><span class="w"> +</span></span></span></code></pre></div><p>In case the name of the file is <code>cool-app.yaml</code>, you can open the sessions with <code>tmuxp load cool-app --yes</code>.</p> \ No newline at end of file diff --git a/tags/toot/atom.xml b/tags/toot/atom.xml new file mode 100644 index 000000000..6343de032 --- /dev/null +++ b/tags/toot/atom.xml @@ -0,0 +1,18 @@ +HugoToot on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/toot/Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/2020-11-16T00:00:00+00:002023-01-06T15:27:21+01:00The rzr/fediverse-action action allows to send a toot in your GitHub Action.

+
name: <NAME>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Publish Toot
+        uses: rzr/fediverse-action@master
+        with:
+          access-token: ${{ secrets.MASTODON_TOKEN }}
+          message: <MESSAGE>
+          host: ${{ secrets.MASTODON_SERVER }}
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
  • <MESSAGE>: Message for the toot.
  • +
+]]>
\ No newline at end of file diff --git a/tags/toot/index.html b/tags/toot/index.html new file mode 100644 index 000000000..bcf7715b3 --- /dev/null +++ b/tags/toot/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Toot

Tag: Toot

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/toot/index.xml b/tags/toot/index.xml new file mode 100644 index 000000000..58920e03b --- /dev/null +++ b/tags/toot/index.xml @@ -0,0 +1,17 @@ +Toot on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/toot/Recent content in Toot on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Send toots with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-send-toot/Mon, 16 Nov 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-send-toot/<p>The <a href="https://github.com/rzr/fediverse-action">rzr/fediverse-action</a> action allows to send a <a href="https://joinmastodon.org/">toot</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;NAME&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Publish Toot</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">rzr/fediverse-action@master</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">access-token</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">message</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;MESSAGE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.MASTODON_SERVER }}</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +<li><code>&lt;MESSAGE&gt;</code>: Message for the toot.</li> +</ul> \ No newline at end of file diff --git a/tags/updates/atom.xml b/tags/updates/atom.xml new file mode 100644 index 000000000..a933fb53a --- /dev/null +++ b/tags/updates/atom.xml @@ -0,0 +1,59 @@ +HugoUpdates on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/updates/Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/2022-01-01T00:00:00+00:002023-01-06T16:22:24+01:00Both Vim and Neovim have a built-in plugin mechanism that loads plugins from ~/.vim/pack/*/{start,opt}/* (Vim) or ~/.local/share/nvim/site/pack/*/{start,opt}/* (Neovim). All you have to do to install new plugins, is to git clone their repository into those directories. To automatically update those clones, create the following script:

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This script git-pulls all installed nvim plugins which are using the built-in
+# nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack
+#
+# Required software that is not in GNU coreutils:
+#   - 'git' to fetch plugin updates from upstream
+###############################################################################
+
+### User specific variables, adjust to your own needs
+
+# folder that contains all nvim plugins
+PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack"
+
+### Script logic
+
+echo "updating all plugins in ${PLUGIN_DIR}"
+# iterate through all directories and git pull em
+for directory in "${PLUGIN_DIR}"/*/{start,opt}/*; do
+    if [ -d "${directory}" ]; then
+        plugin=$(basename "${directory}")
+        echo "updating ${plugin}"
+        git -C "${directory}" pull --quiet
+    fi
+done
+

In case you are using Vim, adjust the PLUGIN_DIR variable to point to your Vim plugin directory instead and save the resulting shell script as a file called update-nvim-plugins.sh in some folder of your choice. Do not forget to set the executable bit with chmod +x /path/to/your/folder/update-nvim-plugins.sh. Since all good developers must be lazy, write the following systemd service to execute the above script automatically:

+
[Unit]
+Description=cron job that triggers an update of all nvim plugins
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/path/to/your/folder/update-nvim-plugins.sh
+RemainAfterExit=false
+

Adjust the ExecStart line to match the location where you saved the above script and place that service definition in a file called nvim-plugins-update.service into your local ~/.config/systemd/user directory. Add another file called nvim-plugins-update.timer next to it that defines a systemd timer with the following content:

+
[Unit]
+Description=Update nvim plugins every week
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+RandomizedDelaySec=5hours
+
+[Install]
+WantedBy=timers.target
+

Adjust how often you want to update the plugins you are using in the OnCalendar line. Enable this service/timer with:

+
$ systemctl --user enable nvim-plugins-update
+

Finally, add the following shell aliases to make it easier to interact with the created systemd units:

+
# trigger an update manually
+alias update-nvim-plugins='systemctl --user start nvim-plugins-update'
+# see status of last auto-update
+alias update-nvim-plugins-status='systemctl --user status nvim-plugins-update'
+# see logs of past auto-updates
+alias update-nvim-plugins-logs='journalctl --user --unit nvim-plugins-update'
+

With this setup in place, all your plugins will be automatically updated once per week or however often you have configured in the timer.

+]]>
\ No newline at end of file diff --git a/tags/updates/index.html b/tags/updates/index.html new file mode 100644 index 000000000..5316326e4 --- /dev/null +++ b/tags/updates/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Updates

Tag: Updates

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/updates/index.xml b/tags/updates/index.xml new file mode 100644 index 000000000..fe3bd05e1 --- /dev/null +++ b/tags/updates/index.xml @@ -0,0 +1,28 @@ +Updates on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/updates/Recent content in Updates on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/<p>Both <a href="https://www.vim.org/">Vim</a> and <a href="https://neovim.io/">Neovim</a> have a <a href="https://vimhelp.org/repeat.txt.html#packages">built-in</a> <a href="https://neovim.io/doc/user/usr_05.html#plugin">plugin mechanism</a> that loads plugins from <code>~/.vim/pack/*/{start,opt}/*</code> (Vim) or <code>~/.local/share/nvim/site/pack/*/{start,opt}/*</code> (Neovim). All you have to do to install new plugins, is to <code>git clone</code> their repository into those directories. To automatically update those clones, create the following script:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env zsh +</span></span></span><span class="line"><span class="cl"><span class="cp"></span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"><span class="c1"># This script git-pulls all installed nvim plugins which are using the built-in</span> +</span></span><span class="line"><span class="cl"><span class="c1"># nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack</span> +</span></span><span class="line"><span class="cl"><span class="c1">#</span> +</span></span><span class="line"><span class="cl"><span class="c1"># Required software that is not in GNU coreutils:</span> +</span></span><span class="line"><span class="cl"><span class="c1"># - &#39;git&#39; to fetch plugin updates from upstream</span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### User specific variables, adjust to your own needs</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># folder that contains all nvim plugins</span> +</span></span><span class="line"><span class="cl"><span class="nv">PLUGIN_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_DATA_HOME</span><span class="k">:-</span><span class="nv">$HOME</span><span class="p">/.local/share</span><span class="si">}</span><span class="s2">/nvim/site/pack&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### Script logic</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;updating all plugins in </span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"><span class="c1"># iterate through all directories and git pull em</span> +</span></span><span class="line"><span class="cl"><span class="k">for</span> directory in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span>/*/<span class="o">{</span>start,opt<span class="o">}</span>/*<span class="p">;</span> <span class="k">do</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> +</span></span><span class="line"><span class="cl"> <span class="nv">plugin</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;updating </span><span class="si">${</span><span class="nv">plugin</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"> git -C <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> pull --quiet +</span></span><span class="line"><span class="cl"> <span class="k">fi</span> +</span></span><span class="line"><span class="cl"><span class="k">done</span> +</span></span></code></pre></div><p>In case you are using Vim, adjust the <code>PLUGIN_DIR</code> variable to point to your Vim plugin directory instead and save the resulting shell script as a file called <code>update-nvim-plugins.sh</code> in some folder of your choice. Do not forget to set the executable bit with <code>chmod +x /path/to/your/folder/update-nvim-plugins.sh</code>. Since all good developers must be lazy, write the following <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html">systemd service</a> to execute the above script automatically:</p> \ No newline at end of file diff --git a/tags/upload/atom.xml b/tags/upload/atom.xml new file mode 100644 index 000000000..803b25d8d --- /dev/null +++ b/tags/upload/atom.xml @@ -0,0 +1,21 @@ +HugoUpload on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/upload/Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/2020-10-19T00:00:00+00:002023-01-06T15:27:21+01:00The actions/upload-release-asset action allows to upload a release artifact in your GitHub Action.

+
name: <PIPELINE>
+jobs:
+  build:
+    runs-on: <RUN_ON>
+    steps:
+      - name: Upload Release Asset
+        id: upload_release_asset
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./some/path/to/file.zip
+          asset_name: public-name-for-file.zip
+          asset_content_type: application/zip
+
    +
  • <PIPELINE>: The name of your pipeline.
  • +
  • <RUN_ON>: The runner to use, see GitHub’s own documentation for possible values.
  • +
+]]>
\ No newline at end of file diff --git a/tags/upload/index.html b/tags/upload/index.html new file mode 100644 index 000000000..9ad11148d --- /dev/null +++ b/tags/upload/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Upload

Tag: Upload

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/upload/index.xml b/tags/upload/index.xml new file mode 100644 index 000000000..d617e30bb --- /dev/null +++ b/tags/upload/index.xml @@ -0,0 +1,20 @@ +Upload on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/upload/Recent content in Upload on Sebastian HoßHugoenFri, 06 Jan 2023 15:27:21 +0100Upload release assets with GitHub Actionshttps://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/Mon, 19 Oct 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/github-actions-upload-release-assets/<p>The <a href="https://github.com/actions/upload-release-asset">actions/upload-release-asset</a> action allows to upload a <a href="https://developer.github.com/v3/repos/releases/#upload-a-release-asset">release artifact</a> in your <a href="https://github.com/features/actions">GitHub Action</a>.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;PIPELINE&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">build</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;RUN_ON&gt;</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">steps</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Upload Release Asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">upload_release_asset</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/upload-release-asset@v1</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">env</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">GITHUB_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">with</span><span class="p">:</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">upload_url</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.create_release.outputs.upload_url }}</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_path</span><span class="p">:</span><span class="w"> </span><span class="l">./some/path/to/file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_name</span><span class="p">:</span><span class="w"> </span><span class="l">public-name-for-file.zip</span><span class="w"> +</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nt">asset_content_type</span><span class="p">:</span><span class="w"> </span><span class="l">application/zip</span><span class="w"> +</span></span></span></code></pre></div><ul> +<li><code>&lt;PIPELINE&gt;</code>: The name of your pipeline.</li> +<li><code>&lt;RUN_ON&gt;</code>: The runner to use, see GitHub&rsquo;s own <a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on">documentation</a> for possible values.</li> +</ul> \ No newline at end of file diff --git a/tags/versioning/atom.xml b/tags/versioning/atom.xml new file mode 100644 index 000000000..0d83d94bc --- /dev/null +++ b/tags/versioning/atom.xml @@ -0,0 +1,4 @@ +HugoVersioning on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/versioning/Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/2021-12-06T00:00:00+00:002023-01-06T16:46:32+01:00To automatically version Maven projects, I like to use the m-versions-p like this:

+
$ mvn versions:set -DnewVersion=my.new.version -DgenerateBackupPoms=false
+

This will update the version property of every module in the reactor to prepare them for the next release. In case you are using GitHub Actions, consider using a timestamp.

+]]>
\ No newline at end of file diff --git a/tags/versioning/index.html b/tags/versioning/index.html new file mode 100644 index 000000000..b1454dfb3 --- /dev/null +++ b/tags/versioning/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Versioning

Tag: Versioning

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/versioning/index.xml b/tags/versioning/index.xml new file mode 100644 index 000000000..f9e68d285 --- /dev/null +++ b/tags/versioning/index.xml @@ -0,0 +1,3 @@ +Versioning on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/versioning/Recent content in Versioning on Sebastian HoßHugoenFri, 06 Jan 2023 16:46:32 +0100Continuous Versioning with Mavenhttps://seb.xn--ho-hia.de/posts/maven-cd-versioning/Mon, 06 Dec 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-cd-versioning/<p>To automatically version <a href="https://maven.apache.org/">Maven</a> projects, I like to use the <a href="https://www.mojohaus.org/versions-maven-plugin/">m-versions-p</a> like this:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ mvn versions:set -DnewVersion<span class="o">=</span>my.new.version -DgenerateBackupPoms<span class="o">=</span><span class="nb">false</span> +</span></span></code></pre></div><p>This will update the <code>version</code> property of every module in the reactor to prepare them for the next release. In case you are using <a href="https://github.com/features/actions">GitHub Actions</a>, consider using a <a href="../github-actions-create-timestamp">timestamp</a>.</p> \ No newline at end of file diff --git a/tags/vim/atom.xml b/tags/vim/atom.xml new file mode 100644 index 000000000..d72b9dd2e --- /dev/null +++ b/tags/vim/atom.xml @@ -0,0 +1,59 @@ +HugoVim on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/vim/Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/2022-01-01T00:00:00+00:002023-01-06T16:22:24+01:00Both Vim and Neovim have a built-in plugin mechanism that loads plugins from ~/.vim/pack/*/{start,opt}/* (Vim) or ~/.local/share/nvim/site/pack/*/{start,opt}/* (Neovim). All you have to do to install new plugins, is to git clone their repository into those directories. To automatically update those clones, create the following script:

+
#!/usr/bin/env zsh
+
+###############################################################################
+# This script git-pulls all installed nvim plugins which are using the built-in
+# nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack
+#
+# Required software that is not in GNU coreutils:
+#   - 'git' to fetch plugin updates from upstream
+###############################################################################
+
+### User specific variables, adjust to your own needs
+
+# folder that contains all nvim plugins
+PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim/site/pack"
+
+### Script logic
+
+echo "updating all plugins in ${PLUGIN_DIR}"
+# iterate through all directories and git pull em
+for directory in "${PLUGIN_DIR}"/*/{start,opt}/*; do
+    if [ -d "${directory}" ]; then
+        plugin=$(basename "${directory}")
+        echo "updating ${plugin}"
+        git -C "${directory}" pull --quiet
+    fi
+done
+

In case you are using Vim, adjust the PLUGIN_DIR variable to point to your Vim plugin directory instead and save the resulting shell script as a file called update-nvim-plugins.sh in some folder of your choice. Do not forget to set the executable bit with chmod +x /path/to/your/folder/update-nvim-plugins.sh. Since all good developers must be lazy, write the following systemd service to execute the above script automatically:

+
[Unit]
+Description=cron job that triggers an update of all nvim plugins
+Wants=network-online.target
+After=network-online.target
+
+[Service]
+Type=oneshot
+ExecStart=/path/to/your/folder/update-nvim-plugins.sh
+RemainAfterExit=false
+

Adjust the ExecStart line to match the location where you saved the above script and place that service definition in a file called nvim-plugins-update.service into your local ~/.config/systemd/user directory. Add another file called nvim-plugins-update.timer next to it that defines a systemd timer with the following content:

+
[Unit]
+Description=Update nvim plugins every week
+
+[Timer]
+OnCalendar=weekly
+Persistent=true
+RandomizedDelaySec=5hours
+
+[Install]
+WantedBy=timers.target
+

Adjust how often you want to update the plugins you are using in the OnCalendar line. Enable this service/timer with:

+
$ systemctl --user enable nvim-plugins-update
+

Finally, add the following shell aliases to make it easier to interact with the created systemd units:

+
# trigger an update manually
+alias update-nvim-plugins='systemctl --user start nvim-plugins-update'
+# see status of last auto-update
+alias update-nvim-plugins-status='systemctl --user status nvim-plugins-update'
+# see logs of past auto-updates
+alias update-nvim-plugins-logs='journalctl --user --unit nvim-plugins-update'
+

With this setup in place, all your plugins will be automatically updated once per week or however often you have configured in the timer.

+]]>
\ No newline at end of file diff --git a/tags/vim/index.html b/tags/vim/index.html new file mode 100644 index 000000000..7296b4c56 --- /dev/null +++ b/tags/vim/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Vim

Tag: Vim

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/vim/index.xml b/tags/vim/index.xml new file mode 100644 index 000000000..f3131318a --- /dev/null +++ b/tags/vim/index.xml @@ -0,0 +1,28 @@ +Vim on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/vim/Recent content in Vim on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100Automatically update plugins for vim/nvimhttps://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/Sat, 01 Jan 2022 00:00:00 +0000https://seb.xn--ho-hia.de/posts/nvim-plugin-auto-updates/<p>Both <a href="https://www.vim.org/">Vim</a> and <a href="https://neovim.io/">Neovim</a> have a <a href="https://vimhelp.org/repeat.txt.html#packages">built-in</a> <a href="https://neovim.io/doc/user/usr_05.html#plugin">plugin mechanism</a> that loads plugins from <code>~/.vim/pack/*/{start,opt}/*</code> (Vim) or <code>~/.local/share/nvim/site/pack/*/{start,opt}/*</code> (Neovim). All you have to do to install new plugins, is to <code>git clone</code> their repository into those directories. To automatically update those clones, create the following script:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env zsh +</span></span></span><span class="line"><span class="cl"><span class="cp"></span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"><span class="c1"># This script git-pulls all installed nvim plugins which are using the built-in</span> +</span></span><span class="line"><span class="cl"><span class="c1"># nvim plugin manager. Those plugins are located in .local/share/nvim/site/pack</span> +</span></span><span class="line"><span class="cl"><span class="c1">#</span> +</span></span><span class="line"><span class="cl"><span class="c1"># Required software that is not in GNU coreutils:</span> +</span></span><span class="line"><span class="cl"><span class="c1"># - &#39;git&#39; to fetch plugin updates from upstream</span> +</span></span><span class="line"><span class="cl"><span class="c1">###############################################################################</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### User specific variables, adjust to your own needs</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># folder that contains all nvim plugins</span> +</span></span><span class="line"><span class="cl"><span class="nv">PLUGIN_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_DATA_HOME</span><span class="k">:-</span><span class="nv">$HOME</span><span class="p">/.local/share</span><span class="si">}</span><span class="s2">/nvim/site/pack&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1">### Script logic</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;updating all plugins in </span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"><span class="c1"># iterate through all directories and git pull em</span> +</span></span><span class="line"><span class="cl"><span class="k">for</span> directory in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PLUGIN_DIR</span><span class="si">}</span><span class="s2">&#34;</span>/*/<span class="o">{</span>start,opt<span class="o">}</span>/*<span class="p">;</span> <span class="k">do</span> +</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> +</span></span><span class="line"><span class="cl"> <span class="nv">plugin</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span> +</span></span><span class="line"><span class="cl"> <span class="nb">echo</span> <span class="s2">&#34;updating </span><span class="si">${</span><span class="nv">plugin</span><span class="si">}</span><span class="s2">&#34;</span> +</span></span><span class="line"><span class="cl"> git -C <span class="s2">&#34;</span><span class="si">${</span><span class="nv">directory</span><span class="si">}</span><span class="s2">&#34;</span> pull --quiet +</span></span><span class="line"><span class="cl"> <span class="k">fi</span> +</span></span><span class="line"><span class="cl"><span class="k">done</span> +</span></span></code></pre></div><p>In case you are using Vim, adjust the <code>PLUGIN_DIR</code> variable to point to your Vim plugin directory instead and save the resulting shell script as a file called <code>update-nvim-plugins.sh</code> in some folder of your choice. Do not forget to set the executable bit with <code>chmod +x /path/to/your/folder/update-nvim-plugins.sh</code>. Since all good developers must be lazy, write the following <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html">systemd service</a> to execute the above script automatically:</p> \ No newline at end of file diff --git a/tags/waybar/atom.xml b/tags/waybar/atom.xml new file mode 100644 index 000000000..06daa9c75 --- /dev/null +++ b/tags/waybar/atom.xml @@ -0,0 +1,18 @@ +HugoWaybar on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/waybar/Waybar on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-waybar/2021-08-23T00:00:00+00:002023-01-06T16:40:24+01:00Waybar can be used as a status bar for SwayWM. You tell Sway to use it with the following snippet in your Sway configuration:

+
bar {
+    swaybar_command waybar
+}
+

Configure Waybar itself in ~/.config/waybar/config:

+
{
+    "layer": "top",
+    "modules-left": ["sway/workspaces", "sway/mode"],
+    "modules-center": ["sway/window"],
+    "modules-right": ["clock"],
+    "sway/window": {
+        "max-length": 50
+    },
+    "clock": {
+        "format-alt": "{:%a, %d. %b  %H:%M}"
+    }
+}
+
]]>
\ No newline at end of file diff --git a/tags/waybar/index.html b/tags/waybar/index.html new file mode 100644 index 000000000..40320a699 --- /dev/null +++ b/tags/waybar/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Waybar

Tag: Waybar

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/waybar/index.xml b/tags/waybar/index.xml new file mode 100644 index 000000000..b7f23a6c9 --- /dev/null +++ b/tags/waybar/index.xml @@ -0,0 +1,18 @@ +Waybar on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/waybar/Recent content in Waybar on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Waybar on SwayWMhttps://seb.xn--ho-hia.de/posts/sway-waybar/Mon, 23 Aug 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/sway-waybar/<p><a href="https://github.com/Alexays/Waybar/">Waybar</a> can be used as a status bar for <a href="https://swaywm.org/">SwayWM</a>. You tell Sway to use it with the following snippet in your Sway configuration:</p> +<pre tabindex="0"><code>bar { + swaybar_command waybar +} +</code></pre><p>Configure Waybar itself in <code>~/.config/waybar/config</code>:</p> +<pre tabindex="0"><code>{ + &#34;layer&#34;: &#34;top&#34;, + &#34;modules-left&#34;: [&#34;sway/workspaces&#34;, &#34;sway/mode&#34;], + &#34;modules-center&#34;: [&#34;sway/window&#34;], + &#34;modules-right&#34;: [&#34;clock&#34;], + &#34;sway/window&#34;: { + &#34;max-length&#34;: 50 + }, + &#34;clock&#34;: { + &#34;format-alt&#34;: &#34;{:%a, %d. %b %H:%M}&#34; + } +} +</code></pre> \ No newline at end of file diff --git a/tags/web-app/atom.xml b/tags/web-app/atom.xml new file mode 100644 index 000000000..28bbf5621 --- /dev/null +++ b/tags/web-app/atom.xml @@ -0,0 +1,26 @@ +HugoWeb App on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/web-app/Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/2021-01-11T00:00:00+00:002023-01-06T16:40:24+01:00To publish a web app manifest document with your Hugo site, configure a media type in your config.toml:

+
[mediaTypes."application/manifest+json"]
+  suffixes = ["webmanifest"]
+[outputFormats.Webmanifest]
+  name = "Web App Manifest"
+  mediaType = "application/manifest+json"
+  baseName = "manifest"
+  isPlainText = false
+  rel = "alternate"
+  isHTML = false
+  noUgly = true
+  permalinkable = false
+

Create a new layout in _default/home.manifest.json with the following content:

+
{
+  "name": "{{ .Site.Title }}",
+  "short_name": "{{ .Site.Title }}",
+  "start_url": ".",
+  "display": "minimal-ui",
+  "background_color": "#fff",
+  "description": "{{ .Site.Params.description }}"
+}
+
+ +]]>
\ No newline at end of file diff --git a/tags/web-app/index.html b/tags/web-app/index.html new file mode 100644 index 000000000..5ad32ebda --- /dev/null +++ b/tags/web-app/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Web App

Tag: Web App

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/web-app/index.xml b/tags/web-app/index.xml new file mode 100644 index 000000000..8266c852b --- /dev/null +++ b/tags/web-app/index.xml @@ -0,0 +1,25 @@ +Web App on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/web-app/Recent content in Web App on Sebastian HoßHugoenFri, 06 Jan 2023 16:40:24 +0100Web app manifests with Hugohttps://seb.xn--ho-hia.de/posts/hugo-webmanifest/Mon, 11 Jan 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/hugo-webmanifest/<p>To publish a <a href="https://developer.mozilla.org/en-US/docs/Web/Manifest">web app manifest</a> document with your <a href="https://gohugo.io/">Hugo</a> site, configure a <a href="https://en.wikipedia.org/wiki/Media_type">media type</a> in your <code>config.toml</code>:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mediaTypes</span><span class="p">.</span><span class="s2">&#34;application/manifest+json&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">suffixes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;webmanifest&#34;</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Webmanifest</span><span class="p">]</span> +</span></span><span class="line"><span class="cl"> <span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;Web App Manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;application/manifest+json&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;manifest&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isPlainText</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">rel</span> <span class="p">=</span> <span class="s2">&#34;alternate&#34;</span> +</span></span><span class="line"><span class="cl"> <span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">false</span> +</span></span><span class="line"><span class="cl"> <span class="nx">noUgly</span> <span class="p">=</span> <span class="kc">true</span> +</span></span><span class="line"><span class="cl"> <span class="nx">permalinkable</span> <span class="p">=</span> <span class="kc">false</span> +</span></span></code></pre></div><p>Create a new layout in <code>_default/home.manifest.json</code> with the following content:</p> +<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">{ + &#34;name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;short_name&#34;: &#34;{{ .Site.Title }}&#34;, + &#34;start_url&#34;: &#34;.&#34;, + &#34;display&#34;: &#34;minimal-ui&#34;, + &#34;background_color&#34;: &#34;#fff&#34;, + &#34;description&#34;: &#34;{{ .Site.Params.description }}&#34; +} +</code></pre><h2 id="links">Links</h2> +<ul> +<li><a href="https://web.dev/add-manifest/">https://web.dev/add-manifest/</a></li> +</ul> \ No newline at end of file diff --git a/tags/windows/atom.xml b/tags/windows/atom.xml new file mode 100644 index 000000000..f25c340bb --- /dev/null +++ b/tags/windows/atom.xml @@ -0,0 +1,10 @@ +HugoWindows on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/windows/Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/2021-05-31T00:00:00+00:002023-01-06T17:32:06+01:00Maven projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.

+
<properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+</properties>
+
+ +]]>
\ No newline at end of file diff --git a/tags/windows/index.html b/tags/windows/index.html new file mode 100644 index 000000000..dfc524801 --- /dev/null +++ b/tags/windows/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Windows

Tag: Windows

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/windows/index.xml b/tags/windows/index.xml new file mode 100644 index 000000000..f285b6402 --- /dev/null +++ b/tags/windows/index.xml @@ -0,0 +1,9 @@ +Windows on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/windows/Recent content in Windows on Sebastian HoßHugoenFri, 06 Jan 2023 17:32:06 +0100Specify encoding for Maven projectshttps://seb.xn--ho-hia.de/posts/maven-encoding/Mon, 31 May 2021 00:00:00 +0000https://seb.xn--ho-hia.de/posts/maven-encoding/<p><a href="https://maven.apache.org/">Maven</a> projects by default use the file encoding of the operating system. This can be problematic in case different operating systems with different encoding settings are used to build the project. Specify the encoding of your source code and resource files as in the following snippets to fix that problem.</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;properties&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span> +</span></span><span class="line"><span class="cl"> <span class="nt">&lt;project.reporting.outputEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.reporting.outputEncoding&gt;</span> +</span></span><span class="line"><span class="cl"><span class="nt">&lt;/properties&gt;</span> +</span></span></code></pre></div><h2 id="links">Links</h2> +<ul> +<li><a href="https://maven.apache.org/pom.html#Properties">https://maven.apache.org/pom.html#Properties</a></li> +</ul> \ No newline at end of file diff --git a/tags/xdg/atom.xml b/tags/xdg/atom.xml new file mode 100644 index 000000000..f2648f745 --- /dev/null +++ b/tags/xdg/atom.xml @@ -0,0 +1,22 @@ +HugoXdg on Sebastian Hoß2024-12-02T02:59:16+00:00https://seb.xn--ho-hia.de/tags/xdg/XDG Base Directory Specificationhttps://seb.xn--ho-hia.de/posts/xdg-dot-files/2020-07-27T00:00:00+00:002023-01-06T16:22:24+01:00The XDG Base Directory Specification has been around for a while, yet not every software has adopted it yet. Here is an incomplete list of fixes:

+
# use existing env variables or define new
+[ -z "$XDG_CACHE_HOME"  ] && export XDG_CACHE_HOME="$HOME/.cache"
+[ -z "$XDG_CONFIG_DIRS" ] && export XDG_CONFIG_DIRS="/etc/xdg"
+[ -z "$XDG_CONFIG_HOME" ] && export XDG_CONFIG_HOME="$HOME/.config"
+[ -z "$XDG_DATA_DIRS"   ] && export XDG_DATA_DIRS="/usr/local/share:/usr/share"
+[ -z "$XDG_DATA_HOME"   ] && export XDG_DATA_HOME="$HOME/.local/share"
+
+# gradle
+export GRADLE_USER_HOME="$XDG_DATA_HOME/gradle"
+
+# httpie
+export HTTPIE_CONFIG_DIR="$XDG_CONFIG_HOME/httpie"
+
+# npm
+export NPM_CONFIG_USERCONFIG="$XDG_CONFIG_HOME/npm/npmrc"
+export npm_config_cache="$XDG_CACHE_HOME/npm"
+
+# password-store
+export PASSWORD_STORE_DIR="$XDG_DATA_HOME/password-store"
+

To make your own software XDG-aware, consider using the dirs-dev or configdir libraries.

+]]>
\ No newline at end of file diff --git a/tags/xdg/index.html b/tags/xdg/index.html new file mode 100644 index 000000000..95c164b94 --- /dev/null +++ b/tags/xdg/index.html @@ -0,0 +1,11 @@ +Sebastian Hoß – Xdg

Tag: Xdg

CC0
To the extent possible under law, +Sebastian Hoß +has waived all copyright and related or neighboring rights to +seb.xn--ho-hia.de. +This work is published from: +Germany

\ No newline at end of file diff --git a/tags/xdg/index.xml b/tags/xdg/index.xml new file mode 100644 index 000000000..2a2780923 --- /dev/null +++ b/tags/xdg/index.xml @@ -0,0 +1,21 @@ +Xdg on Sebastian Hoßhttps://seb.xn--ho-hia.de/tags/xdg/Recent content in Xdg on Sebastian HoßHugoenFri, 06 Jan 2023 16:22:24 +0100XDG Base Directory Specificationhttps://seb.xn--ho-hia.de/posts/xdg-dot-files/Mon, 27 Jul 2020 00:00:00 +0000https://seb.xn--ho-hia.de/posts/xdg-dot-files/<p>The <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html">XDG Base Directory Specification</a> has been around for a while, yet not every software has adopted it yet. Here is an incomplete list of fixes:</p> +<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># use existing env variables or define new</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CACHE_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CACHE_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.cache&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_DIRS</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CONFIG_DIRS</span><span class="o">=</span><span class="s2">&#34;/etc/xdg&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_CONFIG_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_DATA_DIRS</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_DATA_DIRS</span><span class="o">=</span><span class="s2">&#34;/usr/local/share:/usr/share&#34;</span> +</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">export</span> <span class="nv">XDG_DATA_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/share&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># gradle</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">GRADLE_USER_HOME</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">/gradle&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># httpie</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">HTTPIE_CONFIG_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">/httpie&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># npm</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">NPM_CONFIG_USERCONFIG</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CONFIG_HOME</span><span class="s2">/npm/npmrc&#34;</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">npm_config_cache</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_CACHE_HOME</span><span class="s2">/npm&#34;</span> +</span></span><span class="line"><span class="cl"> +</span></span><span class="line"><span class="cl"><span class="c1"># password-store</span> +</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PASSWORD_STORE_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_DATA_HOME</span><span class="s2">/password-store&#34;</span> +</span></span></code></pre></div><p>To make your own software XDG-aware, consider using the <a href="https://github.com/dirs-dev">dirs-dev</a> or <a href="https://github.com/shibukawa/configdir">configdir</a> libraries.</p> \ No newline at end of file