feat: initial setup (1.0.0)

This commit is contained in:
rizaldy 2023-02-20 02:47:35 +07:00
parent 87fba23e51
commit 6671cf3a2a
No known key found for this signature in database
GPG Key ID: 510753C31098D86C
39 changed files with 1144 additions and 0 deletions

17
.eleventy.js Normal file
View File

@ -0,0 +1,17 @@
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
module.exports = (config) => {
config.addPlugin(syntaxHighlight);
config.addWatchTarget("./assets/css");
config.addPassthroughCopy("./assets");
config.addPassthroughCopy("./src/**/*.{jpg,png}");
return {
dir: {
input: "src",
output: "dist",
},
};
};

129
assets/css/reset.css Normal file
View File

@ -0,0 +1,129 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

349
assets/css/style.css Normal file
View File

@ -0,0 +1,349 @@
:root {
--body-bg-color: #eee;
--footer-fg-color: #666;
--base-font-size: 18px;
--font-family-serif: "Sentient", serif;
--font-family-sans-serif: "Clash Display", sans-serif;
--font-family-monospace: "JetBrains Mono", monospace;
--font-heading: var(--font-family-sans-serif);
--font-body: var(--font-family-serif);
--img-border-radius: 5px;
--img-transform: rotate(-1.337deg);
--img-border: 2px solid #333;
--layout-fragment-max-width: 666px;
--layout-fragment-padding: 4rem;
--layout-bg-color: #fff;
--line-color: #eee;
--anchor-color: inherit;
}
::selection {
background-color: #f8c8dc;
}
body {
background-color: var(--body-bg-color);
font-size: 16px;
font-family: var(--font-body);
text-rendering: optimizeLegibility;
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: left;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: var(--font-heading);
font-weight: 600;
}
a {
color: var(--anchor-color);
}
img {
border-radius: var(--img-border-radius);
transform: var(--img-transform);
border: var(--img-border);
max-width: 100%;
}
hr {
border: none;
border-top: 1px solid var(--line-color);
}
ul {
margin-top: 1rem;
margin-bottom: 1rem;
padding-left: 1.5rem;
}
li {
list-style: square;
line-height: 1.8rem;
}
p {
line-height: 1.8rem;
margin-bottom: 0.5rem;
}
p + p {
margin-top: 1.3rem;
}
strong {
font-weight: bold;
}
em {
font-style: italic;
}
code[class*="language-"],
pre[class*="language-"],
code,
pre {
font-family: var(--font-family-monospace);
font-size: 14px;
}
pre[class*="language-"] {
border-radius: 5px;
margin-top: 1.5rem;
margin-bottom: 1.5rem;
padding: 1rem;
}
code {
background-color: #eee;
font-size: 14px;
padding: 2px 6px;
border-radius: 3px;
}
pre[class*="language-"] code {
padding: 0;
}
.l-container {
max-width: 1024px;
margin: 0;
background-color: var(--layout-bg-color);
padding-top: 0.8rem;
padding-bottom: 0.8rem;
border-radius: 5px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.l-fragment {
margin: auto;
padding: 1rem;
max-width: var(--layout-fragment-max-width);
}
.l-fragment h2 {
font-size: 2rem;
}
.l-fragment h3 {
font-size: 1.6rem;
margin-top: 2rem;
line-height: 2rem;
text-align: left;
}
.l-fragment h2 + p {
margin-top: 0.5rem;
}
.l-fragment--blog {
text-align: left;
}
.l-fragment--blog h1 {
font-size: 2.3rem;
margin-bottom: 0.3rem;
}
.l-fragment--blog h2 {
margin-top: 2rem;
line-height: 2.3rem;
}
.l-fragment--blog img {
transform: rotate(0deg);
border: none;
}
.l-fragment--blog li {
margin-bottom: 0.3rem;
}
.c-bio {
margin-top: 1.8rem;
}
.c-bio__avatar {
float: left;
width: 100%;
}
.c-bio__info {
float: left;
width: 100%;
}
.c-bio__info h2 {
margin-top: 1rem;
}
.c-bio__info ul {
padding-left: 1.2rem;
}
.c-bio__info li {
line-height: 1.5rem;
}
.c-footer {
border-top: 1px solid var(--line-color);
color: var(--footer-fg-color);
font-size: 14px;
padding: 0 1.3rem;
padding-top: 1.5rem;
}
.c-footer__copyleft {
float: left;
width: 100%;
}
.c-footer__links {
float: left;
width: 100%;
}
.c-footer__copyleft p {
line-height: 1.3rem;
}
.c-footer ul {
margin-top: 1rem;
padding-left: 0;
}
.c-footer li {
display: block;
}
.c-article__meta-info {
font-size: 16px;
color: #666;
}
.c-article__meta-info-tag {
margin-right: 0.3rem;
}
/* https://nicolasgallagher.com/micro-clearfix-hack/ */
.u-clearfix {
zoom: 1;
}
.u-clearfix:before,
.u-clearfix:after {
content: " ";
display: table;
}
.u-clearfix:after {
clear: both;
}
.u-no-underline {
text-decoration: none;
}
.u-underline--hover:hover {
text-decoration: underline;
}
.u-text-left {
text-align: left;
}
.u-text-right {
text-align: right;
}
@media screen and (min-width: 30em) {
body {
text-align: justify;
font-size: var(--base-font-size);
}
.l-container {
margin: 8rem auto;
margin-bottom: 0;
}
.l-fragment {
padding: var(--layout-fragment-padding);
}
.l-fragment h1 {
font-size: 2.8rem;
}
.l-fragment h2 {
font-size: 2.6rem;
}
.l-fragment h3 {
font-size: 2rem;
margin-bottom: 0.3rem;
line-height: 2.5rem;
}
.l-fragment--blog h2 {
line-height: 2.8rem;
}
.l-fragment--blog li {
margin-bottom: 0.5rem;
}
.c-bio__avatar {
width: 25%;
}
.c-bio__info {
width: 75%;
}
.c-bio__info h2 {
margin-top: -8px;
}
.c-bio__info ul {
padding-left: 1.2rem;
}
.c-footer__links {
width: 60%;
text-align: right;
}
.c-footer__copyleft {
width: 40%;
}
.c-footer ul {
margin-top: 0;
}
.c-footer li {
display: inline-block;
padding-left: 1.5rem;
}
}

BIN
assets/img/avatar.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

15
src/_data/consulting.js Normal file
View File

@ -0,0 +1,15 @@
const CONSULTING_STATUS = {
AVAILABLE: "AVAILABLE",
UNAVAILABLE: "UNAVAILABLE",
};
const consulting = {
status: CONSULTING_STATUS.UNAVAILABLE,
clear_status_after: 1685552400,
timezone: "UTC+7",
hours_per_week: 25,
};
module.exports = () => {
return consulting;
};

21
src/_data/contacts.js Normal file
View File

@ -0,0 +1,21 @@
const contacts = [
{
name: "Email",
label: "me@rizaldy.club",
uri: "mailto:me@rizaldy.club",
},
{
name: "Keybase",
label: "keybase.io/rizaldy",
uri: "https://keybase.io/rizaldy",
},
{
name: "CV",
label: "read.cv/rizaldy",
uri: "https://read.cv/rizaldy",
},
];
module.exports = () => {
return contacts;
};

9
src/_data/employer.js Normal file
View File

@ -0,0 +1,9 @@
const currentEmployment = {
name: "a Data Infrastructure Company",
position: "Senior DevOps Engineer",
location: "Jakarta, ID",
};
module.exports = () => {
return currentEmployment;
};

13
src/_data/site.js Normal file
View File

@ -0,0 +1,13 @@
const siteConfig = {
name: "rizaldy.today",
repo: "https://github.com/faultables/rizaldy.today",
last_commit: "main",
author: {
name: "Rizaldy",
avatar: "/assets/img/avatar.jpeg",
},
};
module.exports = () => {
return siteConfig;
};

View File

@ -0,0 +1,24 @@
<article class="c-article">
{%- for post in posts limit: limit reversed -%}
<div>
<h3>
<a class="u-no-underline u-underline--hover" href="{{ post.url }}">{{ post.data.title }}</a>
</h3>
{%- if with_meta_info? -%}
<p class="c-article__meta-info">
<time>{{ post.date | date: '%b %d, %Y' }}</time> •
{% for tag in post.data.tags %}
{% unless tag == 'posts' %}
<span class="c-article__meta-info-tag"
><a class="u-no-underline u-underline--hover" href="#{{tag}}">{{ tag | upcase }}</a></span
>
{% endunless %}
{% endfor %}
</p>
{%- endif -%}
{{ post.content | split: '\n' | first }}
</div>
{%- endfor -%}
</article>

View File

@ -0,0 +1,20 @@
<div class="c-bio u-clearfix">
<div class="c-bio__avatar">
<img src="{{ site.author.avatar }}" alt="{{ site.author.name }}">
</div>
<div class="c-bio__info">
<h2>{{ site.author.name }}</h2>
<p>{{ employer.position }}</p>
<ul>
{%- for contact in contacts -%}
<li>
<a class="u-no-underline u-underline--hover" href="{{ contact.uri }}">
{{ contact.name }}: {{ contact.label -}}
</a>
</li>
{%- endfor -%}
</ul>
</div>
</div>

View File

@ -0,0 +1,20 @@
<footer class="c-footer u-clearfix">
<div class="c-footer__copyleft">
<p class="u-text-left">
&copy; MMXXIII {{ site.author.name }}. Any and all opinions listed here are my own and not representative of my
employers; future, past and present.
</p>
</div>
<div class="c-footer__links">
<ul>
<li><a class="u-no-underline u-underline--hover" href="/">About</a></li>
<li><a class="u-no-underline u-underline--hover" href="/blog">Writings</a></li>
<li><a class="u-no-underline u-underline--hover" href="/colophon">Colophon</a></li>
<li>
<a class="u-no-underline u-underline--hover" href="{{ site.repo }}/commit/{{ site.last_commit }}"
>Source Code</a
>
</li>
</ul>
</div>
</footer>

View File

@ -0,0 +1,20 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>
{{ title }}
- rizaldy.today
</title>
<link rel="preconnect" href="https://unpkg.com">
<link rel="preconnect" href="https://api.fontshare.com">
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://unpkg.com/prismjs@1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
<link href="https://api.fontshare.com/v2/css?f[]=clash-display@1&f[]=sentient@1&display=swap" rel="stylesheet">
<link href="https://fonts.bunny.net/css?family=jetbrains-mono:400" rel="stylesheet">
<link href="/assets/css/reset.css" rel="stylesheet">
<link href="/assets/css/style.css" rel="stylesheet">
</head>

View File

@ -0,0 +1,7 @@
<script
async
defer
data-website-id="868b4c29-d510-44fb-9404-50c19680d322"
data-do-not-track="true"
src="https://u.rizaldy.today/umami.js"
></script>

View File

@ -0,0 +1,15 @@
<div class="l-fragment">
{% render "elements/bio", site: site, contacts: contacts, employer: employer %}
Hi, I'm {{ site.author.name }}. I am currently a {{ employer.position }} at {{ employer.name }} based in {{ employer.location }}. There have been so many changes in my life, but a few things that have remained unchanged are my interests in:
- Infrastructure Management
- System Administration
- Developer Experience
I used to do software development profesionally since 2015, mostly web application development. Since the 2020s my deep interests has been in making software developers more productive & happy while keeping systems up & running, securely.
I will be happy to help solve problems in any organization in any industry with my expertise.
</div>

View File

@ -0,0 +1,27 @@
<div class="l-fragment">
## Services
{% if consulting.status == "AVAILABLE" %}
I am currently available for consulting work and if you need hand with an issue you currently have in your organization, let's talk. My
current time zone is {{ consulting.timezone }} and I'm available to work {{ consulting.hours_per_week }} hours per week.
{% else %}
I am currently unavailable for consulting work (expected until {{ consulting.clear_status_after | date: "%b %Y" }}).
{% endif %}
{% for service in services %}
<div>
<h3>
<a class="u-no-underline u-underline--hover" href="{{ service.url }}">
{{ service.data.title }}
</a>
</h3>
{{ service.content | split: '\n' | first }}
</div>
{% endfor %}
</div>

View File

@ -0,0 +1,13 @@
<div class="l-fragment">
## Writings
I love writing. Here are the 3 most recent posts:
{% render "elements/archives", posts: posts, limit: 3 %}
<br />
[View all posts](/blog)
</div>

View File

@ -0,0 +1,8 @@
---
layout: layouts/base
---
<div class="l-fragment">
{{ content }}
{% render 'elements/archives', posts: collections.posts, with_meta_info?: true %}
</div>

View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
{% render 'elements/head', title: title %}
<body>
<main class="l-container">
{{ content }}
{% render 'elements/footer', site: site %}
</main>
{% render 'elements/scripts' %}
</body>
</html>

View File

@ -0,0 +1,21 @@
---
layout: layouts/base
---
<div class="l-fragment l-fragment--blog">
<div class="c-article">
<h1>{{ title }}</h1>
<p class="c-article__meta-info">
<time>{{ date | date: '%b %d, %Y' }}</time> •
{% for tag in tags %}
{% unless tag == 'posts' %}
<span class="c-article__meta-info-tag"
><a class="u-no-underline u-underline--hover" href="#{{tag}}">{{ tag | upcase }}</a></span
>
{% endunless %}
{% endfor %}
</p>
{{ content }}
</div>
</div>

View File

@ -0,0 +1,9 @@
---
layout: layouts/base
---
<div class="l-fragment">
<h2>Still WIP</h2>
<p>Thank you for stopping by.</p>
<br>
<a href="/"> Back to home </a>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -0,0 +1,197 @@
---
title: Expose Web Services at Home via Tailscale for Fun
layout: layouts/blog
date: "2021-10-16T13:37:56+07:00"
tags:
- posts
- tailscale
---
I have a small homelab server at home running TrueNAS Core. My home network sits behind NAT and there are probably 3 routers in front of me.
On the other hand I also have a small VM that has a static public IP address somewhere in Singapore. Some of my services need to be exposed to the internet for my friends to access—for example—this blog via DNS because remembering a domain address is more fun than a random number with 4 dots.
The problem is that this blog is running on my NAS and using a private IP address.
Also, my ISP assigns a public IP Address to my network dynamically. The simplest way to expose my services on my NAS to the Internet might be using Dynamic DNS but sometimes it's not as easy as it sounds.
So maybe I need to connect one of my VMs in Singapore with my NAS at home. Since it is not possible to connect ethernet cable from NAS to DigitalOcean data center in Singapore, so I have to connect it virtually.
And yes, by creating a VPN.
## Tailscale VPN
Previously I used [Wireguard](https://wireguard.com) with a hub-and-spoke network because managing the keys on each of my machines was quite a chore.
Then I found out [Zerotier](https://zerotier.com) from a random page on Reddit. Zerotier uses a mesh network and it's cute how my machines can talk to each other on a peer-to-peer basis.
My problem with Zerotier is sometimes the network is somewhat unreliable and maybe it's my poor VM's fault. Also sometimes my machines just randomly can't talk to each other via the Zerotier assigned address and I believe it's a firewall issue.
So I found out Tailscale from one of my friends on Twitter (actually he is my boss at work). Tailscale is built on Wireguard and is a mesh network. Even though Wireguard has [its own](https://github.com/WireGuard/wg-dynamic) mesh network solution, it's still WIP and using [alternative](https://github.com/k4yt3x/wg-meshconf) is quite difficult because, again, managing keys is a pain.
Then I give Tailscale a try. Installing and running Tailscale is easy enough that even my gf (who is non an IT person) can use it without wondering what public/private key means.
Our devices can talk to each other even though we're on different networks, and that's cool. In most cases, we can communicate peer-to-peer and that's really great.
## Exposing web services on different private networks
We have a secret journal running on my NAS and only accessible over the local network. The domain address is [1460.rizaldy.club](https://1460.rizaldy.club) and resolves to a local IP address on the 192.168.1.0/24 subnet so maybe if you access it you will see a random web page (or none at all) rather than our secret journal.
My gf's private network uses the 10.26.0.0/24 while our journal lives in 192.168.1.242. The key is I use [subnet routers](https://tailscale.com/kb/1019/subnets/) and I have Tailscale on my router (and on my device as well) at home. While Tailscale for iOS (and others) has "accept routes" enabled by default, that means our secret journal is directly accessible out of the box because my router advertises the 192.168.1.0/24 subnet and we're on the same tailnet.
![some diagram](./Untitled-2021-10-16-0110.png)
And that's cool.
I never even touched Tailscale.app on her phone just to make sure everything was working fine, because it is. We can access it anywhere without having to expose the service to the internet, and that's it the point.
## Exposing web services on a private network to the Internet
I have a minio instance on my NAS and sometimes I upload cat photos there. My minio is accessible at [faultables-s3.lan](https://faultables-s3.lan) address and since I'm using [Magic DNS](https://tailscale.com/kb/1081/magicdns) too, devices on my tailnet can resolve that domain (thanks to split tunnel) then anyone on my tailnet can see the cat photo I uploaded so maybe I can stop use imgur service as well.
But I also want my friends to know because my friends are nice and they deserve to see cats. To allow my friend to access it without doing anything, I need a static public IP address and my VM has it. Connecting my VM with my NAS via Tailscale was the answer and that's why you can see [this cat](https://s3.edgy.social/0x0/bff5d074d399bdfec6071e9168398406.jpg) right on your screen.
The setup is pretty simple, I just pointed s3.faultable.dev to my VM's IP, set up [Caddy Web Server](https://caddyserver.com) there, and told Caddy to proxy the request to 192.168.1.170:9000.
Here's another diagram:
![I did my best to visualize it](./Untitled-2021-10-15-2135-2048x985.png)
Packets between my VM and my router are transmitted in an end-to-end encrypted using the Wireguard protocol so no one can see and/or modify the packets even if no one cares and that's cool.
## Reverse Proxy as a Service for fun
So I just mentioned Tailscale on Twitter about my recent random thoughts:
![https://static-tweet.vercel.app/1448966405391405056](./Screen-Shot-2021-10-15-at-9.47.40-PM.png)
[@apenwarr's](https://twitter.com/apenwarr/status/1448972965110898693) answer regarding my thoughts is perfectly understandable: it's not Tailscale's main business (nor focus) so why don't I create one?
Imagine you don't have to touch any server to just proxy web requests from the public internet to machines in your tailnet. Invite my machine to your tailnet, tell me the address of the domain you own, then tell me where to proxy requests for it.
In my mind is to setup an OpenResty instance with Redis as a data store, so let's make it official.
I'll be using a service from Fly.io (because I'm very interested in their service) to deploy OpenResty. For a high-level view of how it works, here's another cool diagram:
![](./Untitled-2021-10-16-0110-2.png)
Actually I haven't deployed any instances to fly.io when creating this diagram, but if you can see this post it's very likely the diagram above is working.
Now let's try this out.
## Minimum Viable RPaaS
We'll use openresty/openresty Docker's image as base image because we'll deploy it to fly.io plus we'll bring the Tailscale app on it later.
We'll be using the [openresty/openresty](https://hub.docker.com/r/openresty/openresty) docker image as the base image as we'll be deploying it to fly.io plus we'll be bringing the Tailscale app over later.
I'll be using a managed Redis solution from [Upstash](https://upstash.com) instead of Fly.io and we'll talk about it later. Now let's write the nginx.conf file:
```nginx
worker_processes 2;
error_log logs/error.log info;
events {
worker_connections 1024;
}
env REDIS_HOST;
env REDIS_PORT;
env REDIS_PASSWORD;
http {
server {
listen 80;
location / {
set $upstream '';
access_by_lua '
local redis = require "resty.redis"
local redisc = redis:new()
local target = ngx.var.host
local redis_host = os.getenv("REDIS_HOST")
local redis_port = os.getenv("REDIS_PORT")
local redis_password = os.getenv("REDIS_PASSWORD")
local connect, err = redisc:connect(redis_host, redis_port, {
ssl = true
})
if not connect then
ngx.log(ngx.ERR, "failed to connect to redis", err)
return ngx.exit(500)
end
local auth, err = redisc:auth(redis_password)
if not auth then
ngx.say("failed to authenticate", err)
return ngx.exit(500)
end
local get_upstream, err = redisc:get(target)
if not get_upstream or get_upstream == ngx.null then
ngx.log(ngx.ERR, "no host found for key", key)
return ngx.exit(404)
end
ngx.var.upstream = get_upstream
';
proxy_pass http://$upstream;
}
}
}
```
The configuration above in short is to tell OpenResty to handle the request based on its Host header and forward the request based on the existing values. When the host is not listed in our Redis record, we will return a 404 Not Found page because it is.
The very simple data we're storing for now is just like this:
```bash
<target_host>:<upstream_ip>:<upstream_port>
```
So if I want to handle every incoming request from nginx.init8.lol to 100.73.204.66:42069, the operation is as simple as `SET nginx.init8.lol 100.73.204.66:42069`.
[![](./Screen-Shot-2021-10-16-at-4.07.16-AM-2048x1305.png)](./Screen-Shot-2021-10-16-at-4.07.16-AM-2048x1305.png)
Please keep in mind that since packets are end-to-end encrypted means that target upstream need to be directly to be the Tailscale IP. For example, port forwarding from your WAN to your LAN on your router won't work because packets are encrypted unless you set reverse proxy on your router (and let the proxy do the job).
## Making it more official
Now let's deploy our OpenResty to the fly.io platform. In order to connect Tailscale with Fly.io we can use [this guide](https://tailscale.com/kb/1132/flydotio) from Tailscale Docs. Our fly.io instance will only have Tailscale IPv6 so we need to point our domain to use Tailscale IPv6 so fly.io can route traffic to your machine.
So I'm going to create a frontend for this service so that it can interact with Redis via a REST API. It's still a WIP at the moment (I'm writing this post while creating the service lol) but I wanted to let my internet friends know I'm developing something fun (or at least for me).
The common setting than this is to set [ngrok.io](https://ngrok.io), tell ngrok.io which port you want to expose then you will get a unique URL. That's pretty cool but what if I'm dogfooding this?
So I point app.init8.lol to ts-proxy.fly.io, invite the machine to my tailnet, then tell the proxy to point that domain to [fd7a:115c:a1e0:ab12:4843:cd96:6258:9a11]:80 which is my Tailscale IPv6 address on my mac that running Next.js app on tmux.
![](./Screen-Shot-2021-10-16-at-5.43.20-AM.png)
Go [visit this](https://app.init8.lol) to try it out before my mac dies! (dead)
## What's next?
My main point is to destroy my DigitalOcean droplet which only does one thing which is proxying requests. Every time I run new service, I need to SSH into my VM; update `Caddyfile`, `systemctl reload caddy`, and so on. Apart from that I need to manage & maintain the server and I'm too lazy for that.
In future I would like to add some nice functionality like:
- Issuing an SSL certificate from Let's Encrypt. In theory this is possible as long as your domain can complete HTTP-01 challenge.
- GUI access to manage proxies so I don't have to use curl anymore when adding new service
- Hardening the security
- Make it work properly & correctly
This project is pretty fun and it took me 5-ish hours while I wrote this blog post to create a PoC.
If you have a web service at home and want to expose it to the public internet via the Fly.io network, mention me on ~~Twitter [@200GbE](https://twitter.com/200GbE) (updated)~~ Mastodon [@rizaldy@edgy.social](https://edgy.social/@rizaldy) and let's chat.
You can also check out [this project](https://git.edgy.social/rizaldy/rpaas) on this repository to learn more, especially there's something I haven't attached here like the Dockerfile and fly.toml files.
## Demo (dead)
- [Demo 1](https://nginx.init8.lol) (machine with a static public IP address, my devbox)
- [Demo 2](https://app.init8.lol) (machine with a dynamic public IP address, my laptop)
- [Demo 3](https://ts-proxy.fly.dev) (fly.io instance for this project)

8
src/blog/index.md Normal file
View File

@ -0,0 +1,8 @@
---
title: Blog
layout: layouts/archives
---
## Blog
Technically a blog.

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -0,0 +1,76 @@
---
title: Syncthing Anywhere With Tailscale
layout: layouts/blog
date: "2022-01-04T13:37:13+07:00"
tags:
- posts
- tailscale
- syncthing
---
I don't archive data very often but when I do it must be for a very important one. On the other hand, I somewhat don't trust "cloud" providers and would avoid them as much as I can since my paranoid level is kinda high.
And just like everyone else, I run servers at home. The servers are not that powerful but sufficient for my needs. I have two servers run but the most important (and data-heavy) is the box with TrueNAS OS.
Previously I use NextCloud to store my data plus I could access it anywhere. NextCloud is a powerful platform with rich features, I even can run an ActivityPub-based social network there. But the client app is somewhat heavy and the server consumes more resources than I thought, plus the caching system on the client app is somewhat buggy. I can't depend my life on NextCloud, or maybe someday will.
I know I can use `rsync(1)` or even `rclone(1)` on my computer but they both do different jobs. I need a system to pull-and-push data not just push.
And don't ask me why I'm not using BTSync.
Then I found [Syncthing](https://syncthing.net) somewhere. It only does one thing and probably does it well: **sync data**. Its tagline is _"Syncthing is a **continuous file synchronization program**. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how it's transmitted over the internet."_ and it really took my heart.
And then I installed Syncthing, used it for some time (until now), and that's why we are here.
## Syncing concepts
Syncthing will establish direct connections between clients (peer-to-peer) as much as possible, and as we know, p2p connection is never easy. And if that's not possible, traffic is bounced through the "relay" until both computers figured out how to establish a direct connection — once again, if possible.
Syncthing uses ["Device ID"](https://docs.syncthing.net/dev/device-ids.html) as an identifier so that neither party needs to know each other's IP address thanks to [Global Discovery](https://docs.syncthing.net/users/stdiscosrv.html). I just used the Local Discovery feature and turned the 'Enable Relaying' option off so I know the only connection made to .syncthing.net should be just sending an (anonymous) usage report, and I'm fine with that.
While this approach only makes my life harder, at least I know what I know even though all packets are encrypted in transit (via TLS) and are every device is authenticated by a cryptographic thing.
## LAN anywhere
All my servers at home are almost locked-down. Only traffic to port 80 is allowed in on my WAN. Not even port forwarding because I don't know much about computer and networking security.
So I designed my home network to be limited to working only on LAN. I don't know exactly how but I just made TCP listen only to the private network. Like, If I can only visit Syncthing GUI via 192.168.1.56 then I can only visit Syncthing GUI over 192.168.1.0/24 network, on my network.
Speaking of my Syncthing, it's running on FreeBSD jail with a dedicated IP address from my DHCP server.
And as always, the only way to connect a private network over public networks is by establishing a VPN, assuming I'm not interested yet in a commercial "Zero Trust Network" solution that maybe tunnels any TCP/UDP packets through a commercial reverse proxy because the packets are not end-to-end encrypted.
Anyways, I use [Tailscale](https://tailscale.com) and am a huge fan of both the team and the product. Tailscale is built on top Wireguard, and I used to use direct Wireguard until I was overwhelmed with mesh networking.
Using Tailscale is pretty straight forward, I don't have to manage keys; know each peer's IP address and public key, define DNS and even perform key rotation. It just works.
The problem is that my Syncthing is running on the FreeBSD jail and can't dial `devd(8)` in the jail whereas Tailscale needs to monitor network state changes (they have a [workaround](https://github.com/tailscale/tailscale/pull/3508) for this! but I haven't tried it yet). Since I'm also running Tailscale on my TrueNAS, I can use ["subnet router"](https://tailscale.com/kb/1019/subnets) which in short acts as a gateway of my physical subnet.
```bash
tailscale up --advertise-routes=192.168.1.0/24
```
With the above command, I can access my Syncthing (and other jails too) wherever I am!
## Throughput
Although the average latency in transmitting ICMP packets is unnoticeable, in the real world we mostly deal with TCP packets.
Syncthing transmits packets over TLS on top of TCP (which is great!) but in my needs it adds overhead as packets are already transmitted over secure protocol (Wireguard).
When I'm on my home network, the throughput is about 44 Mbits/s to my Syncthing jail since my laptop is connected over a WiFi network and on a different subnet. Ideally if on the same subnet (and over wired connection) my router and server can reach up to 900MiB/s.
What if I'm out of the house?
On a 50 Mbps network, using iperf3, I get around 6.49 MiB/s to my Syncthing jail and 8.56 MiB/s to direct host (depending on your network) which is… acceptable, kinda.
How about transferring 1.2GB files to my Syncthing jail over Tailscale?
![](./CleanShot-2022-01-04-at-9.18.04@2x.png)
2.02 MiB/s is not bad enough, I guess?
## Conclusion
Tailscale here is optional as Syncthing does the NAT traversal for you and also uses a secure protocol. Syncthing will do its best to establish a peer to peer connection and that's great!
However, with Tailscale I can access my "shared directory" via SAMBA on my other devices, anywhere. And also don't need any "Relay Server" only if my device can't talk peer-to-peer as Tailscale will do it for me :))
As closing, using both Tailscale and Syncthing is the best combination if you don't want to depend on a (cloud) storage providers.

View File

@ -0,0 +1,10 @@
---
title: "Unboxing The Cloud (UTC): Intro"
layout: layouts/blog
eleventyExcludeFromCollections: true # TODO :))
tags:
- posts
- utc
---
WIP

View File

@ -0,0 +1,10 @@
---
title: "Unboxing The Cloud (UTC): VMs"
layout: layouts/blog
eleventyExcludeFromCollections: true # TODO :))
tags:
- posts
- utc
---
WIP

36
src/colophon.md Normal file
View File

@ -0,0 +1,36 @@
---
title: Colophon
layout: layouts/base
---
<div class="l-fragment">
## Website colophon
This page outlines technical details about this website. You can learn more about the purpose and content of the website, as well as the author on the [About](/) page.
This site was built using a boring [static site generator](https://www.11ty.dev), written in a boring [template](https://daringfireball.net/projects/markdown/) [language](https://shopify.github.io/liquid/) and served by boring [S3 compatible storage](https://min.io) behind a boring [reverse proxy](https://nginx.org/en/).
The three primary fonts I use are:
- [Sentient](https://www.fontshare.com/fonts/sentient) (serif)
- [Clash Display](https://www.fontshare.com/fonts/clash-display) (sans-serif)
- [JetBrains Mono](https://www.jetbrains.com/lp/mono/) (monospace)
Syntax highlighting is done by the great [Prism.js](https://prismjs.com/), statically generated of course.
Every request to this site is handled by [Bunny CDNs](https://bunny.net/?ref=2jpxrtyw72) exclusively in the APAC region for a reason. Nothing here is dynamic, so, maybe stop scanning `config.php`, `../../etc/passwd`, `.git` or something like that.
I'm experimenting with [stale-while-revalidate](https://www.rfc-editor.org/rfc/rfc5861) in Bunny by keeping track of the latest commit id under this site to maximize the cache-hit ratio, so apologies if you run into any inconsistency issues.
And please don't [poison the cache](https://portswigger.net/research/practical-web-cache-poisoning), I guess.
If you enjoy the site, consider to [giving me a word]({{ site.repo }}/discussions/new?category=show-and-tell) or [some love](https://github.com/sponsors/faultables) (my love language is words of affirmation and github sponsors btw).
---
_You may notice that I use [self-hosted](https://u.rizaldy.today), [open source privacy-focused web analytics](https://umami.is/). I respect your [DNT preferences](https://www.w3.org/TR/tracking-dnt/) (despite its [depreciation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/DNT)) and it's likely that your uBlock Origin rule is blocking the `umami.js` just like mine._
_Otherwise, no personal information is stored unless you treat your user agent as your own unique identity (sorry)._
</div>

14
src/index.md Normal file
View File

@ -0,0 +1,14 @@
---
title: About
layout: layouts/base
---
{% render "fragments/about.md", site: site, contacts: contacts, employer: employer %}
---
{% render "fragments/writings.md", posts: collections.posts %}
---
{% render "fragments/services.md", services: collections.services, consulting: consulting %}

8
src/services/devops.md Normal file
View File

@ -0,0 +1,8 @@
---
title: DevOps Engineering
layout: layouts/service
tags:
- services
---
I can help organizations move fast and break less thing by adopting best practies from system administration; release engineering, infrastructure provisioning and management, security, to DevOps advocacy.

9
src/services/devrel.md Normal file
View File

@ -0,0 +1,9 @@
---
title: DevRel
layout: layouts/service
eleventyExcludeFromCollections: true # TODO :))
tags:
- services
---
I have no experience in "devrel-ing" profesionally. But I write a lot, and I love writing. Sometimes I give talks too. Some people tend to like my writing by tipping and/or signing up for a service I use with my referral code—maybe that helps.

8
src/services/rnd.md Normal file
View File

@ -0,0 +1,8 @@
---
title: R&D Engineer
layout: layouts/service
tags:
- services
---
Reduce time to market without compromising quality and stability by choosing the right tools for the job. I can help you to choose the right one.

8
src/services/sre.md Normal file
View File

@ -0,0 +1,8 @@
---
title: Site Reliability Engineering
layout: layouts/service
tags:
- services
---
"Hope is not a strategy" as traditional SRE would say. I can help scale from availability; latency, performance, efficiency, change management, monitoring, emergency response, to capacity planning.

9
src/services/webapp.md Normal file
View File

@ -0,0 +1,9 @@
---
title: Web App Development
layout: layouts/service
eleventyExcludeFromCollections: true # TODO :))
tags:
- services
---
I don't write web apps professionally anymore but I have a great team to do so.