mirror of
https://github.com/davegallant/davegallant.github.io.git
synced 2025-08-13 20:00:20 +00:00
17 lines
36 KiB
HTML
17 lines
36 KiB
HTML
<!doctype html><html lang=en data-theme=dark><head><meta name=generator content="Hugo 0.121.1"><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=preload as=font type=font/woff2 href=/fonts/roboto-slab-latin-400.woff2 crossorigin=anonymous><link rel=preload as=font type=font/woff2 href=/fonts/roboto-slab-latin-700.woff2 crossorigin=anonymous><link rel=preload as=font type=font/woff2 href=/fonts/fira-code-latin-300.woff2 crossorigin=anonymous><link rel=preload as=font type=font/woff2 href=/fonts/fira-code-latin-400.woff2 crossorigin=anonymous><link rel=preload as=font type=font/woff2 href=/fonts/fira-code-latin-700.woff2 crossorigin=anonymous><meta name=robots content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"><title>davegallant.ca — blog</title>
|
|
<meta name=description content="This is a space where I share notes about problems and solutions I&rsquo;ve been exploring. You can reach out by email, social media, or by commenting on the posts below. Continuous improvement is what motivates me to keep learning."><link rel=canonical href=/><meta name=twitter:card content="summary"><meta name=twitter:title content="Welcome"><meta name=twitter:description content="A retro-looking Hugo theme inspired by gruvbox. The pastel colors are high contrast, easily distinguishable, pleasing to the eye, and feature light and dark color palettes."><meta property="og:title" content="Welcome"><meta property="og:description" content="A retro-looking Hugo theme inspired by gruvbox. The pastel colors are high contrast, easily distinguishable, pleasing to the eye, and feature light and dark color palettes."><meta property="og:type" content="website"><meta property="og:url" content="/"><meta itemprop=name content="Welcome"><meta itemprop=description content="A retro-looking Hugo theme inspired by gruvbox. The pastel colors are high contrast, easily distinguishable, pleasing to the eye, and feature light and dark color palettes."><link rel=alternate type=application/rss+xml href=/index.xml title=davegallant.ca><style>/*! purgecss start ignore */@font-face{font-display:swap;font-family:Roboto Slab;font-style:normal;font-weight:100;src:local("Roboto Slab Thin "),local("Roboto Slab-Thin"),url(/fonts/roboto-slab-latin-100.woff2) format("woff2"),url(/fonts/roboto-slab-latin-100.woff) format("woff")}@font-face{font-display:swap;font-family:Roboto Slab;font-style:normal;font-weight:200;src:local("Roboto Slab Extra Light "),local("Roboto Slab-Extra Light"),url(/fonts/roboto-slab-latin-200.woff2) format("woff2"),url(/fonts/roboto-slab-latin-200.woff) format("woff")}@font-face{font-display:swap;font-family:Roboto Slab;font-style:normal;font-weight:300;src:local("Roboto Slab Light "),local("Roboto Slab-Light"),url(/fonts/roboto-slab-latin-300.woff2) format("woff2"),url(/fonts/roboto-slab-latin-300.woff) format("woff")}@font-face{font-display:swap;font-family:Roboto Slab;font-style:normal;font-weight:400;src:local("Roboto Slab Regular "),local("Roboto Slab-Regular"),url(/fonts/roboto-slab-latin-400.woff2) format("woff2"),url(/fonts/roboto-slab-latin-400.woff) format("woff")}@font-face{font-display:swap;font-family:Roboto Slab;font-style:normal;font-weight:500;src:local("Roboto Slab Medium "),local("Roboto Slab-Medium"),url(/fonts/roboto-slab-latin-500.woff2) format("woff2"),url(/fonts/roboto-slab-latin-500.woff) format("woff")}@font-face{font-display:swap;font-family:Roboto Slab;font-style:normal;font-weight:600;src:local("Roboto Slab SemiBold "),local("Roboto Slab-SemiBold"),url(/fonts/roboto-slab-latin-600.woff2) format("woff2"),url(/fonts/roboto-slab-latin-600.woff) format("woff")}@font-face{font-display:swap;font-family:Roboto Slab;font-style:normal;font-weight:700;src:local("Roboto Slab Bold "),local("Roboto Slab-Bold"),url(/fonts/roboto-slab-latin-700.woff2) format("woff2"),url(/fonts/roboto-slab-latin-700.woff) format("woff")}@font-face{font-display:swap;font-family:Roboto Slab;font-style:normal;font-weight:800;src:local("Roboto Slab ExtraBold "),local("Roboto Slab-ExtraBold"),url(/fonts/roboto-slab-latin-800.woff2) format("woff2"),url(/fonts/roboto-slab-latin-800.woff) format("woff")}@font-face{font-display:swap;font-family:Roboto Slab;font-style:normal;font-weight:900;src:local("Roboto Slab Black "),local("Roboto Slab-Black"),url(/fonts/roboto-slab-latin-900.woff2) format("woff2"),url(/fonts/roboto-slab-latin-900.woff) format("woff")}@font-face{font-display:swap;font-family:Fira Code;font-style:normal;font-weight:300;src:local("Fira Code Light "),local("Fira Code-Light"),url(/fonts/fira-code-latin-300.woff2) format("woff2"),url(/fonts/fira-code-latin-300.woff) format("woff")}@font-face{font-display:swap;font-family:Fira Code;font-style:normal;font-weight:400;src:local("Fira Code Regular "),local("Fira Code-Regular"),url(/fonts/fira-code-latin-400.woff2) format("woff2"),url(/fonts/fira-code-latin-400.woff) format("woff")}@font-face{font-display:swap;font-family:Fira Code;font-style:normal;font-weight:500;src:local("Fira Code Medium "),local("Fira Code-Medium"),url(/fonts/fira-code-latin-500.woff2) format("woff2"),url(/fonts/fira-code-latin-500.woff) format("woff")}@font-face{font-display:swap;font-family:Fira Code;font-style:normal;font-weight:600;src:local("Fira Code SemiBold "),local("Fira Code-SemiBold"),url(/fonts/fira-code-latin-600.woff2) format("woff2"),url(/fonts/fira-code-latin-600.woff) format("woff")}@font-face{font-display:swap;font-family:Fira Code;font-style:normal;font-weight:700;src:local("Fira Code Bold "),local("Fira Code-Bold"),url(/fonts/fira-code-latin-700.woff2) format("woff2"),url(/fonts/fira-code-latin-700.woff) format("woff")}
|
|
|
|
/*! 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;-webkit-text-decoration:underline;text-decoration:underline;-webkit-text-decoration:underline dotted;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}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-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}[hidden],template{display:none}
|
|
|
|
/*! purgecss end ignore */
|
|
|
|
/*! CC BY-SA 3.0 License | https://stackoverflow.com/a/36118384/1154965 */@keyframes blink{50%{opacity:0}to{opacity:1}}
|
|
|
|
/*! MIT License | github.com/schnerring/hugo-theme-gruvbox */:root[data-theme=light]{--bg:var(--bg0);--bg0:#fbf1c7;--bg0_h:#f9f5d7;--bg0_s:#f2e5bc;--bg1:#ebdbb2;--bg2:#d5c4a1;--bg3:#bdae93;--bg4:#a89984;--fg:var(--fg1);--fg0:#282828;--fg1:#3c3836;--fg2:#504945;--fg3:#665c54;--fg4:#7c6f64;--gray1:var(--fg4);--gray2:#928374;--red1:#cc241d;--red2:#9d0006;--green1:#98971a;--green2:#797403;--yellow1:#d79921;--yellow2:#b57614;--blue1:#458588;--blue2:#076678;--purple1:#b16286;--purple2:#8f3f71;--aqua1:#689d6a;--aqua2:#427b58;--orange1:#d65d0e;--orange2:#af3a03}[data-theme=light]:root .light--hidden{display:none}:root[data-theme=dark]{--bg:var(--bg0);--bg0:#282828;--bg0_h:#1d2021;--bg0_s:#32302f;--bg1:#3c3836;--bg2:#504945;--bg3:#665c54;--bg4:#7c6f64;--fg:var(--fg1);--fg0:#fbf1c7;--fg1:#ebdbb2;--fg2:#d5c4a1;--fg3:#bdae93;--fg4:#a89984;--gray1:var(--fg4);--gray2:#928374;--red1:#cc241d;--red2:#fb4934;--green1:#98971a;--green2:#b8bb26;--yellow1:#d79921;--yellow2:#fabd2f;--blue1:#458588;--blue2:#83a598;--purple1:#b16286;--purple2:#d3869b;--aqua1:#689d6a;--aqua2:#8ec07c;--orange1:#d65d0e;--orange2:#fe8019}[data-theme=dark]:root .dark--hidden{display:none}:root{--primary:var(--blue1);--primary-alt:var(--blue2);--font-monospace:"Fira Code","Lucida Console",Monaco,monospace;--font-sans-serif:Verdana,Helvetica,sans-serif;--font-serif:"Roboto Slab",Georgia,serif}html{font-family:Roboto Slab,Georgia,serif;font-family:var(--font-serif);font-size:1rem;scroll-behavior:smooth}body{background:var(--bg);color:var(--fg);line-height:1.675;word-wrap:break-word}strong{letter-spacing:.35px}a{color:inherit;-webkit-text-decoration:none;text-decoration:none}a.link--external:after{content:"\2009↗"}img,video{border:2px solid var(--bg1);height:auto;max-width:100%}figure{display:inline-block}figcaption{color:var(--fg3);font-family:Roboto Slab,Georgia,serif;font-family:var(--font-serif);font-size:.9rem}::-moz-selection{background:var(--bg4);color:var(--fg0)}::selection{background:var(--bg4);color:var(--fg0)}h1,h2,h3,h4,h5,h6{color:var(--fg0);font-family:Fira Code,Lucida Console,Monaco,monospace;font-family:var(--font-monospace);font-weight:300;line-height:1.4}h1 code,h2 code,h3 code,h4 code,h5 code,h6 code{font-size:1em}h2,h3,h4,h5,h6{border-bottom:1px solid var(--bg1)}h1,h2{font-weight:400}h1{font-size:1.875rem}h2{font-size:1.75rem}h3{font-size:1.625rem}@media (min-width:768px){h1{font-size:2.375rem}h2{font-size:2rem}h3{font-size:1.75rem}}h4{font-size:1.5rem}h5{font-size:1.375rem}h6{font-size:1.25rem}table{border-collapse:collapse;margin:2rem 0;table-layout:fixed;width:100%}table,td,th{border:1px solid var(--bg1);padding:.5rem}hr{background:var(--bg1);border:none;height:1px;margin:3rem auto;width:80%}mark{background:var(--yellow1);color:var(--bg0)}abbr{text-underline-offset:.2rem}blockquote,code,kbd,mark,pre{border-radius:.2rem;padding:0 .2em}pre code{padding:0}blockquote,code,kbd,pre,th{background:var(--bg1)}code,kbd,pre,th{font-family:Fira Code,Lucida Console,Monaco,monospace;font-family:var(--font-monospace)}code code,code kbd,kbd code,kbd kbd{background:var(--bg2)}blockquote,pre{padding:1rem}pre{background:var(--bg1)!important;overflow:auto}pre code{background:none}blockquote,blockquote.twitter-tweet{border-left:5px solid var(--primary-alt);margin:.5rem 0}blockquote.twitter-tweet code,blockquote:not(.does-not-exist) code{background:var(--bg2)}blockquote.twitter-tweet p:first-of-type,blockquote:not(.does-not-exist) p:first-of-type{margin-top:0}blockquote.twitter-tweet p:last-of-type,blockquote:not(.does-not-exist) p:last-of-type{margin-bottom:0}blockquote.twitter-tweet{border-color:var(--blue2);color:inherit;font:inherit;font-size:inherit;line-height:inherit}blockquote.twitter-tweet a{color:var(--blue2)}blockquote.twitter-tweet a:hover{color:var(--blue1);-webkit-text-decoration:none!important;text-decoration:none!important}pre::-webkit-scrollbar{height:.5rem;scrollbar-width:auto}pre::-webkit-scrollbar-track{background:var(--bg2);border-radius:.2rem}pre::-webkit-scrollbar-thumb{background:var(--bg4);border-radius:.2rem}.layout{display:grid;grid-template-areas:"header" "main" "footer";grid-template-rows:auto 1fr auto;height:100vh}main{align-items:start;display:grid;grid-area:main;grid-template-areas:"empty content sidebar";grid-template-columns:2fr minmax(0,860px) 2fr}header{background:var(--bg1);grid-area:header}footer{grid-area:footer}footer,main{margin:.5em 1.1em}.content{grid-area:content}.sidebar{display:none;flex-direction:column;grid-area:sidebar;margin-top:3rem;position:sticky;top:2rem}@media (min-width:992px){.sidebar{display:flex}}header{display:grid;font-family:Fira Code,Lucida Console,Monaco,monospace;font-family:var(--font-monospace);font-size:1.125rem;grid-template-areas:"heading search nav theme-toggle";grid-template-columns:auto auto 1fr auto;padding:.75rem}.logo{color:var(--fg0);display:flex;font-weight:700;grid-area:heading}.logo:hover .logo__cursor{animation:blink 1s infinite;opacity:1}.logo__chevron,.logo__cursor{margin-left:.5rem}.logo__cursor{opacity:0}.logo__text{display:none}@media (min-width:768px){.logo__text{display:block}}
|
|
|
|
/*! purgecss start ignore */.search{display:flex;grid-area:search;margin:0 1rem}#search__text{background:var(--bg2);border:1px solid var(--bg2);border-radius:.2rem;caret-color:var(--fg);color:var(--fg);outline:none;padding:0 .5rem;width:100%}#search__text:hover{border-color:var(--bg3)}#search__text:focus{border-color:var(--bg4)}#search__text::-moz-placeholder{color:var(--fg3)}#search__text::placeholder{color:var(--fg3)}#search__text[type=search]::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}#search__suggestions{background:var(--bg);border-radius:.2rem;box-shadow:0 .5rem 1rem var(--bg1);font-family:Roboto Slab,Georgia,serif;font-family:var(--font-serif);left:0;margin-top:2rem;position:absolute;width:95vw;z-index:1000}@media (min-width:768px){.search{position:relative}#search__suggestions{width:60vw}}.search__suggestions--hidden{display:none}.search__suggestion-item{border-bottom:1px dashed var(--bg2);display:grid;grid-template-columns:1fr 2fr}.search__suggestion-item:focus,.search__suggestion-item:focus-visible,.search__suggestion-item:hover{background:var(--bg1);cursor:pointer;outline:none}.search__suggestion-item:last-child{border:none}.search__suggestion-description,.search__suggestion-title{margin:1rem 0;padding:0 1rem}.search__suggestion-title{font-weight:700}.search__suggestion-description{border-left:1px solid var(--bg2)}.search__no-results{padding:.75rem}
|
|
|
|
/*! purgecss end ignore */.theme__toggle{align-items:center;background:none;border:none;color:var(--yellow1);cursor:pointer;display:flex;grid-area:theme-toggle;margin:0 1rem}.theme__toggle:hover{color:var(--yellow2)}.theme__toggle svg{height:28px;width:28px}nav#menu{align-items:center;display:flex;grid-area:nav;justify-content:flex-end}nav#menu .menu__item{color:var(--fg)}nav#menu .menu__item:hover{color:var(--fg3);cursor:pointer}nav#menu ul{list-style:none;margin:0;padding:0}nav#menu ul.menu--horizontal{align-items:center;display:none}nav#menu ul.menu--horizontal li{display:inline-block;margin:0 .75rem}@media (min-width:768px){nav#menu ul.menu--horizontal{display:flex}}nav#menu ul.menu--vertical{background:var(--fg0);bottom:0;margin:0;padding:3rem;position:fixed;right:0;top:0;transform:translate(100%);transition:transform .5s cubic-bezier(.9,0,.1,1);width:50%;z-index:10}nav#menu ul.menu--vertical .menu__item{color:var(--bg1)}nav#menu ul.menu--vertical .menu__item:hover{color:var(--bg4)}nav#menu .menu__burger{display:flex;height:24px;width:24px}nav#menu .menu__burger>*{position:absolute}nav#menu .menu__burger svg{height:inherit;width:inherit;z-index:20}nav#menu .menu__burger svg line{transition-duration:.5s;transition-property:stroke,opacity,transform;transition-timing-function:cubic-bezier(.9,0,.1,1)}nav#menu .menu__burger svg line:first-of-type{transform-origin:center 6px}nav#menu .menu__burger svg line:nth-of-type(2){transform-origin:center 12px}nav#menu .menu__burger svg line:nth-of-type(3){transform-origin:center 18px}nav#menu .menu__burger input{height:inherit;opacity:0;width:inherit;z-index:30}nav#menu .menu__burger input:checked~ul.menu--vertical{transform:none}nav#menu .menu__burger input:checked~svg{stroke:var(--bg1)}nav#menu .menu__burger input:checked~svg line:first-of-type{transform:translateY(6px) rotate(45deg)}nav#menu .menu__burger input:checked~svg line:nth-of-type(2){opacity:0;transform:scale(.2)}nav#menu .menu__burger input:checked~svg line:nth-of-type(3){transform:translateY(-6px) rotate(-45deg)}@media (min-width:768px){nav#menu .menu__burger{display:none}}.sidebar{font-family:Fira Code,Lucida Console,Monaco,monospace;font-family:var(--font-monospace);margin-left:auto;margin-right:auto;max-width:350px;padding-left:2.5rem}.sidebar hr{margin:1.5rem auto}.sidebar svg{fill:var(--fg)}.sidebar__heading{font-size:1.3rem}aside.toc a{color:var(--blue2);color:var(--primary-alt)}aside.toc a:hover{color:var(--blue1);color:var(--primary)}aside.toc ul{list-style:none;margin:0;padding:0}aside.toc ul ul{font-size:.9rem;margin-left:.5rem}aside.toc ul li{line-height:1.1}aside.toc ul li a{display:block;padding:.2rem 0}.jr-basics__image{background:var(--bg1);border:2px solid var(--bg2)}.jr-basics__summary{color:var(--fg3);font-family:Roboto Slab,Georgia,serif;font-family:var(--font-serif);margin:.75rem 0}.jr-basics__profile a:hover{color:var(--fg3)}.jr-basics__profile a:hover svg{fill:var(--fg3)}.tag-cloud{line-height:1.1;text-align:justify}.tag-cloud__tag:hover{color:var(--fg3)}.tag-cloud__tag--active{-webkit-text-decoration:underline;text-decoration:underline}.content-section,.post{border-bottom:2px dotted var(--bg1);padding:2rem 0}.post figure,.post img:not(figure img),.post video:not(figure video){box-sizing:border-box;margin:.5rem 0}.post-content__read-more,.post-header{font-family:Fira Code,Lucida Console,Monaco,monospace;font-family:var(--font-monospace)}.post-meta__author{font-weight:700}.post-content{margin:1.3rem 0}.post-content__read-more{margin-top:1.3rem}.post-content a,.post-content__read-more,.post-header a{color:var(--blue2);color:var(--primary-alt)}.post-content a:hover,.post-header a:hover{color:var(--blue1);color:var(--primary)}.post-tags{align-items:center;display:flex;flex-wrap:wrap;gap:.9rem;margin:1rem 0}.post-tag{font-size:.9rem;line-height:1}.post-tag:before{content:"#"}.post-heading__anchor{display:none}h1:hover .post-heading__anchor,h2:hover .post-heading__anchor,h3:hover .post-heading__anchor,h4:hover .post-heading__anchor,h5:hover .post-heading__anchor,h6:hover .post-heading__anchor{display:inline-block}.jr__item-meta{align-items:start;flex-flow:column;font-family:Fira Code,Lucida Console,Monaco,monospace;font-family:var(--font-monospace)}@media (min-width:768px){.jr__item-meta{align-items:center;flex-flow:row wrap}.jr-work__location,.jr__date,.jr__date-range{flex-grow:1;text-align:right}.jr-awards__awarder,.jr-education__institution,.jr-publications__publisher,.jr-volunteer__organization{flex-basis:100%}}.social-share{align-items:center;border-top:2px dotted var(--bg1);display:flex;flex-wrap:wrap;gap:.9rem;margin:3rem 0;padding-top:3rem}.social-share svg{fill:var(--fg);height:24px;width:24px}.social-share svg.icon-tabler{fill:none;stroke:var(--fg)}.social-share__item{background:var(--bg1);display:flex;padding:.5rem}</style><link rel=preload href="/css/non-critical.9214dbb3cce2c661a6e5356aef8f0893fa89801812e469e054a8858124096266eebf2c796b034928c657e085be8db512a997d44ac204174f066dac9326c7ab84.css" as=style onload='this.onload=null,this.rel="stylesheet"'><link id=prism-dark rel=preload href=/prism-themes/prism-gruvbox-dark.min.54aecc64074623a4f9898544dcbdab9e804f1560ef0b38f4cf8e10fcaaf72264e798cb407c601aca6ecd833ec4eb93d66535581f18d45ba202cf848b70dbc332.css as=style onload='this.onload=null,this.rel="stylesheet"'><link id=prism-light rel=preload href=/prism-themes/prism-gruvbox-light.min.42a221741efe997fcc94187c39d63c555560678789ac9ca856c74a5f0ddb2aa6c50d38b2ffbecc7a99038cbbd2efa99746e862267f781c559e0cfec10b88a5fc.css as=style onload='this.onload=null,this.rel="stylesheet"' disabled><noscript><link rel=stylesheet href=/prism-themes/prism-gruvbox-dark.min.54aecc64074623a4f9898544dcbdab9e804f1560ef0b38f4cf8e10fcaaf72264e798cb407c601aca6ecd833ec4eb93d66535581f18d45ba202cf848b70dbc332.css><link rel=stylesheet href="/css/non-critical.9214dbb3cce2c661a6e5356aef8f0893fa89801812e469e054a8858124096266eebf2c796b034928c657e085be8db512a997d44ac204174f066dac9326c7ab84.css"></noscript><script>(()=>{function s(){if(localStorage&&localStorage.getItem("theme"))return localStorage.getItem("theme")}function o(){if(window.matchMedia)return window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark"}function e(e){document.documentElement.setAttribute("data-theme",e);let t=document.getElementById("prism-dark"),n=document.getElementById("prism-light");t.toggleAttribute("disabled",e==="light"),n.toggleAttribute("disabled",e==="dark")}function i(e){localStorage.setItem("theme",e)}var t=s(),n=o();t?e(t):n&&e(n);function a(t){let n=t.currentTarget.classList.contains("light--hidden")?"light":"dark";e(n),i(n)}document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(".theme__toggle").forEach(e=>{e.addEventListener("click",a)})})})()</script><link rel=apple-touch-icon sizes=180x180 href=/apple-touch-icon.png><link rel=icon type=image/png sizes=32x32 href=/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=/favicon-16x16.png><link rel=manifest href=/site.webmanifest><link rel=mask-icon href=/safari-pinned-tab.svg color=#282828><meta name=msapplication-TileColor content="#282828"><meta name=theme-color content="#282828"></head><body><div class=layout><header><a class=logo href=/><div class=logo__text>davegallant.ca</div><div class=logo__chevron>></div><div class=logo__cursor>█</div></a><div class=search><input id=search__text type=search placeholder=Search... aria-label=Search autocomplete=off><div id=search__suggestions class=search__suggestions--hidden></div></div><nav id=menu><ul class=menu--horizontal><li class=menu__item><a href=/contact>Contact</a></li><li class=menu__item><a href=/>Posts</a></li><li class=menu__item><a href=/about>About</a></li><li class=menu__item><a href=/index.xml>RSS</a></li><li class=menu__item><a href=/credits>Credits</a></li></ul><div class=menu__burger><input class=menu__item type=checkbox aria-label="Open main menu"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-menu-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h16"/></svg><ul class=menu--vertical><li><a class=menu__item href=/contact>Contact</a></li><li><a class=menu__item href=/>Posts</a></li><li><a class=menu__item href=/about>About</a></li><li><a class=menu__item href=/index.xml>RSS</a></li><li><a class=menu__item href=/credits>Credits</a></li></ul></div></nav><button class="theme__toggle light--hidden" aria-label="Toggle light mode">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-sun" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-4 0a4 4 0 108 0 4 4 0 10-8 0"/><path d="M3 12h1m8-9v1m8 8h1m-9 8v1M5.6 5.6l.7.7m12.1-.7-.7.7m0 11.4.7.7m-12.1-.7-.7.7"/></svg></button>
|
|
<button class="theme__toggle dark--hidden" aria-label="Toggle dark mode"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-moon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 3c.132.0.263.0.393.0a7.5 7.5.0 007.92 12.446A9 9 0 1112 2.992z"/></svg></button></header><main><div class=content><div class=content-section><h1>Welcome</h1><div><p>This is a space where I share notes about problems and solutions I’ve been exploring. You can reach out by email, social media, or by commenting on the posts below. Continuous improvement is what motivates me to keep learning.</p></div></div><article class=post><div class=post-header><h2><a href=/blog/2023/12/10/setting-up-gitea-actions-with-tailscale/>Setting up Gitea Actions with Tailscale</a></h2><div class=post-meta><span>2023-12-10</span><span> by </span><span class=post-meta__author>Dave Gallant</span><div class=post-tags><a class=post-tag href=/tags/gitea>gitea</a><a class=post-tag href=/tags/gitea-actions>gitea‑actions</a><a class=post-tag href=/tags/github-actions>github‑actions</a><a class=post-tag href=/tags/tailscale>tailscale</a><a class=post-tag href=/tags/self-hosted>self‑hosted</a></div></div></div><div class=post-content><p>In this post I’ll go through the process of setting up Gitea Actions and <a href=https://tailscale.com/ class=link--external target=_blank rel=noreferrer>Tailscale</a>, unlocking a simple and secure way to automate workflows.</p><div class=post-content__read-more><a href=/blog/2023/12/10/setting-up-gitea-actions-with-tailscale/>Read more ></a></div></div></article><article class=post><div class=post-header><h2><a href=/blog/2023/05/22/using-aks-and-socks-to-connect-to-a-private-azure-db/>Using AKS and SOCKS to connect to a private Azure DB</a></h2><div class=post-meta><span>2023-05-22</span><span> by </span><span class=post-meta__author>Dave Gallant</span><div class=post-tags><a class=post-tag href=/tags/aks>aks</a><a class=post-tag href=/tags/aws>aws</a><a class=post-tag href=/tags/azure>azure</a><a class=post-tag href=/tags/bastion>bastion</a><a class=post-tag href=/tags/cloud-sql-proxy>cloud‑sql‑proxy</a><a class=post-tag href=/tags/database>database</a><a class=post-tag href=/tags/eks>eks</a><a class=post-tag href=/tags/k8s>k8s</a><a class=post-tag href=/tags/kubectl-plugin-socks5-proxy>kubectl‑plugin‑socks5‑proxy</a><a class=post-tag href=/tags/proxy>proxy</a><a class=post-tag href=/tags/socat>socat</a><a class=post-tag href=/tags/socks>socks</a></div></div></div><div class=post-content><p>I ran into a roadblock recently where I wanted to be able to conveniently connect to a managed postgres database within Azure that was not running on public subnets. And by conveniently, I mean that I’d rather not have to spin up an ephemeral virtual machine running in the same network and proxy the connection, and I’d like to use a local client (preferably with a GUI). After several web searches, it became evident that Azure does not readily provide much tooling to support this.</p><div class=post-content__read-more><a href=/blog/2023/05/22/using-aks-and-socks-to-connect-to-a-private-azure-db/>Read more ></a></div></div></article><article class=post><div class=post-header><h2><a href=/blog/2022/12/10/watching-youtube-in-private/>Watching YouTube in private</a></h2><div class=post-meta><span>2022-12-10</span><span> by </span><span class=post-meta__author>Dave Gallant</span><div class=post-tags><a class=post-tag href=/tags/invidious>invidious</a><a class=post-tag href=/tags/youtube>youtube</a><a class=post-tag href=/tags/yewtu.be>yewtu.be</a><a class=post-tag href=/tags/tailscale>tailscale</a><a class=post-tag href=/tags/privacy>privacy</a><a class=post-tag href=/tags/self-hosted>self‑hosted</a></div></div></div><div class=post-content><p>I recently stumbled upon <a href=https://yewtu.be class=link--external target=_blank rel=noreferrer>yewtu.be</a> and found it intriguing. It not only allows you to watch YouTube without <em>being on YouTube</em>, but it also allows you to create an account and subscribe to channels without a Google account. What sort of wizardry is going on under the hood? It turns out that it’s a hosted instance of <a href=https://invidious.io/ class=link--external target=_blank rel=noreferrer>invidious</a>.</p><div class=post-content__read-more><a href=/blog/2022/12/10/watching-youtube-in-private/>Read more ></a></div></div></article><article class=post><div class=post-header><h2><a href=/blog/2022/04/02/virtualizing-my-router-with-pfsense/>Virtualizing my router with pfSense</a></h2><div class=post-meta><span>2022-04-02</span><span> by </span><span class=post-meta__author>Dave Gallant</span><div class=post-tags><a class=post-tag href=/tags/pfsense>pfsense</a><a class=post-tag href=/tags/router>router</a><a class=post-tag href=/tags/openwrt>openwrt</a><a class=post-tag href=/tags/router-on-a-stick>router‑on‑a‑stick</a><a class=post-tag href=/tags/proxmox>proxmox</a><a class=post-tag href=/tags/vlan>vlan</a><a class=post-tag href=/tags/self-hosted>self‑hosted</a></div></div></div><div class=post-content><p>My aging router has been running <a href=https://en.wikipedia.org/wiki/OpenWrt class=link--external target=_blank rel=noreferrer>OpenWrt</a> for years and for the most part has been quite reliable. OpenWrt is an open-source project used on embedded devices to route network traffic. It supports many different configurations and there exists a <a href=https://openwrt.org/packages/index/start class=link--external target=_blank rel=noreferrer>large index of packages</a>. Ever since I’ve connected some standalone wireless access points, I’ve had less of a need for an off-the-shelf all-in-one wireless router combo. I’ve also recently been experiencing instability with my router (likely the result of a combination of configuration tweaking and firmware updating). OpenWrt has served me well, but it is time to move on!</p><div class=post-content__read-more><a href=/blog/2022/04/02/virtualizing-my-router-with-pfsense/>Read more ></a></div></div></article><article class=post><div class=post-header><h2><a href=/blog/2022/03/13/backing-up-gmail-with-synology/>Backing up gmail with Synology</a></h2><div class=post-meta><span>2022-03-13</span><span> by </span><span class=post-meta__author>Dave Gallant</span><div class=post-tags><a class=post-tag href=/tags/synology>synology</a><a class=post-tag href=/tags/gmail>gmail</a><a class=post-tag href=/tags/backup>backup</a><a class=post-tag href=/tags/ransomware>ransomware</a></div></div></div><div class=post-content><p>I’ve used gmail since the beta launched touting a whopping 1GB of storage. I thought this was a massive leap in email technology at the time. I was lucky enough to get an invite fairly quickly. Not suprisingly, I have many years of emails, attachments, and photos. I certainly do not want to lose the content of many of these emails. Despite the redundancy of the data that Google secures, I still feel better retaining a copy of this data on my own physical machines.</p><div class=post-content__read-more><a href=/blog/2022/03/13/backing-up-gmail-with-synology/>Read more ></a></div></div></article><article class=post><div class=post-header><h2><a href=/blog/2021/11/14/running-k3s-in-lxc-on-proxmox/>Running K3s in LXC on Proxmox</a></h2><div class=post-meta><span>2021-11-14</span><span> by </span><span class=post-meta__author>Dave Gallant</span><div class=post-tags><a class=post-tag href=/tags/k3s>k3s</a><a class=post-tag href=/tags/proxmox>proxmox</a><a class=post-tag href=/tags/lxc>lxc</a><a class=post-tag href=/tags/self-hosted>self‑hosted</a></div></div></div><div class=post-content>It has been a while since I’ve actively used Kubernetes and wanted to explore the evolution of tools such as Helm and Tekton. I decided to deploy K3s, since I’ve had success with deploying it on resource-contrained Raspberry Pis in the past. I thought that this time it’d be convenient to have K3s running in a LXC container on Proxmox. This would allow for easy snapshotting of the entire Kubernetes deployment.<div class=post-content__read-more><a href=/blog/2021/11/14/running-k3s-in-lxc-on-proxmox/>Read more ></a></div></div></article><article class=post><div class=post-header><h2><a href=/blog/2021/10/11/replacing-docker-with-podman-on-macos-and-linux/>Replacing docker with podman on macOS (and Linux)</a></h2><div class=post-meta><span>2021-10-11</span><span> by </span><span class=post-meta__author>Dave Gallant</span><div class=post-tags><a class=post-tag href=/tags/docker>docker</a><a class=post-tag href=/tags/podman>podman</a><a class=post-tag href=/tags/containers>containers</a></div></div></div><div class=post-content><p>There are a number of reasons why you might want to replace docker, especially on macOS. The following feature bundled in Docker Desktop might have motivated you enough to consider replacing docker:</p><div class=post-content__read-more><a href=/blog/2021/10/11/replacing-docker-with-podman-on-macos-and-linux/>Read more ></a></div></div></article><article class=post><div class=post-header><h2><a href=/blog/2021/09/17/automatically-rotating-aws-access-keys/>Automatically rotating AWS access keys</a></h2><div class=post-meta><span>2021-09-17</span><span> by </span><span class=post-meta__author>Dave Gallant</span><div class=post-tags><a class=post-tag href=/tags/aws>aws</a><a class=post-tag href=/tags/python>python</a><a class=post-tag href=/tags/security>security</a><a class=post-tag href=/tags/aws-vault>aws‑vault</a></div></div></div><div class=post-content>Rotating credentials is a security best practice. This morning, I read a question about automatically rotating AWS Access Keys without having to go through the hassle of navigating the AWS console. There are some existing solutions already, but I decided to write a script since it was incredibly simple. The script could be packed up as a systemd/launchd service to continually rotate access keys in the background.
|
|
In the longer term, migrating my local workflows to aws-vault seems like a more secure solution.<div class=post-content__read-more><a href=/blog/2021/09/17/automatically-rotating-aws-access-keys/>Read more ></a></div></div></article><article class=post><div class=post-header><h2><a href=/blog/2021/09/08/why-i-threw-out-my-dotfiles/>Why I threw out my dotfiles</a></h2><div class=post-meta><span>2021-09-08</span><span> by </span><span class=post-meta__author>Dave Gallant</span><div class=post-tags><a class=post-tag href=/tags/nix>nix</a><a class=post-tag href=/tags/dotfiles>dotfiles</a><a class=post-tag href=/tags/home-manager>home‑manager</a></div></div></div><div class=post-content><p>Over the years I have collected a number of dotfiles that I have shared across both Linux and macOS machines (<code>~/.zshrc</code>, <code>~/.config/git/config</code>, <code>~/.config/tmux/tmux.conf</code>, etc). I have tried several different ways to manage them, including <a href=https://www.atlassian.com/git/tutorials/dotfiles class=link--external target=_blank rel=noreferrer>bare git repos</a> and utilities such as <a href=https://www.gnu.org/software/stow/ class=link--external target=_blank rel=noreferrer>GNU Stow</a>. These solutions work well enough, but I have since found what I would consider a much better solution for organizing user configuration: <a href=https://github.com/nix-community/home-manager class=link--external target=_blank rel=noreferrer>home-manager</a>.</p><div class=post-content__read-more><a href=/blog/2021/09/08/why-i-threw-out-my-dotfiles/>Read more ></a></div></div></article><article class=post><div class=post-header><h2><a href=/blog/2021/09/06/what-to-do-with-a-homelab/>What to do with a homelab</a></h2><div class=post-meta><span>2021-09-06</span><span> by </span><span class=post-meta__author>Dave Gallant</span><div class=post-tags><a class=post-tag href=/tags/self-hosted>self‑hosted</a><a class=post-tag href=/tags/proxmox>proxmox</a><a class=post-tag href=/tags/tailscale>tailscale</a></div></div></div><div class=post-content><p>A homelab can be an inexpensive way to host a multitude of internal/external services and learn <em>a lot</em> in the process.</p><div class=post-content__read-more><a href=/blog/2021/09/06/what-to-do-with-a-homelab/>Read more ></a></div></div></article><div class=pagination><span class="pagination__button pagination__button--next"><a href=/page/2/>Older Posts ></a></span></div></div><div class=sidebar></div></main><footer><div class=copyright>Dave Gallant</div></footer><script src=/js/main.4be06c129d6a89e60a661c6ac8c8e0434d58fb0fa2f685f85e2c306aca62adc5e77e7c63cb1c8a2cc5794ea42927281cf868514bcdce21ddf23dc3520e6743e7.js></script><script src=/js/flexsearch.4fd7ac1adbaa0c340287cb25bb9b138edbdbba390fd9c227d67a5c6ae446390942a1f2e90dff66a996d66f934f3ec15fd4d7b5c34dfd126af631626967f5442e.js></script><script defer src=https://static.cloudflareinsights.com/beacon.min.js data-cf-beacon='{"token": "b96799f53f9940dca6f660e6052ba009"}'></script></div></body></html> |