Compare commits

...

15 Commits

15 changed files with 640 additions and 267 deletions

View File

@@ -1,7 +1,7 @@
Title: My Backup Strategy
Date: 2021-04-08
Category: Writing
Summary: Details about the backup system for my data.
Summary: Details about the backup system for all of my data.
Wide: true
[TOC]
@@ -32,7 +32,7 @@ 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 parent's house.
rotate between my bag, offsite, and my parents' house.
## Backup Sources
@@ -127,6 +127,8 @@ Telegram Messenger is my main app for communication. My parents, most of my
friends, and friend groups are on there so I don't want to lose those messages
in case Telegram disappears or my account gets banned.
<span class="aside">Saves the messages to a sqlite db</span>
Telegram includes a data export feature, but it can't be automated. Instead I
run the deprecated software
[telegram-export](https://github.com/expectocode/telegram-export) hourly with
@@ -197,9 +199,9 @@ I run `rdiff-backup` on the remote server with cron:
55 14 1 * * rdiff-backup --remove-older-than 12B --force tbotbak@remotebackup::/mnt/backup/remote/tbotbak/monthly/t0txt/
```
The user `tbotbak` has write access only to the `/mnt/backup/remote/tbotbak`
directory. It has its own passwordless SSH key that's only permitted to run the
`rdiff-backup --server` command for security.
The `tbotbak` user has write access to the `/mnt/backup/remote/tbotbak`
directory only. It has its own passwordless SSH key that's only permitted to run
the `rdiff-backup --server` command for security.
### Protospace
@@ -217,7 +219,7 @@ an archive I download daily:
https://api.my.protospace.ca/backup/
```
The main website and [wiki](https://wiki.protospace.ca) that I sysadmin gets
The website and [wiki](https://wiki.protospace.ca) that I sysadmin get
backed up weekly:
```

392
content/bypassing-ports.md Normal file
View File

@@ -0,0 +1,392 @@
Title: Bypassing ISP Blocked Ports
Date: 2021-04-10
Category: Writing
Summary: 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 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 own username, this is temporary.
<span class="aside">The `.rnd` file prevents a warning</span>
```
$ cd easy-rsa/
$ ./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 DiffieHellman 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, 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.
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.

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">🏴‍☠️</text></svg>

After

Width:  |  Height:  |  Size: 119 B

View File

@@ -8,7 +8,7 @@ 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.
![a rusted metalic hand]({static}/images/hand-of-ozymandias/hand1.jpg)
![a rusted hand welded together out of scrap square stock metal tubing]({static}/images/hand-of-ozymandias/hand1.jpg)
## The Name
@@ -25,4 +25,4 @@ grinder. It was made in a machine shop with no real planning done ahead of time.
In between welds, I used my own hand as a reference. Below is a picture of me
adding a bead to it.
![me welding the hand causing a very bright light]({static}/images/hand-of-ozymandias/hand2.jpg)
![me welding the hand causing a very bright white light that washes out the photo]({static}/images/hand-of-ozymandias/hand2.jpg)

View File

@@ -8,14 +8,14 @@ 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 blue dress with a number of LEDs shining through the fabric]({static}/images/dress/dress1.jpg)
![a girl wearing a blue dress with a number of LEDs shining through the fabric]({static}/images/dress/dress1.jpg)
## Construction
The LEDs came from that strip that was cut up and soldered together with very
small wires. Each of the LEDs can be controlled individually.
![the controller circuit board on the left, and the soldered together LEDs on the right]({static}/images/dress/dress2.jpg)
![the controller circuit board, and the string of soldered together LEDs]({static}/images/dress/dress2.jpg)
21 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

View File

@@ -12,7 +12,7 @@ face-plate and turned it into a capacitive touch sensor. The slightest touch
anywhere on the plate is enough to toggle the light. I had to electrically
isolate the metal screws from it because they screw into a grounded switch box.
![a rusted metalic hand]({static}/images/light-switch/light1.jpg)
![my custom light switch, a Raspberry Pi computer, and an old iPhone]({static}/images/light-switch/light1.jpg)
## Function
@@ -26,8 +26,8 @@ by an AC-DC converter.
This entire process happens quicker than half a second, so it feels instant.
![a rusted metalic hand]({static}/images/light-switch/light2.jpg)
![the front side which has several electrical components]({static}/images/light-switch/light2.jpg)
<span class="aside">Black stuff's liquid electrical tape</span>
![a rusted metalic hand]({static}/images/light-switch/light3.jpg)
![the back side which has wires soldered to connect all the components]({static}/images/light-switch/light3.jpg)

View File

@@ -9,7 +9,7 @@ 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.
![a painting of water pouring out of a vase and into a hand]({static}/images/painting/painting1.jpg)
![a painting of water pouring out of a vase and into a hand, then turning to sand]({static}/images/painting/painting1.jpg)
## The Meaning
@@ -37,4 +37,4 @@ in. It was quite difficult to get the blending and shadows perfect, but I had
Laura to tell me when things didn't look right. Below I am trying to figure out
what a hand looks like in a mirror.
![me looking at my own hand in the mirror]({static}/images/painting/painting2.jpg)
![me looking at my own hand in the mirror as a guide]({static}/images/painting/painting2.jpg)

View File

@@ -38,4 +38,4 @@ it would run backwards until the tube was cleared of water.
<span class="aside">Also dead :(</span>
![the new version beside a big Min aralia plant]({static}/images/plant-waterer/waterer3.jpg)
![the new version beside a big Ming aralia plant with bushy drooping leaves and skinny stems]({static}/images/plant-waterer/waterer3.jpg)

View File

@@ -9,7 +9,7 @@ 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.
![the MPPT device, a printed circuit board with components]({static}/images/solar-car/solar1.jpg)
![the MPPT device, a printed circuit board with bulky round electrical components held in my hand]({static}/images/solar-car/solar1.jpg)
## The Helianthus MPPT
@@ -20,4 +20,4 @@ without them. The Generation IV car, Schulich Delta (pictured below) uses seven
of them: one per section of solar cells with similar lighting conditions. Andrei
and I designed the MPPT above.
![a photo of the solar car from the side]({static}/images/solar-car/solar2.jpg)
![the solar car from an angle with a driver inside]({static}/images/solar-car/solar2.jpg)

View File

@@ -34,6 +34,12 @@ MARKDOWN = {
'output_format': 'html5',
}
STATIC_PATHS = ['images', 'extra']
EXTRA_PATH_METADATA = {
'extra/favicon.svg': {'path': 'favicon.svg'},
}
# Uncomment following line if you want document-relative URLs when developing
#RELATIVE_URLS = True

View File

@@ -34,6 +34,12 @@ MARKDOWN = {
'output_format': 'html5',
}
STATIC_PATHS = ['images', 'extra']
EXTRA_PATH_METADATA = {
'extra/favicon.svg': {'path': 'favicon.svg'},
}
# Uncomment following line if you want document-relative URLs when developing
#RELATIVE_URLS = True
@@ -48,10 +54,10 @@ AUTHORS_SAVE_AS = ''
CATEGORIES_SAVE_AS = ''
TAGS_SAVE_AS = ''
INDEX_SAVE_AS = 'index.php'
INDEX_SAVE_AS = 'index.html'
ARTICLE_URL = '{slug}/'
ARTICLE_SAVE_AS = '{slug}/index.php'
ARTICLE_SAVE_AS = '{slug}/index.html'
PAGE_URL = '{slug}/'
PAGE_SAVE_AS = '{slug}/index.php'
PAGE_SAVE_AS = '{slug}/index.html'
PROD = True

View File

@@ -26,8 +26,10 @@
{% block info %}
Tanner Collin
<p class='theme-select'>
<a onClick="setTheme('light')">Light</a> / <a onClick="setTheme('dark')">Dark</a>
<p class="contact-icons">
<a href="mailto:site2@tannercollin.com" rel="noreferrer noopener"><img alt="email" src="/theme/mail.svg" width="20" height="20" /></a>
<a href="https://t.me/tannercollin" target="_blank" rel="noreferrer noopener"><img alt="telegram" src="/theme/telegram.svg" width="20" height="20" /></a>
<a href="https://github.com/tannercollin" target="_blank" rel="noreferrer noopener"><img alt="github" src="/theme/github.svg" width="20" height="20" /></a>
</p>
{% endblock %}
@@ -39,10 +41,6 @@
<div class="content">
{% endif %}
<div class="topbar">
{{ info() }}
</div>
<p><a href="/">← Return to Home</a></p>
<header>
<h1>{{ article.title }}</h1>

View File

@@ -11,8 +11,8 @@
<html lang="en">
<head>
{% block head %}
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8"/>
<title>{% block title %}{{ SITENAME }}{% endblock title %}</title>
<meta name="author" content="{{ AUTHOR }}" />
@@ -28,62 +28,30 @@
<link rel="preload" href="/theme/fonts/Lato-Italic.ttf" as="font" type="font/ttf" crossorigin="anonymous">
<link rel="preload" href="/theme/fonts/Lato-Regular.ttf" as="font" type="font/ttf" crossorigin="anonymous">
<link rel="icon" href="favicon.svg">
<style>
{% include 'style.css' %}
{% include 'fonts.css' %}
</style>
<script defer src="/theme/instant-page.js"></script>
<noscript>
<style type="text/css">
.theme-select {
display: none !important;
}
</style>
</noscript>
</head>
{% if PROD %}
<body class="<?php echo $themeClass; ?>">
{% else %}
<body>
{% endif %}
<body>
<div class="container">
<div class="bar">
{% block info %}
{% endblock %}
</div>
{%- macro info() -%}
{% block info %}{% endblock %}
{%- endmacro -%}
<div class="container">
<div class="sidebar">
{{ info() }}
{% block content %}
{% endblock %}
</div>
{% block content %}
{% endblock %}
</div>
<p class="copyright">
© 20122021 Tanner Collin
</p>
<script>
function setTheme(theme) {
console.log('Setting theme to', theme);
if (theme == 'dark') {
document.body.classList.add('dark');
document.body.classList.remove('light');
} else if (theme == 'light') {
document.body.classList.add('light');
document.body.classList.remove('dark');
}
document.cookie = 'theme=' + theme + '; Max-Age=31536000; Path=/; SameSite=Lax';
}
</script>
<p class="copyright">
© 20122021 Tanner Collin
</p>
</body>
</html>

View File

@@ -8,63 +8,115 @@
{% endblock %}
{% block info %}
<img src="/theme/me.jpg" class="me" alt="A picture of me smiling" />
<img src="/theme/me.jpg" width="128" class="me" alt="me smiling wide and looking into the camera lit up brightly" />
<div class="info">
Tanner Collin
<h1>Tanner Collin</h1>
<p class="contact-icons">
<a href="mailto:site2@tannercollin.com" rel="noreferrer noopener"><img alt="email icon" src="/theme/mail.svg" /></a>
<a href="https://t.me/tannercollin" target="_blank" rel="noreferrer noopener"><img alt="telegram logo" src="/theme/telegram.svg" /></a>
<a href="https://github.com/tannercollin" target="_blank" rel="noreferrer noopener"><img alt="github logo" src="/theme/github.svg" /></a>
</p>
<p class='theme-select'>
<a onClick="setTheme('light')">Light</a> / <a onClick="setTheme('dark')">Dark</a>
<a href="mailto:site2@tannercollin.com" rel="noreferrer noopener"><img alt="email" src="/theme/mail.svg" width="20" height="20" /></a>
<a href="https://t.me/tannercollin" target="_blank" rel="noreferrer noopener"><img alt="telegram" src="/theme/telegram.svg" width="20" height="20" /></a>
<a href="https://github.com/tannercollin" target="_blank" rel="noreferrer noopener"><img alt="github" src="/theme/github.svg" width="20" height="20" /></a>
</p>
</div>
{% endblock %}
{% block content %}
<div class="content">
<div class="content content-index">
<p>
Hi, I'm Tanner! I do firmware and web development in Calgary.
</p>
<div class="topbar">
{{ info() }}
<div class="leftcol">
<div class="inside">
<h2>Contact Info</h2>
<p>
Email: <a href="mailto:site2@tannercollin.com">site2@tannercollin.com</a> <br />
Telegram: <a href="https://t.me/tannercollin" target="_blank" rel="noreferrer noopener">@tannercollin</a>
</p>
<h2>Resume</h2>
<ul>
<li>Firmware Engineer at <a href="https://cabanablockchain.com" target="_blank" rel="noreferrer noopener">Cabana Blockchain</a>, 2018</li>
<li>Lead Hardware Engineer at <a href="https://criticalcontrol.com/" target="_blank" rel="noreferrer noopener">Critical Control</a>, 20162018</li>
<li>Electrical Engineer at <a href="https://www.opener.aero/" target="_blank" rel="noreferrer noopener">Opener Aero</a>, 20162016</li>
<li>Electrical Engineer Intern at <a href="https://www.pason.com/" target="_blank" rel="noreferrer noopener">Pason Systems</a>, 20142015</li>
<li>BSc. Electrical Engineering from University of Calgary</li>
</ul>
<h2>Projects</h2>
<p>
My main hobby is working on software projects. I typically design websites or
build tools that make my life easier.
</p>
<h3>
<a href="https://news.t0.vc/" target="_blank" rel="noreferrer noopener">QotNews</a>
<a class="source" href="https://git.tannercollin.com/tanner/qotnews" target="_blank" rel="noreferrer noopener">source code</a>
</h3>
<div class="summary">
<p>Hacker News, Reddit, Lobsters, and Tildes articles pre-rendered in reader mode. Optimized for speed and distraction-free reading.</p>
</div>
<h3>
<a href="https://notica.us" target="_blank" rel="noreferrer noopener">Notica</a>
<a class="source" href="https://github.com/tannercollin/Notica" target="_blank" rel="noreferrer noopener">source code</a>
</h3>
<div class="summary">
<p>Send browser notifications from your terminal. No installation. No registration.</p>
</div>
<h3>
<a href="https://my.protospace.ca" target="_blank" rel="noreferrer noopener">Spaceport</a>
<a class="source" href="https://github.com/Protospace/spaceport" target="_blank" rel="noreferrer noopener">source code</a>
</h3>
<div class="summary">
<p>Makerspace members' portal for Calgary Protospace. It tracks membership, courses, training, access cards, and more.</p>
</div>
<h3>
<a href="https://txt.t0.vc" target="_blank" rel="noreferrer noopener">t0txt</a>
<a class="source" href="https://github.com/tannercollin/t0txt" target="_blank" rel="noreferrer noopener">source code</a>
</h3>
<div class="summary">
<p>Minimal command line pastebin. Allows you to upload text notes from a bash pipe or web browser.</p>
</div>
<h3>
<a href="https://github.com/tannercollin/standardnotes-fs" target="_blank" rel="noreferrer noopener">standardnotes-fs</a>
</h3>
<div class="summary">
<p>A filesystem that mounts your Standard Notes account as a directory of text files that you can edit.</p>
</div>
</div>
</div>
<p>
Hi, I'm Tanner! I do firmware and web development in Calgary.
</p>
<div class="rightcol">
<div class="inside">
<h2>Creations</h2>
<h2>Contact Info</h2>
<p>Sometimes I create art or interactive tech.</p>
<p>
Email: <a href="mailto:site2@tannercollin.com">site2@tannercollin.com</a> <br />
Telegram: <a href="https://t.me/tannercollin" target="_blank" rel="noreferrer noopener">@tannercollin</a>
</p>
{% for article in articles_page.object_list if article.category.name == 'Projects' %}
<h3><a href="{{ article.url }}">{{ article.title }}</a></h3>
<div class="summary">
{{ article.summary }}
</div>
{% endfor %}
<h2>Resume</h2>
<h2>Writing</h2>
<ul>
<li>Firmware Engineer at <a href="https://cabanablockchain.com" target="_blank" rel="noreferrer noopener">Cabana Blockchain</a>, 2018</li>
<li>Lead Hardware Engineer at <a href="https://criticalcontrol.com/" target="_blank" rel="noreferrer noopener">Critical Control</a>, 20162018</li>
<li>Electrical Engineer at <a href="https://www.opener.aero/" target="_blank" rel="noreferrer noopener">Opener Aero</a>, 20162016</li>
<li>Electrical Engineer Intern at <a href="https://www.pason.com/" target="_blank" rel="noreferrer noopener">Pason Systems</a>, 20142015</li>
<li>BSc. Electrical Engineering from University of Calgary</li>
</ul>
<p>Various articles, mostly about computers.</p>
<h2>Projects</h2>
<p>
My main hobby is working on technical projects. I typically design websites or
build tools that make my life easier. Sometimes art.
</p>
<p>
You can find my code on <a href="https://github.com/tannercollin" target="_blank" rel="noreferrer noopener">GitHub</a>.
</p>
{% for article in articles_page.object_list if article.category.name == 'Projects' %}
<h3><a href="{{ article.url }}">{{ article.title }}</a></h3>
<div class="summary">
{{ article.summary }}
{% for article in articles_page.object_list if article.category.name == 'Writing' %}
<h3><a href="{{ article.url }}">{{ article.title }}</a></h3>
<div class="summary">
{{ article.summary }}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@@ -12,6 +12,14 @@ a {
outline: none;
}
.info h1 {
font: 1.2rem/1.0 Lato,sans-serif;
}
.source {
font: 1rem/1.5 Apparatus SIL,serif;
}
pre {
font-size: 1rem;
padding: 1rem;
@@ -33,77 +41,6 @@ pre {
text-align: center;
}
.theme-select {
font-size: 1rem;
margin-bottom: 0;
cursor: pointer;
}
.sidebar {
margin-top: 2px;
float: left;
width: 8rem;
text-align: right;
}
.sidebar .me {
width: 100%;
display: block;
}
.sidebar .info {
margin-top: 2rem;
}
.sidebar .contact-icons a {
border-bottom: none;
}
.sidebar .contact-icons img {
width: 1.25rem;
height: 1.25rem;
margin-left: 0.5rem;
}
.topbar {
display: table;
overflow: auto;
margin: auto;
margin-top: -0.5rem;
font-size: 1.5rem;
}
.topbar .me {
float: left;
height: 6.5rem;
width: auto;
}
.topbar .info {
float: left;
margin-top: -0.25rem;
margin-left: 1.5rem;
}
.topbar .contact-icons {
margin-left: 0.25rem;
margin-bottom: 0;
}
.topbar .contact-icons a {
border-bottom: none;
}
.topbar .contact-icons img {
width: 1.5rem;
height: 1.5rem;
margin-right: 1rem;
}
.topbar .theme-select {
margin-top: 0.5rem;
}
.toc {
float: right;
padding: 0.75rem;
@@ -136,9 +73,17 @@ pre {
}
.content-wide {
max-width: 46rem;;
}
.content-index {
max-width: none;
}
.content-index h3 {
font: 1.2rem/1.5 Apparatus SIL,serif;
}
.content p {
font: 1.2rem/1.5 Apparatus SIL,serif;
}
@@ -165,32 +110,103 @@ pre {
font-size: 1rem;
}
.toclink:not(:hover)::after {
visibility: hidden;
}
.toclink::after {
color: #999;
content: "\00B6";
margin-left: 0.5rem;
.toclink:not(:hover) {
border-bottom: none;
}
@media screen and (min-width:36rem) {
.content {
margin-left: 10rem;
}
.topbar {
display: none;
.bar {
margin-top: 2px;
float: left;
width: 8rem;
text-align: right;
}
.bar .me {
width: 100%;
display: block;
}
.bar .info {
margin-top: 1.75rem;
}
.bar .contact-icons a {
border-bottom: none;
}
.bar .contact-icons img {
width: 1.25rem;
height: 1.25rem;
margin-left: 0.5rem;
}
}
@media screen and (max-width:36rem) {
.sidebar {
display: none;
.bar {
display: table;
overflow: auto;
margin: auto;
margin-top: -0.5rem;
}
.bar .me {
float: left;
height: 6.5rem;
width: auto;
}
.bar .info {
float: left;
margin-top: 0.5rem;
margin-left: 1.5rem;
}
.bar .contact-icons {
margin-top: 1rem;
margin-left: 0.25rem;
margin-bottom: 0;
}
.bar .contact-icons a {
border-bottom: none;
}
.bar .contact-icons img {
width: 1.25rem;
height: 1.25rem;
margin-right: 1rem;
}
}
@media screen and (min-width:58rem) {
.container {
max-width: 75rem;
}
.leftcol {
float: left;
width: 50%;
margin-top: -1rem;
}
.leftcol > .inside {
padding-right: 1rem;
}
.rightcol {
overflow: hidden;
margin-top: -1rem;
}
.rightcol > .inside {
padding-left: 1rem;
}
}
body {
background-color: #eee;
@@ -218,75 +234,7 @@ pre {
color: #555;
}
body.dark {
background-color: #000;
color: #eee;
}
body.dark a {
color: #eee;
border-bottom: 1px solid #eee;
}
body.dark pre {
background-color: #222;
}
body.dark :not(pre)>code {
background-color: #222;
}
body.dark .toc {
background-color: #222;
}
body.dark .content p.metadata {
color: #aaa;
}
body.dark .content img {
filter: brightness(75%);
}
body.dark .contact-icons img {
filter: invert(1);
}
@media (prefers-color-scheme: dark) {
body.light {
background-color: #eee;
color: #000;
}
body.light a {
color: #000;
border-bottom: 1px solid #000;
}
body.light pre {
background-color: #ddd;
}
body.light :not(pre)>code {
background-color: #ddd;
}
body.light .toc {
background-color: #ddd;
}
body.light .content p.metadata {
color: #555;
}
body.light .contact-icons img {
filter: none;
}
body.light .content img {
filter: none;
}
body {
background-color: #000;
color: #eee;