Compare commits
86 Commits
12a1c86435
...
master
Author | SHA1 | Date | |
---|---|---|---|
644dc626a4 | |||
2c770a4b17 | |||
d5fff56284 | |||
21707d4cf8 | |||
2977b1b916 | |||
2ac68f5a6a | |||
cb8b17f5f4 | |||
52983eb698 | |||
87fd31bcda | |||
d3d8bbf84c | |||
25c848abfa | |||
7552f260aa | |||
0978e0479d | |||
5331fcef6c | |||
3093ab9cfa | |||
257ea3d1a0 | |||
5c23a13501 | |||
85cc2e3dbf | |||
665811bb57 | |||
005a371dcb | |||
118c471d00 | |||
6666320cbd | |||
9c8965625b | |||
283f31aa89 | |||
ea2c9519cd | |||
219c44054d | |||
6757e4178a | |||
1c083132a2 | |||
86f3a08bbc | |||
02f2346c93 | |||
b9d6083fca | |||
59cd6d8358 | |||
870ab7b6fc | |||
58970bc8ef | |||
5f866fbeb0 | |||
babe21ed14 | |||
612930411a | |||
ab776d8662 | |||
d653bc948d | |||
a13bdc1e08 | |||
60ffbf9b5d | |||
4b5d909db7 | |||
0737b5ec9e | |||
710f6cc8b0 | |||
16c0dac56c | |||
7f0e569139 | |||
729c49bad5 | |||
e2d5ca3762 | |||
f4f781e1ea | |||
e15ab3e1a3 | |||
6846caa527 | |||
da95a0af98 | |||
60c6fa0415 | |||
f443a77323 | |||
593bd5024c | |||
43edace864 | |||
a7f9448a25 | |||
bc806ab72b | |||
424583e75f | |||
9e8e1e7b1b | |||
66a37b8372 | |||
048592d9c4 | |||
0fb32ee7fc | |||
457dacc6f4 | |||
b432b3f31d | |||
c059b3164a | |||
2a2ecdf72e | |||
67eecb9f16 | |||
3d84fe1c7b | |||
08c4ea0d82 | |||
67340aca4c | |||
ce064be053 | |||
ed19695083 | |||
1255ecc4b8 | |||
69bee26ff4 | |||
35a2fc5e69 | |||
dbb9315e4e | |||
55d2109099 | |||
a3a6f5ca08 | |||
bead9622df | |||
6bd413c951 | |||
fc5c89f305 | |||
75e4736068 | |||
6dfa58c66c | |||
c0acfb8836 | |||
cb30b9a3c5 |
1
.gitignore
vendored
@@ -112,3 +112,4 @@ test/
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
output/
|
output/
|
||||||
|
.aider*
|
||||||
|
@@ -25,5 +25,7 @@
|
|||||||
"file-recovery": true,
|
"file-recovery": true,
|
||||||
"publish": false,
|
"publish": false,
|
||||||
"sync": false,
|
"sync": false,
|
||||||
"canvas": true
|
"canvas": true,
|
||||||
|
"bookmarks": true,
|
||||||
|
"properties": false
|
||||||
}
|
}
|
48
content/.obsidian/core-plugins.json
vendored
@@ -1,16 +1,32 @@
|
|||||||
[
|
{
|
||||||
"file-explorer",
|
"file-explorer": true,
|
||||||
"global-search",
|
"global-search": true,
|
||||||
"switcher",
|
"switcher": true,
|
||||||
"graph",
|
"graph": true,
|
||||||
"backlink",
|
"backlink": true,
|
||||||
"canvas",
|
"outgoing-link": false,
|
||||||
"page-preview",
|
"tag-pane": false,
|
||||||
"note-composer",
|
"page-preview": true,
|
||||||
"command-palette",
|
"daily-notes": false,
|
||||||
"editor-status",
|
"templates": true,
|
||||||
"markdown-importer",
|
"note-composer": true,
|
||||||
"outline",
|
"command-palette": true,
|
||||||
"word-count",
|
"slash-command": false,
|
||||||
"file-recovery"
|
"editor-status": true,
|
||||||
]
|
"starred": false,
|
||||||
|
"markdown-importer": true,
|
||||||
|
"zk-prefixer": false,
|
||||||
|
"random-note": false,
|
||||||
|
"outline": true,
|
||||||
|
"word-count": true,
|
||||||
|
"slides": false,
|
||||||
|
"audio-recorder": false,
|
||||||
|
"workspaces": false,
|
||||||
|
"file-recovery": true,
|
||||||
|
"publish": false,
|
||||||
|
"sync": false,
|
||||||
|
"canvas": true,
|
||||||
|
"bookmarks": true,
|
||||||
|
"properties": false,
|
||||||
|
"webviewer": false
|
||||||
|
}
|
162
content/.obsidian/workspace.json
vendored
@@ -13,10 +13,54 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "markdown",
|
"type": "markdown",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "Things I Recommend.md",
|
"file": "Secret Garden.md",
|
||||||
"mode": "source",
|
"mode": "source",
|
||||||
"source": false
|
"source": false
|
||||||
|
},
|
||||||
|
"icon": "lucide-file",
|
||||||
|
"title": "Secret Garden"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "238ba022d07a7436",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "markdown",
|
||||||
|
"state": {
|
||||||
|
"file": "Fake Dog.md",
|
||||||
|
"mode": "source",
|
||||||
|
"source": false
|
||||||
|
},
|
||||||
|
"icon": "lucide-file",
|
||||||
|
"title": "Fake Dog"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d9a16803d250ddc4",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "markdown",
|
||||||
|
"state": {
|
||||||
|
"file": "Secret Garden.md",
|
||||||
|
"mode": "source",
|
||||||
|
"source": false
|
||||||
|
},
|
||||||
|
"icon": "lucide-file",
|
||||||
|
"title": "Secret Garden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "24a62ccdfd18a884",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "markdown",
|
||||||
|
"state": {
|
||||||
|
"file": "Custom Air Quality Monitor.md",
|
||||||
|
"mode": "source",
|
||||||
|
"source": false
|
||||||
|
},
|
||||||
|
"icon": "lucide-file",
|
||||||
|
"title": "Custom Air Quality Monitor"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -25,14 +69,30 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "markdown",
|
"type": "markdown",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "Sensors.md",
|
"file": "Helios Alpha.md",
|
||||||
"mode": "source",
|
"mode": "source",
|
||||||
"source": false
|
"source": false
|
||||||
|
},
|
||||||
|
"icon": "lucide-file",
|
||||||
|
"title": "Helios Alpha"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "60389a84493f7fa2",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "markdown",
|
||||||
|
"state": {
|
||||||
|
"file": "Hydroponics.md",
|
||||||
|
"mode": "source",
|
||||||
|
"source": false
|
||||||
|
},
|
||||||
|
"icon": "lucide-file",
|
||||||
|
"title": "Hydroponics"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"currentTab": 1
|
"currentTab": 3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "vertical"
|
"direction": "vertical"
|
||||||
@@ -51,8 +111,11 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "file-explorer",
|
"type": "file-explorer",
|
||||||
"state": {
|
"state": {
|
||||||
"sortOrder": "alphabetical"
|
"sortOrder": "alphabetical",
|
||||||
}
|
"autoReveal": false
|
||||||
|
},
|
||||||
|
"icon": "lucide-folder-closed",
|
||||||
|
"title": "Files"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,14 +130,26 @@
|
|||||||
"collapseAll": false,
|
"collapseAll": false,
|
||||||
"extraContext": false,
|
"extraContext": false,
|
||||||
"sortOrder": "alphabetical"
|
"sortOrder": "alphabetical"
|
||||||
|
},
|
||||||
|
"icon": "lucide-search",
|
||||||
|
"title": "Search"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7bdb31d1bda5b8c9",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "bookmarks",
|
||||||
|
"state": {},
|
||||||
|
"icon": "lucide-bookmark",
|
||||||
|
"title": "Bookmarks"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "horizontal",
|
"direction": "horizontal",
|
||||||
"width": 300
|
"width": 200
|
||||||
},
|
},
|
||||||
"right": {
|
"right": {
|
||||||
"id": "260bba8f76f307a9",
|
"id": "260bba8f76f307a9",
|
||||||
@@ -90,7 +165,7 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "backlink",
|
"type": "backlink",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "Sensors.md",
|
"file": "Custom Air Quality Monitor.md",
|
||||||
"collapseAll": false,
|
"collapseAll": false,
|
||||||
"extraContext": false,
|
"extraContext": false,
|
||||||
"sortOrder": "alphabetical",
|
"sortOrder": "alphabetical",
|
||||||
@@ -98,7 +173,9 @@
|
|||||||
"searchQuery": "",
|
"searchQuery": "",
|
||||||
"backlinkCollapsed": false,
|
"backlinkCollapsed": false,
|
||||||
"unlinkedCollapsed": true
|
"unlinkedCollapsed": true
|
||||||
}
|
},
|
||||||
|
"icon": "links-coming-in",
|
||||||
|
"title": "Backlinks for Custom Air Quality Monitor"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -107,8 +184,13 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "outline",
|
"type": "outline",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "Sensors.md"
|
"file": "Custom Air Quality Monitor.md",
|
||||||
}
|
"followCursor": false,
|
||||||
|
"showSearch": false,
|
||||||
|
"searchQuery": ""
|
||||||
|
},
|
||||||
|
"icon": "lucide-list",
|
||||||
|
"title": "Outline of Custom Air Quality Monitor"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -116,7 +198,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "horizontal",
|
"direction": "horizontal",
|
||||||
"width": 300
|
"width": 300,
|
||||||
|
"collapsed": true
|
||||||
},
|
},
|
||||||
"left-ribbon": {
|
"left-ribbon": {
|
||||||
"hiddenItems": {
|
"hiddenItems": {
|
||||||
@@ -124,28 +207,47 @@
|
|||||||
"graph:Open graph view": false,
|
"graph:Open graph view": false,
|
||||||
"canvas:Create new canvas": false,
|
"canvas:Create new canvas": false,
|
||||||
"command-palette:Open command palette": false,
|
"command-palette:Open command palette": false,
|
||||||
"markdown-importer:Open format converter": false
|
"markdown-importer:Open format converter": false,
|
||||||
|
"templates:Insert template": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"active": "1f1f024283ea8110",
|
"active": "24a62ccdfd18a884",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
"About.md",
|
"media/airmonitor3.png",
|
||||||
"ChatGPT Peanut Butter Cookies.md",
|
"media/airmonitor2.png",
|
||||||
"Bypassing Ports.md",
|
"media/Pasted image 20250622165324.png",
|
||||||
"media/cookies3.jpg",
|
"media/airmonitor2.png",
|
||||||
"media/cookies4.jpg",
|
"media/airmonitor1.jpg",
|
||||||
"media/cookies1.jpg",
|
"Sensors.md",
|
||||||
"media/cookies2.jpg",
|
"Custom Air Quality Monitor.md",
|
||||||
"media/Pasted image 20230324140743.png",
|
"Protovac Retro Terminal.md",
|
||||||
"media/cookies1.png",
|
"QotNews.md",
|
||||||
"Acoustic Panels.md",
|
"Notica.md",
|
||||||
"Fake Dog.md",
|
"Spaceport.md",
|
||||||
"Things I Recommend.md",
|
"media/spaceport1.png",
|
||||||
"Backup Strategy.md",
|
|
||||||
"Hydroponics.md",
|
|
||||||
"Hydroponics Aphid War.md",
|
|
||||||
"Hand of Ozymandias.md",
|
|
||||||
"Garage Door Opener.md",
|
"Garage Door Opener.md",
|
||||||
"Plant Waterer.md"
|
"media/protovac1.jpg",
|
||||||
|
"Fake Dog.md",
|
||||||
|
"Bypassing Ports.md",
|
||||||
|
"Backup Strategy.md",
|
||||||
|
"Algae Growth.md",
|
||||||
|
"t0 Services.md",
|
||||||
|
"Solar Car.md",
|
||||||
|
"Protospace.md",
|
||||||
|
"Helios Alpha.md",
|
||||||
|
"Hand of Ozymandias.md",
|
||||||
|
"Airflow.md",
|
||||||
|
"media/protovac1.png",
|
||||||
|
"media/fake-dog.jpg",
|
||||||
|
"media/dress1.jpg",
|
||||||
|
"media/cookies2.jpg",
|
||||||
|
"Acoustic Panels.md",
|
||||||
|
"About.md",
|
||||||
|
"Hydroponics Log 3.md",
|
||||||
|
"Hydroponics Log 2.md",
|
||||||
|
"Hydroponics Log 1.md",
|
||||||
|
"pages/writing.md",
|
||||||
|
"pages/projects.md",
|
||||||
|
"pages/creations.md"
|
||||||
]
|
]
|
||||||
}
|
}
|
@@ -2,7 +2,6 @@ Title: About
|
|||||||
Date: 2022-07-23
|
Date: 2022-07-23
|
||||||
Category: Notes
|
Category: Notes
|
||||||
Summary: About me and my website.
|
Summary: About me and my website.
|
||||||
Short: a
|
|
||||||
Wide: true
|
Wide: true
|
||||||
|
|
||||||
## Me
|
## Me
|
||||||
@@ -14,7 +13,7 @@ I do my computing on a ThinkPad X1 Carbon laptop running Debian GNU/Linux with G
|
|||||||
I don't like tweaking or configuring settings so I try to leave things default unless something really annoys me or it improves my workflow greatly. It's easy to sink an infinite amount of time into optimizing your workflow and then die having made nothing.
|
I don't like tweaking or configuring settings so I try to leave things default unless something really annoys me or it improves my workflow greatly. It's easy to sink an infinite amount of time into optimizing your workflow and then die having made nothing.
|
||||||
|
|
||||||
## Website
|
## Website
|
||||||
There's two versions of this website, a main version at <https://tannercollin.com> and a lite version at <https://t0.vc>. The reason is because I found myself continually removing features from the main version for sport and to satisfy my millennial craving for brutalist design. I was already running several [[t0 Services | services]] on t0.vc subdomains but had nothing on the main domain. So it's the perfect use for it and I can experiment to see how brutalist I can make it.
|
There's two versions of this website, a main version at <https://tanner.vc> and a lite version at <https://t0.vc>. The reason is because I found myself continually removing features from the main version for sport and to satisfy my millennial craving for brutalist design. I was already running several [[t0 Services | services]] on t0.vc subdomains but had nothing on the main domain. So it's the perfect use for it and I can experiment to see how brutalist I can make it.
|
||||||
|
|
||||||
### Colophon
|
### Colophon
|
||||||
I use the static site generator Pelican to build the websites from a folder of markdown documents which I edit with Obsidian in Vim mode. This makes writing content feel like taking notes, since media and internal links are taken care of. The two versions are simply different themes loaded by different Pelican configs. The output is uploaded to my host via rsync and served by Nginx.
|
I use the static site generator Pelican to build the websites from a folder of markdown documents which I edit with Obsidian in Vim mode. This makes writing content feel like taking notes, since media and internal links are taken care of. The two versions are simply different themes loaded by different Pelican configs. The output is uploaded to my host via rsync and served by Nginx.
|
||||||
|
@@ -2,7 +2,9 @@ Title: Theatre Acoustic Panels
|
|||||||
Date: 2021-10-21
|
Date: 2021-10-21
|
||||||
Category: Creations
|
Category: Creations
|
||||||
Summary: Panels for acoustic treatment in my home theatre.
|
Summary: Panels for acoustic treatment in my home theatre.
|
||||||
Short: 3
|
Image: panel3.jpg
|
||||||
|
Tags: feed
|
||||||
|
Guid: 763087bc038b49199d305f031cfaa6c3
|
||||||
|
|
||||||
Acoustic treatment is one of the most overlooked aspects of home audio. There's no point in spending money on premium speakers if the room they are playing in has poor acoustics.
|
Acoustic treatment is one of the most overlooked aspects of home audio. There's no point in spending money on premium speakers if the room they are playing in has poor acoustics.
|
||||||
|
|
||||||
|
15
content/Airflow.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Title: Airflow
|
||||||
|
Date: 2023-03-07
|
||||||
|
Category: Notes
|
||||||
|
Summary: Point a fan at indoor hydroponic plants once they develop their true leaves.
|
||||||
|
|
||||||
|
Airflow across plants is important because it helps with [transpiration][1], the process of water movement through a plant and evaporation off its leaves. The water is used to move minerals up the plant from its roots. Quoting Wikipedia:
|
||||||
|
|
||||||
|
> In still air, water lost due to transpiration can accumulate in the form of vapor close to the leaf surface. This will reduce the rate of water loss, as the water potential gradient from inside to outside of the leaf is then slightly less. The wind blows away much of this water vapor near the leaf surface, making the potential gradient steeper and speeding up the diffusion of water molecules into the surrounding air. Even in wind, though, there may be some accumulation of water vapor in a thin boundary layer of slower moving air next to the leaf surface. The stronger the wind, the thinner this layer will tend to be, and the steeper the water potential gradient.
|
||||||
|
|
||||||
|
A lack of airflow can cause [mineral deficiencies][2] which is why it's recommended to point a fan at plants grown hydroponically. The plant can't do anything to fix too little airflow, but can close [stomas][3] to correct for too much airflow.
|
||||||
|
|
||||||
|
|
||||||
|
[1]: https://en.wikipedia.org/wiki/Transpiration
|
||||||
|
[2]: https://hortamericas.com/blog/science/how-to-avoid-calcium-deficiency-in-controlled-environment-food-crops/
|
||||||
|
[3]: https://en.wikipedia.org/wiki/Stoma
|
8
content/Algae Growth.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Title: Algae Growth
|
||||||
|
Date: 2023-03-11
|
||||||
|
Category: Notes
|
||||||
|
Summary: Prevent hydroponic algae from growing by blocking light to any nutrient solution.
|
||||||
|
|
||||||
|
Algae will grow anywhere light is able to touch hydroponic nutrient solution. This won't be a problem if the solution is regularly flushed. In Kratky systems the algae will compete for resources with your plants and grow exponentially.
|
||||||
|
|
||||||
|
The solution is to prevent light from reaching the solution by using dark containers and covering exposed parts with and opaque material such as aluminium foil.
|
@@ -2,8 +2,10 @@ Title: My Backup Strategy
|
|||||||
Date: 2021-04-08
|
Date: 2021-04-08
|
||||||
Category: Writing
|
Category: Writing
|
||||||
Summary: Details about the backup system for all of my data.
|
Summary: Details about the backup system for all of my data.
|
||||||
|
Image: backup1.svg
|
||||||
Wide: true
|
Wide: true
|
||||||
Short: backup
|
Tags: feed
|
||||||
|
Guid: c0afe12a1c4943839df1da082c2e0938
|
||||||
|
|
||||||
[TOC]
|
[TOC]
|
||||||
|
|
||||||
@@ -20,12 +22,15 @@ Backups need to be tested to ensure they are correct and happening regularly. M
|
|||||||
Backups should also be incremental when possible (rather than mirror copies) so an accidental deletion isn't propagated into the backups, making the file irrecoverable.
|
Backups should also be incremental when possible (rather than mirror copies) so an accidental deletion isn't propagated into the backups, making the file irrecoverable.
|
||||||
|
|
||||||
## Strategy
|
## Strategy
|
||||||
|
|
||||||
The key is to have one central location that all your files, projects, and data are cloned to and then back that directory up to multiple locations.
|
The key is to have one central location that all your files, projects, and data are cloned to and then back that directory up to multiple locations.
|
||||||
|
|
||||||
I have one backup folder `/mnt/backup` on my media server at home that serves as the destination for all my backup sources. All scheduled automatic backups write to their own subfolder inside of it.
|
I have one backup folder `/mnt/backup` on my media server at home that serves as the destination for all my backup sources. All scheduled automatic backups write to their own subfolder inside of it.
|
||||||
|
|
||||||
This backup folder is then synced to encrypted 2.5" 1 TB hard drives which I rotate between my bag, offsite, and my parents' house.
|
This backup folder is then synced to encrypted 2.5" 1 TB hard drives which I rotate between my bag, offsite, and my parents' house.
|
||||||
|
|
||||||
|
![[backup1.svg | a diagram of my setup. servers and computers on the left, pointing to my home server in the middle, pointing to external hard drives on the right]]
|
||||||
|
|
||||||
## Backup Sources
|
## Backup Sources
|
||||||
|
|
||||||
I use the tool `rdiff-backup` extensively because it allows me to take incremental backups locally or over SSH. It acts very similar to `rsync` and has no configuration.
|
I use the tool `rdiff-backup` extensively because it allows me to take incremental backups locally or over SSH. It acts very similar to `rsync` and has no configuration.
|
||||||
|
369
content/Bypassing Ports Old.md
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
Title: Bypassing ISP Blocked Ports (Old)
|
||||||
|
Date: 2021-04-10
|
||||||
|
Category: Notes
|
||||||
|
Summary: (Old) Bypass ISP blocked ports using VPN port forwarding for public access.
|
||||||
|
Wide: true
|
||||||
|
|
||||||
|
[TOC]
|
||||||
|
|
||||||
|
My residential ISP blocks inbound traffic to common ports like 22, 80, and 443. I use an OpenVPN tunnel to forward these ports so that I can self-host a public media server. It does __not__ require users to be on the VPN.
|
||||||
|
|
||||||
|
This article explains how I set it up and is targeted towards Linux sysadmins.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
I have a cheap $5 per month virtual server with [Digital Ocean](https://digitalocean.com) that runs Debian GNU/Linux 10. An OpenVPN server is running on this virtual server.
|
||||||
|
|
||||||
|
My media server at home has an OpenVPN client connected to the server and is assigned a static IP on the VPN network.
|
||||||
|
|
||||||
|
The virtual server has routing enabled and forwards inbound traffic __from the internet__ to my media server at home. This allows me to have external HTTP and SSH access.
|
||||||
|
|
||||||
|
## Server Setup
|
||||||
|
|
||||||
|
Spin up a Debian 10 virtual server on your favourite hosting provider and set your user up as you would normally. You should probably harden this server. Assign a subdomain to it like `vpn.example.com`.
|
||||||
|
|
||||||
|
Install the following requirements:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo apt update
|
||||||
|
$ sudo apt install openvpn easy-rsa ufw
|
||||||
|
```
|
||||||
|
|
||||||
|
### OpenVPN Server
|
||||||
|
|
||||||
|
These steps roughly follow [this guide](https://wiki.debian.org/OpenVPN#TLS-enabled_VPN).
|
||||||
|
|
||||||
|
Generate TLS certificates and keys:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd /etc/openvpn
|
||||||
|
$ sudo openvpn --genkey --secret static.key
|
||||||
|
$ sudo make-cadir easy-rsa/
|
||||||
|
$ sudo chown -R tanner:tanner easy-rsa/
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `tanner` with your Linux username, this is temporary.
|
||||||
|
|
||||||
|
<span class="aside">(The `.rnd` file prevents a warning)</span>
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd easy-rsa/
|
||||||
|
$ export EASYRSA_CERT_EXPIRE=36500
|
||||||
|
$ export EASYRSA_CA_EXPIRE=36500
|
||||||
|
$ ./easyrsa init-pki
|
||||||
|
$ head /dev/urandom > pki/.rnd
|
||||||
|
$ ./easyrsa build-ca
|
||||||
|
```
|
||||||
|
|
||||||
|
Enter a password you won't forget in case you want to add another client later. The Common Name you choose is not important.
|
||||||
|
|
||||||
|
Generate Diffie–Hellman params:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./easyrsa gen-dh
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate a server cert:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./easyrsa build-server-full server nopass
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate a client cert:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./easyrsa build-client-full mediaserver nopass
|
||||||
|
```
|
||||||
|
|
||||||
|
We make a `mediaserver` client because we want to assign a static IP to it. You need to make a different one for each client you want with a static IP.
|
||||||
|
|
||||||
|
Also, if you want generic clients that all get dynamic IPs for use on your laptop, phone, etc. to protect you from public WiFi (like a normal VPN), create only a single extra one:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./easyrsa build-client-full client nopass # optional
|
||||||
|
```
|
||||||
|
|
||||||
|
Leave off `nopass` if you want to password protect the config file keys when you set up a new client.
|
||||||
|
|
||||||
|
Create the server config file `/etc/openvpn/server.conf`:
|
||||||
|
|
||||||
|
<span class="aside">(Can't use port 443 here since it'll be forwarded)</span>
|
||||||
|
|
||||||
|
```
|
||||||
|
port 1194
|
||||||
|
proto udp
|
||||||
|
dev tun
|
||||||
|
topology subnet
|
||||||
|
ca /etc/openvpn/easy-rsa/pki/ca.crt
|
||||||
|
cert /etc/openvpn/easy-rsa/pki/issued/server.crt
|
||||||
|
key /etc/openvpn/easy-rsa/pki/private/server.key
|
||||||
|
dh /etc/openvpn/easy-rsa/pki/dh.pem
|
||||||
|
tls-auth /etc/openvpn/static.key 0
|
||||||
|
client-config-dir /etc/openvpn/ccd
|
||||||
|
server 10.8.0.0 255.255.255.0
|
||||||
|
client-to-client
|
||||||
|
duplicate-cn
|
||||||
|
keepalive 10 120
|
||||||
|
cipher AES-256-GCM
|
||||||
|
auth SHA256
|
||||||
|
comp-lzo
|
||||||
|
max-clients 10
|
||||||
|
user nobody
|
||||||
|
group nogroup
|
||||||
|
persist-key
|
||||||
|
persist-tun
|
||||||
|
```
|
||||||
|
|
||||||
|
Assign a static IP + chmod:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd /etc/openvpn
|
||||||
|
$ sudo chown -R root:root easy-rsa/
|
||||||
|
$ sudo mkdir ccd
|
||||||
|
$ sudo touch ccd/mediaserver
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `mediaserver` with whatever client name you used above. Edit it like so:
|
||||||
|
|
||||||
|
<span class="aside">(Your home server will be `10.8.0.100`)</span>
|
||||||
|
|
||||||
|
```
|
||||||
|
ifconfig-push 10.8.0.100 255.255.255.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Test your config by running:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo openvpn --config /etc/openvpn/server.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
If you run `ip addr` in another terminal, you should see an entry like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> stuff
|
||||||
|
link/none
|
||||||
|
inet 10.8.0.1/24 brd 10.8.0.255 scope global tun0
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
inet6 fe80::d9fc:b2f9:34e6:5ed2/64 scope link stable-privacy
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
```
|
||||||
|
|
||||||
|
### systemd
|
||||||
|
|
||||||
|
If it works fine, persist OpenVPN with systemd:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo systemctl enable openvpn@server
|
||||||
|
$ sudo systemctl start openvpn@server
|
||||||
|
$ sudo systemctl daemon-reload
|
||||||
|
$ sudo service openvpn restart
|
||||||
|
```
|
||||||
|
|
||||||
|
Test it works by rebooting:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo reboot
|
||||||
|
$ ssh vpn.example.com
|
||||||
|
$ ip addr
|
||||||
|
```
|
||||||
|
|
||||||
|
### Port Forwarding
|
||||||
|
|
||||||
|
I use `ufw` to handle the iptables rules because I use it anyway as a firewall when I harden my servers.
|
||||||
|
|
||||||
|
Enable routing:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo sysctl net.ipv4.ip_forward=1
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `/etc/sysctl.conf` to set:
|
||||||
|
|
||||||
|
```
|
||||||
|
net.ipv4.ip_forward=1
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `/etc/default/ufw` to set:
|
||||||
|
|
||||||
|
```
|
||||||
|
DEFAULT_FORWARD_POLICY="ACCEPT"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add this to the top of `/etc/ufw/before.rules`:
|
||||||
|
|
||||||
|
```
|
||||||
|
*nat
|
||||||
|
:POSTROUTING ACCEPT [0:0]
|
||||||
|
|
||||||
|
# ssh port forwarding
|
||||||
|
-A PREROUTING -d 123.123.123.123 -p tcp --dport 2222 -j DNAT --to-dest 10.8.0.100:2222
|
||||||
|
-A POSTROUTING -d 10.8.0.100 -p tcp --dport 2222 -j SNAT --to-source 10.8.0.1
|
||||||
|
|
||||||
|
# Allow traffic from OpenVPN client to eth0
|
||||||
|
-A POSTROUTING -s 10.8.0.0/8 -o eth0 -j MASQUERADE
|
||||||
|
COMMIT
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `123.123.123.123` with your VPN server's external IP address and `eth0` with the external interface.
|
||||||
|
|
||||||
|
This will forward TCP traffic on port 2222 to your home server. If you want to use port 22, then you need to set the VPN SSH server to something else.
|
||||||
|
|
||||||
|
A full example of `/etc/ufw/before.rules` with other ports included can be found here:
|
||||||
|
|
||||||
|
[https://txt.t0.vc/URUG](https://txt.t0.vc/URUG)
|
||||||
|
|
||||||
|
Apply the changes to `ufw`:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo ufw disable && sudo ufw enable
|
||||||
|
```
|
||||||
|
|
||||||
|
## Client Setup
|
||||||
|
|
||||||
|
Switch to your home server or client machine.
|
||||||
|
|
||||||
|
Install OpenVPN:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo apt update
|
||||||
|
$ sudo apt install openvpn
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client Configs
|
||||||
|
|
||||||
|
For static IP clients (like your home server), create the config file `/etc/openvpn/client.conf`:
|
||||||
|
|
||||||
|
```
|
||||||
|
client
|
||||||
|
dev tun
|
||||||
|
proto udp
|
||||||
|
remote vpn.example.com 1194
|
||||||
|
resolv-retry infinite
|
||||||
|
nobind
|
||||||
|
persist-key
|
||||||
|
persist-tun
|
||||||
|
remote-cert-tls server
|
||||||
|
cipher AES-256-GCM
|
||||||
|
auth SHA256
|
||||||
|
comp-lzo
|
||||||
|
key-direction 1
|
||||||
|
<ca>
|
||||||
|
[server /etc/openvpn/easy-rsa/pki/ca.crt]
|
||||||
|
</ca>
|
||||||
|
<cert>
|
||||||
|
[server /etc/openvpn/easy-rsa/pki/issued/mediaserver.crt]
|
||||||
|
</cert>
|
||||||
|
<key>
|
||||||
|
[server /etc/openvpn/easy-rsa/pki/private/mediaserver.key]
|
||||||
|
</key>
|
||||||
|
<tls-auth>
|
||||||
|
[server /etc/openvpn/static.key]
|
||||||
|
</tls-auth>
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace the `[server ...]` lines with the contents of that file on the __VPN server__, for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo cat /etc/openvpn/easy-rsa/pki/ca.crt
|
||||||
|
---> copy & paste result
|
||||||
|
```
|
||||||
|
|
||||||
|
Also replace `vpn.example.com` with the subdomain you assigned earlier.
|
||||||
|
|
||||||
|
For device clients (like your laptop and phone), create the config file `client.ovpn`:
|
||||||
|
|
||||||
|
<span class="aside">(`redirect-gateway def1` forces traffic over the VPN)</span>
|
||||||
|
|
||||||
|
```
|
||||||
|
client
|
||||||
|
dev tun
|
||||||
|
proto udp
|
||||||
|
remote vpn.example.com 1194
|
||||||
|
redirect-gateway def1
|
||||||
|
resolv-retry infinite
|
||||||
|
nobind
|
||||||
|
persist-key
|
||||||
|
persist-tun
|
||||||
|
remote-cert-tls server
|
||||||
|
cipher AES-256-GCM
|
||||||
|
auth SHA256
|
||||||
|
comp-lzo
|
||||||
|
key-direction 1
|
||||||
|
<ca>
|
||||||
|
[server /etc/openvpn/easy-rsa/pki/ca.crt]
|
||||||
|
</ca>
|
||||||
|
<cert>
|
||||||
|
[server /etc/openvpn/easy-rsa/pki/issued/client.crt]
|
||||||
|
</cert>
|
||||||
|
<key>
|
||||||
|
[server /etc/openvpn/easy-rsa/pki/private/client.key]
|
||||||
|
</key>
|
||||||
|
<tls-auth>
|
||||||
|
[server /etc/openvpn/static.key]
|
||||||
|
</tls-auth>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `client.ovpn` file is ready to be imported into your VPN clients.
|
||||||
|
|
||||||
|
Test your config by running:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo openvpn --config /etc/openvpn/client.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
If you run `ip addr` in another terminal, you should see an entry like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
7: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> stuff
|
||||||
|
link/none
|
||||||
|
inet 10.8.0.100/24 brd 10.8.0.255 scope global tun0
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
inet6 fe80::b2:ed71:6c98:4bc9/64 scope link stable-privacy
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
```
|
||||||
|
|
||||||
|
Try pinging the server:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ping 10.8.0.1
|
||||||
|
PING 10.8.0.1 (10.8.0.1) 56(84) bytes of data.
|
||||||
|
64 bytes from 10.8.0.1: icmp_seq=1 ttl=64 time=71.5 ms
|
||||||
|
64 bytes from 10.8.0.1: icmp_seq=2 ttl=64 time=73.0 ms
|
||||||
|
... etc
|
||||||
|
```
|
||||||
|
|
||||||
|
### systemd
|
||||||
|
|
||||||
|
If it works fine, persist OpenVPN with systemd:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo chown root:root /etc/openvpn/client.conf
|
||||||
|
$ sudo chmod 600 /etc/openvpn/client.conf
|
||||||
|
$ sudo systemctl enable openvpn@client
|
||||||
|
$ sudo systemctl start openvpn@client
|
||||||
|
$ sudo systemctl daemon-reload
|
||||||
|
$ sudo service openvpn restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client Apps
|
||||||
|
|
||||||
|
On Android I use "OpenVPN for Android" and on Linux I use the `network-manager-openvpn-gnome` Debian package.
|
||||||
|
|
||||||
|
To add your VPN on Gnome, open VPN settings, import file, and select `client.ovpn`. If the private key is missing, select it from `~/.cert/nm-openvpn/`.
|
||||||
|
|
||||||
|
## Closing Thoughts
|
||||||
|
|
||||||
|
You should now be fine to access your home server from over the internet.
|
||||||
|
|
||||||
|
To forward additional ports, just edit the `/etc/ufw/before.rules` file like above and apply the changes to `ufw`.
|
||||||
|
|
||||||
|
You can now point a domain to your virtual server's IP and use that to connect to your home server. Use a CNAME to make it easy to change later:
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME TYPE VALUE
|
||||||
|
--------------------------------------------------
|
||||||
|
vpn.example.com. A 123.123.123.123
|
||||||
|
myserver.example.com. CNAME vpn.example.com.
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, make sure any server programs are listening / bound to `10.8.0.100` or `0.0.0.0` so that they can get traffic from that interface.
|
@@ -1,9 +1,11 @@
|
|||||||
Title: Bypassing ISP Blocked Ports
|
Title: Bypassing ISP Blocked Ports
|
||||||
Date: 2021-04-10
|
Date: 2023-12-10
|
||||||
Category: Writing
|
Category: Writing
|
||||||
Summary: Bypass ISP blocked ports using VPN port forwarding for public access.
|
Summary: Bypass ISP blocked ports using VPN port forwarding for public access.
|
||||||
|
Image: ports1.svg
|
||||||
Wide: true
|
Wide: true
|
||||||
Short: ports
|
Tags: feed
|
||||||
|
Guid: 1742dbf6802349c68eb232333f6a256c
|
||||||
|
|
||||||
[TOC]
|
[TOC]
|
||||||
|
|
||||||
@@ -13,50 +15,53 @@ This article explains how I set it up and is targeted towards Linux sysadmins.
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
I have a cheap $5 per month virtual server with [Digital Ocean](https://digitalocean.com) that runs Debian GNU/Linux 10. An OpenVPN server is running on this virtual server.
|
I have a cheap $6 per month virtual server with [Digital Ocean](https://digitalocean.com) that runs Debian GNU/Linux 12. An OpenVPN server is running on this virtual server.
|
||||||
|
|
||||||
My media server at home has an OpenVPN client connected to the server and is assigned a static IP on the VPN network.
|
My media server at home has an OpenVPN client connected to the server and is assigned a static IP on the VPN network.
|
||||||
|
|
||||||
The virtual server has routing enabled and forwards inbound traffic __from the internet__ to my media server at home. This allows me to have external HTTP and SSH access.
|
The virtual server has routing enabled and forwards inbound traffic __from the internet__ to my media server at home. This allows me to have external HTTP and SSH access.
|
||||||
|
|
||||||
|
![[ports1.svg | a diagram of my setup. the client computer connecting to my home server through the cloud using a VPN tunnel.]]
|
||||||
|
|
||||||
## Server Setup
|
## Server Setup
|
||||||
|
|
||||||
Spin up a Debian 10 virtual server on your favourite hosting provider and set your user up as you would normally. You should probably harden this server. Assign a subdomain to it like `vpn.example.com`.
|
Spin up a Debian 12 server on your favourite hosting provider. If you're using an older version of Debian, you can follow the [[Bypassing Ports Old |old version of this article]]. You should harden this server. Assign a subdomain to it like `vpn.example.com`.
|
||||||
|
|
||||||
Install the following requirements:
|
Install the following requirements:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sudo apt update
|
$ sudo apt update
|
||||||
$ sudo apt install openvpn easy-rsa ufw
|
$ sudo apt install openvpn easy-rsa ufw
|
||||||
|
$ sudo ufw allow ssh
|
||||||
|
$ sudo ufw allow 1194 # openvpn's port
|
||||||
```
|
```
|
||||||
|
|
||||||
### OpenVPN Server
|
### OpenVPN Server
|
||||||
|
|
||||||
These steps roughly follow [this guide](https://wiki.debian.org/OpenVPN#TLS-enabled_VPN).
|
These steps roughly follow [this guide](https://wiki.debian.org/OpenVPN#TLS-enabled_VPN_connection).
|
||||||
|
|
||||||
Generate TLS certificates and keys:
|
Generate TLS certificates and keys:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cd /etc/openvpn
|
$ cd /etc/openvpn
|
||||||
$ sudo openvpn --genkey --secret static.key
|
$ sudo openvpn --genkey secret static.key
|
||||||
$ sudo make-cadir easy-rsa/
|
$ sudo make-cadir easy-rsa/
|
||||||
$ sudo chown -R tanner:tanner easy-rsa/
|
$ sudo chown -R tanner:tanner /etc/openvpn
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace `tanner` with your Linux username, this is temporary.
|
Replace `tanner` with your Linux username, this is temporary.
|
||||||
|
|
||||||
<span class="aside">(The `.rnd` file prevents a warning)</span>
|
<span class="aside">(The certs will expire in 100 years)</span>
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cd easy-rsa/
|
$ cd easy-rsa/
|
||||||
$ export EASYRSA_CERT_EXPIRE=36500
|
$ export EASYRSA_CERT_EXPIRE=36500
|
||||||
$ export EASYRSA_CA_EXPIRE=36500
|
$ export EASYRSA_CA_EXPIRE=36500
|
||||||
$ ./easyrsa init-pki
|
$ ./easyrsa init-pki
|
||||||
$ head /dev/urandom > pki/.rnd
|
|
||||||
$ ./easyrsa build-ca
|
$ ./easyrsa build-ca
|
||||||
```
|
```
|
||||||
|
|
||||||
Enter a password you won't forget in case you want to add another client later. The Common Name you choose is not important.
|
Enter passwords you won't forget in case you want to add another client later. The Common Name you choose is not important.
|
||||||
|
|
||||||
Generate Diffie–Hellman params:
|
Generate Diffie–Hellman params:
|
||||||
|
|
||||||
@@ -70,10 +75,12 @@ Generate a server cert:
|
|||||||
$ ./easyrsa build-server-full server nopass
|
$ ./easyrsa build-server-full server nopass
|
||||||
```
|
```
|
||||||
|
|
||||||
Generate a client cert:
|
Generate client certs:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ./easyrsa build-client-full mediaserver nopass
|
$ ./easyrsa build-client-full mediaserver nopass
|
||||||
|
$ ./easyrsa build-client-full anotherserver nopass
|
||||||
|
... etc
|
||||||
```
|
```
|
||||||
|
|
||||||
We make a `mediaserver` client because we want to assign a static IP to it. You need to make a different one for each client you want with a static IP.
|
We make a `mediaserver` client because we want to assign a static IP to it. You need to make a different one for each client you want with a static IP.
|
||||||
@@ -84,7 +91,7 @@ Also, if you want generic clients that all get dynamic IPs for use on your lapto
|
|||||||
$ ./easyrsa build-client-full client nopass # optional
|
$ ./easyrsa build-client-full client nopass # optional
|
||||||
```
|
```
|
||||||
|
|
||||||
Leave off `nopass` if you want to password protect the config file keys when you set up a new client.
|
Leave off `nopass` if you want to password protect the config file keys when you set up a new client (PEM pass phrase).
|
||||||
|
|
||||||
Create the server config file `/etc/openvpn/server.conf`:
|
Create the server config file `/etc/openvpn/server.conf`:
|
||||||
|
|
||||||
@@ -115,21 +122,15 @@ persist-key
|
|||||||
persist-tun
|
persist-tun
|
||||||
```
|
```
|
||||||
|
|
||||||
Assign a static IP + chmod:
|
Assign a static IP:
|
||||||
|
|
||||||
```
|
|
||||||
$ cd /etc/openvpn
|
|
||||||
$ sudo chown -R root:root easy-rsa/
|
|
||||||
$ sudo mkdir ccd
|
|
||||||
$ sudo touch ccd/mediaserver
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace `mediaserver` with whatever client name you used above. Edit it like so:
|
|
||||||
|
|
||||||
<span class="aside">(Your home server will be `10.8.0.100`)</span>
|
<span class="aside">(Your home server will be `10.8.0.100`)</span>
|
||||||
|
|
||||||
```
|
```
|
||||||
ifconfig-push 10.8.0.100 255.255.255.0
|
$ cd /etc/openvpn
|
||||||
|
$ mkdir ccd
|
||||||
|
$ echo "ifconfig-push 10.8.0.100 255.255.255.0" > mediaserver
|
||||||
|
$ echo "ifconfig-push 10.8.0.101 255.255.255.0" > anotherserver
|
||||||
```
|
```
|
||||||
|
|
||||||
Test your config by running:
|
Test your config by running:
|
||||||
@@ -149,6 +150,12 @@ If you run `ip addr` in another terminal, you should see an entry like this:
|
|||||||
valid_lft forever preferred_lft forever
|
valid_lft forever preferred_lft forever
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Change back ownership:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo chown -R root:root /etc/openvpn
|
||||||
|
```
|
||||||
|
|
||||||
### systemd
|
### systemd
|
||||||
|
|
||||||
If it works fine, persist OpenVPN with systemd:
|
If it works fine, persist OpenVPN with systemd:
|
||||||
@@ -201,7 +208,7 @@ Add this to the top of `/etc/ufw/before.rules`:
|
|||||||
-A POSTROUTING -d 10.8.0.100 -p tcp --dport 2222 -j SNAT --to-source 10.8.0.1
|
-A POSTROUTING -d 10.8.0.100 -p tcp --dport 2222 -j SNAT --to-source 10.8.0.1
|
||||||
|
|
||||||
# Allow traffic from OpenVPN client to eth0
|
# Allow traffic from OpenVPN client to eth0
|
||||||
-A POSTROUTING -s 10.8.0.0/8 -o eth0 -j MASQUERADE
|
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
|
||||||
COMMIT
|
COMMIT
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -2,7 +2,9 @@ Title: ChatGPT Peanut Butter Cookies
|
|||||||
Date: 2023-03-24
|
Date: 2023-03-24
|
||||||
Category: Writing
|
Category: Writing
|
||||||
Summary: A recipe generated by ChatGPT for peanut butter cookies.
|
Summary: A recipe generated by ChatGPT for peanut butter cookies.
|
||||||
Short: 2
|
Image: cookies3.jpg
|
||||||
|
Tags: feed
|
||||||
|
Guid: 83fe535323d34830a2fcfe718779d7a6
|
||||||
|
|
||||||
<span class="aside">(describe a house fire in the style of a crappy blog post that people put above recipes that tell their life story before getting to the point about how their house burned down) </span>The flames danced around the living room, illuminating the memories that were now nothing more than ash. It was a warm summer evening, and we were all gathered in the backyard, enjoying the company of good friends and family. Little did we know that our lives were about to change forever.
|
<span class="aside">(describe a house fire in the style of a crappy blog post that people put above recipes that tell their life story before getting to the point about how their house burned down) </span>The flames danced around the living room, illuminating the memories that were now nothing more than ash. It was a warm summer evening, and we were all gathered in the backyard, enjoying the company of good friends and family. Little did we know that our lives were about to change forever.
|
||||||
|
|
||||||
@@ -27,12 +29,12 @@ Instructions:
|
|||||||
7. Remove from oven and allow to cool on the baking sheet for 5 minutes before transferring to a wire rack to cool completely.
|
7. Remove from oven and allow to cool on the baking sheet for 5 minutes before transferring to a wire rack to cool completely.
|
||||||
|
|
||||||
|
|
||||||
![[cookies1.jpg]]
|
![[cookies1.jpg | sugar, one egg, and peanut butter in a metal pot]]
|
||||||
|
|
||||||
![[cookies2.jpg]]
|
![[cookies2.jpg | dough after mixing the ingredients together. the colour is light brown and fluffy.]]
|
||||||
|
|
||||||
![[cookies3.jpg]]
|
![[cookies3.jpg | twelve raw cookies on a baking sheet with crosshatch patterns made by a fork]]
|
||||||
|
|
||||||
![[cookies4.jpg]]
|
![[cookies4.jpg | the finished cookies. crosshatch barely visible.]]
|
||||||
|
|
||||||
Human: The cookies turned out not bad. Too sweet, unsurprisingly with all that sugar. I like peanut butter cookies that are chewy, I'll ask for that next time. The mouthfeel is basically peanut butter giving way to grains of sugar.
|
Human: The cookies turned out not bad. Too sweet, unsurprisingly with all that sugar. I like peanut butter cookies that are chewy, I'll ask for that next time. The mouthfeel is basically peanut butter giving way to grains of sugar.
|
30
content/Custom Air Quality Monitor.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Title: Custom Air Quality Monitor
|
||||||
|
Date: 2025-01-13
|
||||||
|
Category: Creations
|
||||||
|
Summary: An air quality monitor made from sensor modules.
|
||||||
|
Image: airmonitor1.jpg
|
||||||
|
xTags: feed
|
||||||
|
|
||||||
|
I made custom air quality monitors to track the air in my kitchen, living room, and bedroom. They use sensor modules that detect PM2.5, PM10, CO<sub>2</sub>, VOCs, Light level, pressure, temperature and humidity. The data is sent to my [[Sensors]] database once per minute.
|
||||||
|
|
||||||
|
![[airmonitor1.jpg]]
|
||||||
|
|
||||||
|
The modules connect to an ESP8266 Arduino over I<sup>2</sup>C and UART serial. They are mounted on press-fit pegs to a 3D printed base plate I designed in FreeCAD.
|
||||||
|
|
||||||
|
The sensors used are:
|
||||||
|
|
||||||
|
- [HPMA115S0](https://www.digikey.ca/en/products/detail/honeywell-sensing-and-productivity-solutions/HPMA115S0-XXX/7202204) - Particulate Sensor
|
||||||
|
- [Adafruit SCD-30](https://www.adafruit.com/product/4867) - NDIR CO2 Temperature and Humidity Sensor
|
||||||
|
- [Adafruit BH1750](https://www.adafruit.com/product/4681) - Light Sensor
|
||||||
|
- [Adafruit SGP40](https://www.adafruit.com/product/4829) - VOC Sensor
|
||||||
|
- [Adafruit LPS22](https://www.adafruit.com/product/4633) - Pressure Sensor
|
||||||
|
|
||||||
|
The light data is used by my home automation system to know when it's night time so that motion sensors turn lights on. I also learned that the CO<sub>2</sub> level in my bedroom was doubling from 650 ppm to 1251 ppm overnight while I slept. This is bad since [high CO<sub>2</sub> concentration affects sleep](https://www.sciencedirect.com/science/article/pii/S0360132323011459), so now my automation system turns the furnace blower on while I sleep.
|
||||||
|
|
||||||
|
Before running the blower automatically (24 hour graph, CO<sub>2</sub> is the blue line):
|
||||||
|
|
||||||
|
![[airmonitor2.png]]
|
||||||
|
|
||||||
|
After:
|
||||||
|
|
||||||
|
![[airmonitor3.png]]
|
@@ -2,7 +2,9 @@ Title: Fake Dog for Home Security
|
|||||||
Date: 2022-06-27
|
Date: 2022-06-27
|
||||||
Category: Creations
|
Category: Creations
|
||||||
Summary: Fake dog barking for home security while on vacation.
|
Summary: Fake dog barking for home security while on vacation.
|
||||||
Short: 7
|
Image: fake-dog.jpg
|
||||||
|
Tags: feed
|
||||||
|
Guid: 0c80d4cf5e414254b158ef9f9b082f8f
|
||||||
|
|
||||||
I set up a fake dog that barks if my surveillance cameras are triggered while I'm out of town on vacation. It's a pair of computer speakers plugged into a Raspberry Pi, which is an inexpensive single-board computer. One speaker faces the front door and the other faces the side door.
|
I set up a fake dog that barks if my surveillance cameras are triggered while I'm out of town on vacation. It's a pair of computer speakers plugged into a Raspberry Pi, which is an inexpensive single-board computer. One speaker faces the front door and the other faces the side door.
|
||||||
|
|
||||||
@@ -10,14 +12,14 @@ When the front door camera is triggered my surveillance camera system sends a me
|
|||||||
|
|
||||||
You can find the [source code](https://git.tannercollin.com/tanner/woof) on my Gitea.
|
You can find the [source code](https://git.tannercollin.com/tanner/woof) on my Gitea.
|
||||||
|
|
||||||
![[fake-dog.jpg]]
|
![[fake-dog.jpg | a speaker connected to a circuit board on my hardwood floor]]
|
||||||
|
|
||||||
## Technical Details
|
## Technical Details
|
||||||
My surveillance cameras sit on a separate network without internet access and their RTSP streams are consumed by the Blue Iris NVR software running on a dedicated Windows box. When an object is detected moving through a defined area for certain cameras, Blue Iris is configured to send an MQTT message to the `iot/cameras` topic via the Mosquitto broker running on my media server.
|
My surveillance cameras sit on a separate network without internet access and their RTSP streams are consumed by the Blue Iris NVR software running on a dedicated Windows box. When an object is detected moving through a defined area for certain cameras, Blue Iris is configured to send an MQTT message to the `iot/cameras` topic via the Mosquitto broker running on my media server.
|
||||||
|
|
||||||
A Python script kept alive by Supervisor runs on the Raspberry Pi and listens to the topic using the `asyncio-mqtt` module. It receives and tries to decode a JSON message like `{"serial": "SE-N-ZoneB"}`. If the camera's serial is found in a dict at the top of the script, the corresponding audio file is played using Pygame. Controlling which speaker barks is done by muting the left or right channel in the stereo audio file.
|
A Python script kept alive by Supervisor runs on the Raspberry Pi and listens to the topic using the `asyncio-mqtt` module. It receives and tries to decode a JSON message like `{"serial": "SE-N-ZoneB"}`. If the camera's serial is found in a dict at the top of the script, the corresponding audio file is played using Pygame. Controlling which speaker barks is done by muting the left or right channel in the stereo audio file.
|
||||||
|
|
||||||
![[fake-dog2.png]]
|
![[fake-dog2.png | a console log output reporting the camera has activated]]
|
||||||
## Future Improvements
|
## Future Improvements
|
||||||
The dog has a lot of false positives from the cameras being triggered by car headlights or small animals<span class="aside"> (like a real dog)</span>. This isn't a big deal since no one is home to hear it bark and it isn't loud enough for my neighbours to hear (I've asked). I would rather have a false positive than a false negative.
|
The dog has a lot of false positives from the cameras being triggered by car headlights or small animals<span class="aside"> (like a real dog)</span>. This isn't a big deal since no one is home to hear it bark and it isn't loud enough for my neighbours to hear (I've asked). I would rather have a false positive than a false negative.
|
||||||
|
|
||||||
@@ -30,6 +32,6 @@ According to [former burglars](https://news.t0.vc/TRMA), barking dogs and securi
|
|||||||
|
|
||||||
I previously caught a prowler who went into my backyard and tested my garage door handle to see if it was locked. He then broke into my neighbour's truck and garage after. He gets to have his face on my website:
|
I previously caught a prowler who went into my backyard and tested my garage door handle to see if it was locked. He then broke into my neighbour's truck and garage after. He gets to have his face on my website:
|
||||||
|
|
||||||
![[fake-dog3.jpg]]
|
![[fake-dog3.jpg | a would-be burglar testing my garage door handle on the left, a close-up of his face on the right]]
|
||||||
|
|
||||||
The extra peace of mind while I'm away is worth the evening it took to set up. It was very easy to make because I reused the code I wrote for [[Protospace]]'s PA system doorbell that you can find on [GitHub](https://github.com/protospace/doorbell). It operates similarly by playing an audio file based on which 433 MHz doorbell is pressed.
|
The extra peace of mind while I'm away is worth the evening it took to set up. It was very easy to make because I reused the code I wrote for [[Protospace]]'s PA system doorbell that you can find on [GitHub](https://github.com/protospace/doorbell). It operates similarly by playing an audio file based on which 433 MHz doorbell is pressed.
|
||||||
|
@@ -2,11 +2,13 @@ Title: Garage Door Opener Hack
|
|||||||
Date: 2021-12-26
|
Date: 2021-12-26
|
||||||
Category: Creations
|
Category: Creations
|
||||||
Summary: Hacking my garage door opener to work over Wifi.
|
Summary: Hacking my garage door opener to work over Wifi.
|
||||||
Short: 5
|
Image: garage3.jpg
|
||||||
|
Tags: feed
|
||||||
|
Guid: 3e386396748b400ea7434de28e1759ec
|
||||||
|
|
||||||
In the quest to automate as much of my house as possible, I thought it would be useful to be able to remotely control my garage door from my home automation system. If I suspected that I forgot to close it while leaving, I could check in my security cameras and then close it from anywhere. It's nice having this peace of mind, even if it almost never happens.
|
On the quest to automate as much of my house as possible, I thought it would be useful to be able to remotely control my garage door from my home automation system. If I suspected that I forgot to close it while leaving, I could check in my security cameras and then close it from anywhere. It's nice having this peace of mind, even if it almost never happens.
|
||||||
|
|
||||||
Instead of reverse engineering the wireless protocol, cracking the encryption, and sending my own commands, I figured it would be much easier to hack the hardware. I cracked open a spare remote to find that it contained a basic PCB with simple tactile switches.
|
Instead of reverse engineering the wireless protocol, cracking the encryption, and sending my own commands, I figured it would be much easier to hack the hardware. I pried open a spare remote to find that it contained a basic PCB with simple tactile switches.
|
||||||
|
|
||||||
![[garage1.jpg | the six parts of the spare remote on my desk: metal clip, plastic buttons, battery, PCB, and two halves of the case]]
|
![[garage1.jpg | the six parts of the spare remote on my desk: metal clip, plastic buttons, battery, PCB, and two halves of the case]]
|
||||||
|
|
||||||
|
@@ -2,7 +2,9 @@ Title: Hand of Ozymandias
|
|||||||
Date: 2012-03-23
|
Date: 2012-03-23
|
||||||
Category: Creations
|
Category: Creations
|
||||||
Summary: A withered hand I welded out of scrap metal.
|
Summary: A withered hand I welded out of scrap metal.
|
||||||
Short: hand
|
Image: hand1.jpg
|
||||||
|
Tags: feed
|
||||||
|
Guid: 0bc567cd5c45479d8380214b24a35563
|
||||||
|
|
||||||
I was visiting my cousins in Radium, BC and decided to learn stick welding at their shop. I wanted to create a sculpture, so with pieces of scrap metal I welded together this hand. The beads are far from perfect. Working with small pieces of rusted metal made it difficult.
|
I was visiting my cousins in Radium, BC and decided to learn stick welding at their shop. I wanted to create a sculpture, so with pieces of scrap metal I welded together this hand. The beads are far from perfect. Working with small pieces of rusted metal made it difficult.
|
||||||
|
|
||||||
|
40
content/Helios Alpha.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
Title: Helios Alpha
|
||||||
|
Date: 2024-02-21
|
||||||
|
Category: Notes
|
||||||
|
Summary: Design of my previous Kratky method hydroponics system.
|
||||||
|
Wide: true
|
||||||
|
|
||||||
|
Helios Alpha is a hydroponics system that can grow up to six plants. It's designed around a single 102 L plastic tote. It holds enough water to harvest lettuce once before refilling. After the initial setup, the system can be ignored for weeks.
|
||||||
|
|
||||||
|
![[heliosalpha1.jpg | the hydroponics system from two angles. a black tub with yellow lid, covered in tin foil with six holes for plants. grow lights above suspended by metal shelving.]]
|
||||||
|
|
||||||
|
Here's directions on how you can make your own:
|
||||||
|
|
||||||
|
## Parts
|
||||||
|
- [HDX 102L Stackable Strong Storage Tote Bin](https://www.homedepot.ca/product/hdx-102l-stackable-strong-storage-tote-bin-plastic-organizer-box-black-base-yellow-snap-on-lid/1000706729)
|
||||||
|
- [HDX 36-inch W x 54-inch H x 14-inch D 4-Shelf](https://www.homedepot.ca/product/hdx-36-inch-w-x-54-inch-h-x-14-inch-d-4-shelf-steel-wire-shelving-unit-with-adjustable-shelves-in-chrome/1000790396)
|
||||||
|
- 2 x [TS-1000 Grow Lights](https://www.amazon.ca/s?k=TS-1000+grow+light)
|
||||||
|
- 6 x 3" hydroponic net cups
|
||||||
|
- Medium to large fan
|
||||||
|
- Aluminium foil
|
||||||
|
- Wall outlet timer
|
||||||
|
- Power strip
|
||||||
|
- Zip ties
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
A hole saw drill bit with the same diameter of your net cups is recommended.
|
||||||
|
|
||||||
|
### Assembly
|
||||||
|
Assemble the metal shelf with one shelf at the very top and another only a foot below it as seen in the photo. Only one shelf is used, but two are needed for stability.
|
||||||
|
|
||||||
|
Hang the grow lights with the included hangers off the lower shelf. The height can be adjusted later. Plug the lights into a power strip and secure all cords to the lower shelf.
|
||||||
|
|
||||||
|
Drill six holes in the plastic lid for where you want the plant to go. Bias the holes towards the edges so the plants are more spread out for air flow.
|
||||||
|
|
||||||
|
Cover the lid with two strips aluminium foil to prevent light from passing into the nutrient solution. This prevents [[Algae Growth |algae growth]] which will kill your plants and reflect the light back up to the leaves. Tape the aluminium foil to the lid and then cut holes in it the size of the net cups. Scrunch excess foil down into the hole.
|
||||||
|
|
||||||
|
Insert the six net cups into the six holes. Place the lid back on the tote and position it under the grow lights.
|
||||||
|
|
||||||
|
Point a fan towards where the plants will grow. [[Airflow |Airflow is important]] indoors to enable water transpiration so the leaves don't get mineral deficiencies. Plug the fan in but keep it off until the starters have grown their "true leaves".
|
||||||
|
|
||||||
|
Plug the power strip into the outlet timer and adjust it for 16 hours on, 8 hours off. The specific timing will depend on the plant.
|
@@ -3,7 +3,6 @@ Date: 2022-07-29
|
|||||||
Category: Notes
|
Category: Notes
|
||||||
Summary: I lost a war against aphids and mould.
|
Summary: I lost a war against aphids and mould.
|
||||||
Wide: true
|
Wide: true
|
||||||
Short: hwar
|
|
||||||
|
|
||||||
[TOC]
|
[TOC]
|
||||||
|
|
||||||
@@ -18,22 +17,22 @@ While maintaining the garden, I noticed small white flecks which I just assumed
|
|||||||
|
|
||||||
The white flecks turned out to be eggs the aphids were laying. The sticky substance was honeydew, a sugary liquid that aphids secrete.
|
The white flecks turned out to be eggs the aphids were laying. The sticky substance was honeydew, a sugary liquid that aphids secrete.
|
||||||
|
|
||||||
![[hydro-war1.jpg]]
|
![[hydro-war1.jpg | underside of a green leaf in a plastic bag. several dozen aphids can be seen on it.]]
|
||||||
|
|
||||||
I bought a bottle of Safer's insecticidal soap to use against them. I have a walk-in shower in the downstairs basement bathroom beside the grow room, so I moved all the plants into there. I then sprayed the leaves as much as I could, top and bottom. I let it drain for a while and then rinsed thoroughly with water.
|
I bought a bottle of Safer's insecticidal soap to use against them. I have a walk-in shower in the downstairs basement bathroom beside the grow room, so I moved all the plants into there. I then sprayed the leaves as much as I could, top and bottom. I let it drain for a while and then rinsed thoroughly with water.
|
||||||
|
|
||||||
![[hydro-war2.jpg]]
|
![[hydro-war2.jpg | six plants sitting in a tote container lid suspended with their white roots dangling down. all inside a shower.]]
|
||||||
|
|
||||||
Three days later I noticed the aphids were still alive. So I bought five more bottles of aphid spray and emptied them into a bucket. I then took each plant out of the system and dunked them in the bucket, covering all leaves and the net cup. I pushed the plants in and out of the bucket forcefully, to make sure all the aphids were flushed off. I then rinsed the plants in the shower thoroughly like before.
|
Three days later I noticed the aphids were still alive. So I bought five more bottles of aphid spray and emptied them into a bucket. I then took each plant out of the system and dunked them in the bucket, covering all leaves and the net cup. I pushed the plants in and out of the bucket forcefully, to make sure all the aphids were flushed off. I then rinsed the plants in the shower thoroughly like before.
|
||||||
|
|
||||||
![[hydro-war3.jpg]]
|
![[hydro-war3.jpg | a strawberry plant being submerged in a white bucket of aphid spray. its roots are dangling off the side of the bucket.]]
|
||||||
|
|
||||||
Three days later I did this all over again, just to make sure they were gone. This seemed to work but I noticed the aphids returned a couple months later. It was one of the reasons I decided to end this round of growth experiment.
|
Three days later I did this all over again, just to make sure they were gone. This seemed to work but I noticed the aphids returned a couple months later. It was one of the reasons I decided to end this round of growth experiment.
|
||||||
|
|
||||||
## Mould
|
## Mould
|
||||||
A few weeks after the aphid dunking I noticed some kind of mould or powdery mildew on the strawberry crowns, right in the centre of the plants.
|
A few weeks after the aphid dunking I noticed some kind of mould or powdery mildew on the strawberry crowns, right in the centre of the plants.
|
||||||
|
|
||||||
![[hydro-war4.jpg]]
|
![[hydro-war4.jpg | up close view of a strawberry plant crown covered in fuzzy grey mould spores]]
|
||||||
|
|
||||||
I assumed this was because I let the plants' leaves get too dense and they were blocking airflow to the crown. The airstone bubbles air into the water reservoir and the only place the humid air can escape is through the net cups and strawberry crown.
|
I assumed this was because I let the plants' leaves get too dense and they were blocking airflow to the crown. The airstone bubbles air into the water reservoir and the only place the humid air can escape is through the net cups and strawberry crown.
|
||||||
|
|
||||||
|
@@ -3,7 +3,6 @@ Date: 2022-07-29
|
|||||||
Category: Notes
|
Category: Notes
|
||||||
Summary: Grow log for my home hydroponics setup, round 1.
|
Summary: Grow log for my home hydroponics setup, round 1.
|
||||||
Wide: true
|
Wide: true
|
||||||
Short: hlog1
|
|
||||||
|
|
||||||
2022-02-21: Started germinating
|
2022-02-21: Started germinating
|
||||||
2022-02-24: First noticed sprouts
|
2022-02-24: First noticed sprouts
|
||||||
|
@@ -3,7 +3,6 @@ Date: 2022-07-29
|
|||||||
Category: Notes
|
Category: Notes
|
||||||
Summary: Grow log for my home hydroponics setup, round 2.
|
Summary: Grow log for my home hydroponics setup, round 2.
|
||||||
Wide: true
|
Wide: true
|
||||||
Short: hlog2
|
|
||||||
|
|
||||||
2022-03-05: Started germinating
|
2022-03-05: Started germinating
|
||||||
2022-03-07: Just barely noticed germination
|
2022-03-07: Just barely noticed germination
|
||||||
|
@@ -3,7 +3,6 @@ Date: 2022-07-29
|
|||||||
Category: Notes
|
Category: Notes
|
||||||
Summary: Grow log for my home hydroponics setup, round 3.
|
Summary: Grow log for my home hydroponics setup, round 3.
|
||||||
Wide: true
|
Wide: true
|
||||||
Short: hlog3
|
|
||||||
|
|
||||||
2022-04-14: Planted strawberry starters.
|
2022-04-14: Planted strawberry starters.
|
||||||
2022-04-21: Observed fresh white roots shooting out horizontally.
|
2022-04-21: Observed fresh white roots shooting out horizontally.
|
||||||
|
@@ -3,14 +3,15 @@ Date: 2022-07-29
|
|||||||
Category: Writing
|
Category: Writing
|
||||||
Summary: My experiments growing food with hydroponics.
|
Summary: My experiments growing food with hydroponics.
|
||||||
Wide: true
|
Wide: true
|
||||||
Short: h
|
Tags: feed
|
||||||
|
Guid: 5cf23ab1f9894a4b91e6593ce498a73a
|
||||||
|
|
||||||
[TOC]
|
[TOC]
|
||||||
|
|
||||||
Hydroponics is a method of growing plants without soil by delivering nutrients via water. In theory, it offers many advantages over soil: no dirt, no weeding or pests<span class="aside"> (therefore no herbicides or pesticides)</span>, no mould, no root rot, year-round growing indoors, less space required, 90% less water usage, and 30-50% faster growth. Downsides include electricity consumption if using grow lights or water pumps and the need to hand-pollinate flowers from the lack of bees.
|
Hydroponics is a method of growing plants without soil by delivering nutrients via water. In theory, it offers many advantages over soil: no dirt, no weeding or pests<span class="aside"> (therefore no herbicides or pesticides)</span>, no mould, no root rot, year-round growing indoors, less space required, 90% less water usage, and 30-50% faster growth. Downsides include electricity consumption if using grow lights or water pumps and the need to hand-pollinate flowers from the lack of bees.
|
||||||
|
|
||||||
## Home Experiments
|
## Home Experiments
|
||||||
I'll outline my hydroponics experiments here, describing what I tried, what worked, what didn't, and lessons I learned. I have a small storage room in my basement that I use for hydroponics. It's about 5' x 10' large with unpainted drywall and a concrete floor.
|
I'll outline my hydroponics experiments here, describing what I tried, what worked, what didn't, and lessons I learned. I have a small storage room in my basement that I use for my [[Secret Garden]]. It's about 5' x 10' large with unpainted drywall and a concrete floor.
|
||||||
|
|
||||||
### Home: Round 1
|
### Home: Round 1
|
||||||
Started: 2022-02-21, ended: 2022-03-04.
|
Started: 2022-02-21, ended: 2022-03-04.
|
||||||
@@ -21,7 +22,7 @@ I tried germinating iceberg lettuce, romaine lettuce, butterhead lettuce, arugul
|
|||||||
|
|
||||||
Lessons learned: what leggy sprouts look like and to use enough light.
|
Lessons learned: what leggy sprouts look like and to use enough light.
|
||||||
|
|
||||||
![[hydroponics1.jpg]]
|
![[hydroponics1.jpg | twelve rockwool starter cubes with thin, leggy seedlings in a mess]]
|
||||||
|
|
||||||
### Home: Round 2
|
### Home: Round 2
|
||||||
Started: 2022-03-05, ended: 2022-04-14.
|
Started: 2022-03-05, ended: 2022-04-14.
|
||||||
@@ -40,7 +41,7 @@ Lessons learned:
|
|||||||
|
|
||||||
<span class="aside">(Top: lettuces, bottom: arugula, cress, arugula)</span>
|
<span class="aside">(Top: lettuces, bottom: arugula, cress, arugula)</span>
|
||||||
|
|
||||||
![[hydroponics2.jpg]]
|
![[hydroponics2.jpg | six plants sitting in holes cut out of pink foam board]]
|
||||||
|
|
||||||
### Home: Round 3
|
### Home: Round 3
|
||||||
Started: 2022-04-14, ended: 2022-07-23.
|
Started: 2022-04-14, ended: 2022-07-23.
|
||||||
@@ -60,4 +61,4 @@ Lessons learned:
|
|||||||
- Hand-pollinating flowers is tedious.
|
- Hand-pollinating flowers is tedious.
|
||||||
- Strawberries drop tons of pedals all over the floor.
|
- Strawberries drop tons of pedals all over the floor.
|
||||||
|
|
||||||
![[hydroponics3.jpg]]
|
![[hydroponics3.jpg | six strawberry plants with crazy dark green leaves creating a thick bush. there are pink flowers on top under two grow lights.]]
|
||||||
|
62
content/Japan Photography.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
Title: Japan Photography
|
||||||
|
Date: 2023-10-19
|
||||||
|
Category: Writing
|
||||||
|
Summary: Photos from my trip to Japan.
|
||||||
|
Image: japan06lo.jpg
|
||||||
|
Nofilter: true
|
||||||
|
Tags: feed
|
||||||
|
Guid: 4dbb422703be4c84b20132ffaa883f58
|
||||||
|
|
||||||
|
All photos are unmodified (not even cropped) and taken with a Pixel 6a.
|
||||||
|
|
||||||
|
Click each photo for the full resolution.
|
||||||
|
|
||||||
|
Former Yasuda Garden, Tokyo:
|
||||||
|
|
||||||
|
<a href="/media/japan01hi.jpg">![[japan01lo.jpg | a bridge crosses over greenish water with vibrant trees in the background]]</a>
|
||||||
|
|
||||||
|
Shinjuku Gyoen National Garden, Tokyo:
|
||||||
|
|
||||||
|
<a href="/media/japan02hi.jpg">![[japan02lo.jpg | a tree trunk splits into a Y with ivy growing on one side]]</a>
|
||||||
|
|
||||||
|
Downtown Shinjuku, Tokyo:
|
||||||
|
|
||||||
|
<a href="/media/japan03hi.jpg">![[japan03lo.jpg | clean skyscrapers. the centre one resembles a game console.]]</a>
|
||||||
|
|
||||||
|
Shinjuku Golden Gai, Tokyo:
|
||||||
|
|
||||||
|
<a href="/media/japan04hi.jpg">![[japan04lo.jpg | a street wet from rain with bars on both sides. bar signs light up the pavement.]]</a>
|
||||||
|
|
||||||
|
Bamboo Forest, Kyoto:
|
||||||
|
|
||||||
|
<a href="/media/japan05hi.jpg">![[japan05lo.jpg | tall bamboo trees with leaves at the very top, letting some light in]]</a>
|
||||||
|
|
||||||
|
Giōji Temple, Kyoto:
|
||||||
|
|
||||||
|
<a href="/media/japan06hi.jpg">![[japan06lo.jpg | four trees with dark bark stand out against a vibrant green moss background]]</a>
|
||||||
|
|
||||||
|
Gohodo Benzaiten, Kyoto:
|
||||||
|
|
||||||
|
<a href="/media/japan07hi.jpg">![[japan07lo.jpg | a shrine is surrounded by trees, like they are consuming it in foliage]]</a>
|
||||||
|
|
||||||
|
Train to Hofu, Yamaguchi:
|
||||||
|
|
||||||
|
<a href="/media/japan08hi.jpg">![[japan08lo.jpg | train tracks taken out the front window of a train. power lines on each side.]]</a>
|
||||||
|
|
||||||
|
Ozu Island military tunnel, Yamaguchi:
|
||||||
|
|
||||||
|
<a href="/media/japan09hi.jpg">![[japan09lo.jpg | an ocean bay photo from inside a tunnel]]</a>
|
||||||
|
|
||||||
|
Ozu Island from the ferry:
|
||||||
|
|
||||||
|
<a href="/media/japan10hi.jpg">![[japan10lo.jpg | contrasting mountains in the background and a boat's wake in the foreground]]</a>
|
||||||
|
|
||||||
|
Gifu countryside:
|
||||||
|
|
||||||
|
<a href="/media/japan11hi.jpg">![[japan11lo.jpg | two hills covered in trees are reflected in the still water at the bottom]]</a>
|
||||||
|
|
||||||
|
Ueno, Tokyo:
|
||||||
|
|
||||||
|
<a href="/media/japan12hi.jpg">![[japan12lo.jpg | a restaurant under a train track. several people fill the adjacent street.]]</a>
|
||||||
|
|
||||||
|
All photos are released into the public domain / CC0 licensed.
|
@@ -2,7 +2,9 @@ Title: LED Dress
|
|||||||
Date: 2016-03-18
|
Date: 2016-03-18
|
||||||
Category: Creations
|
Category: Creations
|
||||||
Summary: A dress made out of LEDs that twinkle like stars.
|
Summary: A dress made out of LEDs that twinkle like stars.
|
||||||
Short: 4
|
Image: dress1.jpg
|
||||||
|
Tags: feed
|
||||||
|
Guid: 420a9ca8533c4667a89822d2b5df186d
|
||||||
|
|
||||||
A friend of mine was attending a stars and constellations themed ball. She wanted to wear a dress that was lit up with LEDs acting as twinkling stars. Seven of the 28 stars are aligned to resemble the Big Dipper constellation and twinkle differently than the rest, which twinkle in a random pattern.
|
A friend of mine was attending a stars and constellations themed ball. She wanted to wear a dress that was lit up with LEDs acting as twinkling stars. Seven of the 28 stars are aligned to resemble the Big Dipper constellation and twinkle differently than the rest, which twinkle in a random pattern.
|
||||||
|
|
||||||
@@ -16,7 +18,9 @@ The LEDs came from that strip that was cut up and soldered together with very sm
|
|||||||
|
|
||||||
Twenty-one of the stars are light magenta in color and twinkle by fading randomly. The seven LEDs that form the Big Dipper continually scroll through a gradient of three colors. Instead of calculating the values of each color in the gradient as the program runs, a lookup table is used.
|
Twenty-one of the stars are light magenta in color and twinkle by fading randomly. The seven LEDs that form the Big Dipper continually scroll through a gradient of three colors. Instead of calculating the values of each color in the gradient as the program runs, a lookup table is used.
|
||||||
|
|
||||||
<video autoplay muted loop style="display:block; margin: 0 auto;">
|
<video autoplay="true" muted="true" loop="true" style="display:block; margin: 0 auto;">
|
||||||
<source src="{static}/media/dress3.mp4" type="video/mp4">
|
<source src="{static}/media/dress3.mp4" type="video/mp4">
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
|
A video of the dress is above. Right click -> Play if needed.
|
||||||
|
@@ -2,7 +2,9 @@ Title: Remote Control Light Switch
|
|||||||
Date: 2014-10-09
|
Date: 2014-10-09
|
||||||
Category: Creations
|
Category: Creations
|
||||||
Summary: A device to toggle my lights remotely.
|
Summary: A device to toggle my lights remotely.
|
||||||
Short: remote
|
Image: light1.jpg
|
||||||
|
Tags: feed
|
||||||
|
Guid: 9f716895d0e7400c9538e9a5f9b327ce
|
||||||
|
|
||||||
I wanted the ability to toggle my bedroom light remotely for convenience. I designed a circuit that allows me to control my light with any device that can load a webpage.
|
I wanted the ability to toggle my bedroom light remotely for convenience. I designed a circuit that allows me to control my light with any device that can load a webpage.
|
||||||
|
|
||||||
|
@@ -2,8 +2,10 @@ Title: Choosing a Linux Flavour
|
|||||||
Date: 2020-10-31
|
Date: 2020-10-31
|
||||||
Category: Writing
|
Category: Writing
|
||||||
Summary: A recommendation on which flavour of Linux to run.
|
Summary: A recommendation on which flavour of Linux to run.
|
||||||
|
Image: distro1.png
|
||||||
Wide: true
|
Wide: true
|
||||||
Short: linux
|
Tags: feed
|
||||||
|
Guid: fedd81aa796847f09e559df2d5e5e917
|
||||||
|
|
||||||
[TOC]
|
[TOC]
|
||||||
|
|
||||||
@@ -21,6 +23,8 @@ When people refer to the "flavour of Linux" they are talking about a Linux distr
|
|||||||
|
|
||||||
The major Linux distros are practically all the same. If you master one it's easy to pick up the others. The main differences you'll run into are which tools you use to install new software, and the desktop environment, which is what all the windows and buttons look like.
|
The major Linux distros are practically all the same. If you master one it's easy to pick up the others. The main differences you'll run into are which tools you use to install new software, and the desktop environment, which is what all the windows and buttons look like.
|
||||||
|
|
||||||
|
![[distro1.png]]
|
||||||
|
|
||||||
I recommend two Linux distros, Debian and Ubuntu. Ubuntu is based off of Debian, so they are very similar.
|
I recommend two Linux distros, Debian and Ubuntu. Ubuntu is based off of Debian, so they are very similar.
|
||||||
|
|
||||||
## Pros of Debian
|
## Pros of Debian
|
||||||
|
67
content/Makerspace Tours.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
Title: Makerspace Tours
|
||||||
|
Date: 2024-07-18
|
||||||
|
Category: Writing
|
||||||
|
Summary: A collection of makerspaces I've toured.
|
||||||
|
Tags: feed
|
||||||
|
Guid: 27ca744e77c042c8bb1df9edd37112ae
|
||||||
|
|
||||||
|
When I travel I often try to tour Makerspaces and then share what I've learned with the one that I'm a part of, [[Protospace]]. Below you'll find links to the posts I've made on our forums about the makerspaces I've visited.
|
||||||
|
|
||||||
|
I'm interested in learning about:
|
||||||
|
|
||||||
|
- the space's history, policies, and procedures
|
||||||
|
- how are they documented / communicated to new members
|
||||||
|
- membership demographics
|
||||||
|
- how to join
|
||||||
|
- how decisions are made and conflicts resolved
|
||||||
|
- how toxic members are dealt with
|
||||||
|
|
||||||
|
## Italy
|
||||||
|
|
||||||
|
November 2024: [LOFOIO Makerspace in Florence](https://forum.protospace.ca/t/lofoio-makerspace-in-florence/7956)
|
||||||
|
|
||||||
|
## Japan
|
||||||
|
|
||||||
|
September 2023: [Take Space in Hamamatsu](https://forum.protospace.ca/t/take-space-in-hamamatsu/4800)
|
||||||
|
|
||||||
|
September 2023: [Tokyo Hackerspace Tour](https://forum.protospace.ca/t/tokyo-hackerspace-tour/4769)
|
||||||
|
|
||||||
|
## Spain
|
||||||
|
|
||||||
|
December 2022: [Benimakers Makerspace in Valencia](https://forum.protospace.ca/t/benimakers-makerspace-in-valencia/3100)
|
||||||
|
|
||||||
|
November 2022: [Makespace Madrid Tour](https://forum.protospace.ca/t/makespace-madrid-tour/3084)
|
||||||
|
|
||||||
|
November 2022: [Fab Lab tour in Seville](https://forum.protospace.ca/t/fab-lab-tour-in-seville/3067)
|
||||||
|
|
||||||
|
## Portugal
|
||||||
|
|
||||||
|
November 2022: [Fab Farm Makerspace in Algarve](https://forum.protospace.ca/t/fab-farm-makerspace-in-algarve/3024)
|
||||||
|
|
||||||
|
November 2022: [Fab Lab Makerspace in Lisbon](https://forum.protospace.ca/t/fab-lab-makerspace-in-lisbon/2970)
|
||||||
|
|
||||||
|
November 2022: [MILL Creative Space in Lisbon](https://forum.protospace.ca/t/mill-creative-space-in-lisbon/2964)
|
||||||
|
|
||||||
|
## United States
|
||||||
|
|
||||||
|
February 2023: [SYN Shop Makerspace Las Vegas](https://forum.protospace.ca/t/syn-shop-makerspace-las-vegas/3508)
|
||||||
|
|
||||||
|
## Canada
|
||||||
|
|
||||||
|
September 2024: [Makerspace Nanaimo](https://forum.protospace.ca/t/makerspace-nanaimo-tour/7576)
|
||||||
|
|
||||||
|
October 2022: [Makerspace Tour in Turner Valley](https://forum.protospace.ca/t/makerspace-tour-in-turner-valley/2795)
|
||||||
|
|
||||||
|
June 2022: [Makerspace in Prince George, BC](https://forum.protospace.ca/t/makerspace-in-prince-george-bc/2112)
|
||||||
|
|
||||||
|
## Honorable Mentions
|
||||||
|
|
||||||
|
Makerspaces I've visited, but didn't do a write-up tour for:
|
||||||
|
|
||||||
|
- Protospace in Calgary
|
||||||
|
- Archloft in Calgary
|
||||||
|
- Fuse33 in Calgary
|
||||||
|
- Fab Lab Barcelona
|
||||||
|
- FabCafe Barcelona
|
||||||
|
- TMDC Barcelona
|
||||||
|
- C2 Space Kyoto
|
@@ -2,7 +2,9 @@ Title: Notica
|
|||||||
Date: 2022-05-17
|
Date: 2022-05-17
|
||||||
Category: Projects
|
Category: Projects
|
||||||
Summary: Send browser notifications from your terminal. No installation. No registration.
|
Summary: Send browser notifications from your terminal. No installation. No registration.
|
||||||
Short: n
|
Image: notica1.png
|
||||||
|
Tags: feed
|
||||||
|
Guid: 75d87817471c4c13a986032fe64f8eb7
|
||||||
|
|
||||||
[Notica](https://notica.us) allows you to send browser notifications from your terminal to know when a slow command has finished running. It doesn't require installing anything or registering an account. It also works over ssh unlike `notify-send`.
|
[Notica](https://notica.us) allows you to send browser notifications from your terminal to know when a slow command has finished running. It doesn't require installing anything or registering an account. It also works over ssh unlike `notify-send`.
|
||||||
|
|
||||||
|
@@ -2,7 +2,9 @@ Title: Man's Reach Exceeds His Grasp
|
|||||||
Date: 2012-04-11
|
Date: 2012-04-11
|
||||||
Category: Creations
|
Category: Creations
|
||||||
Summary: My first attempt at painting with acrylic.
|
Summary: My first attempt at painting with acrylic.
|
||||||
Short: painting
|
Image: painting1.jpg
|
||||||
|
Tags: feed
|
||||||
|
Guid: a8c8430f531549418601ae166545529e
|
||||||
|
|
||||||
The painting is called “Man’s Reach Exceeds His Grasp”. I've always wanted to try painting and thought I had a good idea, so after a couple of drawings I attempted to paint it. I eventually got it framed at Michaels. Many thanks to my friend Laura for the opportunity to do this, I couldn't have done it without her help.
|
The painting is called “Man’s Reach Exceeds His Grasp”. I've always wanted to try painting and thought I had a good idea, so after a couple of drawings I attempted to paint it. I eventually got it framed at Michaels. Many thanks to my friend Laura for the opportunity to do this, I couldn't have done it without her help.
|
||||||
|
|
||||||
|
@@ -2,7 +2,9 @@ Title: Automatic Plant Waterer
|
|||||||
Date: 2014-06-05
|
Date: 2014-06-05
|
||||||
Category: Creations
|
Category: Creations
|
||||||
Summary: A device that automatically waters plants.
|
Summary: A device that automatically waters plants.
|
||||||
short: waterer
|
Image: waterer2.jpg
|
||||||
|
Tags: feed
|
||||||
|
Guid: 42d4bea55f674ed48fa07f369bd24aeb
|
||||||
|
|
||||||
One day I decided watering my one plant was too much work, so I automated it. It's also great for when I'm on vacation. The plant is a year old now and doesn't look as good as it used to (kinda like you). So this machine is like its life support.
|
One day I decided watering my one plant was too much work, so I automated it. It's also great for when I'm on vacation. The plant is a year old now and doesn't look as good as it used to (kinda like you). So this machine is like its life support.
|
||||||
|
|
||||||
|
@@ -2,8 +2,10 @@ Title: Protospace
|
|||||||
Date: 2022-05-01
|
Date: 2022-05-01
|
||||||
Category: Writing
|
Category: Writing
|
||||||
Summary: An outline of my projects at Calgary's makerspace Protospace.
|
Summary: An outline of my projects at Calgary's makerspace Protospace.
|
||||||
|
Image: protospace1.jpg
|
||||||
Wide: true
|
Wide: true
|
||||||
Short: p
|
Tags: feed
|
||||||
|
Guid: 0f2fac18522b4c268f0df7ec10cdc171
|
||||||
|
|
||||||
[Protospace](https://protospace.ca) is Calgary's original makerspace, a place where people go to make things and work on projects. It's a two-bay industrial shop with a full wood working area, metal working area, electronics lab, two laser cutters, five 3D printers, and sewing room. Members pay $55/month for 24/7 access to the facility and everyone is equal: Protospace has no owners and decisions are made by the membership.
|
[Protospace](https://protospace.ca) is Calgary's original makerspace, a place where people go to make things and work on projects. It's a two-bay industrial shop with a full wood working area, metal working area, electronics lab, two laser cutters, five 3D printers, and sewing room. Members pay $55/month for 24/7 access to the facility and everyone is equal: Protospace has no owners and decisions are made by the membership.
|
||||||
|
|
||||||
@@ -29,7 +31,7 @@ I set up a simple hydroponics garden in a broken medical lung testing chamber th
|
|||||||
|
|
||||||
A picture of the garden is taken every 5 minutes and uploaded to Spaceport. I'll eventually make a time lapse of the vegetable growth and plot a graph of the internal air temperature.
|
A picture of the garden is taken every 5 minutes and uploaded to Spaceport. I'll eventually make a time lapse of the vegetable growth and plot a graph of the internal air temperature.
|
||||||
|
|
||||||
![[protospace-garden.jpg]]
|
![[protospace-garden.jpg | a side-by-side photo of a cucumber plant before and after growing huge inside a glass lung testing chamber box.]]
|
||||||
|
|
||||||
### Telemetry
|
### Telemetry
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ Telemetry is a catch-all project for random sensors and displays around Protospa
|
|||||||
|
|
||||||
Here's a graph of the air quality on Spaceport:
|
Here's a graph of the air quality on Spaceport:
|
||||||
|
|
||||||
![[protospace-dust.png]]
|
![[protospace-dust.png | two graphs comparing the dust levels in the classroom and woodshop. spikes can be seen in the woodshop graph.]]
|
||||||
|
|
||||||
### Airlock
|
### Airlock
|
||||||
|
|
||||||
|
111
content/Protovac Retro Terminal.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
Title: Protovac Retro Terminal
|
||||||
|
Date: 2025-06-14
|
||||||
|
Category: Creations
|
||||||
|
Summary: A retro dumb terminal interface at my local makerspace.
|
||||||
|
Image: protovac1.jpg
|
||||||
|
xTags: feed
|
||||||
|
|
||||||
|
Protovac is a retro dumb terminal interface that lives at my local makerspace, [[Protospace]]. Its main use is printing storage labels and name tags for members and guests when they visit.
|
||||||
|
|
||||||
|
![[protovac1.jpg]]
|
||||||
|
|
||||||
|
An 85-year-old member donated the 1983 Morrow MDT-60 video display terminal that he bought new from London Drugs and kept in his closet. Originally this terminal is supposed to connect to a mainframe computer (perhaps in a different room) and display text over a serial connection.
|
||||||
|
|
||||||
|
In this case it connects to a Raspberry Pi computer mounted to the back over 9600 baud serial UART. The Pi has been configured to output a terminal over its UART pins and auto login the protovac user with `agetty`. The protovac user's shell has been replaced with the Python script that runs the curses-based TUI.
|
||||||
|
|
||||||
|
You can find the [source code](https://github.com/Protospace/protovac) on Protospace's GitHub.
|
||||||
|
|
||||||
|
In addition to printing labels for members, Protovac:
|
||||||
|
|
||||||
|
- can control the train in the Protospace welcome room
|
||||||
|
- displays stats about Protospace (next meeting, next class, member counts, etc)
|
||||||
|
- can send a message to our marquee LED sign
|
||||||
|
- has a chat interface to message ChatGPT
|
||||||
|
- has an interface to access Wolfram Alpha
|
||||||
|
- can play the games NetHack, Moria, 2048, Zork, and Hitchhiker's
|
||||||
|
|
||||||
|
Here's what the home screen looks like:
|
||||||
|
```
|
||||||
|
_______ _______ ___ _________ ___ ____ ____ _ ______
|
||||||
|
|_ __ \|_ __ \ .' `. | _ _ | .' `.|_ _| |_ _|/ \ .' ___ |
|
||||||
|
| |__) | | |__) | / .-. \|_/ | | \_|/ .-. \ \ \ / / / _ \ / .' \_|
|
||||||
|
| ___/ | __ / | | | | | | | | | | \ \ / / / ___ \ | |
|
||||||
|
_| |_ _| | \ \_\ `-' / _| |_ \ `-' / \ ' /_/ / \ \_\ `.___.'\
|
||||||
|
|_____| |____| |___|`.___.' |_____| `.___.' \_/|____| |____|`.____ .'
|
||||||
|
|
||||||
|
[I] Info [N] Nametag UNIVERSAL COMPUTER
|
||||||
|
. * - )-
|
||||||
|
[S] Stats [L] Label . * o . *
|
||||||
|
|
|
||||||
|
[G] LED Sign [Z] Games . . -O-
|
||||||
|
| * . -0-
|
||||||
|
[C] Classes [V] Protovac Sign
|
||||||
|
. . | *
|
||||||
|
[P] Protocoin * -O- .
|
||||||
|
. * | ,
|
||||||
|
[M] Message . o
|
||||||
|
.---.
|
||||||
|
[T] Think = _/__[0]\_ . * o '
|
||||||
|
= = (_________) .
|
||||||
|
[A] About . *
|
||||||
|
* - ) - *
|
||||||
|
Copyright (c) 1985 Bikeshed Computer Systems Ltd.
|
||||||
|
```
|
||||||
|
|
||||||
|
If you press the "C" key, for example, a list of Protospace classes appears:
|
||||||
|
|
||||||
|
```
|
||||||
|
PROTOVAC UNIVERSAL COMPUTER
|
||||||
|
|
||||||
|
Protospace Classes
|
||||||
|
================== Instructor Cost Students
|
||||||
|
|
||||||
|
[PAST] Woodworking Tools 1: Intro to Saws
|
||||||
|
Sun Jun 22, 2025 2:00 PM Mike M. $20.00 5 / 6
|
||||||
|
|
||||||
|
Woodworking Tools 2: Jointer, Thickness Planer, Drum Sander
|
||||||
|
Sun Jun 22, 2025 5:00 PM Mike M. $20.00 6 / 6
|
||||||
|
|
||||||
|
Blender Phreaking Phrydays
|
||||||
|
Fri Jun 27, 2025 7:00 PM Jeff D. Free 0
|
||||||
|
|
||||||
|
New Member Orientation and Safety
|
||||||
|
Sun Jun 29, 2025 3:00 PM Cole N. Free 3 / 10
|
||||||
|
|
||||||
|
Laser I: Basic Cutting and Engraving (Thunder Laser)
|
||||||
|
Wed Jul 9, 2025 8:30 PM Craig P. $20.00 6 / 8
|
||||||
|
|
||||||
|
Woodworking Tools 1: Intro to Saws
|
||||||
|
Sun Jul 20, 2025 2:00 PM Vince K. $20.00 6 / 6
|
||||||
|
|
||||||
|
[B] Back [J] Down [K] Up
|
||||||
|
```
|
||||||
|
|
||||||
|
The "S" key shows stats about Protospace:
|
||||||
|
|
||||||
|
```
|
||||||
|
PROTOVAC UNIVERSAL COMPUTER
|
||||||
|
|
||||||
|
Protospace Stats
|
||||||
|
================
|
||||||
|
|
||||||
|
Next meeting: None
|
||||||
|
|
||||||
|
Next clean: None
|
||||||
|
|
||||||
|
Next class: Woodworking Tools 2: Jointer, Thickness Planer, Drum Sander
|
||||||
|
Sun Jun 22, 2025 5:00 PM
|
||||||
|
|
||||||
|
Last class: Woodworking Tools 1: Intro to Saws
|
||||||
|
Sun Jun 22, 2025 2:00 PM
|
||||||
|
|
||||||
|
Member count: 464 Green: 408 Paused / expired: 1590
|
||||||
|
|
||||||
|
Card scans: 18
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[B] Back
|
||||||
|
```
|
@@ -2,16 +2,18 @@ Title: QotNews
|
|||||||
Date: 2022-05-18
|
Date: 2022-05-18
|
||||||
Category: Projects
|
Category: Projects
|
||||||
Summary: Hacker News, Reddit, Lobsters, and Tildes articles pre-rendered in reader mode. Optimized for speed and distraction-free reading.
|
Summary: Hacker News, Reddit, Lobsters, and Tildes articles pre-rendered in reader mode. Optimized for speed and distraction-free reading.
|
||||||
Short: q
|
Image: qotnews1.png
|
||||||
|
Tags: feed
|
||||||
|
Guid: 445a54d8799746d2b86ee8134cbc441c
|
||||||
|
|
||||||
[QotNews](https://news.t0.vc) is a news meta-aggregator. It gathers top articles from four news aggregators: Hacker News, Reddit, Lobsters, and Tildes along with their comments. The articles are then transformed into readable versions with consistent formatting and distractions removed. All articles in the main feed are preloaded by the client so they load instantly when clicked on.
|
[QotNews](https://news.t0.vc) is a news meta-aggregator. It gathers top articles from four news aggregators: Hacker News, Reddit, Lobsters, and Tildes along with their comments. The articles are then transformed into readable versions with consistent formatting and distractions removed. All articles in the main feed are preloaded by the client so they load instantly when clicked on.
|
||||||
|
|
||||||
You can find the [source code](https://git.tannercollin.com/tanner/qotnews) on my Gitea.
|
You can find the [source code](https://git.tannercollin.com/tanner/qotnews) on my Gitea.
|
||||||
|
|
||||||
![[qotnews1.png]]
|
![[qotnews1.png | screenshot of the home page with a list of stories to click on]]
|
||||||
|
|
||||||
I tried to make QotNews the perfect news site for me. I easily get annoyed by cookie banners and distracted by visual clutter when reading normal news articles. I especially hate auto-playing videos and "download our app" popups. All articles have consistent styling that's easy to read:
|
I tried to make QotNews the perfect news site for me. I easily get annoyed by cookie banners and distracted by visual clutter when reading normal news articles. I especially hate auto-playing videos and "download our app" popups. All articles have consistent styling that's easy to read:
|
||||||
|
|
||||||
![[qotnews2.png]]
|
![[qotnews2.png | screenshot of an article showing the text and font used]]
|
||||||
|
|
||||||
It's by far my favourite project and has paid the most dividends for the amount of time I invested in programming it. I use it multiple times per day and it's become the main source of all my news. Since all the articles and comments are preloaded and saved in localStorage, it's also great for reading on airplanes.
|
It's by far my favourite project and has paid the most dividends for the amount of time I invested in programming it. I use it multiple times per day and it's become the main source of all my news. Since all the articles and comments are preloaded and saved in localStorage, it's also great for reading on airplanes.
|
@@ -1,48 +1,77 @@
|
|||||||
Title: Things I Recommend
|
Title: Recommendations
|
||||||
Date: 2022-06-24
|
Date: 2022-06-24
|
||||||
Category: Writing
|
Category: Writing
|
||||||
Summary: Software and products that I recommend you use.
|
Summary: Software and products that I recommend you use.
|
||||||
Wide: true
|
Wide: true
|
||||||
Short: 1
|
Tags: feed
|
||||||
|
Guid: ec7e1d66bbe343d59235e0b185ee44d6
|
||||||
|
|
||||||
This outlines some software and devices I recommend you use: uBlock Origin, Sponsorblock, Aegis Authenticator, ThruNite T1 flashlights, Logitech G Pro Wireless mice, and ThinkPad Laptops. Nothing here was sponsored.
|
This outlines some software and devices I recommend you use: uBlock Origin, Sponsorblock, Aegis Authenticator, ThinkPad Laptops, a flashlight, a Leatherman, and various phone apps. Nothing here was sponsored.
|
||||||
|
|
||||||
[TOC]
|
[TOC]
|
||||||
|
|
||||||
## Software
|
## Software
|
||||||
|
|
||||||
### uBlock Origin
|
### uBlock Origin
|
||||||
|
|
||||||
uBlock Origin is an open source ad blocker and something I install immediately on all my devices. Running an ad blocker makes browsing the web way better. It removes distracting ads (even from YouTube), invasive tracking, and makes you safer by removing potentially [fake links](https://news.t0.vc/LOBW/c#drekipus1657325184). It's the best piece of software I use even though it mostly remains unseen. And it even works on your phone.
|
uBlock Origin is an open source ad blocker and something I install immediately on all my devices. Running an ad blocker makes browsing the web way better. It removes distracting ads (even from YouTube), invasive tracking, and makes you safer by removing potentially [fake links](https://news.t0.vc/LOBW/c#drekipus1657325184). It's the best piece of software I use even though it mostly remains unseen. And it even works on your phone.
|
||||||
|
|
||||||
You can install it on [Firefox Desktop](https://addons.mozilla.org/en-CA/firefox/addon/ublock-origin/), [Firefox Android](https://addons.mozilla.org/en-CA/android/addon/ublock-origin/), and [Chrome Desktop](https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm?hl=en). Make sure you install the correct "Origin" version and avoid "ublock.org".
|
You can install it on [Firefox Desktop](https://addons.mozilla.org/en-CA/firefox/addon/ublock-origin/), [Firefox Android](https://addons.mozilla.org/en-CA/android/addon/ublock-origin/), and [Chrome Desktop](https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm?hl=en). Make sure you install the correct "Origin" version and avoid "ublock.org".
|
||||||
|
|
||||||
### SponsorBlock
|
### SponsorBlock
|
||||||
|
|
||||||
SponsorBlock automatically skips over sponsored segments in YouTube videos. Not YouTube ads (that's what uBlock Origin is for), but the actual parts of the video sponsored by companies to advertise their products. It uses a crowd-sourced database of timestamps to seamlessly jump over those parts. It also allows you to skip to the highlight of some videos by pressing "enter" so you aren't wasting time watching exposition.
|
SponsorBlock automatically skips over sponsored segments in YouTube videos. Not YouTube ads (that's what uBlock Origin is for), but the actual parts of the video sponsored by companies to advertise their products. It uses a crowd-sourced database of timestamps to seamlessly jump over those parts. It also allows you to skip to the highlight of some videos by pressing "enter" so you aren't wasting time watching exposition.
|
||||||
|
|
||||||
You can install it on [Firefox Desktop](https://addons.mozilla.org/en-CA/firefox/addon/sponsorblock/) and [Chrome Desktop](https://chrome.google.com/webstore/detail/sponsorblock-for-youtube/mnjggcdmjocbbbhaepdhchncahnbgone?hl=en).
|
You can install it on [Firefox Desktop](https://addons.mozilla.org/en-CA/firefox/addon/sponsorblock/) and [Chrome Desktop](https://chrome.google.com/webstore/detail/sponsorblock-for-youtube/mnjggcdmjocbbbhaepdhchncahnbgone?hl=en).
|
||||||
|
|
||||||
![[recommend1.png]]
|
|
||||||
|
|
||||||
![[recommend2.png]]
|
|
||||||
|
|
||||||
### Aegis Authenticator
|
### Aegis Authenticator
|
||||||
Aegis is a two-factor authenticator (2fa) app for Android that's free and open source. The killer feature and why I recommend it is that it supports automatic encrypted backups of the database in JSON format. You can unlock the app with a password or optional fingerprint. It supported true OLED dark mode. An alternative app is andOTP but there were [problems](https://news.t0.vc/EQYR/c#williamwchuang1553266688) with the backups' encryption back when I switched to Aegis.
|
|
||||||
|
|
||||||
Enabling 2fa on your accounts is essential for security, especially preventing credential stuffing attacks. Backing up your 2fa data is important in case you lose your phone. It saves you having to dig out recovery codes or try and convince someone that you own the account.
|
Aegis is a two-factor authenticator (2fa) app for Android that's free and open source. The killer feature and why I recommend it is that it supports automatic encrypted backups of the database in JSON format. You can unlock the app with a password or fingerprint. An alternative app is andOTP but there were [problems](https://news.t0.vc/EQYR/c#williamwchuang1553266688) with the backups' encryption back when I switched to Aegis.
|
||||||
|
|
||||||
You can install it on Android via the [Play Store](https://play.google.com/store/apps/details?id=com.beemdevelopment.aegis) or [F-Droid](https://f-droid.org/en/packages/com.beemdevelopment.aegis).
|
You can install it on Android via the [Play Store](https://play.google.com/store/apps/details?id=com.beemdevelopment.aegis) or [F-Droid](https://f-droid.org/en/packages/com.beemdevelopment.aegis).
|
||||||
|
|
||||||
|
### Misc. Phone Apps
|
||||||
|
|
||||||
|
FOSS App Store: F-Droid
|
||||||
|
|
||||||
|
QR code scanner: Binary Eye
|
||||||
|
|
||||||
|
Plant identification: PlantNet
|
||||||
|
|
||||||
|
Isolate apps: Shelter
|
||||||
|
|
||||||
|
Audiobook player: Voice
|
||||||
|
|
||||||
|
Phone sensors: phyphox
|
||||||
|
|
||||||
|
Maps: Organic Maps
|
||||||
|
|
||||||
|
Photos: Immich
|
||||||
|
|
||||||
|
Calculator: andanCalc PRO
|
||||||
|
|
||||||
|
PDF viewer: Pdf Viewer Plus
|
||||||
|
|
||||||
|
NFC reader: NFC Tools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Devices
|
## Devices
|
||||||
### ThruNite T1 Flashlight
|
|
||||||
This is a tiny rechargeable flashlight that can output 1500 lumens, enough to hurt your eyes if you look directly into it. It has a magnet on the back to stick to things and an optional pocket-hat clip. I liked it so much I've bought three of them and gave one to a friend who then bought himself a couple more.
|
|
||||||
|
|
||||||
![[recommend3.png]]
|
|
||||||
|
|
||||||
### Logitech G Pro Wireless Mice
|
|
||||||
I've used a lot of wireless mice, and this one is by far my favourite. It has a USB dongle which I prefer because Bluetooh pairing annoys me. If you disable the LEDs the battery lasts for a few weeks of solid usage.
|
|
||||||
|
|
||||||
I like it so much that I own five of them: one for my office desk, nook desk, laptop bag, home theatre, and surveillance cameras NVR. I leave the last one plugged in so that when a mouse battery dies, I can swap it with a fully charged mouse that's ready to go. It also ensures they wear at a somewhat even rate.
|
|
||||||
|
|
||||||
### ThinkPad Laptops
|
### ThinkPad Laptops
|
||||||
|
|
||||||
I've had several different laptops over the years and have settled on buying ThinkPads going forward. I hate Lenovo as company because of their [Superfish scandal](https://en.wikipedia.org/wiki/Superfish#Lenovo_security_incident), but I can't deny that ThinkPads are absolutely solid. I currently own a ThinkPad X1 Carbon 6th Gen.
|
I've had several different laptops over the years and have settled on buying ThinkPads going forward. I hate Lenovo as company because of their [Superfish scandal](https://en.wikipedia.org/wiki/Superfish#Lenovo_security_incident), but I can't deny that ThinkPads are absolutely solid. I currently own a ThinkPad X1 Carbon 6th Gen.
|
||||||
|
|
||||||
A lot of Linux developers use ThinkPads which means Linux is well supported on them and the drivers just work. The laptops are easy to pop open and service. Many parts are user-replaceable. My laptop charges off a small USB-C phone charger that I carry around. In a state of sickness-induced exhaustion, I spilled an entire glass of Gatorade on it and then drenched it in water to try and flush it away. I took the back off, drained it, and then pointed a fan at it for 24 hours. The laptop was working fine the next day.
|
A lot of Linux developers use ThinkPads which means Linux is well supported on them and the drivers just work. The laptops are easy to pop open and service. Many parts are user-replaceable. My laptop charges off a small USB-C phone charger that I carry around. In a state of sickness-induced exhaustion, I spilled an entire glass of Gatorade on it and then drenched it in water to try and flush it away. I took the back off, drained it, and then pointed a fan at it for 24 hours. The laptop was working fine the next day.
|
||||||
|
|
||||||
|
### RovyVon E5 Flashlight
|
||||||
|
|
||||||
|
Small USB-C flashlight that's very bright and has a side light that's useful for camping.
|
||||||
|
|
||||||
|
<https://www.amazon.ca/dp/B0B465DBW3>
|
||||||
|
|
||||||
|
### Leatherman Skeletool CX
|
||||||
|
|
||||||
|
My preferred multitool that I everyday carry. It's light, thin, and the cutters cross over instead of pinching.
|
||||||
|
|
||||||
|
<https://www.amazon.ca/LEATHERMAN-Lightweight-Minimalist-Multi-Tool-Nightshade/dp/B0CX2HRTBP>
|
33
content/Secret Garden.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
Title: Secret Garden
|
||||||
|
Date: 2024-02-21
|
||||||
|
Category: Notes
|
||||||
|
Summary: About the hydroponics garden in my basement.
|
||||||
|
|
||||||
|
I have a "Secret Garden" in my basement. It's a [[Hydroponics | hydroponics]] system growing leafy greens and herbs. You can see an hourly photo of it below:
|
||||||
|
|
||||||
|
<a href="/media/garden_hi.jpg"></a>
|
||||||
|
|
||||||
|
Click the above photo for a larger version.
|
||||||
|
|
||||||
|
There's usually kale, spinach, cilantro, parsley, and green onion growing. Sometimes dill and basil.
|
||||||
|
|
||||||
|
## Kratky Method
|
||||||
|
|
||||||
|
This garden uses the [Kratky method](https://en.wikipedia.org/wiki/Kratky_method) of hydroponics. The plants sit in a 30 L reservoir of nutrient solution. As the plants grow, they drink the water level down until a layer of air forms. This air is what oxygenates the plants after the oxygen dissolved in the water is depleted. The resulting system is completely passive, requiring no air pumps, water pumps, recirculation, filters, or heat and no risk of leaks or flooding.
|
||||||
|
|
||||||
|
## Previous Garden
|
||||||
|
|
||||||
|
My last garden used nutrient film technique (NFT) to continuously deliver a shallow stream of nutrient solution to the plants growing in 2" ABS pipe. A submersible fountain water pump sends nutrients up 1/4" irrigation hose to each of the four pipes. The nutrients flow down the slope passed all the roots and return to the ~40 L reservoir. An air stone oxygenates the water. Two computer case fans ensure adequate [[Airflow|airflow]].
|
||||||
|
|
||||||
|
![[nft1.png | four black pipes supported horizontally by a wooden frame. eight small seedlings are growing out of the pipe under four grow lights total. two fans on the right. a pink towel is down below, covering the reservoir.]]
|
||||||
|
|
||||||
|
The nutrients are kept in a reservoir underneath the towel, which helps block light and limit [[Algae Growth|algae growth]]:
|
||||||
|
|
||||||
|
![[nft2.jpg | the reservoir with the towel removed. the yellow lid is cut in half and has a tube poking out of it with a distribution cap for the 1/4 inch irrigation lines to connect to. black return pipes are on the left, pointing down into the reservoir through a mesh bag acting as a filter.]]
|
||||||
|
|
||||||
|
## First Garden
|
||||||
|
|
||||||
|
My first system [[Helios Alpha]] could grow up to six plants. It's designed around a single 102 L plastic tote. It holds enough water to harvest lettuce a few times before refilling. After the initial setup, the system can be ignored for weeks.
|
||||||
|
|
||||||
|
![[heliosalpha1.jpg | the hydroponics system from two angles. a black tub with yellow lid, covered in tin foil with six holes for plants. grow lights above suspended by metal shelving.]]
|
||||||
|
|
@@ -2,13 +2,12 @@ Title: Sensors
|
|||||||
Date: 2022-05-24
|
Date: 2022-05-24
|
||||||
Category: Notes
|
Category: Notes
|
||||||
Summary: Graphs of various sensors around my house.
|
Summary: Graphs of various sensors around my house.
|
||||||
Short: d
|
|
||||||
|
|
||||||
## Graphs
|
## 24h Graphs
|
||||||
|
|
||||||
These graphs are live and updated once per minute, assuming the script works:
|
These graphs are live and generated every 10 minutes, assuming the script works:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Black: power (W), green: energy (kWh)
|
Black: power (W), green: energy (kWh)
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ Black: temperature (°C), blue: humidity (%)
|
|||||||
|
|
||||||
Black: temperature (°C), blue: humidity (%)
|
Black: temperature (°C), blue: humidity (%)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Black: temperature (°C), blue: humidity (%)
|
Black: temperature (°C), blue: humidity (%)
|
||||||
|
|
||||||
@@ -60,6 +59,8 @@ Most of the data is captured by two cheap RTL-SDRs (software-defined radios) tha
|
|||||||
|
|
||||||
The data gets collected by a central Python script that process and stores it in an InfluxDB database for "efficient" storage. The script also runs a web server that queries the database and exposes the data over an API to the dashboard at various dates and ranges. The dashboard is written in JavaScript / React using a simple chart library.
|
The data gets collected by a central Python script that process and stores it in an InfluxDB database for "efficient" storage. The script also runs a web server that queries the database and exposes the data over an API to the dashboard at various dates and ranges. The dashboard is written in JavaScript / React using a simple chart library.
|
||||||
|
|
||||||
My biggest regret was using InfluxDB. It's a stupid database and I wouldn't recommend it to anyone. The documentation is confusing and I ran into timezone issues with `group by time()`. It also assumes the column data type is an integer if your sensor happens to send it a whole number at first and it won't let you change that. Their docs are bad. They dropped the SQL-like InfluxQL syntax for querying with a pipeline-like syntax called Flux in version 2.0. Debian's repos seem to be staying with version 1.x though.
|
## InfluxDB Regrets
|
||||||
|
|
||||||
Just stick to Postgres / SQLite.
|
My biggest regret was using InfluxDB. It's a stupid database that I wouldn't recommend it to anyone. I ran into timezone issues with `group by time()`. It assumes the column data type is an integer if your sensor happens to send it a whole number at first and it won't let you change it. Their docs are a confusing mess. They dropped the SQL-like InfluxQL syntax for querying with a pipeline-like syntax called Flux in version 2.0. Debian's repos seem to be staying with version 1.x though. You can only delete data by time ranges, not values. It also logs every single thing to `/var/log/syslog` and there's no easy way to disable it (completely). They shut down InfluxDB cloud in Belgium and [didn't warn customers](https://community.influxdata.com/t/getting-weird-results-from-gcp-europe-west1/30615/7) before deleting all their data. They changed schemas again in version 3.x and made useful features closed-source. Read the comments [here](https://news.t0.vc/TUTF/c#doctoboggan1750217574).
|
||||||
|
|
||||||
|
Just stick to SQLite or Postgres.
|
||||||
|
@@ -2,7 +2,9 @@ Title: Solar Car
|
|||||||
Date: 2013-04-27
|
Date: 2013-04-27
|
||||||
Category: Creations
|
Category: Creations
|
||||||
Summary: About my time volunteering with the University of Calgary Solar Car Team, where I designed a maximum power point tracker.
|
Summary: About my time volunteering with the University of Calgary Solar Car Team, where I designed a maximum power point tracker.
|
||||||
Short: car
|
Image: solar2.jpg
|
||||||
|
Tags: feed
|
||||||
|
Guid: 7259d46cfc0440acba56d43d1749314b
|
||||||
|
|
||||||
I joined the University of Calgary Solar Car Team in my first semester for a chance to learn things, gain practical experience, and meet people that share my interests. The car was the top Canadian team in a 3000 km race from Darwin to Adelaide, Australia in 2011. We met up at a shop on campus every Saturday morning to work on the new Generation IV of the solar car.
|
I joined the University of Calgary Solar Car Team in my first semester for a chance to learn things, gain practical experience, and meet people that share my interests. The car was the top Canadian team in a 3000 km race from Darwin to Adelaide, Australia in 2011. We met up at a shop on campus every Saturday morning to work on the new Generation IV of the solar car.
|
||||||
|
|
||||||
|
@@ -2,13 +2,15 @@ Title: Spaceport
|
|||||||
Date: 2022-05-16
|
Date: 2022-05-16
|
||||||
Category: Projects
|
Category: Projects
|
||||||
Summary: Member portal for Calgary Protospace. It tracks dues, courses, training, access cards, and more.
|
Summary: Member portal for Calgary Protospace. It tracks dues, courses, training, access cards, and more.
|
||||||
Short: m
|
Image: spaceport1.png
|
||||||
|
Tags: feed
|
||||||
|
Guid: 3a7b5606eefc45cbad523d7548a864a5
|
||||||
|
|
||||||
[Spaceport](https://my.protospace.ca) is the member portal that I wrote for [[Protospace]], a makerspace that I frequent in Calgary. It is by far my largest project and the one I've spent the most time on. It has a database of all our members and tracks their transactions like dues and training fees. It allows members to sign up for classes and our instructors to teach courses. It also manages the access cards that members use to get into the building.
|
[Spaceport](https://my.protospace.ca) is the member portal that I wrote for [[Protospace]], a makerspace that I frequent in Calgary. It is by far my largest project and the one I've spent the most time on. It has a database of all our members and tracks their transactions like dues and training fees. It allows members to sign up for classes and our instructors to teach courses. It also manages the access cards that members use to get into the building.
|
||||||
|
|
||||||
You can find the [source code](https://github.com/Protospace/spaceport) on Github.
|
You can find the [source code](https://github.com/Protospace/spaceport) on Github.
|
||||||
|
|
||||||
![[spaceport1.png]]
|
![[spaceport1.png | a screenshot of spaceport's home page. a photo of me to the left, below that my latest training and transactions. links and stats related to Protospace to the right.]]
|
||||||
|
|
||||||
Spaceport is tightly coupled to Protospace and has many integrations:
|
Spaceport is tightly coupled to Protospace and has many integrations:
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ Spaceport is tightly coupled to Protospace and has many integrations:
|
|||||||
- Displays charts of various environmental sensors
|
- Displays charts of various environmental sensors
|
||||||
- Displays a photo of our garden
|
- Displays a photo of our garden
|
||||||
|
|
||||||
![[spaceport2.png]]
|
![[spaceport2.png | a list of all the classes. they are group by course and have colourful tags to make them easier to find.]]
|
||||||
|
|
||||||
As of writing this there's 234 current Protospace members and 1408 historical or inactive memberships that it manages. Data is stored in a 49 MB SQLite database which makes it easy to back up or sync with my development server. The back end is written in Django / Python and the front end is React / JavaScript with Semantic UI for the graphics.
|
As of writing this there's 234 current Protospace members and 1408 historical or inactive memberships that it manages. Data is stored in a 49 MB SQLite database which makes it easy to back up or sync with my development server. The back end is written in Django / Python and the front end is React / JavaScript with Semantic UI for the graphics.
|
||||||
|
|
||||||
|
@@ -2,7 +2,9 @@ Title: Wine Crate Coffee Table
|
|||||||
Date: 2018-09-12
|
Date: 2018-09-12
|
||||||
Category: Creations
|
Category: Creations
|
||||||
Summary: A coffee table made out of wooden wine creates.
|
Summary: A coffee table made out of wooden wine creates.
|
||||||
Short: 0
|
Image: wine3.jpg
|
||||||
|
Tags: feed
|
||||||
|
Guid: f0e36fec844d422eb7e0d626788c7b0a
|
||||||
|
|
||||||
My close friend Odai saw a simple coffee table design online that was built out of four wooden wine crates. They are quite cheap and available at any hardware store. We each wanted to make one so went and bought eight crates and some plywood to use as a base.
|
My close friend Odai saw a simple coffee table design online that was built out of four wooden wine crates. They are quite cheap and available at any hardware store. We each wanted to make one so went and bought eight crates and some plywood to use as a base.
|
||||||
|
|
||||||
|
BIN
content/media/airmonitor1.jpg
Normal file
After Width: | Height: | Size: 269 KiB |
BIN
content/media/airmonitor2.png
Normal file
After Width: | Height: | Size: 219 KiB |
BIN
content/media/airmonitor3.png
Normal file
After Width: | Height: | Size: 198 KiB |
4
content/media/backup1.svg
Normal file
After Width: | Height: | Size: 185 KiB |
BIN
content/media/distro1.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
content/media/heliosalpha1.jpg
Normal file
After Width: | Height: | Size: 530 KiB |
BIN
content/media/japan01hi.jpg
Normal file
After Width: | Height: | Size: 5.5 MiB |
BIN
content/media/japan01lo.jpg
Normal file
After Width: | Height: | Size: 511 KiB |
BIN
content/media/japan02hi.jpg
Normal file
After Width: | Height: | Size: 5.8 MiB |
BIN
content/media/japan02lo.jpg
Normal file
After Width: | Height: | Size: 639 KiB |
BIN
content/media/japan03hi.jpg
Normal file
After Width: | Height: | Size: 3.6 MiB |
BIN
content/media/japan03lo.jpg
Normal file
After Width: | Height: | Size: 330 KiB |
BIN
content/media/japan04hi.jpg
Normal file
After Width: | Height: | Size: 4.2 MiB |
BIN
content/media/japan04lo.jpg
Normal file
After Width: | Height: | Size: 372 KiB |
BIN
content/media/japan05hi.jpg
Normal file
After Width: | Height: | Size: 5.7 MiB |
BIN
content/media/japan05lo.jpg
Normal file
After Width: | Height: | Size: 517 KiB |
BIN
content/media/japan06hi.jpg
Normal file
After Width: | Height: | Size: 5.5 MiB |
BIN
content/media/japan06lo.jpg
Normal file
After Width: | Height: | Size: 642 KiB |
BIN
content/media/japan07hi.jpg
Normal file
After Width: | Height: | Size: 6.8 MiB |
BIN
content/media/japan07lo.jpg
Normal file
After Width: | Height: | Size: 589 KiB |
BIN
content/media/japan08hi.jpg
Normal file
After Width: | Height: | Size: 3.1 MiB |
BIN
content/media/japan08lo.jpg
Normal file
After Width: | Height: | Size: 321 KiB |
BIN
content/media/japan09hi.jpg
Normal file
After Width: | Height: | Size: 3.3 MiB |
BIN
content/media/japan09lo.jpg
Normal file
After Width: | Height: | Size: 352 KiB |
BIN
content/media/japan10hi.jpg
Normal file
After Width: | Height: | Size: 4.5 MiB |
BIN
content/media/japan10lo.jpg
Normal file
After Width: | Height: | Size: 258 KiB |
BIN
content/media/japan11hi.jpg
Normal file
After Width: | Height: | Size: 3.1 MiB |
BIN
content/media/japan11lo.jpg
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
content/media/japan12hi.jpg
Normal file
After Width: | Height: | Size: 3.4 MiB |
BIN
content/media/japan12lo.jpg
Normal file
After Width: | Height: | Size: 300 KiB |
Before Width: | Height: | Size: 32 KiB |
BIN
content/media/nft1.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
content/media/nft2.jpg
Normal file
After Width: | Height: | Size: 164 KiB |
4
content/media/ports1.svg
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
content/media/protovac1.jpg
Normal file
After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 140 KiB |
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 209 KiB |
4
content/media/t0services1.svg
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
content/media/t0txt1.png
Normal file
After Width: | Height: | Size: 22 KiB |
@@ -1,6 +1,6 @@
|
|||||||
Title: Creations
|
Title: Creations
|
||||||
Template: creations
|
Template: creations
|
||||||
Slug: z
|
Slug: creations
|
||||||
|
|
||||||
|
|
||||||
Qot.
|
Qot.
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
Title: Projects
|
Title: Projects
|
||||||
Template: projects
|
Template: projects
|
||||||
Slug: y
|
Slug: projects
|
||||||
|
|
||||||
|
|
||||||
Qot.
|
Qot.
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
Title: Writing
|
Title: Writing
|
||||||
Template: writing
|
Template: writing
|
||||||
Slug: x
|
Slug: writing
|
||||||
|
|
||||||
|
|
||||||
Qot.
|
Qot.
|
||||||
|
@@ -2,8 +2,10 @@ Title: t-zero Services
|
|||||||
Date: 2022-05-27
|
Date: 2022-05-27
|
||||||
Category: Writing
|
Category: Writing
|
||||||
Summary: A list of minimal microservices on my t0.vc domain.
|
Summary: A list of minimal microservices on my t0.vc domain.
|
||||||
|
Image: t0services1.svg
|
||||||
Wide: true
|
Wide: true
|
||||||
Short: 6
|
Tags: feed
|
||||||
|
Guid: 0440222b638144d5a6c376e1aaf43755
|
||||||
|
|
||||||
The t-zero Services are a collection of minimalist microservices that I host on my t0.vc domain. The letter "t" meaning me, and "0" meaning small. They're all meant to do exactly one thing reliably and stay online for as long as I can host them.
|
The t-zero Services are a collection of minimalist microservices that I host on my t0.vc domain. The letter "t" meaning me, and "0" meaning small. They're all meant to do exactly one thing reliably and stay online for as long as I can host them.
|
||||||
|
|
||||||
@@ -12,6 +14,8 @@ The smallest t-zero is the main domain itself at [t0.vc](https://t0.vc) and it s
|
|||||||
|
|
||||||
The rest of the t-zero services are hosted on its subdomains.
|
The rest of the t-zero services are hosted on its subdomains.
|
||||||
|
|
||||||
|
![[t0services1.svg]]
|
||||||
|
|
||||||
## t0txt
|
## t0txt
|
||||||
The second t-zero I wrote was [[t0txt]], a pastebin that is compatible with the command line and `curl`. This allows me to very easily pipe text data into it and immediately get a URL that I can share. I copied the idea from [sprunge.us](http://sprunge.us/) which kept going down because he'd forget to pay his Google Cloud bill.
|
The second t-zero I wrote was [[t0txt]], a pastebin that is compatible with the command line and `curl`. This allows me to very easily pipe text data into it and immediately get a URL that I can share. I copied the idea from [sprunge.us](http://sprunge.us/) which kept going down because he'd forget to pay his Google Cloud bill.
|
||||||
|
|
||||||
|
@@ -2,12 +2,16 @@ Title: t0txt
|
|||||||
Date: 2022-05-15
|
Date: 2022-05-15
|
||||||
Category: Projects
|
Category: Projects
|
||||||
Summary: Minimal command line pastebin. Allows you to upload text notes from a bash pipe or web browser.
|
Summary: Minimal command line pastebin. Allows you to upload text notes from a bash pipe or web browser.
|
||||||
Short: t
|
Image: t0txt1.png
|
||||||
|
Tags: feed
|
||||||
|
Guid: a5fd74baa289491e9c4e931cdfcd2170
|
||||||
|
|
||||||
[t0txt](https://txt.t0.vc) is a minimalist pastebin. You can upload text notes from the command line by using a bash alias or by submitting text through the web form.
|
[t0txt](https://txt.t0.vc) is a minimalist pastebin. You can upload text notes from the command line by using a bash alias or by submitting text through the web form.
|
||||||
|
|
||||||
You can find the [source code](https://github.com/tannercollin/t0txt) on Github.
|
You can find the [source code](https://github.com/tannercollin/t0txt) on Github.
|
||||||
|
|
||||||
|
![[t0txt1.png]]
|
||||||
|
|
||||||
The pastes you upload take the form of [txt.t0.vc/IMLV](https://txt.t0.vc/IMLV), where they are identified by four unique capital letters. This makes it easy to memorize the URL while moving it between devices.
|
The pastes you upload take the form of [txt.t0.vc/IMLV](https://txt.t0.vc/IMLV), where they are identified by four unique capital letters. This makes it easy to memorize the URL while moving it between devices.
|
||||||
|
|
||||||
I wrote t0txt in July 2019 and plan to continue hosting it indefinitely. I use it quite often for sysadmin and automation work, so I'm committed to keeping it alive. Here's an example use case:
|
I wrote t0txt in July 2019 and plan to continue hosting it indefinitely. I use it quite often for sysadmin and automation work, so I'm committed to keeping it alive. Here's an example use case:
|
||||||
|
93
generate_thumbnails.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
from pelican import signals
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
THUMBNAIL_MAX_SIZE = 448
|
||||||
|
|
||||||
|
def generator_finalized(generator):
|
||||||
|
"""
|
||||||
|
Generates thumbnails for images specified in article metadata.
|
||||||
|
"""
|
||||||
|
output_path = generator.settings['OUTPUT_PATH']
|
||||||
|
content_path = generator.settings['PATH']
|
||||||
|
media_path = os.path.join(content_path, 'media')
|
||||||
|
thumb_dir = os.path.join(output_path, 'media', 'thumbnails')
|
||||||
|
|
||||||
|
if not os.path.exists(thumb_dir):
|
||||||
|
try:
|
||||||
|
os.makedirs(thumb_dir)
|
||||||
|
log.info(f"Created thumbnail directory: {thumb_dir}")
|
||||||
|
except OSError as e:
|
||||||
|
log.error(f"Could not create thumbnail directory {thumb_dir}: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
for article in generator.articles:
|
||||||
|
if hasattr(article, 'image'):
|
||||||
|
image_path_rel_to_content = article.image
|
||||||
|
# image_path_rel_to_content is often like 'media/imagename.jpg'
|
||||||
|
# or just 'imagename.jpg' if it's directly in 'content/media/'
|
||||||
|
# and STATIC_PATHS includes 'media'.
|
||||||
|
# We assume article.image is a path relative to the 'content' folder.
|
||||||
|
|
||||||
|
source_image_full_path = os.path.join(media_path, image_path_rel_to_content)
|
||||||
|
|
||||||
|
if not os.path.exists(source_image_full_path):
|
||||||
|
log.warning(f"Source image not found for article '{article.slug}': {source_image_full_path}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
image_filename = os.path.basename(image_path_rel_to_content)
|
||||||
|
thumb_path = os.path.join(thumb_dir, image_filename)
|
||||||
|
|
||||||
|
_, ext = os.path.splitext(image_filename)
|
||||||
|
ext_lower = ext.lower()
|
||||||
|
if ext_lower not in ['.jpg', '.jpeg', '.png']:
|
||||||
|
log.info(f"Skipping non-JPG/PNG image for article '{article.slug}': {image_filename}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
log.debug(f"Processing image: {source_image_full_path}")
|
||||||
|
img = Image.open(source_image_full_path)
|
||||||
|
|
||||||
|
# Preserve original format, handle potential conversion issues for some modes
|
||||||
|
original_format = img.format
|
||||||
|
if img.mode == 'P' and 'transparency' in img.info: # Palette mode with transparency
|
||||||
|
img = img.convert('RGBA')
|
||||||
|
elif img.mode not in ('RGB', 'RGBA', 'L'): # L is grayscale
|
||||||
|
log.info(f"Converting image {image_filename} from mode {img.mode} to RGB for thumbnailing.")
|
||||||
|
img = img.convert('RGB')
|
||||||
|
|
||||||
|
img.thumbnail((THUMBNAIL_MAX_SIZE, THUMBNAIL_MAX_SIZE))
|
||||||
|
|
||||||
|
save_kwargs = {}
|
||||||
|
if original_format:
|
||||||
|
save_kwargs['format'] = original_format
|
||||||
|
if original_format == 'JPEG':
|
||||||
|
save_kwargs['quality'] = 95 # Adjust quality for JPEGs
|
||||||
|
save_kwargs['optimize'] = True
|
||||||
|
elif original_format == 'PNG':
|
||||||
|
save_kwargs['optimize'] = True
|
||||||
|
|
||||||
|
img.save(thumb_path, **save_kwargs)
|
||||||
|
log.info(f"Generated thumbnail for '{article.slug}': {thumb_path}")
|
||||||
|
|
||||||
|
# Optionally, add thumbnail URL to article metadata if needed by templates
|
||||||
|
# This depends on how SITEURL and paths are structured.
|
||||||
|
# For now, just creating the file.
|
||||||
|
# article.thumbnail_url = f"{generator.settings.get('SITEURL', '')}/media/thumbs/{image_filename}"
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
log.error(f"Image file not found: {source_image_full_path}")
|
||||||
|
except IOError as e:
|
||||||
|
log.error(f"Could not open or process image {source_image_full_path}: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"An unexpected error occurred while processing {source_image_full_path}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
signals.article_generator_finalized.connect(generator_finalized)
|
||||||
|
|
@@ -50,7 +50,7 @@
|
|||||||
inkscape:groupmode="layer"
|
inkscape:groupmode="layer"
|
||||||
id="layer1">
|
id="layer1">
|
||||||
<rect
|
<rect
|
||||||
style="fill:#000000;stroke-width:0.264999"
|
style="fill:#1a1a1a;stroke-width:0.264999"
|
||||||
id="rect909"
|
id="rect909"
|
||||||
width="212.19583"
|
width="212.19583"
|
||||||
height="121.17917"
|
height="121.17917"
|
||||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
@@ -47,7 +47,7 @@
|
|||||||
inkscape:groupmode="layer"
|
inkscape:groupmode="layer"
|
||||||
id="layer1">
|
id="layer1">
|
||||||
<rect
|
<rect
|
||||||
style="fill:#000000;stroke-width:0.264999"
|
style="fill:#1a1a1a;stroke-width:0.264999"
|
||||||
id="rect909"
|
id="rect909"
|
||||||
width="212.19583"
|
width="212.19583"
|
||||||
height="121.17917"
|
height="121.17917"
|
||||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
@@ -47,7 +47,7 @@
|
|||||||
inkscape:groupmode="layer"
|
inkscape:groupmode="layer"
|
||||||
id="layer1">
|
id="layer1">
|
||||||
<rect
|
<rect
|
||||||
style="fill:#000000;stroke-width:0.264999"
|
style="fill:#1a1a1a;stroke-width:0.264999"
|
||||||
id="rect909"
|
id="rect909"
|
||||||
width="212.19583"
|
width="212.19583"
|
||||||
height="121.17917"
|
height="121.17917"
|
||||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
70
misc/backup-strategy-diagram.drawio
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<mxfile host="app.diagrams.net" modified="2024-01-26T21:51:42.500Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0" etag="rxPVBCqqDlizBv9YwVwk" version="23.0.2" type="device">
|
||||||
|
<diagram name="Page-1" id="2peR5UrzPdhCN7KmwIpr">
|
||||||
|
<mxGraphModel dx="1114" dy="630" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-28" value="" style="ellipse;whiteSpace=wrap;html=1;strokeWidth=3;fillColor=none;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="590" y="310" width="150" height="150" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-1" value="" style="fontColor=#0066CC;verticalAlign=top;verticalLabelPosition=bottom;labelPosition=center;align=center;html=1;outlineConnect=0;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.server_storage;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="380.63" y="300" width="105" height="105" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-2" value="<font style="font-size: 25px;">Home Server</font>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="350" y="260" width="166.25" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-4" value="<font style="font-size: 25px;">/mnt/backup</font>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="355.95" y="420" width="154.37" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;" edge="1" parent="1" source="20e5nqs_UBeXcxDGWj9N-5" target="20e5nqs_UBeXcxDGWj9N-4">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-5" value="" style="fontColor=#0066CC;verticalAlign=top;verticalLabelPosition=bottom;labelPosition=center;align=center;html=1;outlineConnect=0;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.pc;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="140" y="230" width="100" height="70" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;" edge="1" parent="1" source="20e5nqs_UBeXcxDGWj9N-6" target="20e5nqs_UBeXcxDGWj9N-4">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-6" value="" style="fontColor=#0066CC;verticalAlign=top;verticalLabelPosition=bottom;labelPosition=center;align=center;html=1;outlineConnect=0;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.web_server;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="150" y="330" width="90" height="85" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeWidth=3;" edge="1" parent="1" source="20e5nqs_UBeXcxDGWj9N-7" target="20e5nqs_UBeXcxDGWj9N-4">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-7" value="" style="fontColor=#0066CC;verticalAlign=top;verticalLabelPosition=bottom;labelPosition=center;align=center;html=1;outlineConnect=0;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.mail_server;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="150" y="430" width="90" height="90" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-8" value="<font style="font-size: 25px;">Backup Sources</font>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="100" y="180" width="190" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-9" value="<font style="font-size: 25px;">Computers</font>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="15" y="250" width="100" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-10" value="<div style="font-size: 25px;"><font style="font-size: 25px;">Web Services</font></div>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry y="337.5" width="130" height="70" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-11" value="<font style="font-size: 25px;">Email, Nextcloud, etc.<br></font>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="5" y="445" width="110" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-19" value="" style="fontColor=#0066CC;verticalAlign=top;verticalLabelPosition=bottom;labelPosition=center;align=center;html=1;outlineConnect=0;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.external_storage;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="580" y="390" width="70" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-20" value="" style="fontColor=#0066CC;verticalAlign=top;verticalLabelPosition=bottom;labelPosition=center;align=center;html=1;outlineConnect=0;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.external_storage;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="640" y="280" width="70" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-21" value="" style="fontColor=#0066CC;verticalAlign=top;verticalLabelPosition=bottom;labelPosition=center;align=center;html=1;outlineConnect=0;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.external_storage;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="700" y="390" width="70" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-22" value="<font style="font-size: 25px;">External Hard Drives</font>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="590" y="210" width="160" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-23" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;strokeWidth=3;" edge="1" parent="1" source="20e5nqs_UBeXcxDGWj9N-4" target="20e5nqs_UBeXcxDGWj9N-19">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20e5nqs_UBeXcxDGWj9N-29" value="<font style="font-size: 25px;">rsync</font>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="290" y="440" width="100" height="55" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
48
misc/bypass-ports-diagram.drawio
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<mxfile host="app.diagrams.net" modified="2024-01-26T05:01:15.100Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0" etag="JJ-bykZ30vwY7yotJaLz" version="23.0.2" type="device">
|
||||||
|
<diagram name="Page-1" id="bfe91b75-5d2c-26a0-9c1d-138518896778">
|
||||||
|
<mxGraphModel dx="138" dy="-1000" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" background="none" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
<mxCell id="y_adAsICl-CzPuRvVA7X-1" value="" style="html=1;fillColor=#f5f5f5;strokeColor=#666666;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.cloud;rounded=0;shadow=0;comic=0;align=center;fontSize=28;fontColor=#333333;verticalAlign=bottom;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1710" y="1840" width="730" height="390" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="y_adAsICl-CzPuRvVA7X-2" value="<div align="center"><font style="font-size: 25px;">DigitalOcean VPS</font><br></div>" style="fontColor=#0066CC;verticalAlign=bottom;verticalLabelPosition=top;labelPosition=center;align=center;html=1;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.server_storage;rounded=0;shadow=0;comic=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1847.5" y="2073" width="105" height="105" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="y_adAsICl-CzPuRvVA7X-5" value="" style="fontColor=#0066CC;verticalAlign=top;verticalLabelPosition=bottom;labelPosition=center;align=center;html=1;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.pc;rounded=0;shadow=0;comic=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1850.0000000000002" y="2330.0017647058826" width="100" height="70" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="y_adAsICl-CzPuRvVA7X-6" value="<font style="font-size: 25px;">Home Server<br></font>" style="fontColor=#0066CC;verticalAlign=bottom;verticalLabelPosition=top;labelPosition=center;align=center;html=1;fillColor=#CCCCCC;strokeColor=#6881B3;gradientColor=none;gradientDirection=north;strokeWidth=2;shape=mxgraph.networks.server_storage;rounded=0;shadow=0;comic=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="2510" y="2073" width="105" height="105" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="y_adAsICl-CzPuRvVA7X-9" value="" style="shape=singleArrow;direction=west;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1960" y="2065" width="510" height="120" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="y_adAsICl-CzPuRvVA7X-10" value="<font style="font-size: 25px;">OpenVPN Tunnel<br></font>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="2090" y="2100" width="280" height="130" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="y_adAsICl-CzPuRvVA7X-11" value="<font style="font-size: 25px;">Internet</font>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="2120" y="1900" width="60" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="y_adAsICl-CzPuRvVA7X-16" value="" style="curved=1;endArrow=classic;html=1;rounded=0;strokeWidth=5;" edge="1" parent="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="1910" y="2320" as="sourcePoint" />
|
||||||
|
<mxPoint x="2500" y="2130" as="targetPoint" />
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="1910" y="2150" />
|
||||||
|
<mxPoint x="1940" y="2120" />
|
||||||
|
<mxPoint x="2190" y="2130" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="y_adAsICl-CzPuRvVA7X-18" value="<font style="font-size: 25px;">HTTP<br></font>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1930" y="2270" width="60" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="y_adAsICl-CzPuRvVA7X-19" value="<font style="font-size: 25px;">Client</font>" style="text;strokeColor=none;align=center;fillColor=none;html=1;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontColor=#0066CC;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="1870" y="2410" width="60" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
31
misc/distro-diagram.drawio
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<mxfile host="app.diagrams.net" modified="2024-01-27T01:41:03.200Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0" etag="OL6IpkjM6-81aN4SoHv_" version="23.0.2" type="device">
|
||||||
|
<diagram name="Page-1" id="5-Wfztd7QZj7HGqn7hWo">
|
||||||
|
<mxGraphModel dx="928" dy="525" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
<mxCell id="xaIvDc9CACh0lx6NfuLJ-1" value="" style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://upload.wikimedia.org/wikipedia/commons/4/4a/Debian-OpenLogo.svg;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="210" y="210" width="109" height="144" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="xaIvDc9CACh0lx6NfuLJ-2" value="" style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://upload.wikimedia.org/wikipedia/commons/9/9e/UbuntuCoF.svg;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="580" y="212" width="140" height="140" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="xaIvDc9CACh0lx6NfuLJ-3" value="" style="shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;image=https://upload.wikimedia.org/wikipedia/commons/3/35/Tux.svg;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="380" y="212" width="134" height="134" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="xaIvDc9CACh0lx6NfuLJ-4" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=5;strokeColor=#808080;" edge="1" parent="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="320" y="307" as="sourcePoint" />
|
||||||
|
<mxPoint x="370" y="257" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="xaIvDc9CACh0lx6NfuLJ-5" value="" style="endArrow=none;html=1;rounded=0;strokeWidth=5;strokeColor=#808080;" edge="1" parent="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="520" y="304" as="sourcePoint" />
|
||||||
|
<mxPoint x="570" y="254" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
31
misc/t0-services-diagram.drawio
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<mxfile host="app.diagrams.net" modified="2024-01-27T01:25:37.000Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0" etag="WvQ24rCeBP-gvlG-6t5J" version="23.0.2" type="device">
|
||||||
|
<diagram name="Page-1" id="dd472eb7-4b8b-5cd9-a60b-b15522922e76">
|
||||||
|
<mxGraphModel dx="1114" dy="630" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" background="none" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
<mxCell id="374e34682ed331ee-1" value="<font style="font-size: 36px;">t0.vc</font>" style="ellipse;whiteSpace=wrap;html=1;rounded=0;shadow=0;dashed=0;comic=0;fontFamily=Verdana;fontSize=22;fontColor=default;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="290" y="180" width="450" height="180" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45236fa5f4b8e91a-1" value="t0txt" style="ellipse;whiteSpace=wrap;html=1;rounded=0;shadow=0;dashed=0;comic=0;fontFamily=Verdana;fontSize=22;fontColor=default;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="430" y="120" width="160" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45236fa5f4b8e91a-2" value="<div>t0url</div>" style="ellipse;whiteSpace=wrap;html=1;rounded=0;shadow=0;dashed=0;comic=0;fontFamily=Verdana;fontSize=22;fontColor=default;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="240" y="285" width="160" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45236fa5f4b8e91a-3" value="t0reg" style="ellipse;whiteSpace=wrap;html=1;rounded=0;shadow=0;dashed=0;comic=0;fontFamily=Verdana;fontSize=22;fontColor=default;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="620" y="280" width="160" height="110" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45236fa5f4b8e91a-6" value="t0pic" style="ellipse;whiteSpace=wrap;html=1;rounded=0;shadow=0;dashed=0;comic=0;fontFamily=Verdana;fontSize=22;fontColor=default;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="620" y="150" width="160" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45236fa5f4b8e91a-7" value="t0sig" style="ellipse;whiteSpace=wrap;html=1;rounded=0;shadow=0;dashed=0;comic=0;fontFamily=Verdana;fontSize=22;fontColor=default;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="435" y="310" width="160" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="45236fa5f4b8e91a-8" value="t0dns" style="ellipse;whiteSpace=wrap;html=1;rounded=0;shadow=0;dashed=0;comic=0;fontFamily=Verdana;fontSize=22;fontColor=default;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="240" y="160" width="160" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
@@ -3,8 +3,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import os
|
import os
|
||||||
|
|
||||||
AUTHOR = 'Tanner Collin'
|
AUTHOR = 'Tanner'
|
||||||
SITENAME = 'Tanner Collin'
|
SITENAME = 'Tanner'
|
||||||
SITEURL = ''
|
SITEURL = ''
|
||||||
|
|
||||||
PATH = 'content'
|
PATH = 'content'
|
||||||
@@ -61,8 +61,8 @@ CATEGORIES_SAVE_AS = ''
|
|||||||
TAGS_SAVE_AS = ''
|
TAGS_SAVE_AS = ''
|
||||||
|
|
||||||
INDEX_SAVE_AS = 'index.html'
|
INDEX_SAVE_AS = 'index.html'
|
||||||
ARTICLE_URL = '{short}'
|
ARTICLE_URL = '{slug}'
|
||||||
ARTICLE_SAVE_AS = '{short}/index.html'
|
ARTICLE_SAVE_AS = '{slug}/index.html'
|
||||||
PAGE_URL = '{slug}'
|
PAGE_URL = '{slug}'
|
||||||
PAGE_SAVE_AS = '{slug}/index.html'
|
PAGE_SAVE_AS = '{slug}/index.html'
|
||||||
|
|
||||||
|
@@ -1,17 +0,0 @@
|
|||||||
<style>
|
|
||||||
body {background: #eee; font: 1.1rem/1.5 serif;}
|
|
||||||
h1, h2 {margin-left: -24; font-family: sans-serif;}
|
|
||||||
a {text-decoration: none; color: #000; border-bottom: 1px solid #000;}
|
|
||||||
</style>
|
|
||||||
<div style="max-width: 600; margin: auto">
|
|
||||||
<h1>Tanner Collin</h1>
|
|
||||||
<p> Hi, I'm Tanner! I do firmware and web development in Calgary.</p>
|
|
||||||
<a href="/">Contact</a> |
|
|
||||||
<a href="/r">Resume</a> |
|
|
||||||
<a href="/p">Projects</a> |
|
|
||||||
<a href="/c">Creations</a> |
|
|
||||||
<a href="/w">Writing</a>
|
|
||||||
<h2>Contact Info</h2>
|
|
||||||
<p>Email: <a href="mailto:site2@tannercollin.com">site2@tannercollin.com</a></p>
|
|
||||||
<p>Telegram: <a href="https://t.me/tannercollin">@tannercollin</a></p>
|
|
||||||
</div>
|
|
@@ -2,6 +2,11 @@
|
|||||||
# -*- coding: utf-8 -*- #
|
# -*- coding: utf-8 -*- #
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.append('.')
|
||||||
|
|
||||||
|
import generate_thumbnails
|
||||||
|
|
||||||
PATH = 'content'
|
PATH = 'content'
|
||||||
|
|
||||||
TIMEZONE = 'Canada/Mountain'
|
TIMEZONE = 'Canada/Mountain'
|
||||||
@@ -33,6 +38,7 @@ MARKDOWN = {
|
|||||||
PLUGINS = [
|
PLUGINS = [
|
||||||
'obsidian',
|
'obsidian',
|
||||||
'linkclass',
|
'linkclass',
|
||||||
|
'generate_thumbnails',
|
||||||
]
|
]
|
||||||
|
|
||||||
STATIC_PATHS = ['media', 'extra']
|
STATIC_PATHS = ['media', 'extra']
|
||||||
|
@@ -3,9 +3,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import os
|
import os
|
||||||
|
|
||||||
AUTHOR = 'Tanner Collin'
|
import sys
|
||||||
SITENAME = 'Tanner Collin'
|
sys.path.append('.')
|
||||||
SITEURL = ''
|
|
||||||
|
import swap_guids
|
||||||
|
|
||||||
|
AUTHOR = 'Tanner'
|
||||||
|
SITENAME = 'Tanner\'s Site (t0.vc)'
|
||||||
|
SITEURL = 'https://t0.vc'
|
||||||
|
|
||||||
PATH = 'content'
|
PATH = 'content'
|
||||||
|
|
||||||
@@ -13,12 +18,17 @@ TIMEZONE = 'Canada/Mountain'
|
|||||||
|
|
||||||
DEFAULT_LANG = 'en'
|
DEFAULT_LANG = 'en'
|
||||||
|
|
||||||
# Feed generation is usually not desired when developing
|
#FEED_MAX_ITEMS = 15
|
||||||
FEED_ALL_ATOM = None
|
FEED_ALL_ATOM = None
|
||||||
CATEGORY_FEED_ATOM = None
|
CATEGORY_FEED_ATOM = None
|
||||||
TRANSLATION_FEED_ATOM = None
|
TRANSLATION_FEED_ATOM = None
|
||||||
AUTHOR_FEED_ATOM = None
|
AUTHOR_FEED_ATOM = None
|
||||||
AUTHOR_FEED_RSS = None
|
AUTHOR_FEED_RSS = None
|
||||||
|
#TAG_FEED_ATOM = 'feeds/{slug}/atom.xml'
|
||||||
|
#TAG_FEED_RSS = 'feeds/{slug}/rss.xml'
|
||||||
|
TAG_FEED_ATOM = 'atom.xml'
|
||||||
|
TAG_FEED_RSS = 'rss.xml'
|
||||||
|
RSS_FEED_SUMMARY_ONLY = False # include full content
|
||||||
|
|
||||||
DEFAULT_PAGINATION = False
|
DEFAULT_PAGINATION = False
|
||||||
|
|
||||||
@@ -38,6 +48,7 @@ MARKDOWN = {
|
|||||||
PLUGINS = [
|
PLUGINS = [
|
||||||
'obsidian',
|
'obsidian',
|
||||||
'linkclass',
|
'linkclass',
|
||||||
|
'swap_guids',
|
||||||
]
|
]
|
||||||
|
|
||||||
STATIC_PATHS = ['media', 'extra', 'text']
|
STATIC_PATHS = ['media', 'extra', 'text']
|
||||||
@@ -61,8 +72,8 @@ CATEGORIES_SAVE_AS = ''
|
|||||||
TAGS_SAVE_AS = ''
|
TAGS_SAVE_AS = ''
|
||||||
|
|
||||||
INDEX_SAVE_AS = 'index.html'
|
INDEX_SAVE_AS = 'index.html'
|
||||||
ARTICLE_URL = '{short}'
|
ARTICLE_URL = '{slug}'
|
||||||
ARTICLE_SAVE_AS = '{short}/index.html'
|
ARTICLE_SAVE_AS = '{slug}/index.html'
|
||||||
PAGE_URL = '{slug}'
|
PAGE_URL = '{slug}'
|
||||||
PAGE_SAVE_AS = '{slug}/index.html'
|
PAGE_SAVE_AS = '{slug}/index.html'
|
||||||
|
|
||||||
|
77
swap_guids.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import logging
|
||||||
|
import pprint
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from pelican import signals
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def modify_feed(context, feed):
|
||||||
|
articles = {}
|
||||||
|
|
||||||
|
for article in context['articles']:
|
||||||
|
if article.title in articles:
|
||||||
|
raise Exception(f"Duplicate article title found: {article.title}")
|
||||||
|
articles[article.title] = article
|
||||||
|
|
||||||
|
for item in feed.items:
|
||||||
|
item_title = item['title']
|
||||||
|
article = articles.get(item_title)
|
||||||
|
if not article:
|
||||||
|
raise Exception(f"Article not found for title: {item_title}")
|
||||||
|
|
||||||
|
if not hasattr(article, 'guid') or not article.guid:
|
||||||
|
log.info(f"Article '{article.title}' ({article.source_path}) is missing a guid. Generating and embedding one.")
|
||||||
|
new_guid_str = uuid.uuid4().hex
|
||||||
|
guid_text_to_embed = f"Guid: {new_guid_str}"
|
||||||
|
|
||||||
|
source_path = article.source_path
|
||||||
|
|
||||||
|
# Ensure article object has the _content attribute
|
||||||
|
if not hasattr(article, '_content'):
|
||||||
|
log.error(f"Article '{article.title}' does not have '_content' attribute. Cannot embed Guid into source file.")
|
||||||
|
raise Exception(f"Cannot find raw content for article '{article.title}' to embed Guid.")
|
||||||
|
|
||||||
|
# Read the original file content.
|
||||||
|
# Python's open() in text mode uses universal newlines by default, converting \r\n and \r to \n.
|
||||||
|
# Pelican's MarkdownReader also provides article._content with \n newlines.
|
||||||
|
try:
|
||||||
|
with open(source_path, 'r', encoding='utf-8') as f:
|
||||||
|
current_body_content = f.read()
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to read original content from '{source_path}': {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Split this body content to find its first paragraph.
|
||||||
|
# Paragraphs in Markdown are separated by one or more blank lines (\n\n).
|
||||||
|
body_parts = current_body_content.split('\n\n', 1)
|
||||||
|
first_paragraph_of_body = body_parts[0]
|
||||||
|
rest_of_body_content = body_parts[1] if len(body_parts) > 1 else ""
|
||||||
|
|
||||||
|
# Append the Guid text to the end of the first paragraph of the body.
|
||||||
|
# .rstrip() removes any trailing whitespace/newlines from the paragraph itself before appending.
|
||||||
|
modified_first_paragraph_of_body = first_paragraph_of_body.rstrip() + '\n' + guid_text_to_embed
|
||||||
|
|
||||||
|
# Construct the full new file content by combining the original metadata part and the new body.
|
||||||
|
# This preserves the original metadata block verbatim (including comments, formatting, and original newline characters if any within it,
|
||||||
|
# as metadata_part_from_file is a direct slice from original_file_content_universal_newlines which has \n newlines).
|
||||||
|
full_new_content = modified_first_paragraph_of_body + '\n\n' + rest_of_body_content
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(source_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(full_new_content)
|
||||||
|
log.info(f"Successfully wrote updated content with embedded Guid to '{source_path}'.")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Failed to write updated content to '{source_path}': {e}")
|
||||||
|
raise # Re-raise the exception to halt processing if file write fails
|
||||||
|
|
||||||
|
# Set article.guid for the current Pelican run, so it's used for the feed item
|
||||||
|
article.guid = new_guid_str
|
||||||
|
log.debug(f"Set in-memory article.guid = '{new_guid_str}' for '{article.title}'.")
|
||||||
|
|
||||||
|
item['unique_id'] = article.guid
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
signals.feed_generated.connect(modify_feed)
|
||||||
|
|
@@ -1,12 +1,8 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block meta %}
|
{% block meta %}
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>{{ article.title|striptags }} | t0.vc</title>
|
<title>{{ article.title|striptags }} | t0.vc</title>
|
||||||
<meta charset=UTF-8>
|
|
||||||
<link rel="canonical" href="https://tannercollin.com/{{ article.slug }}/" />
|
<link rel="canonical" href="https://tannercollin.com/{{ article.slug }}/" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
{% if article.date %}
|
{% if article.date %}
|
||||||
<meta name="date" content="{{article.date}}" />
|
<meta name="date" content="{{article.date}}" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -20,77 +16,19 @@
|
|||||||
{% for tag in article.tags %}
|
{% for tag in article.tags %}
|
||||||
<meta name="tags" content="{{tag}}" />
|
<meta name="tags" content="{{tag}}" />
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if article.image %}
|
||||||
{% endblock %}
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:image" content="https://t0.vc/media/{{article.image}}" />
|
||||||
{% block style %}
|
<meta property="og:image" content="https://t0.vc/media/{{article.image}}" />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<style>
|
|
||||||
a.external {
|
|
||||||
background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Cpath fill='%23fff' stroke='%23000' d='M1.5 4.518h5.982V10.5H1.5z'/%3E%3Cpath fill='%23000' d='M5.765 1H11v5.39L9.427 7.937l-1.31-1.31L5.393 9.35l-2.69-2.688 2.81-2.808L4.2 2.544z'/%3E%3Cpath fill='%23fff' d='m9.995 2.004.022 4.885L8.2 5.07 5.32 7.95 4.09 6.723l2.882-2.88-1.85-1.852z'/%3E%3C/svg%3E%0A");
|
|
||||||
background-position: center right;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
padding-right: 15px;
|
|
||||||
}
|
|
||||||
.toclink:not(:hover) {
|
|
||||||
text-decoration: none;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
max-width: 600px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
.toclink {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
.highlight > pre {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.5rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
:not(pre)>code {
|
|
||||||
padding: 0 2px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
.toc {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
img:not(.floated) {
|
|
||||||
width: 600px;
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
.floated {
|
|
||||||
float: left;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
@media screen and (min-width:49rem) {
|
|
||||||
.content .aside {
|
|
||||||
display: inline;
|
|
||||||
float: right;
|
|
||||||
position: relative;
|
|
||||||
width: 8rem;
|
|
||||||
margin-right: -10rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<body>
|
|
||||||
<div class="content">
|
|
||||||
<p><a href="/">Home | t0.vc</a></p>
|
|
||||||
<h1>{{ article.title }}</h1>
|
<h1>{{ article.title }}</h1>
|
||||||
{{ article.summary }}
|
{{ article.summary }}
|
||||||
{{ article.locale_date }}
|
{{ article.locale_date }}
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
{{ article.content }}
|
{{ article.content }}
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -1,2 +1,81 @@
|
|||||||
{% block meta %}{% endblock %}<link rel=icon href=data:>{% block style %}{% endblock %}{% block content %}
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{% block meta %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
<meta charset=UTF-8>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
p, h1, h2, h3, h4, h5, h6 {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
a.external {
|
||||||
|
background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12'%3E%3Cpath fill='%23fff' stroke='%23000' d='M1.5 4.518h5.982V10.5H1.5z'/%3E%3Cpath fill='%23000' d='M5.765 1H11v5.39L9.427 7.937l-1.31-1.31L5.393 9.35l-2.69-2.688 2.81-2.808L4.2 2.544z'/%3E%3Cpath fill='%23fff' d='m9.995 2.004.022 4.885L8.2 5.07 5.32 7.95 4.09 6.723l2.882-2.88-1.85-1.852z'/%3E%3C/svg%3E%0A");
|
||||||
|
background-position: center right;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
.toclink:not(:hover) {
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
max-width: 50rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.toclink {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.highlight > pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
:not(pre)>code {
|
||||||
|
padding: 0 2px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
.toc {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
img:not(.floated) {
|
||||||
|
width: 600px;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
color-scheme: light;
|
||||||
|
}
|
||||||
|
.floated {
|
||||||
|
float: left;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
@media screen and (min-width:63rem) {
|
||||||
|
.content .aside {
|
||||||
|
display: inline;
|
||||||
|
float: right;
|
||||||
|
position: relative;
|
||||||
|
width: 10rem;
|
||||||
|
margin-right: -12rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{% block style %}
|
||||||
|
{% endblock %}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<p>← <a href="/">Home (t0.vc)</a></p>
|
||||||
|
<p><a href="/rss.xml">RSS Feed</a> | <a href="/atom.xml">Atom Feed</a></p>
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|