Compare commits

..

30 commits

Author SHA1 Message Date
cdb193a5ac new draft: display-tempest-weather-static-site 2024-02-10 22:31:55 -06:00
cc7d386da5 Merge branch 'main' into drafts 2024-02-10 15:01:03 -06:00
ec969dd77c update post with note on using official tailscale image 2024-02-07 19:27:57 -06:00
d858b507f5 update post with note on checking nested virtualization availability 2024-02-06 09:48:17 -06:00
b8c31312d4 update post 2024-02-05 08:15:06 -06:00
37ee46c5b4 update netdata kickstart location 2024-02-03 13:23:23 -06:00
90715d7e9d update simplex status links 2024-01-30 09:16:37 -06:00
ff43435ef9 update links to status page, again 2024-01-29 23:21:41 -06:00
139c9dc2d2 update links to status page 2024-01-29 23:17:07 -06:00
850637aaaf Revert "adjust tinylytics params"
This reverts commit ca490bcbe4.
2024-01-29 08:54:27 -06:00
ca490bcbe4 adjust tinylytics params 2024-01-29 08:43:25 -06:00
218d448c21 update social/contact links 2024-01-28 17:00:56 -06:00
751d7c23e9 change webring style again 2024-01-23 18:16:27 -06:00
0bdb4b2d20 improve vertical spacing on hr elements 2024-01-23 16:49:03 -06:00
5983ffe342 improve styling of webring 2024-01-23 16:00:30 -06:00
d72844ca35 change webring display style 2024-01-23 15:22:08 -06:00
39ea0e9516 hide webring when analytics disabled 2024-01-23 14:01:09 -06:00
838b448b94 wrap webring avatar 2024-01-23 13:54:36 -06:00
8720ced825 test webring integration 2024-01-23 13:24:10 -06:00
0607b54eb5 flag preview deploys 2024-01-23 13:16:09 -06:00
711fac9e1f update name of preview deploy workflow 2024-01-23 13:08:43 -06:00
285cb0f49e configure preview builds on neocities 2024-01-23 13:07:54 -06:00
ac03a9f995 change kudos prompt message 2024-01-22 10:55:58 -06:00
96c5360479 no kudos on 404 2024-01-21 21:58:39 -06:00
0869b0a2c8 post tweaks 2024-01-21 21:43:50 -06:00
7df7a9fad6 make code green 2024-01-21 21:41:18 -06:00
06720c680d improve contrast 2024-01-21 21:22:42 -06:00
b00049b2b1 clean up footnote backlink styling 2024-01-21 21:21:30 -06:00
5a63d1a007 style back-to-top with css 2024-01-21 21:16:15 -06:00
97d7c08d0b update post 2024-01-21 20:42:55 -06:00
21 changed files with 526 additions and 52 deletions

View file

@ -0,0 +1,46 @@
name: Deploy Preview to Neocities
# only run on changes to preview
on:
push:
branches:
- preview
concurrency: # prevent concurrent deploys doing strange things
group: deploy-preview-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 --environment preview
- 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_PREVIEW_API_TOKEN }}
cleanup: true
dist_dir: public

View file

@ -25,6 +25,7 @@ giscusStrict = "0"
giscusTheme = "noborder_gray"
analytics = true
kudos = true
[author]
name = "John Bowdre"
@ -136,6 +137,16 @@ icon = "fa-brands fa-github"
title = "GitHub"
url = "https://github.com/jbowdre"
[[socialLinks]]
icon = "fa-solid fa-heart"
title = "omg.lol"
url = "https://jbowdre.lol"
[[socialLinks]]
icon = "fa-solid fa-sticky-note"
title = "Scribbles 'n Bits"
url = "https://scribbles.jbowdre.lol"
[[socialLinks]]
icon = "fa-solid fa-circle-user"
title = "CounterSocial"
@ -146,21 +157,6 @@ icon = "fa fa-mastodon"
title = "Mastodon"
url = "https://social.lol/@jbowdre"
[[socialLinks]]
icon = "fa-solid fa-heart"
title = "omg.lol"
url = "https://jbowdre.lol"
[[socialLinks]]
icon = "fa-solid fa-comments"
title = "SimpleX Chat"
url = "/simplex"
[[socialLinks]]
icon = "fa fa-matrix-org"
title = "Matrix"
url = "https://matrix.to/#/@jbowdre:omg.lol"
[[socialLinks]]
icon = "fa-solid fa-envelope"
title = "Email"

1
config/preview/hugo.toml Normal file
View file

@ -0,0 +1 @@
baseURL = "https://preview--runtimeterror.neocities.org/"

View file

@ -0,0 +1,2 @@
comments = false
analytics = false

View file

@ -3,6 +3,7 @@ title = "404'd!"
noindex = true
timeless = true
comments = true
kudos = false
+++
We're not sure what you were looking for but it's not here.

View file

@ -23,13 +23,17 @@ 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)
- [Scribbles 'n Bits](https://scribbes.jbowdre.lol)
- [status.lol](https://status.jbowdre.lol)
- [social.lol](https://social.lol/@jbowdre)
- [CounterSocial](https://counter.social/@john_b)
- [/now](https://now.jbowdre.lol)
Connect with me via:
- [SimpleX Chat](/simplex/)
- [Session](https://p.runtimeterror.dev/session-id)
- [Matrix](https://matrix.to/#/@jbowdre:omg.lol)
- [XMPP](xmpp://john@chat.vpota.to)
- [Electronic Mail](mailto:jbowdre@omg.lol)
- [PGP: 613F B70C 4FA7 A077](https://l.runtimeterror.dev/pgp)

View file

@ -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: 2024-01-17
lastmod: 2024-02-06
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.
@ -29,7 +29,16 @@ Also, because I'm a bit of a sadist, I wanted to do this all on my new [Framewor
It took a bit of fumbling, but this article describes what it took to get a Vagrant-powered VM up and running in the [Linux Development Environment](https://chromeos.dev/en/linux) on my Chromebook (which is currently running ChromeOS v111 beta).
### Install the prerequisites
There are are a few packages which need to be installed before we can move on to the Vagrant-specific stuff. It's quite possible that these are already on your system.... but if they *aren't* already present you'll have a bad problem[^problem].
First things first: you should make sure your Chromebook supports nested virtualization. Many newer ones do, but it's not a universal thing. It's easy to check just by looking for the `kvm` device file:
```shell
ls -l /dev/kvm # [tl! .cmd]
crw-rw---- 10,232 root 6 Feb 08:03 /dev/kvm # [tl! .nocopy]
```
As long as you don't get an error like `No such file or directory` then you should be good to go.
With that out of the way, there are are a few packages which need to be installed before we can move on to the Vagrant-specific stuff. It's quite possible that these are already on your system.... but if they *aren't* already present you'll have a bad problem[^problem].
```shell
sudo apt update && sudo apt install \ # [tl! .cmd]
@ -41,7 +50,7 @@ sudo apt update && sudo apt install \ # [tl! .cmd]
[^problem]: and [will not go to space today](https://xkcd.com/1133/).
I'll be configuring Vagrant to use [`libvirt`](https://libvirt.org/) to interface with the [Kernel Virtual Machine (KVM)](https://www.linux-kvm.org/page/Main_Page) virtualization solution (rather than something like VirtualBox that would bring more overhead) so I'll need to install some packages for that as well:
I'll be configuring Vagrant to use [`libvirt`](https://libvirt.org/) to interface with the [Kernel Virtual Machine (KVM)](https://www.linux-kvm.org/page/Main_Page) virtualization solution (rather than something like VirtualBox which would require kernel modules that can't be loaded in ChromeOS) so I'll need to install some packages for that as well:
```shell
sudo apt install virt-manager libvirt-dev # [tl! .cmd]
```

View file

@ -12,19 +12,19 @@ tags:
- 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.
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.
[^lazy]: Also I'm kind of lazy, and not actually very good at web design anyway. I mean, you've seen my work.
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.
Until yesterday, when I saw a post from [Sophie](https://social.lol/@sophie) on [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.
I had been hosting this site on Netlify's free plan [for a couple of years](/hello-hugo/) and haven't really encountered 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 upgraded to the $5/month [Neocities Supporter](https://neocities.org/supporter) plan which would let me use a custom domain for my site (along with higher storage and bandwidth limits).
[^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.
I knew I'd need to make some changes to Sophie's workflow since my site is built 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 also discovered that I'd need to overwrite `/not_found.html` to insert my custom 404 page so I included an extra step to do that. After that, I'll finally be ready to push the results to Neocities.
So after some trial and error, I came up with this workflow:
It took a bit of trial and error, but I eventually adapted this workflow which does the trick:
### The Workflow
```yaml
@ -33,8 +33,10 @@ So after some trial and error, I came up with this workflow:
name: Deploy to Neocities
on:
# Daily build to catch any future-dated posts
schedule:
- cron: 0 13 * * *
# Build on pushes to the main branch only
push:
branches:
- main

View file

@ -0,0 +1,320 @@
---
title: "Displaying Data from Tempest Weather Station on a Static Site"
date: 2024-02-10
# lastmod: 2024-02-10
draft: true
description: "Using a GitHub Actions workflow as a sort of API proxy to retrieve data from an authenticated "
featured: false
toc: true
comments: true
categories: Tips # Backstage, ChromeOS, Code, Self-Hosting, VMware
tags:
- api
- javascript
- meta
- serverless
---
As I covered briefly [in a recent Scribble](https://scribbles.jbowdre.lol/post/near-realtime-weather-on-profile-lol-ku4yq-zr), I spent some time this week with integrating data from my [Weatherflow Tempest weather station](https://shop.weatherflow.com/products/tempest) into my [omg.lol homepage](https://jbowdre.lol). That page is rendered as a static site, so I needed a way to do this *without* including secrets like the station ID or API token in the client-side JavaScript.
I realized I could use a GitHub Actions workflow to retrieve the data from the authenticated Tempest API, post it somewhere publicly accessible, and then have the client-side code fetch the data from there without needing any authentication. This post will cover how I did it.
### Retrieve Weather Data from Tempest API
To start, I want to play with the API a bit to see what the responses look like. Before I can talk to the API, though, I need to generate a new token for my account at `https://tempestwx.com/settings/tokens`. I also make a note of my station ID, and store both of those values in my shell for easier use.
```shell
read wx_token # [tl! .cmd:1]
read wx_station
```
After browsing the Tempest API Explorer a little, it seems to me like the [`/better_forecast` endpoint](https://weatherflow.github.io/Tempest/api/swagger/#!/forecast/getBetterForecast) will probably be the easiest to work with, particularly since it lets the user choose which units will be used in the response. That will keep me from having to do metric-to-imperial conversions for many of the data.
So I start out calling the API like this:
```shell
curl -sL "https://swd.weatherflow.com/swd/rest/better_forecast?station_id=$wx_station&token=$wx_token&units_temp=f&units_wind=mph&units_pressure=inhg&units_precip=in&units_distance=mi" \
| jq # [tl! .cmd:-1]
{ # [tl! .nocopy:start]
"current_conditions": {
"air_density": 1.2,
"air_temperature": 59.0,
"brightness": 1,
"conditions": "Rain Possible",
"delta_t": 2.0,
"dew_point": 55.0,
"feels_like": 59.0,
"icon": "possibly-rainy-night",
"is_precip_local_day_rain_check": true,
"is_precip_local_yesterday_rain_check": true,
"lightning_strike_count_last_1hr": 0,
"lightning_strike_count_last_3hr": 0,
"lightning_strike_last_distance": 12,
"lightning_strike_last_distance_msg": "11 - 13 mi",
"lightning_strike_last_epoch": 1706394852,
"precip_accum_local_day": 0,
"precip_accum_local_yesterday": 0.05,
"precip_minutes_local_day": 0,
"precip_minutes_local_yesterday": 28,
"pressure_trend": "falling",
"relative_humidity": 88,
"sea_level_pressure": 29.89,
"solar_radiation": 0,
"station_pressure": 29.25,
"time": 1707618643,
"uv": 0,
"wet_bulb_globe_temperature": 57.0,
"wet_bulb_temperature": 56.0,
"wind_avg": 2.0,
"wind_direction": 244,
"wind_direction_cardinal": "WSW",
"wind_gust": 2.0
},
"forecast": { # [tl! collapse:start]
"daily": [
{
[...],
"day_num": 10,
[...],
},
{
[...],
"day_num": 11,
[...],
},
{
[...],
"day_num": 12,
[...],
}
]
}
} # [tl! collapse:end .nocopy:end]
```
So that validates that the endpoint will give me what I want, but I don't *really* need the extra 10-day forecast since I'm only interested in showing the current conditions. I can start working some `jq` magic to filter down to just what I'm interested in. And, while I'm at it, I'll stick the API URL in a variable to make that easier to work with.
```shell
endpoint="https://swd.weatherflow.com/swd/rest/better_forecast?station_id=$wx_station&token=$wx_token&units_temp=f&units_wind=mph&units_pressure=inhg&units_precip=in&units_distance=mi" # [tl! .cmd:1]
curl -sL "$endpoint" | jq '.current_conditions'
{ # [tl! .nocopy:start]
"air_density": 1.2,
"air_temperature": 59.0,
"brightness": 1,
"conditions": "Light Rain",
"delta_t": 2.0,
"dew_point": 55.0,
"feels_like": 59.0,
"icon": "rainy",
"is_precip_local_day_rain_check": true,
"is_precip_local_yesterday_rain_check": true,
"lightning_strike_count_last_1hr": 0,
"lightning_strike_count_last_3hr": 0,
"lightning_strike_last_distance": 12,
"lightning_strike_last_distance_msg": "11 - 13 mi",
"lightning_strike_last_epoch": 1706394852,
"precip_accum_local_day": 0,
"precip_accum_local_yesterday": 0.05,
"precip_description": "Light Rain",
"precip_minutes_local_day": 0,
"precip_minutes_local_yesterday": 28,
"pressure_trend": "falling",
"relative_humidity": 88,
"sea_level_pressure": 29.899,
"solar_radiation": 0,
"station_pressure": 29.258,
"time": 1707618703,
"uv": 0,
"wet_bulb_globe_temperature": 57.0,
"wet_bulb_temperature": 56.0,
"wind_avg": 1.0,
"wind_direction": 230,
"wind_direction_cardinal": "SW",
"wind_gust": 2.0
} # [tl! .nocopy:end]
```
Piping the response through `jq '.current_conditions'` works well to select that objects, but I'm still not going to want to display all of that information. After some thought, these are the fields I want to hold on to:
- `air_temperature`
- `conditions`
- `feels_like` (apparent air temperature)
- `icon`
- `precip_accum_local_day` (rainfall total for the day)
- `pressure_trend` (rising, falling, or steady)
- `relative_humidity`
- `sea_level_pressure` (the pressure recorded by the station, adjusted for altitude)
- `time` ([epoch](https://en.wikipedia.org/wiki/Unix_time) timestamp of the report)
- `wind_direction_cardinal` (which way the wind is blowing *from*)
- `wind_gust`
I can use more `jq` wizardry to grab only those fields, and I'll also rename a few of the more cumbersome ones and round some of the values where I don't need full decimal precision:
```shell
curl -sL "$endpoint" | jq '.current_conditions | {temperature: (.air_temperature | round), conditions,
feels_like: (.feels_like | round), icon, rain_today: .precip_accum_local_day, pressure_trend,
humidity: .relative_humidity, pressure: ((.sea_level_pressure * 100) | round | . / 100), time,
wind_direction: .wind_direction_cardinal, wind_gust}'
{ # [tl! .cmd:-4,1 .nocopy:start]
"temperature": 58,
"conditions": "Very Light Rain",
"feels_like": 58,
"icon": "rainy",
"rain_today": 0.01,
"pressure_trend": "steady",
"humidity": 91,
"pressure": 29.9,
"time": 1707620142,
"wind_direction": "W",
"wind_gust": 0.0
} # [tl! .nocopy:end]
```
Now I'm just grabbing the specific data points that I plan to use, and I'm renaming messy names like `precip_accum_local_day` to things like `rain_today` to make them a bit less unwieldy. I'm also rounding the temperatures to whole numbers[^fahrenheit], and reducing the pressure from three decimal points to just two.
[^fahrenheit]: These are degrees Fahrenheit, after all. If I needed precision I'd be using better units.
Now that I've got the data I want, I'll just stash it in a local file for safe keeping:
```shell
curl -sL "$endpoint" | jq '.current_conditions | {temperature: (.air_temperature | round), conditions,
feels_like: (.feels_like | round), icon, rain_today: .precip_accum_local_day, pressure_trend,
humidity: .relative_humidity, pressure: ((.sea_level_pressure * 100) | round | . / 100), time,
wind_direction: .wind_direction_cardinal, wind_gust}' \
> tempest.json # [tl! .cmd:-4,1 **]
```
### Post to paste.lol
I've been using [omg.lol](https://home.omg.lol/) for a couple of months now, and I'm constantly discovering new uses for the bundled services. I thought that the [paste.lol](https://paste.lol/) service would be a great fit for this project. For one it was easy to tie it to a custom domain[^sucker], and it's got an [easy API](https://api.omg.lol/#token-post-pastebin-create-or-update-a-paste-in-a-pastebin) that I can use for automating this.
[^sucker]: I'm such a sucker for basically *anything* that I can tie one of my domains to.
To use the API, I'll of course need a token. I can find that at the bottom of my [omg.lol Account](https://home.omg.lol/account) page, and I'll once again store that as an environment variable. I can then test out the API by creating a new paste:
```shell
curl -L --request POST --header "Authorization: Bearer $omg_token" \ # [tl! .cmd]
"https://api.omg.lol/address/jbowdre/pastebin/" \
--data '{"title": "paste-test", "content": "Tastes like paste."}'
{ # [tl! .nocopy:9]
"request": {
"status_code": 200,
"success": true
},
"response": {
"message": "OK, your paste has been saved. <a href=\"https:\/\/paste.lol\/jbowdre\/paste-test\" target=\"_blank\">View it live<\/a>.",
"title": "paste-test"
}
}
```
And, sure enough, I can view it at my slick custom domain for my pastes, `https://paste.jbowdre.lol/paste-test`
![Simple webpage with the message, Tastes like paste.](paste-test.png)
That page is simple enough, but I'll really want to be sure I can store and retrieve the raw JSON that I captured from the Tempest API. There's a handy button the webpage for that, or I can just append `/raw` to the URL:
![Plaintext page with the same message](raw-paste.png)
Yep, looks like that will do the trick. One small hurdle, though: I have to send the `--data` as a JSON object. I already have the JSON file that I pulled from the Tempest API, but I'll need to wrap that inside another layer of JSON. Fortunately, `jq` can come to the rescue once more.
```shell
request_body='{"title": "tempest.json", "content": '"$(jq -Rsa . tempest.json)"'}' # [tl! .cmd]
```
The `jq` command here reads the `tempest.json` file as plaintext (not as a JSON object), and then formats it as a JSON string so that it can be wrapped in the request body JSON:
```shell
jq -Rsa '.' tempest.json # [tl! .cmd .nocopy:1]
"{\n \"temperature\": 58,\n \"conditions\": \"Heavy Rain\",\n \"feels_like\": 58,\n \"icon\": \"rainy\",\n \"rain_today\": 0.05,\n \"pressure_trend\": \"steady\",\n \"humidity\": 93,\n \"pressure\": 29.89,\n \"time\": 1707620863,\n \"wind_direction\": \"S\",\n \"wind_gust\": 1.0\n}\n"
```
So then I can repeat the earlier `curl` but this time pass in `$request_body` to include the file contents:
```shell
curl -L --request POST --header "Authorization: Bearer $omg_token" \ # [tl! .cmd]
"https://api.omg.lol/address/jbowdre/pastebin/" \
--data "$request_body"
{ # [tl! .nocopy:9]
"request": {
"status_code": 200,
"success": true
},
"response": {
"message": "OK, your paste has been saved. <a href=\"https:\/\/paste.lol\/jbowdre\/tempest.json\" target=\"_blank\">View it live<\/a>.",
"title": "tempest.json"
}
}
```
And there it is, at `https://paste.jbowdre.lol/tempest.json/raw`:
![Plaintext weather data in JSON format](raw-tempest.png)
### Automate with GitHub Actions
At this point, I know the commands needed to retrieve weather data from the Tempest API, and I know what will be needed to post it to the omg.lol pastebin. The process works, but now it's time to automate it. And I'll do that with a simple [GitHub Actions workflow](https://docs.github.com/en/actions).
I create a [new GitHub repo](https://github.com/jbowdre/lolz) to store this (and future?) omg.lol sorcery, and navigate to **Settings > Secrets and variables > Actions**. I'll create four new repository secrets to hold my variables:
- `OMG_ADDR`
- `OMG_TOKEN`
- `TEMPEST_STATION`
- `TEMPEST_TOKEN`
And I'll create a new file at `.github/workflows/tempest.yml` to define my new workflow. Here's the start:
```yaml
# torchlight! {"lineNumbers": true}
name: Tempest Update
on:
schedule:
- cron: "*/5 * * * *"
workflow_dispatch:
push:
branches:
- main
defaults:
run:
shell: bash
```
The `on` block defines when the workflow will run:
1. On a `cron` schedule which fires (roughly) every five minutes
2. On a `workflow_dispatch` event (so I can start it manually if I want)
3. When changes are pushed to the `main` branch
And the `defaults` block makes sure that the following scripts will be run in `bash`:
```yaml
# torchlight! {"lineNumbers": true}
name: Tempest Update # [tl! collapse:start]
on:
schedule:
- cron: "*/5 * * * *"
workflow_dispatch:
push:
branches:
- main
defaults:
run:
shell: bash
# [tl! collapse:end]
jobs:
fetch-and-post-tempest:
runs-on: ubuntu-latest
steps:
- name: Fetch Tempest API data # [tl! **:2,3]
run: |
curl -sL "https://swd.weatherflow.com/swd/rest/better_forecast?station_id=${{ secrets.TEMPEST_STATION }}&token=${{ secrets.TEMPEST_TOKEN }}&units_temp=f&units_wind=mph&units_pressure=inhg&units_precip=in&units_distance=mi" \
| jq '.current_conditions | {temperature: (.air_temperature | round), conditions, feels_like: (.feels_like | round), icon, rain_today: .precip_accum_local_day, pressure_trend, humidity: .relative_humidity, pressure: ((.sea_level_pressure * 100) | round | . / 100), time, wind_direction: .wind_direction_cardinal, wind_gust}' \
> tempest.json
- name: POST to paste.lol # [tl! **:2,4]
run: |
request_body='{"title": "tempest.json", "content": '"$(jq -Rsa . tempest.json)"'}'
curl --location --request POST --header "Authorization: Bearer ${{ secrets.OMG_TOKEN }}" \
"https://api.omg.lol/address/${{ secrets.OMG_ADDR }}/pastebin/" \
--data "$request_body"
```
Each step in the `jobs` section should look pretty familiar since those are almost exactly the commands that I used earlier. The only real difference is that they now use the format `${{ secrets.SECRET_NAME }}` to pull in the repository secrets I just created.
Once I save, commit, and push this new file to the repo, it will automatically execute, and I can go to the [Actions](https://github.com/jbowdre/lolz/actions) tab to confirm that the run was succesful. I can also check `https://paste.jbowdre.lol/tempest.json` to confirm that the contents have updated.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View file

@ -1,7 +1,7 @@
---
title: "Quick Salt State to Deploy Netdata"
date: 2023-12-21
lastmod: 2023-12-22
lastmod: 2024-02-03
description: "A hasty Salt state to deploy netdata monitoring and publish it internally on my tailnet with Tailscale Serve"
featured: false
toc: true
@ -37,7 +37,7 @@ tailscale:
netdata-kickstart:
cmd.run:
- name: curl -Ss https://my-netdata.io/kickstart.sh | sh -s -- --dont-wait
- name: curl -Ss https://get.netdata.cloud/kickstart.sh | sh -s -- --dont-wait
- require:
- pkg: curl
# don't run this block if netdata is already running
@ -77,9 +77,9 @@ minion-name: # [tl! .nocopy:start collapse:start]
----------
ID: netdata-kickstart
Function: cmd.run
Name: curl -Ss https://my-netdata.io/kickstart.sh | sh -s -- --dont-wait
Name: curl -Ss https://get.netdata.cloud/kickstart.sh | sh -s -- --dont-wait
Result: True
Comment: Command "curl -Ss https://my-netdata.io/kickstart.sh | sh -s -- --dont-wait" run
Comment: Command "curl -Ss https://get.netdata.cloud/kickstart.sh | sh -s -- --dont-wait" run
Started: 22:59:05.441217
Duration: 10617.082 ms
Changes:

View file

@ -1,7 +1,7 @@
---
title: "Tailscale Serve in a Docker Compose Sidecar"
date: 2023-12-30
lastmod: 2024-01-01
lastmod: 2024-02-07
description: "Using Docker Compose to deploy containerized applications and make them available via Tailscale Serve and Tailscale Funnel"
featured: false
toc: true
@ -59,6 +59,63 @@ https://tsdemo.tailnet-name.ts.net:8443/
It would be really great if I could directly attach each container to my tailnet and then access the apps with addresses like `https://miniflux.tailnet-name.ts.net` or `https://cyber.tailnet-name.ts.net`. Tailscale does have an [official Docker image](https://hub.docker.com/r/tailscale/tailscale), and at first glance it seems like that would solve my needs pretty directly. Unfortunately, it looks like trying to leverage that container image directly would still require me to configure Tailscale Serve interactively.[^ts_serve_config].
{{% notice note "Update: 2024-02-07" %}}
Tailscale [just published a blog post](https://tailscale.com/blog/docker-tailscale-guide) which shares some details about how to configure Funnel and Serve within the official image. The short version is that the `TS_SERVE_CONFIG` variable should point to a `serve-config.json` file. The name of the file doesn't actually matter, but the contents do - and you can generate a config by running `tailscale serve status -json` on a functioning system... or just copy-pasta'ing this example I just made for the [Cyberchef](#cyberchef) setup I describe later in this post:
```json
// torchlight! {"lineNumbers": true}
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"cyber.tailnet-name.ts.net:443": {
"Handlers": {
"/": {
"Proxy": "http://127.0.0.1:8000"
}
}
}
}//, uncomment to enable funnel
// "AllowFunnel": {
// "cyber.tailnet-name.ts.net:443": true
// }
}
```
Replace the ports and protocols and hostnames and such, and you'll be good to go.
A compose config using this setup might look something like this:
```yaml
# torchlight! {"lineNumbers": true}
services:
tailscale:
image: tailscale/tailscale:latest # [tl! highlight]
container_name: cyberchef-tailscale
restart: unless-stopped
environment:
TS_AUTHKEY: ${TS_AUTHKEY:?err}
TS_HOSTNAME: ${TS_HOSTNAME:-ts-docker}
TS_EXTRA_ARGS: ${TS_EXTRA_ARGS:-}
TS_STATE_DIR: /var/lib/tailscale/
TS_SERVE_CONFIG: /config/serve-config.json # [tl! highlight]
volumes:
- ./ts_data:/var/lib/tailscale/
- ./serve-config.json:/config/serve-config.json # [tl! highlight]
cyberchef:
container_name: cyberchef
image: mpepping/cyberchef:latest
restart: unless-stopped
network_mode: service:tailscale
```
That's a bit cleaner than the workaround I'd put together, but you're totally welcome to keep on reading if you want to see how it compares.
{{% /notice %}}
[^ts_serve_config]: While not documented for the image itself, the `containerboot` binary seems like it should accept a [`TS_SERVE_CONFIG` argument](https://github.com/tailscale/tailscale/blob/5812093d31c8a7f9c5e3a455f0fd20dcc011d8cd/cmd/containerboot/main.go#L43) to designate the file path of the `ipn.ServeConfig`... but I couldn't find any information on how to actually configure that.
And then I came across [Louis-Philippe Asselin's post](https://asselin.engineer/tailscale-docker) about how he set up Tailscale in Docker Compose. When he wrote his post, there was even less documentation on how to do this stuff, so he used a [modified Tailscale docker image](https://github.com/lpasselin/tailscale-docker) which loads a [startup script](https://github.com/lpasselin/tailscale-docker/blob/c6f8d75b5e1235b8dbeee849df9321f515c526e5/images/tailscale/start.sh) to handle some of the configuration steps. His repo also includes a [helpful docker-compose example](https://github.com/lpasselin/tailscale-docker/blob/c6f8d75b5e1235b8dbeee849df9321f515c526e5/docker-compose/stateful-example/docker-compose.yml) of how to connect it together.

View file

@ -19,8 +19,6 @@ Incoming messages are routed through a pool of servers so that your conversation
The app is also packed with other features like disappearing messages, encrypted file transfers, encrypted voice messages, encrypted audio and video calls, decentralized private groups, and a cool incognito mode which connects new conversations to a randomly-generated profile instead of your primary one. There's even a [CLI client](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/CLI.md)!
## Servers
[![Status badge](https://status.runtimeterror.dev/api/badge/11/status)](https://status.runtimeterror.dev/status/simplex)
You can easily host your own [simplexmq server](https://github.com/simplex-chat/simplexmq) for handling your inbound message queue, and I've done just that; in fact, I've deployed three! And, as one of my closest internet friends, *you're welcome to use them as well.*
Just add these in the SimpleX app at **Settings > Network & servers > SMP servers > + Add server...**. Enable the option to use them for new connections, and they'll be added to the pool used for incoming messages in new conversations. If you want to use them immediately for existing conversations, go into each conversation's options menu and use the **Switch receiving address** option. You can also *disable* the option to use the default servers for new conversations if you only want messages to be routed through specific servers, but that does increase the likelikhood of concurrent conversations being routed the same way. More servers, more path options, less metadata in any one place.
@ -32,7 +30,7 @@ Just add these in the SimpleX app at **Settings > Network & servers > SMP server
| | |
| --- | --- |
| [![Status badge](https://status.runtimeterror.dev/api/badge/6/uptime)](https://status.runtimeterror.dev/status/simplex) | [[details](https://l.runtimeterror.dev/smp_status)] |
| [![Uptime](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjbowdre%2Fupptime%2Fmaster%2Fapi%2Fsmp-vpota-to-5223%2Fuptime.json)](https://status.runtimeterror.dev/history/smp-vpota-to-5223) | [[netdata](https://l.runtimeterror.dev/smp_status)] |
---
@ -42,7 +40,7 @@ Just add these in the SimpleX app at **Settings > Network & servers > SMP server
| | |
| --- | --- |
| [![Status badge](https://status.runtimeterror.dev/api/badge/4/uptime)](https://status.runtimeterror.dev/status/simplex) | [[details](https://l.runtimeterror.dev/smp1_status)] |
| [![Uptime](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjbowdre%2Fupptime%2Fmaster%2Fapi%2Fsmp1-vpota-to-5223%2Fuptime.json)](https://status.runtimeterror.dev/history/smp1-vpota-to-5223) | [[netdata](https://l.runtimeterror.dev/smp1_status)] |
---
@ -52,4 +50,4 @@ Just add these in the SimpleX app at **Settings > Network & servers > SMP server
| | |
| --- | --- |
| [![Status badge](https://status.runtimeterror.dev/api/badge/5/uptime)](https://status.runtimeterror.dev/status/simplex) | [[details](https://l.runtimeterror.dev/smp2_status)] |
| [![Uptime](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjbowdre%2Fupptime%2Fmaster%2Fapi%2Fsmp2-vpota-to-5223%2Fuptime.json)](https://status.runtimeterror.dev/history/smp2-vpota-to-5223) | [[netdata](https://l.runtimeterror.dev/smp2_status)] |

View file

@ -42,8 +42,14 @@
{{- end }}
{{- if ne $showComments false }}
<hr>
{{- if eq .Site.Params.analytics true }}
<span class="post_kudos">Enjoyed this post?&nbsp;<button class="tinylytics_kudos"></button></span>
{{- $showKudos := true }}
{{- if eq .Site.Params.kudos false }}
{{- $showKudos = false }}
{{- else if eq .Params.kudos false }}
{{- $showKudos = false }}
{{- end }}
{{- if and (eq .Site.Params.analytics true) (ne $showKudos false) }}
<span class="post_kudos">Celebrate this post:&nbsp;<button class="tinylytics_kudos"></button></span>
{{- end }}
{{- partial "comments" . }}
{{- end }}

View file

@ -42,3 +42,7 @@
<hr>
<h3>status.lol</h3>
<script src="https://status.lol/jbowdre.js?time&link&fluent&pretty"></script>
{{- if eq .Site.Params.analytics true }}
<hr>
<a href="" target="_blank" class="tinylytics_webring" title="Tinylytics Webring">️🕸<img class="tinylytics_webring_avatar" src="" style="display: none"/>💍</a>
{{- end }}

View file

@ -6,11 +6,7 @@
<!-- Back to Top button via https://github.com/vfeskov/vanilla-back-to-top -->
{{ $jsToTop := resources.Get "js/back-to-top.js" | minify }}
<script src="{{ $jsToTop.RelPermalink }}"></script>
<script>addBackToTop({
diameter: 56,
backgroundColor: '#383838',
textColor: '#c45a5a'
})</script>
<script>addBackToTop()</script>
<!-- Search index via https://victoria.dev/blog/add-search-to-hugo-static-sites-with-lunr/ -->
{{ partial "search-index.html" .}}

View file

@ -36,7 +36,7 @@
{{ if eq .Site.Params.analytics true }}
<!-- tinylytics -->
<script src="https://tinylytics.app/embed/z4bwvaCBkF39NcDDLsRu.js?kudos=🎉" defer></script>
<script src="https://tinylytics.app/embed/z4bwvaCBkF39NcDDLsRu.js?kudos=🎉&webring=avatars" defer></script>
{{ end }}
<!-- syntax highlighting -->

View file

@ -260,7 +260,6 @@ form button {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
grid-gap: 0.5rem;
/* justify-content: center; */
margin: 0.5rem 0;
}
@ -283,7 +282,7 @@ blockquote {
padding-left: 0.25rem;
}
/* tinylytics kudos styling*/
/* tinylytics styling*/
.post_kudos {
display: flex;
}
@ -304,6 +303,18 @@ button.tinylytics_kudos:hover {
text-shadow: var(--off-fg) 0 0 1px;
}
img.tinylytics_webring_avatar {
border-radius: 100%;
height: 2rem;
width: 2rem;
vertical-align:middle;
}
a.tinylytics_webring {
text-decoration: none;
font-size: 1.5rem;
}
/* post front matter styling*/
.frontmatter hr {
margin-bottom: 0rem;
@ -334,3 +345,24 @@ button.tinylytics_kudos:hover {
font-size: 0.8rem;
color: var(--off-fg);
}
/* back-to-top styling */
#back-to-top {
background: var(--inner-bg);
height: 56px;
width: 56px;
}
#back-to-top svg {
fill: var(--link);
}
/* footnote link styling */
.footnote-backref {
text-decoration: none;
}
/* hr override */
hr {
margin-top: 1.5rem;
}

View file

@ -2,20 +2,20 @@
*/
:root {
--base00: #181818; /* bg */
--base01: #282828; /* off-bg */
--base02: #383838; /* inner-bg */
--base03: #585858; /* muted */
--base00: #090909; /* bg */
--base01: #1c1c1c; /* off-bg */
--base02: #292929; /* inner-bg */
--base03: #6d6c6c; /* muted */
--base04: #abaaaa; /* off-fg */
--base05: #d8d8d8; /* fg */
--base06: #cfcfcf; /* code */
--base06: #75f558; /* code */
--base07: #5f8700; /* user prompt */
--base08: #ab4642; /* root prompt */
--base09: #dc9656;
--base0A: #f7ca88; /* highlight */
--base0B: #772a28; /* logo */
--base0B: #682523; /* logo */
--base0C: #ab2321; /* hover */
--base0D: #c45a5a; /* link */
--base0D: #d36060; /* link */
--base0E: #ba8baf;
--base0F: #a16946;
}