Compare commits
136 commits
a21c1a6f83
...
7499a6eb94
Author | SHA1 | Date | |
---|---|---|---|
7499a6eb94 | |||
6c3c3437a2 | |||
836118f407 | |||
dd32ddaf6a | |||
e5db4ccd19 | |||
055d86e63b | |||
5e3edb3b43 | |||
cfa4325526 | |||
b1d6aac8b9 | |||
89f02cf46b | |||
3bf2fd4642 | |||
368413597e | |||
fbd7f70939 | |||
50d43c19b9 | |||
577279df27 | |||
bd00a037a3 | |||
80316ec72f | |||
807668d4ae | |||
fb5eea829c | |||
6dcc6ba697 | |||
7faca4f360 | |||
f3746e4047 | |||
05852d3803 | |||
847951b142 | |||
c81969204d | |||
4c460309a7 | |||
a20372f224 | |||
1b90235577 | |||
530a5c2433 | |||
a3da8809ec | |||
92da8b7593 | |||
d2aa4c21fb | |||
57a6f07acc | |||
13c8bdccfa | |||
50597ea7d4 | |||
9dbc4542c2 | |||
7a67dad462 | |||
e310192cd1 | |||
f484bb98d7 | |||
036c7825a2 | |||
a14609c559 | |||
6f1ec82c29 | |||
3442f2d483 | |||
a60ebcb1ec | |||
4e6095e5ff | |||
c100a122df | |||
85a5a68f46 | |||
9f59df16ad | |||
254496777d | |||
2d02179d2e | |||
cdba5eace2 | |||
5991a169a4 | |||
5a790710f0 | |||
50d48780be | |||
5639ff45cf | |||
659b6dfaa4 | |||
1b35ccd64d | |||
788b806981 | |||
|
59d5622fd1 | ||
b425d47395 | |||
e705adfba2 | |||
4be7711404 | |||
edc4c265ba | |||
422d73e16c | |||
ce322837e9 | |||
cb5786bbf9 | |||
41565324af | |||
d6b47055a0 | |||
3bd01ece8b | |||
a0ad23f4c3 | |||
b05c8d5813 | |||
c8509de9ea | |||
97c2a23608 | |||
09cd21ea36 | |||
bc59d3c7a4 | |||
5babcd07ba | |||
7eca2db6fe | |||
ae0ef7a52d | |||
01e0a0b6e0 | |||
1e3d802ea8 | |||
25d657ea47 | |||
d21051a781 | |||
|
71a60c4873 | ||
70f8dc1571 | |||
6da286c017 | |||
9223c6ca9e | |||
34fc5b5d6e | |||
39ff4c2b66 | |||
c11b975fba | |||
|
710b493a74 | ||
f3570754cb | |||
340e1f0cce | |||
d31c4cafdb | |||
35b2791dce | |||
f2f2c5aa9f | |||
|
841767dd66 | ||
3e7c2ddbe8 | |||
cdc261b82e | |||
9a8c130c63 | |||
b5475f0574 | |||
39079ebde1 | |||
cec57e09d1 | |||
7497882d94 | |||
f98628bfce | |||
9ad14f2092 | |||
094fa7ec1a | |||
8dfff8f099 | |||
16880cc516 | |||
56155058b0 | |||
75d47e33cb | |||
30512576f2 | |||
5601fa6129 | |||
b164c18351 | |||
8950a2bf1d | |||
006eb3ce9c | |||
128b9f57e7 | |||
922366762d | |||
bd7751e0ca | |||
dc6939de2d | |||
f3161654d3 | |||
6bbcfded0f | |||
df54406ef6 | |||
59353bf3ca | |||
6d5355541b | |||
36dd55a821 | |||
7fba1865a0 | |||
11093e5c98 | |||
|
dd5c13ecae | ||
bd4f26e1c6 | |||
b9926cb5b3 | |||
13fc5f8789 | |||
f0b0c8076b | |||
a43cef4461 | |||
164dbc6eeb | |||
de52904a76 | |||
dcc98e9dcd |
14
.github/workflows/daily_build.yml
vendored
|
@ -1,14 +0,0 @@
|
|||
name: Daily build
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 13 * * *"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger build webhook on Netlify
|
||||
run: curl -s -X POST "https://api.netlify.com/build_hooks/${TOKEN}"
|
||||
env:
|
||||
TOKEN: ${{ secrets.NETLIFY_CRON_BUILD_HOOK }}
|
48
.github/workflows/deploy-to-neocities.yml
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
name: Deploy to Neocities
|
||||
|
||||
# only run on changes to main
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 13 * * *
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency: # prevent concurrent deploys doing strange things
|
||||
group: deploy-to-neocities
|
||||
cancel-in-progress: true
|
||||
|
||||
# Default to bash
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Build and deploy Hugo site
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Hugo setup
|
||||
uses: peaceiris/actions-hugo@v2.6.0
|
||||
with:
|
||||
hugo-version: '0.121.1'
|
||||
extended: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build with Hugo
|
||||
run: hugo --minify
|
||||
- name: Insert 404 page
|
||||
run: |
|
||||
cp public/404/index.html public/not_found.html
|
||||
- name: Highlight with Torchlight
|
||||
run: |
|
||||
npm i @torchlight-api/torchlight-cli
|
||||
npx torchlight
|
||||
- name: Deploy to Neocities
|
||||
uses: bcomnes/deploy-to-neocities@v1
|
||||
with:
|
||||
api_token: ${{ secrets.NEOCITIES_API_TOKEN }}
|
||||
cleanup: true
|
||||
dist_dir: public
|
2
.gitignore
vendored
|
@ -4,5 +4,5 @@
|
|||
/package.json
|
||||
/public/
|
||||
/resources/
|
||||
/.env
|
||||
/.env*
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
[![Netlify Status](https://api.netlify.com/api/v1/badges/7cd6b595-2b3b-403d-a29f-c4f27e8bd366/deploy-status)](https://app.netlify.com/sites/runtimeterrordev/deploys)
|
||||
[![Neocities Deployment Status](https://github.com/jbowdre/runtimeterror/actions/workflows/deploy-to-neocities.yml/badge.svg)](https://github.com/jbowdre/runtimeterror/actions/workflows/deploy-to-neocities.yml)
|
|
@ -6,53 +6,33 @@ draft: true
|
|||
description: "This is a new post about..."
|
||||
featured: false
|
||||
toc: true
|
||||
comment: true
|
||||
series: Tips # Projects, Code
|
||||
comments: true
|
||||
categories: Tips # Backstage, ChromeOS, Code, Self-Hosting, VMware
|
||||
tags:
|
||||
- 3dprinting
|
||||
- activedirectory
|
||||
- android
|
||||
- api
|
||||
- automation
|
||||
- availability
|
||||
- caddy
|
||||
- certs
|
||||
- chat
|
||||
- chrome
|
||||
- chromeos
|
||||
- cloud
|
||||
- cluster
|
||||
- containers
|
||||
- crostini
|
||||
- docker
|
||||
- gcp
|
||||
- homeassistant
|
||||
- homelab
|
||||
- hugo
|
||||
- iac
|
||||
- javascript
|
||||
- kubernetes
|
||||
- linux
|
||||
- logs
|
||||
- meta
|
||||
- networking
|
||||
- openssl
|
||||
- packer
|
||||
- powercli
|
||||
- powershell
|
||||
- python
|
||||
- regex
|
||||
- rest
|
||||
- salt
|
||||
- security
|
||||
- selfhosting
|
||||
- serverless
|
||||
- shell
|
||||
- tailscale
|
||||
- tasker
|
||||
- terraform
|
||||
- vmware
|
||||
- vpn
|
||||
- windows
|
||||
- wireguard
|
||||
- wsl
|
||||
|
|
1
assets/js/back-to-top.js
Normal file
|
@ -0,0 +1 @@
|
|||
"use strict";function addBackToTop(){var o,t,e,n,i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=i.backgroundColor,d=void 0===r?"#000":r,a=i.cornerOffset,c=void 0===a?20:a,s=i.diameter,l=void 0===s?56:s,u=i.ease,p=void 0===u?function(o){return.5*(1-Math.cos(Math.PI*o))}:u,m=i.id,h=void 0===m?"back-to-top":m,b=i.innerHTML,v=void 0===b?'<svg viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"></path></svg>':b,f=i.onClickScrollTo,x=void 0===f?0:f,w=i.scrollContainer,g=void 0===w?document.body:w,k=i.scrollDuration,y=void 0===k?100:k,T=i.showWhenScrollTopIs,M=void 0===T?1:T,z=i.size,E=void 0===z?l:z,C=i.textColor,L=void 0===C?"#fff":C,N=i.zIndex,I=void 0===N?1:N,A=g===document.body,B=A&&document.documentElement;o=Math.round(.43*E),t=Math.round(.29*E),e="#"+h+"{background:"+d+";-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;bottom:"+c+"px;-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,.26);-moz-box-shadow:0 2px 5px 0 rgba(0,0,0,.26);box-shadow:0 2px 5px 0 rgba(0,0,0,.26);color:"+L+";cursor:pointer;display:block;height:"+E+"px;opacity:1;outline:0;position:fixed;right:"+c+"px;-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-transition:bottom .2s,opacity .2s;-o-transition:bottom .2s,opacity .2s;-moz-transition:bottom .2s,opacity .2s;transition:bottom .2s,opacity .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:"+E+"px;z-index:"+I+"}#"+h+" svg{display:block;fill:currentColor;height:"+o+"px;margin:"+t+"px auto 0;width:"+o+"px}#"+h+".hidden{bottom:-"+E+"px;opacity:0}",(n=document.createElement("style")).appendChild(document.createTextNode(e)),document.head.insertAdjacentElement("afterbegin",n);var D=function(){var o=document.createElement("div");return o.id=h,o.className="hidden",o.innerHTML=v,o.addEventListener("click",function(o){o.preventDefault(),function(){var o="function"==typeof x?x():x,t=window,e=t.performance,n=t.requestAnimationFrame;if(y<=0||void 0===e||void 0===n)return q(o);var i=e.now(),r=j(),d=r-o;n(function o(t){var e=Math.min((t-i)/y,1);q(r-Math.round(p(e)*d)),e<1&&n(o)})}()}),document.body.appendChild(o),o}(),H=!0;function S(){j()>=M?function(){if(!H)return;D.className="",H=!1}():function(){if(H)return;D.className="hidden",H=!0}()}function j(){return g.scrollTop||B&&document.documentElement.scrollTop||0}function q(o){g.scrollTop=o,B&&(document.documentElement.scrollTop=o)}(A?window:g).addEventListener("scroll",S),S()}
|
3475
assets/js/lunr.js
Normal file
45
assets/js/search.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
// based on https://victoria.dev/blog/add-search-to-hugo-static-sites-with-lunr/
|
||||
function displayResults (results, store) {
|
||||
const searchResults = document.getElementById('results');
|
||||
if (results.length) {
|
||||
let resultList = '';
|
||||
for (const n in results) {
|
||||
const item = store[results[n].ref];
|
||||
resultList += '<li><a href="' + item.url + '">' + item.title + '</a></li>'
|
||||
if (item.description)
|
||||
resultList += '<p>' + item.description + '</p>'
|
||||
else
|
||||
resultList += '<p>' + item.content.substring(0, 150) + '...</p>'
|
||||
}
|
||||
searchResults.innerHTML = resultList;
|
||||
} else {
|
||||
searchResults.innerHTML = 'No results found.';
|
||||
}
|
||||
}
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const query = params.get('query');
|
||||
if (query) {
|
||||
document.getElementById('search-query').setAttribute('value', query);
|
||||
const idx = lunr(function () {
|
||||
this.ref('id')
|
||||
this.field('title', {
|
||||
boost: 15
|
||||
})
|
||||
this.field('tags')
|
||||
this.field('content', {
|
||||
boost: 10
|
||||
})
|
||||
for (const key in window.store) {
|
||||
this.add({
|
||||
id: key,
|
||||
title: window.store[key].title,
|
||||
tags: window.store[key].tags,
|
||||
content: window.store[key].content
|
||||
})
|
||||
}
|
||||
})
|
||||
const results = idx.search(query);
|
||||
displayResults(results, window.store)
|
||||
}
|
||||
|
||||
|
2
build.sh
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
# Quick script to run local builds
|
||||
source .env
|
||||
hugo --minify --environment local -D
|
||||
hugo --environment local -D
|
||||
npx torchlight
|
||||
python3 -m http.server --directory public 1313
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
baseURL = "https://runtimeterror.dev"
|
||||
theme = "risotto"
|
||||
title = "runtimeterror"
|
||||
copyright = "© 2018-2023 [runtimeterror](https://runtimeterror.dev)"
|
||||
copyright = "© 2024 John Bowdre"
|
||||
paginate = 10
|
||||
languageCode = "en"
|
||||
DefaultContentLanguage = "en"
|
||||
|
@ -11,7 +11,7 @@ enableInlineShortcodes = true
|
|||
# sectionPagesMenu = "main"
|
||||
|
||||
[outputs]
|
||||
home = ['html', 'rss', 'json']
|
||||
home = ['html', 'rss']
|
||||
section = ['html']
|
||||
taxonomy = ['html',]
|
||||
term = ['html', 'rss']
|
||||
|
@ -58,5 +58,8 @@ enableInlineShortcodes = true
|
|||
|
||||
[taxonomies]
|
||||
tag = "tags"
|
||||
series = "series"
|
||||
category = "categories"
|
||||
|
||||
[minify]
|
||||
disableXML = true
|
||||
minifyOutput = true
|
|
@ -5,21 +5,21 @@
|
|||
# weight = 10
|
||||
|
||||
[[main]]
|
||||
identifier = "projects"
|
||||
name = "projects"
|
||||
url = "/series/projects/"
|
||||
identifier = "self-hosting"
|
||||
name = "self-hosting"
|
||||
url = "/categories/self-hosting/"
|
||||
weight = 1
|
||||
|
||||
[[main]]
|
||||
identifier = "tips"
|
||||
name = "tips"
|
||||
url = "/series/tips/"
|
||||
url = "/categories/tips/"
|
||||
weight = 1
|
||||
|
||||
[[main]]
|
||||
identifier = "code"
|
||||
name = "code"
|
||||
url = "/series/code/"
|
||||
url = "/categories/code/"
|
||||
weight = 1
|
||||
|
||||
[[main]]
|
||||
|
|
|
@ -5,18 +5,32 @@ mainSections = ["posts"]
|
|||
fallBackOgImage = "images/broken-computer.png"
|
||||
numberOfFeaturedPosts = 5
|
||||
numberOfRelatedPosts = 5
|
||||
author = "jbowdre"
|
||||
|
||||
indexTitle = ".-. ..- -. - .. -- . - . .-. .-. --- .-."
|
||||
|
||||
# Comments
|
||||
comments = true
|
||||
utterancesRepo = "jbowdre/site-comments"
|
||||
utterancesIssueTerm = "og:title"
|
||||
utterancesTheme = "gruvbox-dark"
|
||||
giscusCategory = "Announcements"
|
||||
giscusCategoryId = "DIC_kwDOKKEGD84CcG89"
|
||||
giscusCrossOrigin = "anonymous"
|
||||
giscusEmitMetadata = "0"
|
||||
giscusInputPosition = "bottom"
|
||||
giscusLang = "en"
|
||||
giscusLoading = "lazy"
|
||||
giscusMapping = "og:title"
|
||||
giscusReactions = "0"
|
||||
giscusRepo = "jbowdre/site-comments"
|
||||
giscusRepoId = "R_kgDOKKEGDw"
|
||||
giscusStrict = "0"
|
||||
giscusTheme = "noborder_gray"
|
||||
|
||||
analytics = true
|
||||
|
||||
[author]
|
||||
name = "John Bowdre"
|
||||
email = "jbowdre@omg.lol"
|
||||
username = "jbowdre"
|
||||
|
||||
[theme]
|
||||
palette = "runtimeterror"
|
||||
|
||||
|
@ -135,7 +149,7 @@ url = "https://social.lol/@jbowdre"
|
|||
[[socialLinks]]
|
||||
icon = "fa-solid fa-heart"
|
||||
title = "omg.lol"
|
||||
url = "https://jbowdre.omg.lol"
|
||||
url = "https://jbowdre.lol"
|
||||
|
||||
[[socialLinks]]
|
||||
icon = "fa-solid fa-comments"
|
||||
|
@ -157,8 +171,8 @@ title = "hugo"
|
|||
url = "https://gohugo.io"
|
||||
|
||||
[[powerLinks]]
|
||||
title = "netlify"
|
||||
url = "https://www.netlify.com"
|
||||
title = "neocities"
|
||||
url = "https://neocities.org/about"
|
||||
|
||||
[[powerLinks]]
|
||||
title = "risotto"
|
||||
|
@ -169,8 +183,8 @@ title = "torchlight"
|
|||
url = "https://torchlight.dev"
|
||||
|
||||
[[powerLinks]]
|
||||
title = "cabin"
|
||||
url = "https://withcabin.com/privacy/runtimeterror.dev"
|
||||
title = "tinylytics"
|
||||
url = "https://tinylytics.app/home"
|
||||
|
||||
[[verifyLinks]]
|
||||
title = "omg.lol"
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
baseURL = "https://preview--runtimeterrordev.netlify.app"
|
|
@ -1,2 +0,0 @@
|
|||
comments = false
|
||||
analytics = false
|
|
@ -6,7 +6,7 @@ comments = true
|
|||
+++
|
||||
|
||||
We're not sure what you were looking for but it's not here.
|
||||
![](/images/nothing-to-see-here.gif)
|
||||
![Animated GIF from the movie "The Naked Gun". A man in the foreground proclaims "Please disperse. Nothing to see here." while a building explodes in the background.](/images/nothing-to-see-here.gif)
|
||||
|
||||
Maybe head back [home](/)?
|
||||
|
||||
|
|
|
@ -11,27 +11,27 @@ You've (somehow) managed to stumble upon my dark corner of the internet[^1].
|
|||
|
||||
I've enjoyed tinkering with computers and their code since discovering I could alter variable values in [`GORILLA.BAS`](https://en.wikipedia.org/wiki/Gorillas_%28video_game%29) on my dad's work computer to imbue the thrown bananas with enough explosive power to level the entire city. I thought, "hey, that's neat," and then spent much of my childhood free time learning how *else* I could bend computers to my will.
|
||||
|
||||
Once I grew up[^2], I found a career in system administration, and I leveraged my passion for coding to write scripts to help manage systems more efficiently. While managing a global-scale VMware environment, I was tasked with implementing [vRealize Automation](/series/vra8) (now called "Aria Automation"). I didn't realize it at the time but that was the start of my DevOps transformation. I started thinking about infrastructure-as-code, and began using [HashiCorp Packer](https://github.com/jbowdre/packer-vsphere-templates) and a CI/CD pipeline to automatically build fully-up-to-date VM templates on a weekly cadence.
|
||||
Once I grew up[^2], I found a career in system administration, and I leveraged my passion for coding to write scripts to help manage systems more efficiently. While managing a global-scale VMware environment, I was tasked with implementing [vRealize Automation](/categories/vmware) (now called "Aria Automation"). I didn't realize it at the time but that was the start of my DevOps transformation. I started thinking about infrastructure-as-code, and began using [HashiCorp Packer](https://github.com/jbowdre/packer-vsphere-templates) and a CI/CD pipeline to automatically build fully-up-to-date VM templates on a weekly cadence.
|
||||
|
||||
I'm now part of a small platform engineering team within that same large corporation, focused on leveraging DevOps thinking and tools to help our internal customers modernize how they operate IT, build code, and ship products, while designing solutions to help them accomplish those goals. It's a great blend of my virtual infrastructure operations background, hobbyist development experience, and hunger for solving problems, and I really enjoy applying these skills to solve interesting challenges at scale.
|
||||
|
||||
On my off time, I tinker with new [projects](/series/projects) in my little homelab (and share some of those adventures here). I also help out on Google's product support forums as a [Product Expert](https://productexperts.withgoogle.com/what-it-is), where I support Pixel phones, earbuds, and watches, as well as Chromebooks (primarily with Linux-related queries). Helping users troubleshoot their issues scratches my problem-solving itch, and it keeps me connected with some really great like-minded tech enthusiasts.
|
||||
On my off time, I tinker with new [projects](/categories/self-hosting) in my little homelab (and share some of those adventures here). I also help out on Google's product support forums as a [Product Expert](https://productexperts.withgoogle.com/what-it-is), where I support Pixel phones, earbuds, and watches, as well as Chromebooks (primarily with Linux-related queries). Helping users troubleshoot their issues scratches my problem-solving itch, and it keeps me connected with some really great like-minded tech enthusiasts.
|
||||
|
||||
On weekends, I race my daily-driven 2014 Subaru BRZ in local [autocross events](https://www.youtube.com/playlist?list=PLwzr4uKY-x-EwCv-rWNGefdikuW6Oy9O_) or wrench on my 1974 Volkswagen Karmann Ghia.
|
||||
On weekends, I race my daily-driven 2014 Subaru BRZ in local [autocross events](https://l.runtimeterror.dev/my-autox-vids) or wrench on my 1974 Volkswagen Karmann Ghia.
|
||||
|
||||
And in the free time I have left, I game on my Steam Deck.
|
||||
|
||||
See what I've been up to on:
|
||||
- [GitHub](https://github.com/jbowdre)
|
||||
- [CounterSocial](https://counter.social/@john_b)
|
||||
- [status.lol](https://status.lol/jbowdre)
|
||||
- [/now](https://jbowdre.omg.lol/now)
|
||||
- [status.lol](https://status.jbowdre.lol)
|
||||
- [/now](https://now.jbowdre.lol)
|
||||
|
||||
Connect with me via:
|
||||
- [SimpleX Chat](/simplex/)
|
||||
- [Matrix](https://matrix.to/#/@jbowdre:omg.lol)
|
||||
- [Electronic Mail](mailto:jbowdre@omg.lol)
|
||||
- [PGP: 613F B70C 4FA7 A077](https://home.omg.lol/keychain/jbowdre/pgp)
|
||||
- [PGP: 613F B70C 4FA7 A077](https://l.runtimeterror.dev/pgp)
|
||||
|
||||
|
||||
[^1]: Congrats? And also, *thank you.*
|
||||
|
|
5
content/categories/backstage/_index.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Backstage
|
||||
description: >
|
||||
A peek behind the scenes at what it takes to run this site.
|
||||
---
|
5
content/categories/chromeos/_index.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: "ChromeOS"
|
||||
description: >
|
||||
My Chromebook is a lot more than just a browser.
|
||||
---
|
5
content/categories/code/_index.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Code
|
||||
description: >
|
||||
I did a programming and I wanted you to see.
|
||||
---
|
5
content/categories/self-hosting/_index.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Self-Hosting
|
||||
description: >
|
||||
Never met an app I didn't want to deploy.
|
||||
---
|
5
content/categories/tips/_index.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Tips
|
||||
description: >
|
||||
I learned something the hard way so that you wouldn't have to.
|
||||
---
|
4
content/categories/vmware/_index.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: "VMware"
|
||||
description: "vSphere, vCenter, vRealize, vTanzu, vBroadcom..."
|
||||
---
|
|
@ -3,6 +3,7 @@ date: "2020-09-14T08:34:30Z"
|
|||
thumbnail: qDTXt1jp3.png
|
||||
featureImage: qDTXt1jp3.png
|
||||
usePageBundles: true
|
||||
categories: ChromeOS
|
||||
tags:
|
||||
- linux
|
||||
- chromeos
|
||||
|
@ -18,12 +19,12 @@ That's a pretty sweet setup, but I still needed a way to convert STL 3D models i
|
|||
Enter "Crostini," Chrome OS's [Linux (Beta) feature](https://chromium.googlesource.com/chromiumos/docs/+/master/containers_and_vms.md). It consists of a hardened Linux VM named `termina` which runs (by default) a Debian Buster LXD container named `penguin` (though you can spin up just about any container for which you can find an [image](https://us.images.linuxcontainers.org/)) and some fancy plumbing to let Chrome OS and Linux interact in specific clearly-defined ways. It's a brilliant balance between offering the flexibility of Linux while preserving Chrome OS's industry-leading security posture.
|
||||
|
||||
|
||||
![Neofetch in the Crostini terminal](lhTnVwCO3.png)
|
||||
![Screenshot of the 'neofetch' utility](lhTnVwCO3.png)
|
||||
|
||||
There are plenty of great guides (like [this one](https://www.computerworld.com/article/3314739/linux-apps-on-chrome-os-an-easy-to-follow-guide.html)) on how to get started with Linux on Chrome OS so I won't rehash those steps here.
|
||||
|
||||
One additional step you will probably want to take is make sure that your Chromebook is configured to enable hyperthreading, as it may have [hyperthreading disabled by default](https://support.google.com/chromebook/answer/9340236). Just plug `chrome://flags/#scheduler-configuration` into Chrome's address bar, set it to `Enables Hyper-Threading on relevant CPUs`, and then click the button to restart your Chromebook. You'll thank me later.
|
||||
![Enabling hyperthreading](LHax6lAwh.png)
|
||||
![Screenshot of ChromeOS flags page showing that '#scheduler-configuration' is set to 'Enables Hyper-Threading on relevant CPUs](LHax6lAwh.png)
|
||||
|
||||
### The Software
|
||||
I settled on using [FreeCAD](https://www.freecadweb.org/) for parametric modeling and [Ultimaker Cura](https://ultimaker.com/software/ultimaker-cura) for my GCODE slicer, but unfortunately getting them working cleanly wasn't entirely straightforward.
|
||||
|
@ -68,7 +69,7 @@ Comment[de_DE]=Feature-basierter parametrischer Modellierer
|
|||
MimeType=application/x-extension-fcstd
|
||||
```
|
||||
That's it! Get on with your 3D-modeling bad self.
|
||||
![FreeCAD](qDTXt1jp3.png)
|
||||
![Screenshot of FreeCAD showing a 3d model being worked on](qDTXt1jp3.png)
|
||||
Now that you've got a model, be sure to [export it as an STL mesh](https://wiki.freecadweb.org/Export_to_STL_or_OBJ) so you can import it into your slicer.
|
||||
|
||||
#### Ultimaker Cura
|
||||
|
@ -88,12 +89,12 @@ sudo apt update && sudo apt install menulibre # [tl! .cmd:2]
|
|||
menulibre
|
||||
```
|
||||
Just plug in the relevant details (you can grab the appropriate icon [here](https://github.com/Ultimaker/Cura/blob/master/icons/cura-128.png)), hit the filing cabinet Save icon, and you should then be able to search for Cura from the Chrome OS launcher.
|
||||
![Using menulibre to create the launcher shortcut](VTISYOKHO.png)
|
||||
![Screenshot demoing the use of 'menulibre' to create the launcher shortcut](VTISYOKHO.png)
|
||||
|
||||
![Ultimaker Cura](f8nRJcyI6.png)
|
||||
![Screenshot of Ultimake Cura software](f8nRJcyI6.png)
|
||||
|
||||
From there, just import the STL mesh, configure the appropriate settings, slice, and save the resulting GCODE. You can then just upload the GCODE straight to The Spaghetti Detective and kick off the print.
|
||||
|
||||
![Successful print, designed and sliced on Chrome OS!](2g57odtq2.jpeg)
|
||||
![A 3d-printed adapter for mounting a rear reflector on a bicycle, designed, sliced, and printed from a Chromebook](2g57odtq2.jpeg)
|
||||
|
||||
Nice!
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Tips
|
||||
categories: Tips
|
||||
date: "2020-09-24T08:34:30Z"
|
||||
thumbnail: fmLDUWjia.png
|
||||
usePageBundles: true
|
||||
|
@ -17,7 +17,7 @@ Point your browser to `chrome://settings/searchEngines` to see which sites are r
|
|||
Each of these search engine entries has three parts: a name ("Search engine"), a Keyword, and a Query URL. The "Search engine" title is just what will appear in the Omnibox when the search engine gets triggered, the Keyword is what you'll type in the Omnibox to trigger it, and the Query URL tells Chrome how to handle the search. All you have to do is type the keyword, hit your Tab key to activate the search, input your query, and hit Enter:
|
||||
![Using a custom search engine](o_o7rt4pA.gif)
|
||||
|
||||
For sites which register themselves automatically, the keyword is often set to something like `domain.tld` so it might make sense to assign it as something shorter or more descriptive.
|
||||
For sites which register themselves automatically, the keyword is often set to something like `domain.tld` so it might make sense to assign it as something shorter or more descriptive.
|
||||
|
||||
The Query URL is basically just what appears in the address bar when you search the site directly, with `%s` placed where your query text would normally go. You can view these details for a given search entry by tapping the three-dot menu button and selecting "Edit", and you can manually create new entries by hitting that big friendly "Add" button:
|
||||
![Editing a search engine](fmLDUWjia.png)
|
||||
|
@ -55,7 +55,7 @@ This works for pretty much any site which parses the URL to render certain conte
|
|||
Your Query URL doesn't even need to include a query at all! You can use the Custom Search Engines as a sort of hyper-fast shortcut to pages you visit frequently. If I create a new entry with the Keyword `searchax` and `abusing-chromes-custom-search-engines-for-fun-and-profit` as the query URL, I can quickly open to this page by typing `searchax[tab][enter]`:
|
||||
![Custom search shortener](YilNCaHil.png)
|
||||
|
||||
I use that trick pretty regularly for getting back to vCenter appliance management interfaces without having to type out the full FQDN and port number and all that.
|
||||
I use that trick pretty regularly for getting back to vCenter appliance management interfaces without having to type out the full FQDN and port number and all that.
|
||||
|
||||
------
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: vRA8
|
||||
categories: VMware
|
||||
date: "2021-06-01T08:34:30Z"
|
||||
thumbnail: -Fuvz-GmF.png
|
||||
usePageBundles: true
|
||||
|
@ -11,7 +11,7 @@ tags:
|
|||
title: Adding VM Notes and Custom Attributes with vRA8
|
||||
---
|
||||
|
||||
*In [past posts](/series/vra8), I started by [creating a basic deployment infrastructure](/vra8-custom-provisioning-part-one) in Cloud Assembly and using tags to group those resources. I then [wrote an integration](/integrating-phpipam-with-vrealize-automation-8) to let vRA8 use phpIPAM for static address assignments. I [implemented a vRO workflow](/vra8-custom-provisioning-part-two) for generating unique VM names which fit an organization's established naming standard, and then [extended the workflow](/vra8-custom-provisioning-part-three) to avoid any naming conflicts in Active Directory and DNS. And, finally, I [created an intelligent provisioning request form in Service Broker](/vra8-custom-provisioning-part-four) to make it easy for users to get the servers they need. That's got the core functionality pretty well sorted, so moving forward I'll be detailing additions that enable new capabilities and enhance the experience.*
|
||||
*In [past posts](/categories/vmware), I started by [creating a basic deployment infrastructure](/vra8-custom-provisioning-part-one) in Cloud Assembly and using tags to group those resources. I then [wrote an integration](/integrating-phpipam-with-vrealize-automation-8) to let vRA8 use phpIPAM for static address assignments. I [implemented a vRO workflow](/vra8-custom-provisioning-part-two) for generating unique VM names which fit an organization's established naming standard, and then [extended the workflow](/vra8-custom-provisioning-part-three) to avoid any naming conflicts in Active Directory and DNS. And, finally, I [created an intelligent provisioning request form in Service Broker](/vra8-custom-provisioning-part-four) to make it easy for users to get the servers they need. That's got the core functionality pretty well sorted, so moving forward I'll be detailing additions that enable new capabilities and enhance the experience.*
|
||||
|
||||
In this post, I'll describe how to get certain details from the Service Broker request form and into the VM's properties in vCenter. The obvious application of this is adding descriptive notes so I can remember what purpose a VM serves, but I will also be using [Custom Attributes](https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.vcenterhost.doc/GUID-73606C4C-763C-4E27-A1DA-032E4C46219D.html) to store the server's Point of Contact information and a record of which ticketing system request resulted in the server's creation.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Projects
|
||||
categories: Self-Hosting
|
||||
date: "2021-05-27T08:34:30Z"
|
||||
thumbnail: HRRpFOKuN.png
|
||||
usePageBundles: true
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Projects
|
||||
categories: Code
|
||||
date: "2020-11-24T08:34:30Z"
|
||||
lastmod: "2021-03-12"
|
||||
thumbnail: Ki7jo65t3.png
|
||||
|
@ -37,7 +37,7 @@ It's important to use the [open-source](https://github.com/schwabe/ics-openvpn)
|
|||
### OpenVPN config file
|
||||
You can find instructions for configuring the OpenVPN client to work with ProtonVPN [here](https://protonvpn.com/support/android-vpn-setup/) but I'll go ahead and hit the highlights. You'll probably want to go ahead and do all this from your phone so you don't have to fuss with transferring files around, but hey, *you do you*.
|
||||
|
||||
1. Log in to your ProtonVPN account (or sign up for a new free one) at https://account.protonvpn.com/login.
|
||||
1. Log in to your ProtonVPN account (or sign up for a new free one) at https://account.protonvpn.com/login.
|
||||
2. Use the panel on the left side to navigate to **[Downloads > OpenVPN configuration files](https://account.protonvpn.com/downloads#openvpn-configuration-files)**.
|
||||
3. Select the **Android** platform and **UDP** as the protocol, unless you have a [particular reason to use TCP](https://protonvpn.com/support/udp-tcp/#:~:text=When%20to%20use%20UDP%20vs.%20TCP).
|
||||
4. Select and download the desired config file:
|
||||
|
@ -49,7 +49,7 @@ You can find instructions for configuring the OpenVPN client to work with Proton
|
|||
|
||||
Feel free to download more than one if you'd like to have different profiles available within the OpenVPN app.
|
||||
|
||||
ProtonVPN automatically generates a set of user credentials to use with a third-party VPN client so that you don't have to share your personal creds. You'll want to make a note of that randomly-generated username and password so you can plug them in to the OpenVPN app later. You can find the details at **[Account > OpenVPN / IKEv2 username](https://account.protonvpn.com/account#openvpn)**.
|
||||
ProtonVPN automatically generates a set of user credentials to use with a third-party VPN client so that you don't have to share your personal creds. You'll want to make a note of that randomly-generated username and password so you can plug them in to the OpenVPN app later. You can find the details at **[Account > OpenVPN / IKEv2 username](https://account.protonvpn.com/account#openvpn)**.
|
||||
|
||||
**Now that you've got the profile file, skip on down to [The Update](#update) to import it into OpenVPN Connect.**
|
||||
|
||||
|
@ -67,7 +67,7 @@ Now what you've got the config file(s) and your client credentials, it's time to
|
|||
|
||||
Success!
|
||||
|
||||
I don't like to have a bunch of persistent notification icons hanging around (and Android already shows a persistent status icon when a VPN connection is active). If you're like me, long-press the OpenVPN notification and tap the gear icon. Then tap on the **Connection statistics** category and activate the **Minimized** slider. The notification will still appear, but it will collapse to the bottom of your notification stack and you won't get bugged by the icon.
|
||||
I don't like to have a bunch of persistent notification icons hanging around (and Android already shows a persistent status icon when a VPN connection is active). If you're like me, long-press the OpenVPN notification and tap the gear icon. Then tap on the **Connection statistics** category and activate the **Minimized** slider. The notification will still appear, but it will collapse to the bottom of your notification stack and you won't get bugged by the icon.
|
||||
|
||||
![Notification settings](WWuHwVvrk.png)
|
||||
|
||||
|
@ -76,21 +76,21 @@ Open up Tasker and get ready to automate! We're going to wind up with at least t
|
|||
|
||||
Let's start with a profile to track whether or not we're connected to one of our preferred/trusted WiFi networks:
|
||||
|
||||
#### Trusted WiFi
|
||||
#### Trusted WiFi
|
||||
1. Tap the '+' sign to create a new profile, and add a new **State > Net > Wifi Connected** context. This profile will become active whenever your phone connects to WiFi.
|
||||
2. Tap the magnifying glass next to the **SSID** field, which will pop up a list of all detected nearby network identifiers. Tap to select whichever network(s) you'd like to be considered "safe". You can also manually enter the SSID names, separating multiple options with a `/` (ex, `FBI Surveillance Van/TellMyWifiLoveHer/Pretty fly for a WiFi`). Or, for more security, identify the networks based on the MACs instead of the SSIDs - just be sure to capture the MACs for any extenders or mesh nodes too!
|
||||
3. Once you've got your networks added, tap the back button to move *forward* to the next task (Ah, Android!): configuring the *action* which will occur when the context is satisfied.
|
||||
4. Tap the **New Task** option and then tap the check mark to skip giving it a name (no need).
|
||||
3. Once you've got your networks added, tap the back button to move *forward* to the next task (Ah, Android!): configuring the *action* which will occur when the context is satisfied.
|
||||
4. Tap the **New Task** option and then tap the check mark to skip giving it a name (no need).
|
||||
5. Hit the '+' button to add an action and select **Variables > Variable Set**.
|
||||
6. For **Name**, enter `%TRUSTED_WIFI` (all caps to make it a "public" variable), and for the **To** field just enter `1`.
|
||||
7. Hit back to save the action, and back again to save the profile.
|
||||
6. For **Name**, enter `%TRUSTED_WIFI` (all caps to make it a "public" variable), and for the **To** field just enter `1`.
|
||||
7. Hit back to save the action, and back again to save the profile.
|
||||
8. Back at the profile list, long-press on the **Variable Set...** action and then select **Add Exit Task**.
|
||||
9. We want to un-set the variable when no longer connected to a trusted WiFi network so add a new **Variables > Variable Clear** action and set the name to `%TRUSTED_WIFI`.
|
||||
10. And back back out to admire your handiwork. Here's a recap of the profile:
|
||||
```
|
||||
Profile: Trusted Wifi
|
||||
State: Wifi Connected [ SSID:FBI Surveillance Van/TellMyWifiLoveHer/Pretty fly for a WiFi MAC:* IP:* Active:Any ]
|
||||
Enter: Anon
|
||||
Enter: Anon
|
||||
A1: Variable Set [ Name:%TRUSTED_WIFI To:1 Recurse Variables:Off Do Maths:Off Append:Off Max Rounding Digits:0 ]
|
||||
Exit: Anon
|
||||
A1: Variable Clear [ Name:%TRUSTED_WIFI Pattern Matching:Off Local Variables Only:Off Clear All Variables:Off ]
|
||||
|
@ -103,7 +103,7 @@ This profile will kick in if the phone connects to a WiFi network which isn't on
|
|||
1. It starts out the same way by creating a new profile with the **State > Net > Wifi Connected** context but this time don't add any network names to the list.
|
||||
2. For the action, select **Plugin > OpenVpn Tasker Plugin**, tap the pencil icon to edit the configuration, and select your VPN profile from the list under **Connect using profile**
|
||||
3. Back at the Action Edit screen, tap the checkbox next to **If** and enter the variable name `%TRUSTED_WIFI`. Tap the '~' button to change the condition operator to **Isn't Set**. So while this profile will activate every time you connect to WiFi, the action which connects to the VPN will only fire if the WiFi isn't a trusted network.
|
||||
4. Back out to the profile list and add a new Exit Task.
|
||||
4. Back out to the profile list and add a new Exit Task.
|
||||
5. Add another **Plugin > OpenVpn Tasker Plugin** task and this time configure it to **Disconnect VPN**.
|
||||
|
||||
To recap:
|
||||
|
@ -149,7 +149,7 @@ After installing and launching the official [OpenVPN Connect app](https://play.g
|
|||
![Creating a profile in OpenVPN Connect](KjGOX8Yiv.png)
|
||||
|
||||
#### Tasker profiles
|
||||
Go ahead and create the [Trusted Wifi profile](#trusted-wifi) as described above.
|
||||
Go ahead and create the [Trusted Wifi profile](#trusted-wifi) as described above.
|
||||
|
||||
The condition for the [VPN on Strange Wifi profile](#vpn-on-strange-wifi) will be the same, but the task will be different. This time, add a **System > Send Intent** action. You'll need to enter the following details, leaving the other fields blank/default:
|
||||
|
||||
|
@ -176,4 +176,4 @@ Class: net.openvpn.unified.MainActivity
|
|||
Target: Activity
|
||||
```
|
||||
|
||||
All set! You can pop back up to the [Epilogue](#epilogue-working-with-googles-vpn) section to continue tweaking to avoid conflicts with Google's auto-connect VPN if you'd like.
|
||||
All set! You can pop back up to the [Epilogue](#epilogue-working-with-googles-vpn) section to continue tweaking to avoid conflicts with Google's auto-connect VPN if you'd like.
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Code
|
||||
categories: Code
|
||||
date: "2021-04-29T08:34:30Z"
|
||||
usePageBundles: true
|
||||
thumbnail: 20210723-script.png
|
||||
|
@ -11,7 +11,7 @@ title: Automatic unattended expansion of Linux root LVM volume to fill disk
|
|||
toc: false
|
||||
---
|
||||
|
||||
While working on my [vRealize Automation 8 project](/series/vra8), I wanted to let users specify how large a VM's system drive should be and have vRA apply that without any further user intervention. For instance, if the template has a 60GB C: drive and the user specifies that they want it to be 80GB, vRA will embiggen the new VM's VMDK to 80GB and then expand the guest file system to fill up the new free space.
|
||||
While working on my [vRealize Automation 8 project](/categories/vmware), I wanted to let users specify how large a VM's system drive should be and have vRA apply that without any further user intervention. For instance, if the template has a 60GB C: drive and the user specifies that they want it to be 80GB, vRA will embiggen the new VM's VMDK to 80GB and then expand the guest file system to fill up the new free space.
|
||||
|
||||
I'll get into the details of how that's implemented from the vRA side #soon, but first I needed to come up with simple scripts to extend the guest file system to fill the disk.
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
---
|
||||
title: "Automating Security Camera Notifications With Home Assistant and Ntfy"
|
||||
date: 2023-11-25
|
||||
lastmod: 2023-11-27
|
||||
lastmod: 2024-01-15
|
||||
description: "Using the power of Home Assistant automations and Ntfy push notifications to level-up security camera motion detections."
|
||||
featured: true
|
||||
alias: automating-security-camera-notifications-with-home-assistant-and-ntfy
|
||||
toc: true
|
||||
comment: true
|
||||
comments: true
|
||||
thumbnail: thumbnail.png
|
||||
series: Projects
|
||||
categories: Self-Hosting
|
||||
tags:
|
||||
- api
|
||||
- automation
|
||||
|
@ -25,7 +25,7 @@ I figured I could combine the excellent [Reolink integration for Home Assistant]
|
|||
|
||||
### Alert on motion detection
|
||||
{{% notice note "Ntfy Integration" %}}
|
||||
Since manually configuring ntfy in Home Assistant via the [RESTful Notifications integration](easy-push-notifications-with-ntfy/#notify-configuration), I found that a [ntfy-specific integration](https://github.com/ivanmihov/homeassistant-ntfy.sh) was available through the [Home Assistant Community Store](https://hacs.xyz/) addon. That setup is a bit more flexible so I've switched my setup to use it instead:
|
||||
Since manually configuring ntfy in Home Assistant via the [RESTful Notifications integration](/easy-push-notifications-with-ntfy#notify-configuration), I found that a [ntfy-specific integration](https://github.com/ivanmihov/homeassistant-ntfy.sh) was available through the [Home Assistant Community Store](https://hacs.xyz/) addon. That setup is a bit more flexible so I've switched my setup to use it instead:
|
||||
```yaml
|
||||
# configuration.yaml
|
||||
notify:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Projects
|
||||
categories: Self-Hosting
|
||||
date: "2018-09-26T08:34:30Z"
|
||||
lastmod: "2022-03-06"
|
||||
thumbnail: i0UKdXleC.png
|
||||
|
|
|
@ -14,14 +14,14 @@ usePageBundles: true
|
|||
thumbnail: "code.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Code
|
||||
categories: Code
|
||||
tags:
|
||||
- vmware
|
||||
- powercli
|
||||
- python
|
||||
- api
|
||||
- phpipam
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
|
||||
I [recently wrote](/tanzu-community-edition-k8s-homelab/#a-real-workload---phpipam) about getting started with VMware's [Tanzu Community Edition](https://tanzucommunityedition.io/) and deploying [phpIPAM](https://phpipam.net/) as my first real-world Kubernetes workload. Well I've spent much of my time since then working on a script which would help to populate my phpIPAM instance with a list of networks to monitor.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Tips
|
||||
categories: ChromeOS
|
||||
date: "2020-12-23T08:34:30Z"
|
||||
thumbnail: -lp1-DGiM.png
|
||||
usePageBundles: true
|
||||
|
|
|
@ -14,12 +14,12 @@ usePageBundles: true
|
|||
# thumbnail: "thumbnail.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Tips # Projects, Code, vRA8, K8s on vSphere
|
||||
categories: Tips # Projects, Code, vRA8, K8s on vSphere
|
||||
tags:
|
||||
- linux
|
||||
- shell
|
||||
- regex
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
It's super handy when a Linux config file is loaded with comments to tell you precisely how to configure the thing, but all those comments can really get in the way when you're trying to review the current configuration.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Projects
|
||||
categories: Self-Hosting
|
||||
date: "2021-10-28T00:00:00Z"
|
||||
thumbnail: 20211028_wireguard_in_the_cloud.jpg
|
||||
usePageBundles: true
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: "Create Virtual Machines on a Chromebook with HashiCorp Vagrant" # Title of the blog post.
|
||||
date: 2023-02-20 # Date of post creation.
|
||||
lastmod: 2023-02-25
|
||||
lastmod: 2024-01-17
|
||||
description: "Pairing the powerful Linux Development Environment on modern Chromebooks with HashiCorp Vagrant to create and manage local virtual machines for development and testing" # Description used for search engine.
|
||||
featured: true # Sets if post is a featured post, making appear on the home page side bar.
|
||||
draft: false # Sets whether to render this page. Draft of true will not be rendered.
|
||||
|
@ -14,13 +14,13 @@ usePageBundles: true
|
|||
thumbnail: "thumbnail.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Projects
|
||||
categories: ChromeOS
|
||||
tags:
|
||||
- linux
|
||||
- chromeos
|
||||
- homelab
|
||||
- iac
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
I've lately been trying to do more with [Salt](https://saltproject.io/) at work, but I'm still very much a novice with that tool. I thought it would be great to have a nice little portable lab environment where I could deploy a few lightweight VMs and practice managing them with Salt - without impacting any systems that are actually being used for anything. Along the way, I figured I'd leverage [HashiCorp Vagrant](https://www.vagrantup.com/) to create and manage the VMs, which would provide a declarative way to define what the VMs should look like. The VM (or even groups of VMs) would be specified in a single file, and I'd bypass all the tedious steps of creating the virtual hardware, attaching the installation media, installing the OS, and performing the initial configuration. Vagrant will help me build up, destroy, and redeploy a development environment in a simple and repeatable way.
|
||||
|
||||
|
@ -57,6 +57,16 @@ echo "remember_owner = 0" | sudo tee -a /etc/libvirt/qemu.conf # [tl! .cmd:1]
|
|||
sudo systemctl restart libvirtd
|
||||
```
|
||||
|
||||
{{% notice note "Update 2024-01-17" %}}
|
||||
There seems to be an [issue with libvirt in LXC containers on Debian Bookworm](https://gitlab.com/libvirt/libvirt/-/issues/556), which explains why I was getting errors on `vagrant up` after updating my Crostini environment.
|
||||
|
||||
The workaround is to add another line to `qemu.conf`:
|
||||
```shell
|
||||
echo "namespaces = []" | sudo tee -a /etc/libvirt/qemu.conf # [tl! .cmd:1]
|
||||
sudo systemctl restart libvirtd
|
||||
```
|
||||
{{% /notice %}}
|
||||
|
||||
I'm also going to use `rsync` to share a [synced folder](https://developer.hashicorp.com/vagrant/docs/synced-folders/basic_usage) between the host and the VM guest so I'll need to make sure that's installed too:
|
||||
```shell
|
||||
sudo apt install rsync # [tl! .cmd]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: vRA8
|
||||
categories: VMware
|
||||
date: "2021-08-13T00:00:00Z"
|
||||
lastmod: "2022-01-18"
|
||||
usePageBundles: true
|
||||
|
|
89
content/posts/deploy-hugo-neocities-github-actions/index.md
Normal file
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
title: "Deploying a Hugo Site to Neocities with GitHub Actions"
|
||||
date: 2024-01-21
|
||||
# lastmod: 2024-01-21
|
||||
description: "Using GitHub Actions to automatically deploy a Hugo website to Neocities."
|
||||
featured: false
|
||||
toc: true
|
||||
comments: true
|
||||
categories: Backstage
|
||||
tags:
|
||||
- hugo
|
||||
- meta
|
||||
- serverless
|
||||
---
|
||||
I came across [Neocities](https://neocities.org) many months ago, and got really excited by the premise: a free web host with the mission to bring back the *"fun, creativity and independence that made the web great."* I spent a while scrolling through the [gallery](https://neocities.org/browse) of personal sites and was amazed by both the nostalgic vibes and the creativity on display. It's like a portal back to when the web was fun. Neocities seemed like something I wanted to be a part of, so I signed up for an account... and soon realized that I didn't *really* want to go back to crafting artisinal HTML by hand like I did in the early '00s. I didn't see an easy way to leverage my preferred static site generator[^lazy] so I filed it away and moved on.
|
||||
|
||||
[^lazy]: Also I'm kind of lazy, and not actually much of a web design person anyway.
|
||||
|
||||
Until yesterday, when I saw [Sophie](https://social.lol/@sophie)'s post, [How I deploy my Eleventy site to Neocities](https://localghost.dev/blog/how-i-deploy-my-eleventy-site-to-neocities/). I hadn't realized that Neocities had an [API](https://neocities.org/api), or that there was a [deploy-to-neocities](https://github.com/bcomnes/deploy-to-neocities) GitHub Action which uses that API to push content to Neocities. With that new-to-me information, I thought I'd give Neocities another try - a real one this time.
|
||||
|
||||
I'd been hosting this site on Netlify's free plan [for a couple of year](/hello-hugo/) and haven't really had any problems. But I saw Neocities as a better vision of the internet, and I wanted to be a part of that[^passion]. So last night I signed up for the $5/month [Neocities Supporter](https://neocities.org/supporter) plan, which comes with support for custom domains and more bandwidth than even a paid Netlify plan.
|
||||
|
||||
[^passion]: Plus I love supporting passion projects.
|
||||
|
||||
I knew I'd need to make some changes to Sophie's workflow since I build my site with Hugo rather than Eleventy. I did some poking around and found [GitHub Actions for Hugo](https://github.com/peaceiris/actions-hugo) which would take care of installing Hugo for me. Then I'd just need to render the HTML with `hugo --minify` and use the [Torchlight](/spotlight-on-torchlight/) CLI to mark up the code blocks. Along the way, I discovered that I needed to overwrite `/not_found.html` to insert my custom 404 page so I included an extra step to do that. And then I'd finally be ready to push the results to Neocities.
|
||||
|
||||
So after some trial and error, I came up with this workflow:
|
||||
|
||||
### The Workflow
|
||||
```yaml
|
||||
# torchlight! {"lineNumbers": true}
|
||||
# .github/workflows/deploy-to-neocities.yml
|
||||
name: Deploy to Neocities
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 13 * * *
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: deploy-to-neocities
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Build and deploy Hugo site
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Install Hugo in the runner
|
||||
- name: Hugo setup
|
||||
uses: peaceiris/actions-hugo@v2.6.0
|
||||
with:
|
||||
hugo-version: '0.121.1'
|
||||
extended: true
|
||||
# Check out the source for the site
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
# Build the site with Hugo
|
||||
- name: Build with Hugo
|
||||
run: hugo --minify
|
||||
# Copy my custom 404 page to not_found.html so it
|
||||
# will be picked up by Neocities
|
||||
- name: Insert 404 page
|
||||
run: |
|
||||
cp public/404/index.html public/not_found.html
|
||||
# Highlight code blocks with the Torchlight CLI
|
||||
- name: Highlight with Torchlight
|
||||
run: |
|
||||
npm i @torchlight-api/torchlight-cli
|
||||
npx torchlight
|
||||
# Push the rendered site to Neocities and
|
||||
# clean up any orphaned files
|
||||
- name: Deploy to Neocities
|
||||
uses: bcomnes/deploy-to-neocities@v1
|
||||
with:
|
||||
api_token: ${{ secrets.NEOCITIES_API_TOKEN }}
|
||||
cleanup: true
|
||||
dist_dir: public
|
||||
```
|
||||
|
||||
I'm thrilled with how well this works, and happy to have learned a bit more about GitHub Actions in the process. Big thanks to Sophie for pointing me in the right direction!
|
After Width: | Height: | Size: 170 KiB |
|
@ -4,8 +4,8 @@ date: 2023-11-24
|
|||
description: "I moved my homelab from VMware vSphere to Proxmox VE, and my only regret is that I didn't make this change sooner."
|
||||
featured: false
|
||||
toc: true
|
||||
comment: true
|
||||
series: Tips # Projects, Code
|
||||
comments: true
|
||||
categories: Tips # Projects, Code
|
||||
tags:
|
||||
- homelab
|
||||
- linux
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
date: "2020-09-22T08:34:30Z"
|
||||
thumbnail: 8p-PSHx1R.png
|
||||
usePageBundles: true
|
||||
categories: Tips
|
||||
tags:
|
||||
- docker
|
||||
- windows
|
||||
|
|
|
@ -5,8 +5,8 @@ lastmod: 2023-12-22
|
|||
description: "Deploying and configuring a self-hosted pub-sub notification handler, getting another server to send a notifcation when it boots, and integrating the notification handler into Home Assistant."
|
||||
featured: false
|
||||
toc: true
|
||||
comment: true
|
||||
series: Projects
|
||||
comments: true
|
||||
categories: Self-Hosting
|
||||
tags:
|
||||
- android
|
||||
- api
|
||||
|
|
46
content/posts/enable-fips-fix-aria-lifecycle/index.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: "Enabling FIPS Compliance Fixes Aria Lifecycle 8.14"
|
||||
date: 2024-01-19
|
||||
# lastmod: 2024-01-19
|
||||
description: "Never in my life have I seen enabling FIPS *fix* a problem - until now."
|
||||
featured: false
|
||||
comments: true
|
||||
categories: VMware
|
||||
tags:
|
||||
- vmware
|
||||
---
|
||||
This week, VMware posted [VMSA-2024-0001](https://www.vmware.com/security/advisories/VMSA-2024-0001.html) which details a critical (9.9/10) vulnerability in <s>vRealize</s> *Aria* Automation. While working to get our environment patched, I ran into an interesting error on our Aria Lifecycle appliance:
|
||||
|
||||
```log
|
||||
Error Code: LCMVRAVACONFIG590024
|
||||
VMware Aria Automation hostname is not valid or unable to run the product specific commands via SSH on the host. Check if VMware Aria Automation is up and running.
|
||||
VMware Aria Automation hostname is not valid or unable to run the product specific commands via SSH on the host. Check if VMware Aria Automation is up and running.
|
||||
com.vmware.vrealize.lcm.drivers.vra80.exception.VraVaProductNotFoundException: Either provided hostname: <VMwareAriaAutomationFQDN> is not a valid VMware Aria Automation hostname or unable to run the product specific commands via SSH on the host.
|
||||
at com.vmware.vrealize.lcm.drivers.vra80.helpers.VraPreludeInstallHelper.getVraFullVersion(VraPreludeInstallHelper.java:970)
|
||||
at com.vmware.vrealize.lcm.drivers.vra80.helpers.VraPreludeInstallHelper.checkVraApplianceAndVersion(VraPreludeInstallHelper.java:978)
|
||||
at com.vmware.vrealize.lcm.drivers.vra80.helpers.VraPreludeInstallHelper.getVraProductDetails(VraPreludeInstallHelper.java:754)
|
||||
at com.vmware.vrealize.lcm.plugin.core.vra80.task.VraVaImportEnvironmentTask.execute(VraVaImportEnvironmentTask.java:145)
|
||||
at com.vmware.vrealize.lcm.platform.automata.service.Task.retry(Task.java:158)
|
||||
at com.vmware.vrealize.lcm.automata.core.TaskThread.run(TaskThread.java:60)
|
||||
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
|
||||
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
|
||||
at java.base/java.lang.Thread.run(Unknown Source)
|
||||
```
|
||||
|
||||
Digging further into the appliance logs revealed some more details:
|
||||
```log
|
||||
Session.connect: java.security.spec.InvalidKeySpecException: key spec not recognized
|
||||
```
|
||||
|
||||
That seems like a much more insightful error than "the hostname is not valid, dummy."
|
||||
|
||||
Anyhoo, searching for the error took me to a VMware KB on the subject:
|
||||
- [VMware Aria Suite Lifecycle 8.14 Patch 1 Day 2 operations fail for VMware Aria Automation with error code LCMVRAVACONFIG590024 (96243)](https://kb.vmware.com/s/article/96243)
|
||||
|
||||
> After applying VMware Aria Suite Lifecycle 8.14 Patch 1, you may encounter deployment and day-2 operation failures, attributed to the elimination of weak algorithms in Suite Lifecycle. To prevent such issues, it is recommended to either turn on FIPS in VMware Aria Suite Lifecycle or implement the specified workarounds on other VMware Aria Products, as outlined in the article Steps for Removing SHA1 weak Algorithms/Ciphers from all VMware Aria Products.
|
||||
|
||||
That's right. According to the KB, the solution for the untrusted encryption algorithms is to *enable* FIPS compliance. I was skeptical: I've never seen FIPS enforcement fix problems, it always causes them.
|
||||
|
||||
But I gave it a shot, and *holy crap it actually worked!* Enabling FIPS compliance on the Aria Lifecycle appliance got things going again.
|
||||
|
||||
I feel like I've seen everything now.
|
|
@ -14,14 +14,14 @@ usePageBundles: true
|
|||
thumbnail: "tanzu-completion.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Tips
|
||||
categories: VMware
|
||||
tags:
|
||||
- vmware
|
||||
- linux
|
||||
- tanzu
|
||||
- kubernetes
|
||||
- shell
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
|
||||
Lately I've been spending some time [getting more familiar](/tanzu-community-edition-k8s-homelab/) with VMware's [Tanzu Community Edition](https://tanzucommunityedition.io/) Kubernetes distribution, but I'm still not quite familiar enough with the `tanzu` command line. If only there were a better way for me to discover the available commands for a given context and help me type them correctly...
|
||||
|
|
|
@ -14,7 +14,7 @@ featureImage: "quartz64.jpg" # Sets featured image on blog post.
|
|||
thumbnail: "quartz64.jpg" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Projects
|
||||
categories: VMware
|
||||
tags:
|
||||
- vmware
|
||||
- linux
|
||||
|
@ -23,7 +23,7 @@ tags:
|
|||
- tailscale
|
||||
- photon
|
||||
- vpn
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
{{% notice note "ESXi-ARM Fling v1.10 Update" %}}
|
||||
On July 20, 2022, VMware released a [major update](https://blogs.vmware.com/arm/2022/07/20/1-10/) for the ESXi-ARM Fling. Among [other fixes and improvements](https://flings.vmware.com/esxi-arm-edition#changelog), this version enables **in-place ESXi upgrades** and [adds support for the Quartz64's **on-board NIC**](https://twitter.com/jmcwhatever/status/1549935971822706688). To update, I:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Projects
|
||||
categories: Self-Hosting
|
||||
date: "2021-06-28T00:00:00Z"
|
||||
thumbnail: 2xe34VJym.png
|
||||
usePageBundles: true
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Tips
|
||||
categories: Tips
|
||||
date: "2020-09-13T08:34:30Z"
|
||||
usePageBundles: true
|
||||
tags:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: vRA8
|
||||
categories: VMware
|
||||
date: "2021-11-05T00:00:00Z"
|
||||
thumbnail: 20211105_ssc_403.png
|
||||
usePageBundles: true
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
date: "2020-10-07T08:34:30Z"
|
||||
thumbnail: MnmMuA0HC.png
|
||||
usePageBundles: true
|
||||
categories: Tips
|
||||
tags:
|
||||
- windows
|
||||
- linux
|
||||
|
@ -11,15 +12,15 @@ title: Fixing WSL2 connectivity when connected to a VPN with wsl-vpnkit
|
|||
toc: false
|
||||
---
|
||||
|
||||
I was pretty excited to get [WSL2 and Docker working on my Windows 10 1909](/docker-on-windows-10-with-wsl2) laptop a few weeks ago, but I quickly encountered a problem: WSL2 had no network connectivity when connected to my work VPN.
|
||||
I was pretty excited to get [WSL2 and Docker working on my Windows 10 1909](/docker-on-windows-10-with-wsl2) laptop a few weeks ago, but I quickly encountered a problem: WSL2 had no network connectivity when connected to my work VPN.
|
||||
|
||||
Well, that's not *entirely* true; Docker worked just fine, but nothing else could talk to anything outside of the WSL environment. I found a few open issues for this problem in the [WSL2 Github](https://github.com/microsoft/WSL/issues?q=is%3Aissue+is%3Aopen+VPN) with suggested workarounds including modifying Windows registry entries, adjusting the metrics assigned to various virtual network interfaces within Windows, and manually setting DNS servers in `/etc/resolv.conf`. None of these worked for me.
|
||||
|
||||
I eventually came across a solution [here](https://github.com/sakai135/wsl-vpnkit) which did the trick. This takes advantage of the fact that Docker for Windows is already utilizing `vpnkit` for connectivity - so you may also want to be sure Docker Desktop is configured to start at login.
|
||||
|
||||
The instructions worked well for me so I won't rehash them all here. When it came time to modify my `/etc/resolv.conf` file, I added in two of the internal DNS servers followed by the IP for my home router's DNS service. This allows me to use WSL2 both on and off the corporate network without having to reconfigure things.
|
||||
The instructions worked well for me so I won't rehash them all here. When it came time to modify my `/etc/resolv.conf` file, I added in two of the internal DNS servers followed by the IP for my home router's DNS service. This allows me to use WSL2 both on and off the corporate network without having to reconfigure things.
|
||||
|
||||
All I need to do now is execute `sudo ./wsl-vpnkit` and leave that running in the background when I need to use WSL while connected to the corporate VPN.
|
||||
All I need to do now is execute `sudo ./wsl-vpnkit` and leave that running in the background when I need to use WSL while connected to the corporate VPN.
|
||||
|
||||
|
||||
![Successful connection via wsl-vpnkit](MnmMuA0HC.png)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Projects
|
||||
categories: Self-Hosting
|
||||
date: "2021-08-20T00:00:00Z"
|
||||
lastmod: 2022-02-03
|
||||
usePageBundles: true
|
||||
|
@ -57,7 +57,7 @@ Once that's done, I can try my redirect again - and, after a brief moment, it su
|
|||
![Successful redirect](20210820_successful_redirect.png)
|
||||
|
||||
### Link custom domain
|
||||
The whole point of this project is to *shorten* URLs, but I haven't done that yet. I'll want to link in my `go.bowdre.net` domain to use that in place of the rather unwieldy `https://sheets-url-shortener-vrw7x6wdzq-uc.a.run.app`. I do that by going back to the [Cloud Run console](https://console.cloud.google.com/run) and selecting the option at the top to **Manage Custom Domains**.
|
||||
The whole point of this project is to *shorten* URLs, but I haven't done that yet. I'll want to link in my `go.bowdre.net` domain to use that in place of the rather unwieldy `https://sheets-url-shortener-somestring-uc.a.run.app`. I do that by going back to the [Cloud Run console](https://console.cloud.google.com/run) and selecting the option at the top to **Manage Custom Domains**.
|
||||
![Manage custom domains](20210820_manage_custom_domain.png)
|
||||
|
||||
I can then use the **Add Mapping** button, select my `sheets-url-shortener` service, choose one of my verified domains (which I *think* are already verified since they're registered through Google Domains with the same account), and then specify the desired subdomain.
|
||||
|
@ -73,8 +73,6 @@ It took a while for the domain mapping to go live once I've updated the record.
|
|||
Once it did finally update, I was able to hit `https://go.bowdre.net` to get the error/landing page, complete with a valid SSL cert:
|
||||
![Successful error!](20210820_landing_page.png)
|
||||
|
||||
And testing [go.bowdre.net/ghia](https://go.bowdre.net/ghia) works as well!
|
||||
|
||||
### Outro
|
||||
I'm very pleased with how this quick little project turned out. Managing my shortened links with a Google Sheet is quite convenient, and I really like the complete lack of tracking or analytics. Plus I'm a sucker for an excuse to use a cloud technology I haven't played a lot with yet.
|
||||
|
||||
|
@ -82,9 +80,9 @@ And now I can hand out handy-dandy short links!
|
|||
|
||||
| Link | Description|
|
||||
| --- | --- |
|
||||
| [go.bowdre.net/coso](https://go.bowdre.net/coso) | Follow me on CounterSocial |
|
||||
| [go.bowdre.net/conedoge](https://go.bowdre.net/conedoge) | 2014 Subaru BRZ autocross videos |
|
||||
| [go.bowdre.net/cooltechshit](https://go.bowdre.net/cooltechshit) | A collection of cool tech shit (references and resources) |
|
||||
| [go.bowdre.net/stuffiuse](https://go.bowdre.net/stuffiuse) | Things that I use (and think you should use too) |
|
||||
| [go.bowdre.net/shorterer](https://go.bowdre.net/shorterer) | This post! |
|
||||
| [go.bowdre.net/coso](https://l.runtimeterror.dev/coso) | Follow me on CounterSocial |
|
||||
| [go.bowdre.net/conedoge](https://l.runtimeterror.dev/conedoge) | 2014 Subaru BRZ autocross videos |
|
||||
| [go.bowdre.net/cooltechshit](https://l.runtimeterror.dev/cooltechshit) | A collection of cool tech shit (references and resources) |
|
||||
| [go.bowdre.net/stuffiuse](https://l.runtimeterror.dev/stuffiuse) | Things that I use (and think you should use too) |
|
||||
| [go.bowdre.net/shorterer](https://l.runtimeterror.dev/shorterer) | This post! |
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ usePageBundles: true
|
|||
thumbnail: "thumbnail.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: vRA8 # Projects, Code, vRA8
|
||||
categories: VMware # Projects, Code, vRA8
|
||||
tags:
|
||||
- vmware
|
||||
- vra
|
||||
|
@ -23,7 +23,7 @@ tags:
|
|||
- automation
|
||||
- rest
|
||||
- api
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
I've been doing a bit of work lately to make my vRealize Automation setup more flexible and dynamic and less dependent upon hardcoded values. To that end, I thought it was probably about time to learn how to interact with the vRA REST API. I wrote this post to share what I've learned and give a quick crash course on how to start doing things with the API.
|
||||
|
||||
|
@ -312,7 +312,7 @@ This doesn't give me the *name* of the regions, but I could use the `_links.regi
|
|||
You'll notice that HTTPie also prettifies the JSON response to make it easy for humans to parse. This is great for experimenting with requests against different API endpoints and getting a feel for what data can be found where. And firing off tests in HTTPie can be a lot quicker (and easier to format) than with other tools.
|
||||
|
||||
Now let's take what we've learned and see about implementing it as vRO actions.
|
||||
[^pie]: ![](pie.gif)
|
||||
[^pie]: ![GIF from Supernatural wherein Dean ogles some delicious pie.](pie.gif)
|
||||
[^token]: Well, most of it.
|
||||
[^foreshadowing]: That knowledge will come in handy later.
|
||||
### vRealize Orchestrator actions
|
||||
|
|
|
@ -14,7 +14,7 @@ usePageBundles: true
|
|||
thumbnail: "gitea-logo.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Projects
|
||||
categories: Self-Hosting
|
||||
tags:
|
||||
- caddy
|
||||
- linux
|
||||
|
@ -22,7 +22,7 @@ tags:
|
|||
- cloud
|
||||
- tailscale
|
||||
- selfhosting
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
I recently started using [Obsidian](https://obsidian.md/) for keeping notes, tracking projects, and just generally organizing all the information that would otherwise pass into my brain and then fall out the other side. Unlike other similar solutions which operate entirely in *The Cloud*, Obsidian works with Markdown files stored in a local folder[^sync], which I find to be very attractive. Not only will this allow me to easily transfer my notes between apps if I find something I like better than Obsidian, but it also opens the door to using `git` to easily back up all this important information.
|
||||
|
||||
|
|
|
@ -16,10 +16,11 @@ shareImage: "/hugo-logo-wide.png"
|
|||
# shareImage: "/images/path/share.png" # Designate a separate image for social media sharing.
|
||||
codeMaxLines: 10 # Override global value for how many lines within a code block before auto-collapsing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
categories: Backstage
|
||||
tags:
|
||||
- meta
|
||||
- hugo
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
**Oops, I did it again.**
|
||||
|
||||
|
@ -27,15 +28,15 @@ It wasn't [all that long ago](/virtually-potato-migrated-to-github-pages) that I
|
|||
|
||||
While Jekyll is built on Ruby and requires you to install and manage a Ruby environment before being able to use it to generate a site, Hugo is built on Go and requires nothing more than the `hugo` binary. That makes it much easier for me to hop between devices. Getting started with Hugo is [pretty damn simple](https://gohugo.io/getting-started/quick-start/), and Hugo provides some very cool [built-in features](https://gohugo.io/about/features/) which Jekyll would need external plugins to provide. And there are of course [plenty of lovely themes](https://themes.gohugo.io/) to help your site look its best.
|
||||
|
||||
Hugo's real claim to fame, though, is its speed. Building a site with Hugo is *much* faster than with Jekyll, and that makes it quicker to test changes locally before pushing them out onto the internet.
|
||||
Hugo's real claim to fame, though, is its speed. Building a site with Hugo is *much* faster than with Jekyll, and that makes it quicker to test changes locally before pushing them out onto the internet.
|
||||
|
||||
Jekyll was a great way for me to get started on managing my own site with a SSG, but Hugo seems to me like a more modern approach. I decided to start working on migrating Virtually Potato over to Hugo. Hugo even made it easy to import my existing content with the `hugo import jekyll` command.
|
||||
Jekyll was a great way for me to get started on managing my own site with a SSG, but Hugo seems to me like a more modern approach. I decided to start working on migrating Virtually Potato over to Hugo. Hugo even made it easy to import my existing content with the `hugo import jekyll` command.
|
||||
|
||||
After a few hours spent trying out different themes, I landed on the [Hugo Clarity theme](https://github.com/chipzoller/hugo-clarity) which is based on [VMware's Clarity Design](https://clarity.design/). This theme offers a user-selectable light/dark theme, lots of great enhancements for displaying code snippets, and a responsive mobile layout, and I just thought that incorporating some of VMware's style into this site felt somehow appropriate. It did take quite a bit of tweaking to get everything integrated and working the way I wanted it to (and to update the existing content to fit), but I learned a ton in the process so I consider that time well spent.
|
||||
After a few hours spent trying out different themes, I landed on the [Hugo Clarity theme](https://github.com/chipzoller/hugo-clarity) which is based on [VMware's Clarity Design](https://clarity.design/). This theme offers a user-selectable light/dark theme, lots of great enhancements for displaying code snippets, and a responsive mobile layout, and I just thought that incorporating some of VMware's style into this site felt somehow appropriate. It did take quite a bit of tweaking to get everything integrated and working the way I wanted it to (and to update the existing content to fit), but I learned a ton in the process so I consider that time well spent.
|
||||
|
||||
Along the way I also wanted to try out [Netlify](https://www.netlify.com/) for building and serving the site online instead of the rather bare-bones GitHub Pages that I'd been using. Like GitHub Pages, you can configure Netlify to watch a repository (on GitHub, GitLab, or Bitbucket) and it will fire off a build whenever new stuff is committed. By default, that latest build will be automatically published to your site, but Netlify also provides much more control of this process. You can pause publishing, manually publish a certain deployment, quickly rollback in case of any issues, and also preview deployments before they get published to the live site.
|
||||
Along the way I also wanted to try out [Netlify](https://www.netlify.com/) for building and serving the site online instead of the rather bare-bones GitHub Pages that I'd been using. Like GitHub Pages, you can configure Netlify to watch a repository (on GitHub, GitLab, or Bitbucket) and it will fire off a build whenever new stuff is committed. By default, that latest build will be automatically published to your site, but Netlify also provides much more control of this process. You can pause publishing, manually publish a certain deployment, quickly rollback in case of any issues, and also preview deployments before they get published to the live site.
|
||||
|
||||
Putting Netlify in front of the repositories where my site content is stored also enabled a pretty seamless transition once I was ready to actually flip the switch on the new-and-improved Virtually Potato. I had actually been using Netlify to serve the Jekyll version of this site for a week or two. When it was time to change, I disabled the auto-publish feature to pin that version of the site and then reconfigured which repository Netlify was watching. That kicked off a new (unpublished) deploy of the new Hugo site and I was able to preview it to confirm that everything looked just as it had in my local environment. Once I was satisfied I just clicked a button to start publishing the Hugo-based deploy, and the new site was live, instantly - no messing with DNS records or worrying about certificates, that was all taken care of by Netlify.
|
||||
Putting Netlify in front of the repositories where my site content is stored also enabled a pretty seamless transition once I was ready to actually flip the switch on the new-and-improved Virtually Potato. I had actually been using Netlify to serve the Jekyll version of this site for a week or two. When it was time to change, I disabled the auto-publish feature to pin that version of the site and then reconfigured which repository Netlify was watching. That kicked off a new (unpublished) deploy of the new Hugo site and I was able to preview it to confirm that everything looked just as it had in my local environment. Once I was satisfied I just clicked a button to start publishing the Hugo-based deploy, and the new site was live, instantly - no messing with DNS records or worrying about certificates, that was all taken care of by Netlify.
|
||||
|
||||
**Anyway, here we are: the new Virtually Potato, powered by Hugo and Netlify!**
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ timeless: true
|
|||
description: There are no dumb questions - but there are smarter (and dumber) ways to ask them.
|
||||
featured: true
|
||||
aliases: ["how2ask"]
|
||||
series: Tips
|
||||
categories: Tips
|
||||
---
|
||||
I spend a lot of my time and energy answering technical questions, both professionally and "for fun" as a way to scratch that troubleshooting itch. How a question is asked plays a big factor in how effectively I'll be able to answer it.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: vRA8
|
||||
categories: VMware
|
||||
date: "2021-02-22T08:34:30Z"
|
||||
lastmod: 2022-07-25
|
||||
thumbnail: 7_QI-Ti8g.png
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: vRA8
|
||||
categories: VMware
|
||||
date: "2021-07-21T00:00:00Z"
|
||||
thumbnail: 20210721-successful-ad_machine.png
|
||||
usePageBundles: true
|
||||
|
|
|
@ -14,7 +14,7 @@ usePageBundles: true
|
|||
thumbnail: "thumbnail.jpg" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: K8s on vSphere
|
||||
categories: VMware
|
||||
tags:
|
||||
- vmware
|
||||
- linux
|
||||
|
@ -24,7 +24,7 @@ tags:
|
|||
- containers
|
||||
- iac
|
||||
- packer
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
I've been leveraging the open-source Tanzu Community Edition Kubernetes distribution for a little while now, both [in my home lab](/tanzu-community-edition-k8s-homelab) and at work, so I was disappointed to learn that VMware was [abandoning the project](https://github.com/vmware-tanzu/community-edition). TCE had been a pretty good fit for my needs, and now I needed to search for a replacement. VMware is offering a free version of Tanzu Kubernetes Grid as a replacement, but it comes with a license solely for non-commercial use so I wouldn't be able to use it at work. And I'd really like to use the same solution in both environments to make development and testing easier on me.
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ usePageBundles: true
|
|||
thumbnail: "ldaps_test.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
shareImage: "ldaps_test.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: K8s on vSphere
|
||||
categories: VMware
|
||||
tags:
|
||||
- vmware
|
||||
- kubernetes
|
||||
|
@ -23,7 +23,7 @@ tags:
|
|||
- certs
|
||||
- cluster
|
||||
- containers
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
Not long ago, I [deployed a Tanzu Community Edition Kubernetes cluster in my homelab](/tanzu-community-edition-k8s-homelab/), and then I fumbled through figuring out how to [log into it from a different device](/logging-in-tce-cluster-from-new-device/) than the one I'd used for deploying the cluster from the `tanzu` cli. That setup works great for playing with Kubernetes in my homelab but I'd love to do some Kubernetes with my team at work and I really need the ability to authenticate multiple users with domain credentials for that.
|
||||
|
||||
|
|
|
@ -14,12 +14,12 @@ featureImage: "tanzu.png" # Sets featured image on blog post.
|
|||
thumbnail: "tanzu.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Tips
|
||||
categories: VMware
|
||||
tags:
|
||||
- vmware
|
||||
- kubernetes
|
||||
- tanzu
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
When I [set up my Tanzu Community Edition environment](/tanzu-community-edition-k8s-homelab/), I did so from a Linux VM since the containerized Linux environment on my Chromebook doesn't support the `kind` bootstrap cluster used for the deployment. But now that the Kubernetes cluster is up and running, I'd like to be able to connect to it directly without the aid of a jumpbox. How do I get the appropriate cluster configuration over to my Chromebook?
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Code
|
||||
categories: Code
|
||||
date: "2020-09-16T08:34:30Z"
|
||||
thumbnail: LJOcy2oqc.png
|
||||
usePageBundles: true
|
||||
|
|
|
@ -14,14 +14,14 @@ usePageBundles: true
|
|||
thumbnail: "nessus_login.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Tips # Projects, Code, vRA8
|
||||
categories: Self-Hosting
|
||||
tags:
|
||||
- vmware
|
||||
- kubernetes
|
||||
- tanzu
|
||||
- containers
|
||||
- security
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
Now that VMware [has released](https://blogs.vmware.com/vsphere/2022/01/announcing-availability-of-vsphere-7-update-3c.html) [vCenter 7.0U3c](https://docs.vmware.com/en/VMware-vSphere/7.0/rn/vsphere-vcenter-server-70u3c-release-notes.html) to resolve the Log4Shell vulnerabilities I thought it might be fun to run a security scan against the upgraded VCSA in my homelab to see how it looks. Of course, I don't actually have a security scanner in that environment so I'll need to deploy one.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: vRA8
|
||||
categories: VMware
|
||||
date: "2021-08-25T00:00:00Z"
|
||||
usePageBundles: true
|
||||
tags:
|
||||
|
@ -13,7 +13,7 @@ title: Notes on vRA HA with NSX-ALB
|
|||
This is going to be a pretty quick recap of the steps I recently took to convert a single-node instance of vRealize Automation 8.4.2 into a 3-node High-Availability vRA cluster behind a standalone NSX Advanced Load Balancer (without NSX being deployed in the environment). No screenshots or specific details since I ran through this in the lab at work and didn't capture anything along the way, and my poor NUC homelab struggles enough to run a single instance of memory-hogging vRA.
|
||||
|
||||
### Getting started with NSX-ALB
|
||||
I found a lot of information on how to use NSX-ALB as a component of a broader NSX-equipped environment, but not a lot of detail on how to use the ALB *without* NSX - until I found [Rudi Martinsen's blog on the subject](https://rudimartinsen.com/2021/06/25/load-balancing-with-nsx-alb/). That turned out to be a great reference for the ALB configuration so be sure to check it out if you need more details than what I provide in this section.
|
||||
I found a lot of information on how to use NSX-ALB as a component of a broader NSX-equipped environment, but not a lot of detail on how to use the ALB *without* NSX - until I found [Rudi Martinsen's blog on the subject](https://rudimartinsen.com/2021/06/25/load-balancing-with-nsx-alb/). That turned out to be a great reference for the ALB configuration so be sure to check it out if you need more details than what I provide in this section.
|
||||
|
||||
#### Download
|
||||
NSX-ALB is/was formerly known as the Avi Vantage Controller, and downloads are available [here](https://portal.avipulse.vmware.com/software/vantage). You'll need to log in with your VMware Customer Connect account to access the download, and then grab the latest VMware Controller OVA. Be sure to make a note of the default password listed on the right-hand side since you'll need that to log in post-deployment.
|
||||
|
@ -45,7 +45,7 @@ Then go back to **Infastructure > Clouds**, edit the Cloud, and select the IPAM
|
|||
Navigate to **Infrastructure > Cloud Resources > Service Engine Group** and edit the *Default-Group*. I left everything on the *Basic Settings* tab at the defaults. On the *Advanced* tab, I specified which vSphere cluster the Service Engines should be deployed to. And I left everything else with the default settings.
|
||||
|
||||
#### SSL Certificate
|
||||
Hop over to **Templates > Security > SSL/TLS Certificates** and click **Create > Application Certificate**. Give the new cert a name and change the **Type** to `CSR` to generate a new signing request. Enter the **Common Name** you're going to want to use for the load balancer VIP (something like `vra`, perhaps?) and all the usual cert fields. Use the **Subject Alternate Name (SAN)** section at the bottom to add all the other components, like the individual vRA cluster members by both hostname and FQDN. I went ahead and included those IPs as well for good measure.
|
||||
Hop over to **Templates > Security > SSL/TLS Certificates** and click **Create > Application Certificate**. Give the new cert a name and change the **Type** to `CSR` to generate a new signing request. Enter the **Common Name** you're going to want to use for the load balancer VIP (something like `vra`, perhaps?) and all the usual cert fields. Use the **Subject Alternate Name (SAN)** section at the bottom to add all the other components, like the individual vRA cluster members by both hostname and FQDN. I went ahead and included those IPs as well for good measure.
|
||||
|
||||
| Name |
|
||||
|----------------------|
|
||||
|
@ -60,14 +60,14 @@ Hop over to **Templates > Security > SSL/TLS Certificates** and click **Create >
|
|||
| `vra03` |
|
||||
| `192.168.1.43` |
|
||||
|
||||
Click **Save**.
|
||||
Click **Save**.
|
||||
|
||||
Click **Create** again, but this time select **Root/Intermediate CA Certificate** and upload/paste your CA's cert so it can be trusted. Save your work.
|
||||
|
||||
Back at the cert list, find your new application cert and click the pencil icon to edit it. Copy the **Certificate Signing Request** field and go get it signed by your CA. Be sure to grab the certificate chain (base64-encoded) as well if you can. Come back and paste in / upload your shiny new CA-signed certificate file.
|
||||
|
||||
#### Virtual Service
|
||||
Now it's finally time to create the Virtual Service that will function as the load balancer front-end. Pop over to **Applications > Virtual Services** and click **Create Virtual Service > Basic Setup**. Give it a name and set the **Application Type** to `HTTPS`, which will automatically set the port and bind a default self-signed certificate.
|
||||
Now it's finally time to create the Virtual Service that will function as the load balancer front-end. Pop over to **Applications > Virtual Services** and click **Create Virtual Service > Basic Setup**. Give it a name and set the **Application Type** to `HTTPS`, which will automatically set the port and bind a default self-signed certificate.
|
||||
|
||||
Click on the **Certificate** field and select the new cert you created above. Be sure to remove the default cert.
|
||||
|
||||
|
@ -81,12 +81,12 @@ Now that the Virtual Service is created, make a note of the IP address assigned
|
|||
Log into LifeCycle Manager in a new browser tab/window. Make sure that you've mapped an *Install* product binary for your current version of vRA; the upgrade binary that you probably used to do your last update won't cut it. It's probably also a good idea to go make a snapshot of your vRA and IDM instances just in case.
|
||||
|
||||
#### Adding new certificate
|
||||
In LCM, go to **Locker > Certificates** and select the option to **Import**. Switch back to the NSX-ALB tab and go to **Templates > Security > SSL/TLS Certificates**. Click the little down-arrow-in-a-circle "Export" icon next to the application certificate you created earlier. Copy the key section and paste that into LCM. Then open the file containing the certificate chain you got from your CA, copy its contents, and paste it into LCM as well. Do *not* try to upload a certificate file directly to LCM; that will fail unless the file includes both the cert and the private key and that's silly.
|
||||
In LCM, go to **Locker > Certificates** and select the option to **Import**. Switch back to the NSX-ALB tab and go to **Templates > Security > SSL/TLS Certificates**. Click the little down-arrow-in-a-circle "Export" icon next to the application certificate you created earlier. Copy the key section and paste that into LCM. Then open the file containing the certificate chain you got from your CA, copy its contents, and paste it into LCM as well. Do *not* try to upload a certificate file directly to LCM; that will fail unless the file includes both the cert and the private key and that's silly.
|
||||
|
||||
Once the cert is successfully imported, go to the **Lifecycle Operations** component of LCM and navigate to the environment containing your vRA instance. Select the vRA product, hit the three-dot menu, and use the **Replace Certificate** option to replace the old and busted cert with the new HA-ready one. It will take a little bit for this to get applied. Don't move on until vRA services are back up.
|
||||
|
||||
#### Scale out vRA
|
||||
Still on the vRA product page, click on the **+ Add Components** button.
|
||||
Still on the vRA product page, click on the **+ Add Components** button.
|
||||
|
||||
On the **Infrastructure** page, tell LCM where to put the new VRA VMs.
|
||||
|
||||
|
|
|
@ -14,12 +14,12 @@ usePageBundles: true
|
|||
thumbnail: "PowerCLI.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Code
|
||||
categories: Code
|
||||
tags:
|
||||
- vmware
|
||||
- powercli
|
||||
- powershell
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
|
||||
I recently needed to export a list of all the Linux VMs in a rather large vSphere environment spanning multiple vCenters (and the entire globe), and I wanted to include information about which virtual datacenter each VM lived in to make it easier to map VMs to their physical location.
|
||||
|
|
|
@ -14,11 +14,11 @@ usePageBundles: true
|
|||
# thumbnail: "thumbnail.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Code
|
||||
categories: Code
|
||||
tags:
|
||||
- powershell
|
||||
- windows
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
We've been working lately to use [HashiCorp Packer](https://www.packer.io/) to standardize and automate our VM template builds, and we found a need to pull in all of the contents of a specific directory on an internal web server. This would be pretty simple for Linux systems using `wget -r`, but we needed to find another solution for our Windows builds.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Tips
|
||||
categories: VMware
|
||||
date: "2021-01-30T08:34:30Z"
|
||||
thumbnail: XTaU9VDy8.png
|
||||
usePageBundles: true
|
||||
|
|
|
@ -14,13 +14,13 @@ usePageBundles: true
|
|||
# thumbnail: "thumbnail.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Tips # Projects, Code, vRA8, K8s on vSphere
|
||||
categories: VMware # Projects, Code, vRA8, K8s on vSphere
|
||||
tags:
|
||||
- vmware
|
||||
- powershell
|
||||
- windows
|
||||
- powercli
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
{{% notice note "Fix available" %}}
|
||||
VMware has released a fix for this problem in the form of [ESXi 7.0 Update 3k](https://docs.vmware.com/en/VMware-vSphere/7.0/rn/vsphere-esxi-70u3k-release-notes.html#resolvedissues):
|
||||
|
|
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 86 KiB |
182
content/posts/publish-services-cloudflare-tunnel/index.md
Normal file
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
title: "Publish Services with Cloudflare Tunnel"
|
||||
date: 2024-01-15
|
||||
# lastmod: 2024-01-13
|
||||
description: "Exploring Cloudflare Tunnel as an alternative to Tailscale Funnel for secure public access to internal resources."
|
||||
featured: false
|
||||
toc: true
|
||||
comments: true
|
||||
categories: Self-Hosting
|
||||
tags:
|
||||
- cloud
|
||||
- containers
|
||||
- docker
|
||||
- networking
|
||||
- selfhosting
|
||||
---
|
||||
I've written a bit lately about how handy [Tailscale Serve and Funnel](/tailscale-ssh-serve-funnel/) can be, and I continue to get a lot of great use out of those features. But not *every* networking nail is best handled with a Tailscale-shaped hammer. Funnel has two limitations that might make it less than ideal for certain situations.
|
||||
|
||||
First, sites served with Funnel can only have a hostname in the form of `server.tailnet-name.ts.net`. You can't use a custom domain for this, but you might not always want to advertise that a service is shared via Tailscale. Second, Funnel connections have an undisclosed bandwidth limit, which could cause problems if you're hoping to serve media through the Funnel.
|
||||
|
||||
For instance, I've started using [Immich](https://immich.app/) as a self-hosted alternative to Google Photos. Using Tailscale Serve to make my Immich server available on my Tailnet works beautifully, and I initially set up a Funnel connection to use for when I wanted to share certain photos, videos, and albums externally. I quickly realized that it took *f o r e v e r* to load the page when those links were shared publicly. I probably won't share a lot of those public links but I'd like them to be a bit more responsive when I do.
|
||||
|
||||
I went looking for another solution, and I found one in a suite of products I already use.
|
||||
|
||||
### Overview
|
||||
I've been using [Cloudflare's generious free plan](https://www.cloudflare.com/plans/free/) for DNS, content caching, page/domain redirects, email forwarding, and DDoS mitigation[^more] across my dozen or so domains. In addition to these "basic" services and features, Cloudflare also offers a selection of [Zero Trust Network Access](https://www.cloudflare.com/products/zero-trust/zero-trust-network-access/) products, and one of those is [Cloudflare Tunnel](https://www.cloudflare.com/products/tunnel/) - also available with a generous free plan.
|
||||
|
||||
[^more]: And a ton of other things I'm forgetting right now.
|
||||
|
||||
In some ways, Cloudflare Tunnel is quite similar to Tailscale Funnel. Both provide a secure way to publish a resource on the internet without requiring a public IP address, port forwarding, or firewall configuration. Both use a lightweight agent on your server to establish an encrypted outbound tunnel, and inbound traffic gets routed into that tunnel through the provider's network. And both solutions automatically provision trusted SSL certificates to keep traffic safe and browsers happy.
|
||||
|
||||
Tailscale Funnel is very easy to set up, and it doesn't require any additional infrastructure, not even a domain name. There aren't a lot of controls available with Funnel - it's basically on or off, and bound to one of three port numbers. You don't get to pick the domain name where it's served, just the hostname of the Tailscale node - and if you want to share multiple resources on the same host you'll [need to get creative](/tailscale-serve-docker-compose-sidecar/). I think this approach is really ideal for quick development and testing scenarios.
|
||||
|
||||
For longer-term, more production-like use, Cloudflare Tunnels is a pretty great fit. It ties in well with existing Cloudflare services, doesn't enforce a reduced bandwidth limit, and provides a bit more flexibility for how your resource will be presented to the web. It can also integrate tightly with the rest of Cloudflare's Zero Trust offerings to easily provide access controls to further protect your resource. It does, however, require a custom domain managed with Cloudflare DNS in order to work[^dns].
|
||||
|
||||
[^dns]: Cloudflare Tunnel lets you choose what hostname and domain name should be used for fronting your tunnel, and it even takes care of configuring the required DNS record automagically.
|
||||
|
||||
For my specific Immich use case, I decided to share my instance via Tailscale Serve for internal access and Cloudflare Tunnel for public shares, and I used a similar sidecar approach to make it work without too much fuss. For the purposes of this blog post, though, I'm going to run through a less complicated example[^complexity].
|
||||
|
||||
[^complexity]: My Immich stack is using ~10 containers and I don't really feel like documenting that all here - not yet, at least.
|
||||
|
||||
### Speedtest Demo
|
||||
I'm going to deploy a quick [SpeedTest by OpenSpeedTest](https://github.com/openspeedtest/Speed-Test) container, and proxy it with both Tailscale Funnel and Cloudflare Tunnel so that I can compare the bandwidth of the two tunnel solutions directly.
|
||||
|
||||
I'll start with a *very* basic Docker Compose definition for just the Speedtest container:
|
||||
|
||||
```yaml
|
||||
# torchlight! {"lineNumbers":true}
|
||||
# docker-compose.yml
|
||||
services:
|
||||
speedtest:
|
||||
image: openspeedtest/latest
|
||||
container_name: speedtest
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
#### Tailscale Funnel
|
||||
And, as in [my last post](/tailscale-serve-docker-compose-sidecar/) I'll add in my Tailscale sidecar to enable funnel:
|
||||
|
||||
```yaml
|
||||
# torchlight! {"lineNumbers":true}
|
||||
# docker-compose.yml
|
||||
services:
|
||||
speedtest:
|
||||
image: openspeedtest/latest
|
||||
container_name: speedtest
|
||||
restart: unless-stopped
|
||||
network_mode: service:tailscale # [tl! ++:start focus:start]
|
||||
tailscale:
|
||||
image: ghcr.io/jbowdre/tailscale-docker:latest
|
||||
container_name: speedtest-tailscaled
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TS_AUTHKEY: ${TS_AUTHKEY:?err}
|
||||
TS_HOSTNAME: ${TS_HOSTNAME:-tailscale-sidecar}
|
||||
TS_STATE_DIR: "/var/lib/tailscale/"
|
||||
TS_EXTRA_ARGS: ${TS_EXTRA_ARGS:-}
|
||||
TS_SERVE_PORT: ${TS_SERVE_PORT:-}
|
||||
TS_FUNNEL: ${TS_FUNNEL:-} # [tl! ++:end focus:end]
|
||||
```
|
||||
|
||||
{{% notice note "Network Mode" %}}
|
||||
I set `network_mode: service:tailscale` on the `speedtest` container so that it will share its network interface with the `tailscale` container. This allows Tailscale Serve/Funnel to proxy `speedtest` at `http://localhost:3000`, which is nice since Tailscale doesn't currently/officially support proxying remote hosts.
|
||||
{{% /notice %}}
|
||||
|
||||
I'll set up a new auth key in the [Tailscale Admin Portal](https://login.tailscale.com/admin/settings/keys), and insert that (along with hostname, port, and funnel configs) into my `.env` file:
|
||||
|
||||
```shell
|
||||
# torchlight! {"lineNumbers":true}
|
||||
# .env
|
||||
TS_AUTHKEY=tskey-auth-somestring-somelongerstring
|
||||
TS_HOSTNAME=speedtest
|
||||
TS_EXTRA_ARGS=--ssh
|
||||
TS_SERVE_PORT=3000 # the port the speedtest runs on by default
|
||||
TS_FUNNEL=true
|
||||
```
|
||||
|
||||
A quick `docker compose up -d` and my new speedtest is alive!
|
||||
|
||||
First I'll hit it at `http://speedtest.tailnet-name.ts.net:3000` to access it purely inside of my Tailnet:
|
||||
![Speedtest from within the tailnet](speedtest-tailnet.png)
|
||||
|
||||
Not bad! Now let's see what happens when I disable Tailscale on my laptop and hit the public Funnel endpoint at `https://speedtest.tailnet-name.ts.net`:
|
||||
![Speedtest from funnel](speedtest-funnel.png)
|
||||
|
||||
Oof. Routing traffic through the Funnel dropped the download by ~25% and the upload by **~90%**, not to mention the significant ping increase.
|
||||
|
||||
#### Cloudflare Tunnel
|
||||
Alright, let's throw a Cloudflare Tunnel on there and see what happens.
|
||||
|
||||
To start that process, I'll log into my [Cloudflare dashboard](https://dash.cloudflare.com) and then use the side navigation to make my way to the **Zero Trust** (AKA "Cloudflare One") area. From there, I'll drill down through **Access -> Tunnels** and click on **+ Create a tunnel**. I'll give it an appropriate name like `speedtest` and then click **Save tunnel**.
|
||||
|
||||
Now Cloudflare helpfully provides installation instructions for a variety of different platforms. I'm doing that Docker thing so I'll click the appropriate button and review that command snippet:
|
||||
![Tunnel installation instructions](install-connector.png)
|
||||
|
||||
I can easily adapt that and add it to my Docker Compose setup[^network-mode]:
|
||||
```yaml
|
||||
# torchlight! {"lineNumbers":true}
|
||||
# docker-compose.yml
|
||||
services:
|
||||
speedtest:
|
||||
image: openspeedtest/latest
|
||||
container_name: speedtest
|
||||
restart: unless-stopped
|
||||
network_mode: service:tailscale
|
||||
tailscale: # [tl! collapse:start]
|
||||
image: ghcr.io/jbowdre/tailscale-docker:latest
|
||||
container_name: speedtest-tailscaled
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TS_AUTHKEY: ${TS_AUTHKEY:?err}
|
||||
TS_HOSTNAME: ${TS_HOSTNAME:-tailscale}
|
||||
TS_STATE_DIR: "/var/lib/tailscale/"
|
||||
TS_EXTRA_ARGS: ${TS_EXTRA_ARGS:-}
|
||||
TS_SERVE_PORT: ${TS_SERVE_PORT:-}
|
||||
TS_FUNNEL: ${TS_FUNNEL:-} # [tl! collapse:end]
|
||||
cloudflared: # [tl! ++:start focus:start]
|
||||
image: cloudflare/cloudflared
|
||||
container_name: speedtest-cloudflared
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- tunnel
|
||||
- --no-autoupdate
|
||||
- run
|
||||
- --token
|
||||
- ${CLOUDFLARED_TOKEN}
|
||||
network_mode: service:tailscale # [tl! ++:end focus:end]
|
||||
```
|
||||
|
||||
[^network-mode]: Setting the `network_mode` isn't strictly necessary for the `cloudflared` container since Cloudflare Tunnel *does* support proxying remote hosts, but I'll just stick with it here for consistency.
|
||||
|
||||
After dropping the value for `CLOUDFLARED_TOKEN` into my `.env` file, I can do another `docker compose up -d` to bring this online - and that status will be reflected back on the config page as well:
|
||||
![Connector is alive!](connector-online.png)
|
||||
|
||||
I'll click **Next** and proceed with the rest of the configuration, which consists of picking a public hostname for the frontend and defining the private service for the backend:
|
||||
![Tunnel configuration](tunnel-configuration.png)
|
||||
|
||||
I can click **Save tunnel** and... that's it. My tunnel is live, and I can now reach my speedtest at `https://speedtest.runtimeterror.dev`. Let's see how it does:
|
||||
![Cloudflare Tunnel speedtest](speedtest-cloudflared.png)
|
||||
|
||||
So that's *much* faster than Tailscale Funnel, and even faster than a direct transfer within the Tailnet. Cloudflare Tunnel should work quite nicely for sharing photos publicly from my Immich instance.
|
||||
|
||||
#### Bonus: Access Control
|
||||
But what if I don't want *just anyone* to be able to use my new speedtest (or access my Immich instance)? Defining an application in Cloudflare One will let me set some limits.
|
||||
|
||||
So I'll go to **Access -> Applications** and select that I'm adding a **Self-hosted** application. I can then do the basic configuration, basically just telling Cloudflare that I'd like to protect the `https://speedtest.runtimeterror.dev` app:
|
||||
![Defining the application](define-application.png)
|
||||
|
||||
I can leave the rest of that page with the default selections so I'll scroll down and click **Next**.
|
||||
|
||||
Now I need to create a policy to apply to this application. I'm going to be simple and just say that anyone with an `@runtimeterror.dev` email address should be able to use my speedtest:
|
||||
![Creating a policy](create-policy.png)
|
||||
|
||||
Without any external identity providers connected, Cloudflare will default to requiring authentication via a one-time PIN sent to an input email address. That's pretty easy, and it pairs well with allowing access based on email address attributes. There are a bunch of other options I could configure if I wanted... but my needs are simple so I'll just click through and save this new application config.
|
||||
|
||||
Now, if I try to visit my speedtest with a new session I'll get automatically routed to the Cloudflare Access challenge which will prompt for my email address.
|
||||
![Access challenge](access-challenge.png)
|
||||
|
||||
If my email is on the approved list (that is, if it ends with `@runtimeterror.dev`), I'll get emailed a code which I can then use to log in and access the speedtest. If not, I won't get in. And since this thing is served through a Cloudflare Tunnel (rather than a public IP address merely advertised via DNS) there isn't any way to bypass Cloudflare's authentication challenge.
|
||||
|
||||
### Conclusion
|
||||
This has been a quick demo of how easy it is to configure a Cloudflare Tunnel to securely publish resources on the web. I really like being able to share a service publicly without having to manage DNS, port-forwarding, or firewall configurations, and the ability to offload authentication and authorization to Cloudflare is a big plus. I still don't think Tailscale can be beat for sharing stuff internally, but I think Cloudflare Tunnels make more sense for long-term public sharing. And it's awesome that I can run both solutions side-by-side to really get the best of both when I need it.
|
After Width: | Height: | Size: 129 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 99 KiB |
After Width: | Height: | Size: 123 KiB |
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Tips
|
||||
categories: Backstage
|
||||
date: "2021-07-24T16:46:00Z"
|
||||
thumbnail: 20210724-series-navigation.png
|
||||
usePageBundles: true
|
||||
|
@ -9,12 +9,12 @@ tags:
|
|||
title: Recreating Hashnode Series (Categories) in Jekyll on GitHub Pages
|
||||
---
|
||||
|
||||
I recently [migrated this site](/virtually-potato-migrated-to-github-pages) from Hashnode to GitHub Pages, and I'm really getting into the flexibility and control that managing the content through Jekyll provides. So, naturally, after finalizing the move I got to work recreating Hashnode's "Series" feature, which lets you group posts together and highlight them as a collection. One of the things I liked about the Series setup was that I could control the order of the collected posts: my posts about [building out the vRA environment in my homelab](/series/vra8) are probably best consumed in chronological order (oldest to newest) since the newer posts build upon the groundwork laid by the older ones, while posts about my [other one-off projects](/series/projects) could really be enjoyed in any order.
|
||||
I recently [migrated this site](/virtually-potato-migrated-to-github-pages) from Hashnode to GitHub Pages, and I'm really getting into the flexibility and control that managing the content through Jekyll provides. So, naturally, after finalizing the move I got to work recreating Hashnode's "Series" feature, which lets you group posts together and highlight them as a collection. One of the things I liked about the Series setup was that I could control the order of the collected posts: my posts about [building out the vRA environment in my homelab](/categories/vmware) are probably best consumed in chronological order (oldest to newest) since the newer posts build upon the groundwork laid by the older ones, while posts about my [other one-off projects](/categories/self-hosting) could really be enjoyed in any order.
|
||||
|
||||
I quickly realized that if I were hosting this pretty much anywhere *other* than GitHub Pages I could simply leverage the [`jekyll-archives`](https://github.com/jekyll/jekyll-archives) plugin to manage this for me - but, alas, that's not one of the [plugins supported by the platform](https://pages.github.com/versions/). I needed to come up with my own solution, and being still quite new to Jekyll (and this whole website design thing in general) it took me a bit of fumbling to get it right.
|
||||
|
||||
### Reviewing the theme-provided option
|
||||
The Jekyll theme I'm using ([Minimal Mistakes](https://github.com/mmistakes/minimal-mistakes)) comes with [built-in support](https://mmistakes.github.io/mm-github-pages-starter/categories/) for a [category archive page](/series), which (like the [tags page](/tags)) displays all the categorized posts on a single page. Links at the top will let you jump to an appropriate anchor to start viewing the selected category, but it's not really an elegant way to display a single category.
|
||||
The Jekyll theme I'm using ([Minimal Mistakes](https://github.com/mmistakes/minimal-mistakes)) comes with [built-in support](https://mmistakes.github.io/mm-github-pages-starter/categories/) for a [category archive page](/categories), which (like the [tags page](/tags)) displays all the categorized posts on a single page. Links at the top will let you jump to an appropriate anchor to start viewing the selected category, but it's not really an elegant way to display a single category.
|
||||
![Posts by category](20210724-posts-by-category.png)
|
||||
|
||||
It's a start, though, so I took a few minutes to check out how it's being generated. The category archive page lives at [`_pages/category-archive.md`](https://raw.githubusercontent.com/mmistakes/mm-github-pages-starter/master/_pages/category-archive.md):
|
||||
|
@ -144,7 +144,7 @@ Since I can't use a plugin to automatically generate pages for each series, I'll
|
|||
---
|
||||
title: "Adventures in vRealize Automation 8"
|
||||
layout: series
|
||||
permalink: "/series/vra8"
|
||||
permalink: "/categories/vmware"
|
||||
tag: vRA8
|
||||
sort_order: reverse
|
||||
author_profile: true
|
||||
|
@ -155,9 +155,9 @@ header:
|
|||
*Follow along as I create a flexible VMware vRealize Automation 8 environment for provisioning virtual machines - all from the comfort of my Intel NUC homelab.*
|
||||
```
|
||||
|
||||
You can see that this page is referencing the series layout I just created, and it's going to live at `http://localhost/series/vra8` - precisely where this series was on Hashnode. I've tagged it with the category I want to feature on this page, and specified that the posts will be sorted in reverse order so that anyone reading through the series will start at the beginning (I hear it's a very good place to start). I also added a teaser image which will be displayed when I link to the series from elsewhere. And I included a quick little italicized blurb to tell readers what the series is about.
|
||||
You can see that this page is referencing the series layout I just created, and it's going to live at `http://localhost/categories/vmware` - precisely where this series was on Hashnode. I've tagged it with the category I want to feature on this page, and specified that the posts will be sorted in reverse order so that anyone reading through the series will start at the beginning (I hear it's a very good place to start). I also added a teaser image which will be displayed when I link to the series from elsewhere. And I included a quick little italicized blurb to tell readers what the series is about.
|
||||
|
||||
Check it out [here](/series/vra8):
|
||||
Check it out [here](/categories/vmware):
|
||||
![vRA8 series](20210724-vra8-series.png)
|
||||
|
||||
The other series pages will be basically the same, just without the reverse sort directive. Here's `_pages/series-tips.md`:
|
||||
|
@ -202,7 +202,7 @@ author_profile: true
|
|||
```
|
||||
|
||||
### Fixing category links in posts
|
||||
The bottom of each post has a section which lists the tags and categories to which it belongs. Right now, those are still pointing to the category archive page (`/series/#vra8`) instead of the series feature pages I created (`/series/vra8`).
|
||||
The bottom of each post has a section which lists the tags and categories to which it belongs. Right now, those are still pointing to the category archive page (`/series/#vra8`) instead of the series feature pages I created (`/categories/vmware`).
|
||||
![Old category link](20210724-old-category-link.png)
|
||||
|
||||
That *works* but I'd rather it reference the fancy new pages I created. Tracking down where to make that change was a bit of a journey.
|
||||
|
@ -245,7 +245,7 @@ Okay, it looks like [`_include/category-list.html`](https://github.com/mmistakes
|
|||
{% assign categories_sorted = page.categories | sort_natural %}
|
||||
|
||||
<p class="page__taxonomy">
|
||||
<strong><i class="fas fa-fw fa-folder-open" aria-hidden="true"></i> {{ site.data.ui-text[site.locale].categories_label | default: "series:" }} </strong>
|
||||
<strong><i class="fas fa-fw fa-folder-open" aria-hidden="true"></i> {{ site.data.ui-text[site.locale].categories_label | default: "categories:" }} </strong>
|
||||
<span itemprop="keywords">
|
||||
{% for category_word in categories_sorted %}
|
||||
<a href="{{ category_word | slugify | prepend: path_type | prepend: site.category_archive.path | relative_url }}" class="page__taxonomy-item p-category" rel="tag">{{ category_word }}</a>{% unless forloop.last %}<span class="sep">, </span>{% endunless %}
|
||||
|
@ -283,9 +283,9 @@ And, finally, I'll want to update the navigation links at the top of each page t
|
|||
# torchlight! {"lineNumbers": true}
|
||||
main:
|
||||
- title: "vRealize Automation 8"
|
||||
url: /series/vra8
|
||||
url: /categories/vmware
|
||||
- title: "Projects"
|
||||
url: /series/projects
|
||||
url: /categories/self-hosting
|
||||
- title: "Code"
|
||||
url: /series/code
|
||||
- title: "Tips & Tricks"
|
||||
|
|
|
@ -14,12 +14,12 @@ featureImage: "basic-architecture.png" # Sets featured image on blog post.
|
|||
thumbnail: "basic-architecture.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Tips # Projects, Code, vRA8
|
||||
categories: VMware # Projects, Code, vRA8
|
||||
tags:
|
||||
- vmware
|
||||
- vsphere
|
||||
- homelab
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
|
||||
Way back in 2020, VMware released vSphere 7 Update 1 and introduced the new [vSphere Clustering Services (vCLS)](https://core.vmware.com/resource/introduction-vsphere-clustering-service-vcls) to improve how cluster services like the Distributed Resource Scheduler (DRS) operate. vCLS deploys lightweight agent VMs directly on the cluster being managed, and those VMs provide a decoupled and distributed control plane to offload some of the management responsibilities from the vCenter server.
|
||||
|
@ -36,7 +36,7 @@ Fortunately there's a somewhat-hidden way to disable (and re-enable) vCLS on a p
|
|||
Disabling vCLS will break DRS, and could have other unintended side effects. Don't do this in prod if you can avoid it.
|
||||
{{% /notice %}}
|
||||
|
||||
[^off-and-on]: ![](off-and-on.gif)
|
||||
[^off-and-on]: ![GIF from The IT Crowd: "Have you tried turning it off and back on again?"](off-and-on.gif)
|
||||
|
||||
### Find the cluster's domain ID
|
||||
It starts with determining the affected cluster's domain ID, which is very easy to do once you know where to look. Simply browse to the cluster object in the vSphere inventory, and look at the URL:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: vRA8
|
||||
categories: VMware
|
||||
date: "2021-09-03T00:00:00Z"
|
||||
thumbnail: 20210903_action_run_success.png
|
||||
usePageBundles: true
|
||||
|
@ -11,7 +11,7 @@ tags:
|
|||
- vmware
|
||||
title: Run scripts in guest OS with vRA ABX Actions
|
||||
---
|
||||
Thus far in my [vRealize Automation project](/series/vra8), I've primarily been handing the payload over to vRealize Orchestrator to do the heavy lifting on the back end. This approach works really well for complex multi-part workflows (like when [generating unique hostnames](/vra8-custom-provisioning-part-two#the-vro-workflow)), but it may be overkill for more linear tasks (such as just running some simple commands inside of a deployed guest OS). In this post, I'll explore how I use [vRA Action Based eXtensibility (ABX)](https://blogs.vmware.com/management/2020/09/vra-abx-flow.html) to do just that.
|
||||
Thus far in my [vRealize Automation project](/categories/vmware), I've primarily been handing the payload over to vRealize Orchestrator to do the heavy lifting on the back end. This approach works really well for complex multi-part workflows (like when [generating unique hostnames](/vra8-custom-provisioning-part-two#the-vro-workflow)), but it may be overkill for more linear tasks (such as just running some simple commands inside of a deployed guest OS). In this post, I'll explore how I use [vRA Action Based eXtensibility (ABX)](https://blogs.vmware.com/management/2020/09/vra-abx-flow.html) to do just that.
|
||||
|
||||
### The Goal
|
||||
My ABX action is going to use PowerCLI to perform a few steps inside a deployed guest OS (Windows-only for this demonstration):
|
||||
|
@ -97,6 +97,7 @@ resources:
|
|||
|
||||
And I will add in a `storage` property as well which will automatically adjust the deployed VMDK size to match the specified input:
|
||||
```yaml
|
||||
# torchlight! {"torchlightAnnotations": false}
|
||||
# torchlight! {"lineNumbers": true}
|
||||
[...]
|
||||
description: '${input.description}'
|
||||
|
@ -202,7 +203,6 @@ inputs:
|
|||
type: string
|
||||
title: Point of Contact Email
|
||||
default: jack.shephard@example.com
|
||||
pattern: '^[^\s@]+@[^\s@]+\.[^\s@]+$'
|
||||
ticket:
|
||||
type: string
|
||||
title: Ticket/Request Number
|
||||
|
@ -292,7 +292,7 @@ Once all those constants are created I can move on to the meat of this little pr
|
|||
|
||||
#### ABX Action
|
||||
I'll click back to **Extensibility > Library > Actions** and then **+ New Action**. I give the new action a clever title and description:
|
||||
![Create a new action](20210901_create_action.png)]
|
||||
![Create a new action](20210901_create_action.png)
|
||||
|
||||
I then hit the language dropdown near the top left and select to use `powershell` so that I can use those sweet, sweet PowerCLI cmdlets.
|
||||
![Language selection](20210901_action_select_language.png)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Projects
|
||||
categories: Code
|
||||
date: "2020-11-14T08:34:30Z"
|
||||
thumbnail: aeIOr8w6k.png
|
||||
usePageBundles: true
|
||||
|
@ -11,11 +11,11 @@ tags:
|
|||
title: Safeguard your Android's battery with Tasker + Home Assistant
|
||||
---
|
||||
|
||||
A few months ago, I started using the [AccuBattery app](https://play.google.com/store/apps/details?id=com.digibites.accubattery) to keep a closer eye on how I'd been charging my phones. The app has a handy feature that notifies you once the battery level reaches a certain threshold so you can pull the phone off the charger and extend the lithium battery's service life, and it even offers an estimate for what that impact might be. For instance, right now the app indicates that charging my Pixel 5 from 51% to 100% would cause 0.92 wear cycles, while stopping the charge at 80% would impose just 0.17 cycles.
|
||||
A few months ago, I started using the [AccuBattery app](https://play.google.com/store/apps/details?id=com.digibites.accubattery) to keep a closer eye on how I'd been charging my phones. The app has a handy feature that notifies you once the battery level reaches a certain threshold so you can pull the phone off the charger and extend the lithium battery's service life, and it even offers an estimate for what that impact might be. For instance, right now the app indicates that charging my Pixel 5 from 51% to 100% would cause 0.92 wear cycles, while stopping the charge at 80% would impose just 0.17 cycles.
|
||||
|
||||
![AccuBattery screenshot](aeIOr8w6k.png)
|
||||
|
||||
But that depends on me being near my phone and conscious so I can take action when the notification goes off. That's often a big assumption to make - and, frankly, I'm lazy.
|
||||
But that depends on me being near my phone and conscious so I can take action when the notification goes off. That's often a big assumption to make - and, frankly, I'm lazy.
|
||||
|
||||
I'm fortunately also fairly crafty, so I came up with a way to combine my favorite Android automation app with my chosen home automation platform to take my laziness out of the picture.
|
||||
|
||||
|
@ -25,7 +25,7 @@ I'm fortunately also fairly crafty, so I came up with a way to combine my favori
|
|||
- [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm)
|
||||
- [Home Assistant Plug-In for Tasker](https://play.google.com/store/apps/details?id=com.markadamson.taskerplugin.homeassistant)
|
||||
|
||||
I'm not going to go through how to install Home Assistant on the Pi or how to configure it beyond what's strictly necessary for this particular recipe. The official [getting started documentation](https://www.home-assistant.io/getting-started/) is a great place to start.
|
||||
I'm not going to go through how to install Home Assistant on the Pi or how to configure it beyond what's strictly necessary for this particular recipe. The official [getting started documentation](https://www.home-assistant.io/getting-started/) is a great place to start.
|
||||
|
||||
### The Recipe
|
||||
1. Plug the Wemo into a wall outlet, and plug a phone charger into the Wemo. Add the Belkin Wemo integration in Home Assistant, and configure the device and entity. I named mine `switchy`. Make a note of the Entity ID: `switch.switchy`. We'll need that later.
|
||||
|
@ -37,7 +37,7 @@ For the Service field, you need to tell HA what you want it to do. We want it to
|
|||
```json
|
||||
{"entity_id": "switch.switchy"}
|
||||
```
|
||||
Tap Test Service to make sure it works - and verify that the switch does indeed turn off.
|
||||
Tap Test Service to make sure it works - and verify that the switch does indeed turn off.
|
||||
![Creating and testing the service](U3LfmEJ_7.png)
|
||||
4. Hard part is over. Now we just need to set up a profile in Tasker to fire our new task. I named mine 'Charge Limiter'. I started with `State > Power > Battery Level` and set it to trigger between 81-100%., and also added `State > Power > Source: Any` so it will only be active while charging. I also only want this to trigger while my phone is charging at home, so I added `State > Net > Wifi Connected` and then specified my home SSID. Link this profile to the Task you created earlier, and never worry about overcharging your phone again.
|
||||
![Tasker profile to kill power above 80%](h7tl6facr.png)
|
||||
|
|
|
@ -5,8 +5,8 @@ lastmod: 2023-12-22
|
|||
description: "A hasty Salt state to deploy netdata monitoring and publish it internally on my tailnet with Tailscale Serve"
|
||||
featured: false
|
||||
toc: true
|
||||
comment: true
|
||||
series: Code
|
||||
comments: true
|
||||
categories: Code
|
||||
tags:
|
||||
- homelab
|
||||
- iac
|
||||
|
|
|
@ -15,12 +15,12 @@ thumbnail: "thumbnail.png" # Sets thumbnail image appearing inside card on homep
|
|||
# shareImage: "/images/path/share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
codeMaxLines: 30
|
||||
series: Code
|
||||
categories: Backstage
|
||||
tags:
|
||||
- hugo
|
||||
- meta
|
||||
- shell
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
In case you missed [the news](/hello-hugo), I recently migrated this blog from a site built with Jekyll to one built with Hugo. One of Hugo's cool features is the concept of [Page Bundles](https://gohugo.io/content-management/page-bundles/), which _bundle_ a page's resources together in one place instead of scattering them all over the place.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Code
|
||||
categories: Backstage
|
||||
date: "2021-07-19T16:03:30Z"
|
||||
usePageBundles: true
|
||||
tags:
|
||||
|
|
|
@ -14,7 +14,7 @@ featureImageAlt: 'Tailscale Logo' # Alternative text for featured image.
|
|||
thumbnail: "Tailscale-AppIcon.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Projects
|
||||
categories: Self-Hosting
|
||||
tags:
|
||||
- vpn
|
||||
- wireguard
|
||||
|
@ -24,7 +24,7 @@ tags:
|
|||
- networking
|
||||
- security
|
||||
- tailscale
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
Not all that long ago, I shared about a [somewhat-complicated WireGuard VPN setup](/cloud-based-wireguard-vpn-remote-homelab-access/) that I had started using to replace my previous OpenVPN solution. I raved about WireGuard's speed, security, and flexible (if complex) Cryptokey Routing, but adding and managing peers with WireGuard is a fairly manual (and tedious) process. And while I thought I was pretty clever for using a WireGuard peer in GCP to maintain a secure tunnel into my home network without having to punch holes through my firewall, routing all my traffic through The Cloud wasn't really optimal[^egress_fees].
|
||||
|
||||
|
|
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 86 KiB |
182
content/posts/securing-web-servers-cloudflare-tunnel/index.md
Normal file
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
title: "Securing Web Servers with Cloudflare Tunnel"
|
||||
date: 2024-01-14
|
||||
# lastmod: 2024-01-13
|
||||
description: "Exploring Cloudflare Tunnel as an alternative to Tailscale Funnel for secure public access to internal resources."
|
||||
featured: false
|
||||
toc: true
|
||||
comments: true
|
||||
series: Tips # Projects, Code
|
||||
tags:
|
||||
- cloud
|
||||
- containers
|
||||
- docker
|
||||
- networking
|
||||
- selfhosting
|
||||
---
|
||||
I've written a bit lately about how handy [Tailscale Serve and Funnel](/tailscale-ssh-serve-funnel/) can be, and I continue to get a lot of great use out of those features. But not *every* networking nail is best handled with a Tailscale-shaped hammer. Funnel has two limitations that might make it less than ideal for certain situations.
|
||||
|
||||
First, sites served with Funnel can only have a hostname in the form of `server.tailnet-name.ts.net`. You can't use a custom domain for this, but you might not always want to advertise that a service is shared via Tailscale. Second, Funnel connections have an undisclosed bandwidth limit, which could cause problems if you're hoping to serve media through the Funnel.
|
||||
|
||||
For instance, I've started using [Immich](https://immich.app/) as a self-hosted alternative to Google Photos. Using Tailscale Serve to make my Immich server available on my Tailnet works beautifully, and I initially set up a Funnel connection to use for when I wanted to share certain photos, videos, and albums externally. I quickly realized that it took *f o r e v e r* to load the page when those links were shared publicly. I probably won't share a lot of those public links but I'd like them to be a bit more responsive when I do.
|
||||
|
||||
I went looking for another solution, and I found one in a suite of products I already use.
|
||||
|
||||
### Overview
|
||||
I've been using [Cloudflare's generious free plan](https://www.cloudflare.com/plans/free/) for DNS, content caching, page/domain redirects, email forwarding, and DDoS mitigation[^more] across my dozen or so domains. In addition to these "basic" services and features, Cloudflare also offers a selection of [Zero Trust Network Access](https://www.cloudflare.com/products/zero-trust/zero-trust-network-access/) products, and one of those is [Cloudflare Tunnel](https://www.cloudflare.com/products/tunnel/) - also available with a generous free plan.
|
||||
|
||||
[^more]: And a ton of other things I'm forgetting right now.
|
||||
|
||||
In some ways, Cloudflare Tunnel is quite similar to Tailscale Funnel. Both provide a secure way to publish a resource on the internet without requiring a public IP address, port forwarding, or firewall configuration. Both use a lightweight agent on your server to establish an encrypted outbound tunnel, and inbound traffic gets routed into that tunnel through the provider's network. And both solutions automatically provision trusted SSL certificates to keep traffic safe and browsers happy.
|
||||
|
||||
Tailscale Funnel is very easy to set up, and it doesn't require any additional infrastructure, not even a domain name. There aren't a lot of controls available with Funnel - it's basically on or off, and bound to one of three port numbers. You don't get to pick the domain name where it's served, just the hostname of the Tailscale node - and if you want to share multiple resources on the same host you'll [need to get creative](/tailscale-serve-docker-compose-sidecar/). I think this approach is really ideal for quick development and testing scenarios.
|
||||
|
||||
For longer-term, more production-like use, Cloudflare Tunnels is a pretty great fit. It ties in well with existing Cloudflare services, doesn't enforce a reduced bandwidth limit, and provides a bit more flexibility for how your resource will be presented to the web. It can also integrate tightly with the rest of Cloudflare's Zero Trust offerings to easily provide access controls to further protect your resource. It does, however, require a custom domain managed with Cloudflare DNS in order to work[^dns].
|
||||
|
||||
[^dns]: Cloudflare Tunnel lets you choose what hostname and domain name should be used for fronting your tunnel, and it even takes care of configuring the required DNS record automagically.
|
||||
|
||||
For my specific Immich use case, I decided to share my instance via Tailscale Serve for internal access and Cloudflare Tunnel for public shares, and I used a similar sidecar approach to make it work without too much fuss. For the purposes of this blog post, though, I'm going to run through a less complicated example[^complexity].
|
||||
|
||||
[^complexity]: My Immich stack is using ~10 containers and I don't really feel like documenting that all here - not yet, at least.
|
||||
|
||||
### Speedtest Demo
|
||||
I'm going to deploy a quick [SpeedTest by OpenSpeedTest](https://github.com/openspeedtest/Speed-Test) container, and proxy it with both Tailscale Funnel and Cloudflare Tunnel so that I can compare the bandwidth of the two tunnel solutions directly.
|
||||
|
||||
I'll start with a *very* basic Docker Compose definition for just the Speedtest container:
|
||||
|
||||
```yaml
|
||||
# torchlight! {"lineNumbers":true}
|
||||
# docker-compose.yml
|
||||
services:
|
||||
speedtest:
|
||||
image: openspeedtest/latest
|
||||
container_name: speedtest
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
#### Tailscale Funnel
|
||||
And, as in [my last post](/tailscale-serve-docker-compose-sidecar/) I'll add in my Tailscale sidecar to enable funnel:
|
||||
|
||||
```yaml
|
||||
# torchlight! {"lineNumbers":true}
|
||||
# docker-compose.yml
|
||||
services:
|
||||
speedtest:
|
||||
image: openspeedtest/latest
|
||||
container_name: speedtest
|
||||
restart: unless-stopped
|
||||
network_mode: service:tailscale # [tl! ++:start focus:start]
|
||||
tailscale:
|
||||
image: ghcr.io/jbowdre/tailscale-docker:latest
|
||||
container_name: speedtest-tailscaled
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TS_AUTHKEY: ${TS_AUTHKEY:?err}
|
||||
TS_HOSTNAME: ${TS_HOSTNAME:-tailscale-sidecar}
|
||||
TS_STATE_DIR: "/var/lib/tailscale/"
|
||||
TS_EXTRA_ARGS: ${TS_EXTRA_ARGS:-}
|
||||
TS_SERVE_PORT: ${TS_SERVE_PORT:-}
|
||||
TS_FUNNEL: ${TS_FUNNEL:-} # [tl! ++:end focus:end]
|
||||
```
|
||||
|
||||
{{% notice note "Network Mode" %}}
|
||||
I set `network_mode: service:tailscale` on the `speedtest` container so that it will share its network interface with the `tailscale` container. This allows Tailscale Serve/Funnel to proxy `speedtest` at `http://localhost:3000`, which is nice since Tailscale doesn't currently/officially support proxying remote hosts.
|
||||
{{% /notice %}}
|
||||
|
||||
I'll set up a new auth key in the [Tailscale Admin Portal](https://login.tailscale.com/admin/settings/keys), and insert that (along with hostname, port, and funnel configs) into my `.env` file:
|
||||
|
||||
```shell
|
||||
# torchlight! {"lineNumbers":true}
|
||||
# .env
|
||||
TS_AUTHKEY=tskey-auth-somestring-somelongerstring
|
||||
TS_HOSTNAME=speedtest
|
||||
TS_EXTRA_ARGS=--ssh
|
||||
TS_SERVE_PORT=3000 # the port the speedtest runs on by default
|
||||
TS_FUNNEL=true
|
||||
```
|
||||
|
||||
A quick `docker compose up -d` and my new speedtest is alive!
|
||||
|
||||
First I'll hit it at `http://speedtest.tailnet-name.ts.net:3000` to access it purely inside of my Tailnet:
|
||||
![Speedtest from within the tailnet](speedtest-tailnet.png)
|
||||
|
||||
Not bad! Now let's see what happens when I disable Tailscale on my laptop and hit the public Funnel endpoint at `https://speedtest.tailnet-name.ts.net`:
|
||||
![Speedtest from funnel](speedtest-funnel.png)
|
||||
|
||||
Oof. Routing traffic through the Funnel dropped the download by ~25% and the upload by **~90%**, not to mention the significant ping increase.
|
||||
|
||||
#### Cloudflare Tunnel
|
||||
Alright, let's throw a Cloudflare Tunnel on there and see what happens.
|
||||
|
||||
To start that process, I'll log into my [Cloudflare dashboard](https://dash.cloudflare.com) and then use the side navigation to make my way to the **Zero Trust** (AKA "Cloudflare One") area. From there, I'll drill down through **Access -> Tunnels** and click on **+ Create a tunnel**. I'll give it an appropriate name like `speedtest` and then click **Save tunnel**.
|
||||
|
||||
Now Cloudflare helpfully provides installation instructions for a variety of different platforms. I'm doing that Docker thing so I'll click the appropriate button and review that command snippet:
|
||||
![Tunnel installation instructions](install-connector.png)
|
||||
|
||||
I can easily adapt that and add it to my Docker Compose setup[^network-mode]:
|
||||
```yaml
|
||||
# torchlight! {"lineNumbers":true}
|
||||
# docker-compose.yml
|
||||
services:
|
||||
speedtest:
|
||||
image: openspeedtest/latest
|
||||
container_name: speedtest
|
||||
restart: unless-stopped
|
||||
network_mode: service:tailscale
|
||||
tailscale: # [tl! collapse:start]
|
||||
image: ghcr.io/jbowdre/tailscale-docker:latest
|
||||
container_name: speedtest-tailscaled
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TS_AUTHKEY: ${TS_AUTHKEY:?err}
|
||||
TS_HOSTNAME: ${TS_HOSTNAME:-tailscale}
|
||||
TS_STATE_DIR: "/var/lib/tailscale/"
|
||||
TS_EXTRA_ARGS: ${TS_EXTRA_ARGS:-}
|
||||
TS_SERVE_PORT: ${TS_SERVE_PORT:-}
|
||||
TS_FUNNEL: ${TS_FUNNEL:-} # [tl! collapse:end]
|
||||
cloudflared: # [tl! ++:start focus:start]
|
||||
image: cloudflare/cloudflared
|
||||
container_name: speedtest-cloudflared
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- tunnel
|
||||
- --no-autoupdate
|
||||
- run
|
||||
- --token
|
||||
- ${CLOUDFLARED_TOKEN}
|
||||
network_mode: service:tailscale # [tl! ++:end focus:end]
|
||||
```
|
||||
|
||||
[^network-mode]: Setting the `network_mode` isn't strictly necessary for the `cloudflared` container since Cloudflare Tunnel *does* support proxying remote hosts, but I'll just stick with it here for consistency.
|
||||
|
||||
After dropping the value for `CLOUDFLARED_TOKEN` into my `.env` file, I can do another `docker compose up -d` to bring this online - and that status will be reflected back on the config page as well:
|
||||
![Connector is alive!](connector-online.png)
|
||||
|
||||
I'll click **Next** and proceed with the rest of the configuration, which consists of picking a public hostname for the frontend and defining the private service for the backend:
|
||||
![Tunnel configuration](tunnel-configuration.png)
|
||||
|
||||
I can click **Save tunnel** and... that's it. My tunnel is live, and I can now reach my speedtest at `https://speedtest.runtimeterror.dev`. Let's see how it does:
|
||||
![Cloudflare Tunnel speedtest](speedtest-cloudflared.png)
|
||||
|
||||
So that's *much* faster than Tailscale Funnel, and even faster than a direct transfer within the Tailnet. Cloudflare Tunnel should work quite nicely for sharing photos publicly from my Immich instance.
|
||||
|
||||
#### Bonus: Access Control
|
||||
But what if I don't want *just anyone* to be able to use my new speedtest (or access my Immich instance)? Defining an application in Cloudflare One will let me set some limits.
|
||||
|
||||
So I'll go to **Access -> Applications** and select that I'm adding a **Self-hosted** application. I can then do the basic configuration, basically just telling Cloudflare that I'd like to protect the `https://speedtest.runtimeterror.dev` app:
|
||||
![Defining the application](define-application.png)
|
||||
|
||||
I can leave the rest of that page with the default selections so I'll scroll down and click **Next**.
|
||||
|
||||
Now I need to create a policy to apply to this application. I'm going to be simple and just say that anyone with an `@runtimeterror.dev` email address should be able to use my speedtest:
|
||||
![Creating a policy](create-policy.png)
|
||||
|
||||
Without any external identity providers connected, Cloudflare will default to requiring authentication via a one-time PIN sent to an input email address. That's pretty easy, and it pairs well with allowing access based on email address attributes. There are a bunch of other options I could configure if I wanted... but my needs are simple so I'll just click through and save this new application config.
|
||||
|
||||
Now, if I try to visit my speedtest with a new session I'll get automatically routed to the Cloudflare Access challenge which will prompt for my email address.
|
||||
![Access challenge](access-challenge.png)
|
||||
|
||||
If my email is on the approved list (that is, if it ends with `@runtimeterror.dev`), I'll get emailed a code which I can then use to log in and access the speedtest. If not, I won't get in. And since this thing is served through a Cloudflare Tunnel (rather than a public IP address merely advertised via DNS) there isn't any way to bypass Cloudflare's authentication challenge.
|
||||
|
||||
### Conclusion
|
||||
This has been a quick demo of how easy it is to configure a Cloudflare Tunnel to securely publish resources on the web. I really like being able to share a service publicly without having to manage DNS, port-forwarding, or firewall configurations, and the ability to offload authentication and authorization to Cloudflare is a big plus. I still don't think Tailscale can be beat for sharing stuff internally, but I think Cloudflare Tunnels make more sense for long-term public sharing. And it's awesome that I can run both solutions side-by-side to really get the best of both when I need it.
|
After Width: | Height: | Size: 129 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 99 KiB |
After Width: | Height: | Size: 123 KiB |
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
series: Projects
|
||||
categories: ChromeOS
|
||||
date: "2020-10-27T08:34:30Z"
|
||||
lastmod: "2021-05-20"
|
||||
thumbnail: XtmaR9Z0J.png
|
||||
|
|
|
@ -4,6 +4,7 @@ thumbnail: P-x5qEg_9.jpeg
|
|||
usePageBundles: true
|
||||
tags:
|
||||
- chromeos
|
||||
categories: ChromeOS
|
||||
title: 'Showdown: Lenovo Chromebook Duet vs. Google Pixel Slate'
|
||||
---
|
||||
|
||||
|
@ -14,9 +15,9 @@ Okay, okay, this isn't actually going to be a comparison review between the two
|
|||
### Background
|
||||
Up until last week, I'd been using the Slate as my primary personal computing device for the previous 20 months or so, mainly in laptop mode (as opposed to tablet mode). I do a lot of casual web browsing, and I spend a significant portion of my free time helping other users on Google's product support forums as a part of the [Google Product Experts program](https://productexperts.withgoogle.com/what-it-is). I also work a lot with the [Chrome OS Linux (Beta) environment](/setting-up-linux-on-a-new-lenovo-chromebook-duet-bonus-arm64-complications), but I avoid Android apps as much as I can. And I also used the Slate for a bit of Stadia gaming when I wasn't near a Chromecast.
|
||||
|
||||
So the laptop experience is generally more important to me than the tablet one. I need to be able to work with a large number of browser tabs, but I don't typically need to do any heavy processing directly on the computer.
|
||||
So the laptop experience is generally more important to me than the tablet one. I need to be able to work with a large number of browser tabs, but I don't typically need to do any heavy processing directly on the computer.
|
||||
|
||||
I was pretty happy with the Slate, but its expensive keyboard stopped working recently and replacements aren't really available anywhere. Remember, laptop mode is key for my use case so the Pixel Slate became instantly unusable to me.
|
||||
I was pretty happy with the Slate, but its expensive keyboard stopped working recently and replacements aren't really available anywhere. Remember, laptop mode is key for my use case so the Pixel Slate became instantly unusable to me.
|
||||
|
||||
### Size
|
||||
When you put these machines side by side, the first difference that jumps out is the size disparity. The 12.3" Pixel Slate is positively massive next to the 10.1" Lenovo Duet.
|
||||
|
@ -24,14 +25,14 @@ When you put these machines side by side, the first difference that jumps out is
|
|||
|
||||
The Duet is physically smaller so the display itself is of course smaller. I had a brief moment of panic when I first logged in and the setup wizard completely filled the screen. Dialing Chrome OS's display scaling down to 80% strikes a good balance for me between fonts being legible while still displaying enough content to be worthwhile. It can get a bit tight when you've got windows docked side-by-side but I'm getting by okay.
|
||||
|
||||
Of course, the smaller size of the Duet also makes it work better as a tablet in my mind. It's comfortable enough to hold with one hand while you interact with the other, whereas the Slate always felt a little too big for that to me.
|
||||
Of course, the smaller size of the Duet also makes it work better as a tablet in my mind. It's comfortable enough to hold with one hand while you interact with the other, whereas the Slate always felt a little too big for that to me.
|
||||
![One-handing the Duet](qne9SybLi.jpeg)
|
||||
|
||||
### Keyboard
|
||||
A far more impactful size difference is the keyboards though. The Duet keyboard gets a bit cramped, particularly over toward the right side (you know, those pesky braces and semicolons that are *never* needed when coding):
|
||||
![The Duet's keyboard is MUCH smaller](CBziPHD8A.jpeg)
|
||||
|
||||
Getting used to typing on this significantly smaller keyboard has been the biggest adjustment so far. The pad on my pinky finger is wider than the last few keys at the right edge of the keyboard so I've struggled with accurately hitting the correct `[` or `]`, and also with smacking Return (and inevitably sending a malformed chat message) when trying to insert an apostrophe. I feel like I'm slowly getting the hang of it, but like I said, it's been an adjustment.
|
||||
Getting used to typing on this significantly smaller keyboard has been the biggest adjustment so far. The pad on my pinky finger is wider than the last few keys at the right edge of the keyboard so I've struggled with accurately hitting the correct `[` or `]`, and also with smacking Return (and inevitably sending a malformed chat message) when trying to insert an apostrophe. I feel like I'm slowly getting the hang of it, but like I said, it's been an adjustment.
|
||||
|
||||
### Cover
|
||||
![Cover up!](yiCW6XZbF.jpeg)
|
||||
|
@ -39,7 +40,7 @@ The Pixel Slate's keyboard + folio cover is a single (floppy) piece. The keyboar
|
|||
|
||||
![Duet's fabric cover](9_Ze3zyBk.jpeg)
|
||||
|
||||
The Duet's rear cover has a fabric finish kind of similar to the cases Google offers for their phones, and it provides a great texture for holding the tablet. It sticks to the back of the Duet through the magic of magnets, and the lower half of it folds out to create a really sturdy kickstand. And it's completely separate from the keyboard which is great for when you're using the Duet as a tablet (either handheld or propped up for watching a movie or gaming with Stadia).
|
||||
The Duet's rear cover has a fabric finish kind of similar to the cases Google offers for their phones, and it provides a great texture for holding the tablet. It sticks to the back of the Duet through the magic of magnets, and the lower half of it folds out to create a really sturdy kickstand. And it's completely separate from the keyboard which is great for when you're using the Duet as a tablet (either handheld or propped up for watching a movie or gaming with Stadia).
|
||||
|
||||
![Duet kickstand](nWRu2TB8i.jpeg)
|
||||
|
||||
|
@ -48,9 +49,9 @@ And this little kickstand can go *low*, much lower than the Slate. This makes it
|
|||
![The Duet handily wins this limbo competition](BAf7knBk5.jpeg)
|
||||
|
||||
### Performance
|
||||
The Duet does struggle a bit here. It's basically got a [smartphone processor](https://www.notebookcheck.net/Mediatek-Helio-P60T-Processor-Benchmarks-and-Specs.470711.0.html) and half the RAM of the Slate. Switching between windows and tabs sometimes takes an extra moment or two to catch up (particularly if said tab has been silently suspended in the background). Similarly, working with Linux apps is just a bit slower than you'd like it to be. Still, I've spent a bit more than a week now with the Duet as my go-to computer and it's never really been slow enough to bother me.
|
||||
The Duet does struggle a bit here. It's basically got a [smartphone processor](https://www.notebookcheck.net/Mediatek-Helio-P60T-Processor-Benchmarks-and-Specs.470711.0.html) and half the RAM of the Slate. Switching between windows and tabs sometimes takes an extra moment or two to catch up (particularly if said tab has been silently suspended in the background). Similarly, working with Linux apps is just a bit slower than you'd like it to be. Still, I've spent a bit more than a week now with the Duet as my go-to computer and it's never really been slow enough to bother me.
|
||||
|
||||
That arm64 processor does make finding compatible Linux packages a little more difficult than it's been on amd64 architectures but a [little bit of digging](/setting-up-linux-on-a-new-lenovo-chromebook-duet-bonus-arm64-complications) will get past that limitation in most cases.
|
||||
That arm64 processor does make finding compatible Linux packages a little more difficult than it's been on amd64 architectures but a [little bit of digging](/setting-up-linux-on-a-new-lenovo-chromebook-duet-bonus-arm64-complications) will get past that limitation in most cases.
|
||||
|
||||
The upside of that smartphone processor is that the battery life is *insane*. After about seven hours of light usage today I'm sitting at 63% - with an estimated nine hours remaining. This thing keeps going and going, even while Stadia-ing for hours. Being able to play Far Cry 5 without being tethered to a wall is so nice.
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ usePageBundles: true
|
|||
thumbnail: "snikket.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Projects
|
||||
categories: Self-Hosting
|
||||
tags:
|
||||
- linux
|
||||
- cloud
|
||||
|
@ -23,7 +23,7 @@ tags:
|
|||
- chat
|
||||
- selfhosting
|
||||
- caddy
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
**Non-technical users deserve private communications, too.**
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ lastmod: 2023-11-13
|
|||
description: "Syntax highlighting powered by the Torchlight.dev API makes it easier to dress up code blocks. Here's an overview of what I did to replace this blog's built-in Hugo highlighter (Chroma) with Torchlight."
|
||||
featured: false
|
||||
toc: true
|
||||
comment: true
|
||||
series: Projects # Projects, Code
|
||||
comments: true
|
||||
categories: Backstage
|
||||
tags:
|
||||
- javascript
|
||||
- hugo
|
||||
|
@ -536,7 +536,7 @@ Serving HTTP on 0.0.0.0 port 1313 (http://0.0.0.0:1313/) ... # [tl! focus:1]
|
|||
#### Netlify
|
||||
Setting up Netlify to leverage the Torchlight API is kind of similar. I'll start with logging in to the [Netlify dashboard](https://app.netlify.com) and navigating to **Site Configuration > Environment Variables**. There, I'll click on **Add a variable > Add a ingle variable**. I'll give the new variable a key of `TORCHLIGHT_TOKEN` and set its value to the token I obtained earlier.
|
||||
|
||||
![](netlify-env-var.png)
|
||||
![Screenshot showing the creation of the 'TORCHLIGHT_TOKEN' variable in Netlify](netlify-env-var.png)
|
||||
|
||||
Once that's done, I edit the `netlify.toml` file at the root of my site repo to alter the build commands:
|
||||
```toml
|
||||
|
|
|
@ -5,8 +5,8 @@ date: 2023-10-15
|
|||
description: "Quick notes on using `systemctl edit` to override a systemd service to delay its startup."
|
||||
featured: false
|
||||
toc: false
|
||||
comment: true
|
||||
series: Tips # Projects, Code
|
||||
comments: true
|
||||
categories: Tips # Projects, Code
|
||||
tags:
|
||||
- crostini
|
||||
- linux
|
||||
|
|
|
@ -14,7 +14,7 @@ usePageBundles: true
|
|||
thumbnail: "golinks.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Projects # Projects, Code, vRA8, K8s on vSphere
|
||||
categories: Self-Hosting # Projects, Code, vRA8, K8s on vSphere
|
||||
tags:
|
||||
- docker
|
||||
- vpn
|
||||
|
@ -22,7 +22,7 @@ tags:
|
|||
- wireguard
|
||||
- containers
|
||||
- selfhosting
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
I've shared in the past about how I use [custom search engines in Chrome](/abusing-chromes-custom-search-engines-for-fun-and-profit/) as quick web shortcuts. And I may have mentioned [my love for Tailscale](/tags/tailscale/) a time or two as well. Well I recently learned of a way to combine these two passions: [Tailscale golink](https://github.com/tailscale/golink). The [golink announcement post on the Tailscale blog](https://tailscale.com/blog/golink/) offers a great overview of the service:
|
||||
> Using golink, you can create and share simple go/name links for commonly accessed websites, so that anyone in your network can access them no matter the device they’re on — without requiring browser extensions or fiddling with DNS settings. And because golink integrates with Tailscale, links are private to users in your tailnet without any separate user management, logins, or security policies.
|
||||
|
|
|
@ -14,7 +14,7 @@ usePageBundles: true
|
|||
thumbnail: "Tailscale-AppIcon.png" # Sets thumbnail image appearing inside card on homepage.
|
||||
# shareImage: "share.png" # Designate a separate image for social media sharing.
|
||||
codeLineNumbers: false # Override global value for showing of line numbers within code block.
|
||||
series: Tips # Projects, Code, vRA8, K8s on vSphere
|
||||
categories: Tips # Projects, Code, vRA8, K8s on vSphere
|
||||
tags:
|
||||
- vmware
|
||||
- linux
|
||||
|
@ -22,7 +22,7 @@ tags:
|
|||
- networking
|
||||
- security
|
||||
- tailscale
|
||||
comment: true # Disable comment if false.
|
||||
comments: true # Disable comment if false.
|
||||
---
|
||||
You might remember that I'm a [pretty big fan](/secure-networking-made-simple-with-tailscale/) of [Tailscale](https://tailscale.com), which makes it easy to connect your various devices together in a secure [tailnet](https://tailscale.com/kb/1136/tailnet/), or private network. Tailscale is super simple to set up on most platforms, but you'll need to [install it manually](https://tailscale.com/download/linux/static) if there isn't a prebuilt package for your system.
|
||||
|
||||
|
|