feat: use goldmark and minify
parent
3b34119da5
commit
b811761c00
|
@ -1,20 +0,0 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: "Recursive", sans-serif;
|
|
||||||
font-variation-settings: "MONO" 0, "CASL" 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: "Recursive", monospace;
|
|
||||||
font-variation-settings: "MONO" 1, "CASL" 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<a href="index">Back to index</a>
|
|
|
@ -1,138 +1,6 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html><meta charset=utf-8><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap" rel=stylesheet><link rel=stylesheet href=styles.css><h1>Backup my way</h1><p>First thing first, I want to list my own devices, which I have through the years:<ul><li>Laptop Samsung NP300E4Z-S06VN (Old laptop which I give to my mom)<li><a href=https://www.dell.com/support/home/en-vn/product-support/product/inspiron-15-3567-laptop/drivers>Laptop Dell Inspiron 15 3567</a> (My mom bought it for me when I go to college, I give it to my sister afterward)<li><a href=https://www.acer.com/ac/en/US/content/support-product/8841>Laptop Acer Nitro AN515-45</a> (Gaming laptop which I buy for gaming, of course)<li>MacBook Pro M1 2020 (My company laptop)<li>Phone Xiaomi Poco X3 NFC (Primary phone which I use daily)</ul><p>App/Service I use daily:<ul><li><a href=https://bitwarden.com/>Bitwarden</a><li><a href=https://getaegis.app/>Aegis Authenticator</a><li><a href=https://rclone.org/>Rclone</a><li><a href=https://tailscale.com/>Tailscale</a><li>GitHub / GitLab<li>Google Keep / Notion<li>Google Drive (I use 200GB plan)</ul><p>The purpose is that I want my data to be safe, secure, and can be easily recovered if I lost some devices;<br>or in the worst situation, I lost all.<br>Because you know, it is hard to guess what is waiting for us in the future.<p>There are 2 sections which I want to share, the first is <strong>How to backup</strong>, the second is <strong>Recover strategy</strong>.<h2>How to backup</h2><p>Before I talk about backup, I want to talk about data.<br>In specifically, which data should I backup?<p>I use Arch Linux and macOS, primarily work in the terminal so I have too many dotfiles, for example, <code>~/.config/nvim/init.lua</code>.<br>Each time I reinstall Arch Linux (I like it a lot), I need to reconfigure all the settings, and it is time-consuming.<p>So for the DE and UI settings, I keep it as default as possible, unless it's getting in my way, I leave the default setting there and forget about it.<br>The others are dotfiles, which I write my own <a href=https://github.com/haunt98/dotfiles>dotfiles tool</a> to backup and reconfigure easily and quickly.<br>Also, I know that installing Arch Linux is not easy, despite I install it too many times (Like thousand times since I was in high school).<br>Not because it is hard, but as life goes on, the <a href=https://wiki.archlinux.org/title/installation_guide>official install guide</a> keeps getting new update and covering too many cases for my own personal use, so I write my own <a href=https://github.com/haunt98/til/blob/main/install-archlinux.md>guide</a> to quickly capture what I need to do.<br>I back up all my dotfiles inside my dotfiles tool in GitHub and GitLab as I trust them both.<br>Also as I travel the Internet, I discover <a href=https://codeberg.org/>Codeberg</a> and <a href=https://gitea.treehouse.systems/>Treehouse</a> and use them as another backup for git repo.<p>So that is my dotfiles, for my regular data, like Wallpaper or Books, Images, I use Google Drive (Actually I pay for it).<br>But the step: open the webpage, click the upload button and choose files seems boring and time-consuming.<br>So I use Rclone, it supports Google Drive, One Drive and many providers but I only use Google Drive for now.<br>The commands are simple:<pre><code class=language-sh># Sync from local to remote
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>2022-06-08-backup.md</title>
|
|
||||||
<meta name="GENERATOR" content="github.com/gomarkdown/markdown markdown processor for Go">
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: "Recursive", sans-serif;
|
|
||||||
font-variation-settings: "MONO" 0, "CASL" 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: "Recursive", monospace;
|
|
||||||
font-variation-settings: "MONO" 1, "CASL" 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<a href="index">Back to index</a>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="#toc_0">Backup my way</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#toc_1">How to backup</a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_2">Recovery strategy</a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_3">The end</a></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<h1 id="toc_0">Backup my way</h1>
|
|
||||||
|
|
||||||
<p>First thing first, I want to list my own devices, which I have through the years:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Laptop Samsung NP300E4Z-S06VN (Old laptop which I give to my mom)</li>
|
|
||||||
<li><a href="https://www.dell.com/support/home/en-vn/product-support/product/inspiron-15-3567-laptop/drivers">Laptop Dell Inspiron 15 3567</a> (My mom bought it for me when I go to college, I give it to my sister afterward)</li>
|
|
||||||
<li><a href="https://www.acer.com/ac/en/US/content/support-product/8841">Laptop Acer Nitro AN515-45</a> (Gaming laptop which I buy for gaming, of course)</li>
|
|
||||||
<li>MacBook Pro M1 2020 (My company laptop)</li>
|
|
||||||
<li>Phone Xiaomi Poco X3 NFC (Primary phone which I use daily)</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>App/Service I use daily:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://bitwarden.com/">Bitwarden</a></li>
|
|
||||||
<li><a href="https://getaegis.app/">Aegis Authenticator</a></li>
|
|
||||||
<li><a href="https://rclone.org/">Rclone</a></li>
|
|
||||||
<li><a href="https://tailscale.com/">Tailscale</a></li>
|
|
||||||
<li>GitHub / GitLab</li>
|
|
||||||
<li>Google Keep / Notion</li>
|
|
||||||
<li>Google Drive (I use 200GB plan)</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>The purpose is that I want my data to be safe, secure, and can be easily recovered if I lost some devices;
|
|
||||||
or in the worst situation, I lost all.
|
|
||||||
Because you know, it is hard to guess what is waiting for us in the future.</p>
|
|
||||||
|
|
||||||
<p>There are 2 sections which I want to share, the first is <strong>How to backup</strong>, the second is <strong>Recover strategy</strong>.</p>
|
|
||||||
|
|
||||||
<h2 id="toc_1">How to backup</h2>
|
|
||||||
|
|
||||||
<p>Before I talk about backup, I want to talk about data.
|
|
||||||
In specifically, which data should I backup?</p>
|
|
||||||
|
|
||||||
<p>I use Arch Linux and macOS, primarily work in the terminal so I have too many dotfiles, for example, <code>~/.config/nvim/init.lua</code>.
|
|
||||||
Each time I reinstall Arch Linux (I like it a lot), I need to reconfigure all the settings, and it is time-consuming.</p>
|
|
||||||
|
|
||||||
<p>So for the DE and UI settings, I keep it as default as possible, unless it’s getting in my way, I leave the default setting there and forget about it.
|
|
||||||
The others are dotfiles, which I write my own <a href="https://github.com/haunt98/dotfiles">dotfiles tool</a> to backup and reconfigure easily and quickly.
|
|
||||||
Also, I know that installing Arch Linux is not easy, despite I install it too many times (Like thousand times since I was in high school).
|
|
||||||
Not because it is hard, but as life goes on, the <a href="https://wiki.archlinux.org/title/installation_guide">official install guide</a> keeps getting new update and covering too many cases for my own personal use, so I write my own <a href="https://github.com/haunt98/til/blob/main/install-archlinux.md">guide</a> to quickly capture what I need to do.
|
|
||||||
I back up all my dotfiles inside my dotfiles tool in GitHub and GitLab as I trust them both.
|
|
||||||
Also as I travel the Internet, I discover <a href="https://codeberg.org/">Codeberg</a> and <a href="https://gitea.treehouse.systems/">Treehouse</a> and use them as another backup for git repo.</p>
|
|
||||||
|
|
||||||
<p>So that is my dotfiles, for my regular data, like Wallpaper or Books, Images, I use Google Drive (Actually I pay for it).
|
|
||||||
But the step: open the webpage, click the upload button and choose files seems boring and time-consuming.
|
|
||||||
So I use Rclone, it supports Google Drive, One Drive and many providers but I only use Google Drive for now.
|
|
||||||
The commands are simple:</p>
|
|
||||||
|
|
||||||
<pre><code class="language-sh"># Sync from local to remote
|
|
||||||
rclone sync MyBooks remote:MyBooks -P --exclude .DS_Store
|
rclone sync MyBooks remote:MyBooks -P --exclude .DS_Store
|
||||||
|
|
||||||
# Sync from remote to local
|
# Sync from remote to local
|
||||||
rclone sync remote:MyBooks MyBooks -P --exclude .DS_Store
|
rclone sync remote:MyBooks MyBooks -P --exclude .DS_Store
|
||||||
</code></pre>
|
</code></pre><p>Before you use Rclone to sync to Google Drive, you should read <a href=https://rclone.org/drive/>Google Drive rclone configuration</a> first.<p>The next data is my passwords and my OTPs.<br>These are the things which I'm scare to lose the most.<br>First thing first, I enable 2-Step Verification for all of my important accounts, should use both OTP and phone method.<p>I use Bitwarden for passwords (That is a long story, coming from Google Password manager to Firefox Lockwise and then settle down with Bitwarden) and Aegis for OTPs.<br>The reason I choose Aegis, not Authy (I use Authy for so long but Aegis is definitely better) is because Aegis allows me to extract all the OTPs to a single file (Can be encrypted), which I use to transfer or backup easily.<p>As long as Bitwarden provides free passwords stored, I use all of its apps, extensions so that I can easily sync passwords between laptops and phones.<br>The thing I need to remember is the master password of Bitwarden in my head.<p>With Aegis, I export the data, then sync it to Google Drive, also store it locally in my phone.<br>For safety, I also store Aegis data locally on all of my laptops (Encrypted of course).<p>The main problem here is the OTP, I can not store all of my OTPs in the cloud completely.<br>Because if I want to access my OTPs in the cloud, I should log in, and then input my OTP, this is a circle, my friends.<h2>Recovery strategy</h2><p>There are many strategies that I process to react as if something strange is happening to my devices.<p>If I lost my laptops, single laptop or all, do not panic as long as I have my phones.<br>The OTPs are in there, the passwords are in Bitwarden cloud, other data is in Google Drive so nothing is lost here.<p>If I lost my phone, but not my laptops, I use the OTPs which are stored locally in my laptops.<p>In the worst situation, I lost everything, my laptops, my phone.<br>The first step is to recover my SIM, then log in to Google account using the password and SMS OTP.<br>After that, log in to Bitwarden account using the master password and OTP from Gmail, which I open previously.<h2>The end</h2><p>This guide will be updated regularly I promise.
|
||||||
|
|
||||||
<p>Before you use Rclone to sync to Google Drive, you should read <a href="https://rclone.org/drive/">Google Drive rclone configuration</a> first.</p>
|
|
||||||
|
|
||||||
<p>The next data is my passwords and my OTPs.
|
|
||||||
These are the things which I’m scare to lose the most.
|
|
||||||
First thing first, I enable 2-Step Verification for all of my important accounts, should use both OTP and phone method.</p>
|
|
||||||
|
|
||||||
<p>I use Bitwarden for passwords (That is a long story, coming from Google Password manager to Firefox Lockwise and then settle down with Bitwarden) and Aegis for OTPs.
|
|
||||||
The reason I choose Aegis, not Authy (I use Authy for so long but Aegis is definitely better) is because Aegis allows me to extract all the OTPs to a single file (Can be encrypted), which I use to transfer or backup easily.</p>
|
|
||||||
|
|
||||||
<p>As long as Bitwarden provides free passwords stored, I use all of its apps, extensions so that I can easily sync passwords between laptops and phones.
|
|
||||||
The thing I need to remember is the master password of Bitwarden in my head.</p>
|
|
||||||
|
|
||||||
<p>With Aegis, I export the data, then sync it to Google Drive, also store it locally in my phone.
|
|
||||||
For safety, I also store Aegis data locally on all of my laptops (Encrypted of course).</p>
|
|
||||||
|
|
||||||
<p>The main problem here is the OTP, I can not store all of my OTPs in the cloud completely.
|
|
||||||
Because if I want to access my OTPs in the cloud, I should log in, and then input my OTP, this is a circle, my friends.</p>
|
|
||||||
|
|
||||||
<h2 id="toc_2">Recovery strategy</h2>
|
|
||||||
|
|
||||||
<p>There are many strategies that I process to react as if something strange is happening to my devices.</p>
|
|
||||||
|
|
||||||
<p>If I lost my laptops, single laptop or all, do not panic as long as I have my phones.
|
|
||||||
The OTPs are in there, the passwords are in Bitwarden cloud, other data is in Google Drive so nothing is lost here.</p>
|
|
||||||
|
|
||||||
<p>If I lost my phone, but not my laptops, I use the OTPs which are stored locally in my laptops.</p>
|
|
||||||
|
|
||||||
<p>In the worst situation, I lost everything, my laptops, my phone.
|
|
||||||
The first step is to recover my SIM, then log in to Google account using the password and SMS OTP.
|
|
||||||
After that, log in to Bitwarden account using the master password and OTP from Gmail, which I open previously.</p>
|
|
||||||
|
|
||||||
<h2 id="toc_3">The end</h2>
|
|
||||||
|
|
||||||
<p>This guide will be updated regularly I promise.</p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,48 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html><meta charset=utf-8><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap" rel=stylesheet><link rel=stylesheet href=styles.css><h1>Dockerfile for Go</h1><p>Each time I start a new Go project, I repeat many steps.<br>Like set up <code>.gitignore</code>, CI configs, Dockerfile, ...<p>So I decide to have a baseline Dockerfile like this:<pre><code class=language-Dockerfile>FROM golang:1.18-bullseye as builder
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>2022-06-08-dockerfile-go.md</title>
|
|
||||||
<meta name="GENERATOR" content="github.com/gomarkdown/markdown markdown processor for Go">
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: "Recursive", sans-serif;
|
|
||||||
font-variation-settings: "MONO" 0, "CASL" 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: "Recursive", monospace;
|
|
||||||
font-variation-settings: "MONO" 1, "CASL" 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<a href="index">Back to index</a>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="#toc_0">Dockerfile for Go</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<h1 id="toc_0">Dockerfile for Go</h1>
|
|
||||||
|
|
||||||
<p>Each time I start a new Go project, I repeat many steps.
|
|
||||||
Like set up <code>.gitignore</code>, CI configs, Dockerfile, …</p>
|
|
||||||
|
|
||||||
<p>So I decide to have a baseline Dockerfile like this:</p>
|
|
||||||
|
|
||||||
<pre><code class="language-Dockerfile">FROM golang:1.18-bullseye as builder
|
|
||||||
|
|
||||||
RUN go install golang.org/dl/go1.18@latest \
|
RUN go install golang.org/dl/go1.18@latest \
|
||||||
&& go1.18 download
|
&& go1.18 download
|
||||||
|
@ -61,67 +17,19 @@ FROM gcr.io/distroless/base-debian11
|
||||||
COPY --from=builder /build/app /app
|
COPY --from=builder /build/app /app
|
||||||
|
|
||||||
ENTRYPOINT ["/app"]
|
ENTRYPOINT ["/app"]
|
||||||
</code></pre>
|
</code></pre><p>I use <a href=https://docs.docker.com/develop/develop-images/multistage-build/>multi-stage build</a> to keep my image size small.<br>First stage is <a href=https://hub.docker.com/_/golang>Go official image</a>,<br>second stage is <a href=https://github.com/GoogleContainerTools/distroless>Distroless</a>.<p>Before Distroless, I use <a href=https://hub.docker.com/_/alpine>Alpine official image</a>,<br>There is a whole discussion on the Internet to choose which is the best base image for Go.<br>After reading some blogs, I discover Distroless as a small and secure base image.<br>So I stick with it for a while.<p>Also, remember to match Distroless Debian version with Go official image Debian version.<pre><code class=language-Dockerfile>FROM golang:1.18-bullseye as builder
|
||||||
|
</code></pre><p>This is Go image I use as a build stage.<br>This can be official Go image or custom image is required in some companies.<pre><code class=language-Dockerfile>RUN go install golang.org/dl/go1.18@latest \
|
||||||
<p>I use <a href="https://docs.docker.com/develop/develop-images/multistage-build/">multi-stage build</a> to keep my image size small.
|
|
||||||
First stage is <a href="https://hub.docker.com/_/golang">Go official image</a>,
|
|
||||||
second stage is <a href="https://github.com/GoogleContainerTools/distroless">Distroless</a>.</p>
|
|
||||||
|
|
||||||
<p>Before Distroless, I use <a href="https://hub.docker.com/_/alpine">Alpine official image</a>,
|
|
||||||
There is a whole discussion on the Internet to choose which is the best base image for Go.
|
|
||||||
After reading some blogs, I discover Distroless as a small and secure base image.
|
|
||||||
So I stick with it for a while.</p>
|
|
||||||
|
|
||||||
<p>Also, remember to match Distroless Debian version with Go official image Debian version.</p>
|
|
||||||
|
|
||||||
<pre><code class="language-Dockerfile">FROM golang:1.18-bullseye as builder
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>This is Go image I use as a build stage.
|
|
||||||
This can be official Go image or custom image is required in some companies.</p>
|
|
||||||
|
|
||||||
<pre><code class="language-Dockerfile">RUN go install golang.org/dl/go1.18@latest \
|
|
||||||
&& go1.18 download
|
&& go1.18 download
|
||||||
</code></pre>
|
</code></pre><p>This is optional.<br>In my case, my company is slow to update Go image so I use this trick to install latest Go version.<pre><code class=language-Dockerfile>WORKDIR /build
|
||||||
|
|
||||||
<p>This is optional.
|
|
||||||
In my case, my company is slow to update Go image so I use this trick to install latest Go version.</p>
|
|
||||||
|
|
||||||
<pre><code class="language-Dockerfile">WORKDIR /build
|
|
||||||
|
|
||||||
COPY go.mod .
|
COPY go.mod .
|
||||||
COPY go.sum .
|
COPY go.sum .
|
||||||
COPY vendor .
|
COPY vendor .
|
||||||
COPY . .
|
COPY . .
|
||||||
</code></pre>
|
</code></pre><p>I use <code>/build</code> to emphasize that I am building something in that directory.<p>The 4 <code>COPY</code> lines are familiar if you use Go enough.<br>First is <code>go.mod</code> and <code>go.sum</code> because it defines Go modules.<br>The second is <code>vendor</code>, this is optional but I use it because I don't want each time I build Dockerfile, I need to redownload Go modules.<pre><code class=language-Dockerfile>RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app -tags timetzdata -trimpath .
|
||||||
|
</code></pre><p>This is where I build Go program.<p><code>CGO_ENABLED=0</code> because I don't want to mess with C libraries.<br><code>GOOS=linux GOARCH=amd64</code> is easy to explain, Linux with x86-64.<br><code>GOAMD64=v3</code> is new since <a href=https://go.dev/doc/go1.18#amd64>Go 1.18</a>,<br>I use v3 because I read about AMD64 version in <a href=https://gitlab.archlinux.org/archlinux/rfcs/-/blob/master/rfcs/0002-march.rst>Arch Linux rfcs</a>. TLDR's newer computers are already x86-64-v3.<p><code>-tags timetzdata</code> to embed timezone database incase base image does not have.<br><code>-trimpath</code> to support reproduce build.<pre><code class=language-Dockerfile>FROM gcr.io/distroless/base-debian11
|
||||||
<p>I use <code>/build</code> to emphasize that I am building something in that directory.</p>
|
|
||||||
|
|
||||||
<p>The 4 <code>COPY</code> lines are familiar if you use Go enough.
|
|
||||||
First is <code>go.mod</code> and <code>go.sum</code> because it defines Go modules.
|
|
||||||
The second is <code>vendor</code>, this is optional but I use it because I don’t want each time I build Dockerfile, I need to redownload Go modules.</p>
|
|
||||||
|
|
||||||
<pre><code class="language-Dockerfile">RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app -tags timetzdata -trimpath .
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>This is where I build Go program.</p>
|
|
||||||
|
|
||||||
<p><code>CGO_ENABLED=0</code> because I don’t want to mess with C libraries.
|
|
||||||
<code>GOOS=linux GOARCH=amd64</code> is easy to explain, Linux with x86-64.
|
|
||||||
<code>GOAMD64=v3</code> is new since <a href="https://go.dev/doc/go1.18#amd64">Go 1.18</a>,
|
|
||||||
I use v3 because I read about AMD64 version in <a href="https://gitlab.archlinux.org/archlinux/rfcs/-/blob/master/rfcs/0002-march.rst">Arch Linux rfcs</a>. TLDR’s newer computers are already x86-64-v3.</p>
|
|
||||||
|
|
||||||
<p><code>-tags timetzdata</code> to embed timezone database incase base image does not have.
|
|
||||||
<code>-trimpath</code> to support reproduce build.</p>
|
|
||||||
|
|
||||||
<pre><code class="language-Dockerfile">FROM gcr.io/distroless/base-debian11
|
|
||||||
|
|
||||||
COPY --from=builder /build/app /app
|
COPY --from=builder /build/app /app
|
||||||
|
|
||||||
ENTRYPOINT ["/app"]
|
ENTRYPOINT ["/app"]
|
||||||
</code></pre>
|
</code></pre><p>Finally, I copy <code>app</code> to Distroless base image.
|
||||||
|
|
||||||
<p>Finally, I copy <code>app</code> to Distroless base image.</p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,76 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html><meta charset=utf-8><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap" rel=stylesheet><link rel=stylesheet href=styles.css><h1>Bootstrap Go</h1><p>It is hard to write bootstrap tool to quickly create Go service.<br>So I write this guide instead.<br>This is a quick checklist for me every damn time I need to write a Go service from scratch.<br>Also, this is my personal opinion, so feel free to comment.<h2>Structure</h2><pre><code class=language-txt>main.go
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>2022-07-10-bootstrap-go.md</title>
|
|
||||||
<meta name="GENERATOR" content="github.com/gomarkdown/markdown markdown processor for Go">
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: "Recursive", sans-serif;
|
|
||||||
font-variation-settings: "MONO" 0, "CASL" 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: "Recursive", monospace;
|
|
||||||
font-variation-settings: "MONO" 1, "CASL" 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<a href="index">Back to index</a>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="#toc_0">Bootstrap Go</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#toc_1">Structure</a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_2">Do not repeat!</a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_3">External libs</a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#toc_4">Don’t use cli libs (<a href="https://github.com/spf13/cobra">spf13/cobra</a>, <a href="https://github.com/urfave/cli">urfave/cli</a>) just for Go service</a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_5">Don’t use <a href="https://github.com/grpc-ecosystem/grpc-gateway">grpc-ecosystem/grpc-gateway</a></a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_6">Don’t use <a href="https://github.com/uber/prototool">uber/prototool</a>, use <a href="https://github.com/bufbuild/buf">bufbuild/buf</a></a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_7">Use <a href="https://github.com/gin-gonic/gin">gin-gonic/gin</a> for REST.</a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_8">If you want log, just use <a href="https://github.com/uber-go/zap">uber-go/zap</a></a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_9">Don’t overuse ORM libs, no need to handle another layer above SQL.</a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_10">If you want test, just use <a href="https://github.com/stretchr/testify">stretchr/testify</a>.</a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_11">Replace <code>go fmt</code>, <code>goimports</code> with <a href="https://github.com/mvdan/gofumpt">mvdan/gofumpt</a>.</a></li>
|
|
||||||
|
|
||||||
<li><a href="#toc_12">Use <a href="https://github.com/golangci/golangci-lint">golangci/golangci-lint</a>.</a></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<h1 id="toc_0">Bootstrap Go</h1>
|
|
||||||
|
|
||||||
<p>It is hard to write bootstrap tool to quickly create Go service.
|
|
||||||
So I write this guide instead.
|
|
||||||
This is a quick checklist for me every damn time I need to write a Go service from scratch.
|
|
||||||
Also, this is my personal opinion, so feel free to comment.</p>
|
|
||||||
|
|
||||||
<h2 id="toc_1">Structure</h2>
|
|
||||||
|
|
||||||
<pre><code class="language-txt">main.go
|
|
||||||
internal
|
internal
|
||||||
| business_1
|
| business_1
|
||||||
| | http
|
| | http
|
||||||
|
@ -92,103 +20,4 @@ internal
|
||||||
| | | service.go
|
| | | service.go
|
||||||
| | | repository.go
|
| | | repository.go
|
||||||
| | | models.go
|
| | | models.go
|
||||||
</code></pre>
|
</code></pre><p>All business codes are inside <code>internal</code>.<br>Each business has a different directory (<code>business_1</code>, <code>business_2</code>).<p>Inside each business, there are 2 handlers: <code>http</code>, <code>grpc</code>:<ul><li><code>http</code> is for public APIs (Android, iOS,... are clients).<li><code>grpc</code> is for internal APIs (other services are clients).</ul><p>Inside each handler, there are usually 3 layers: <code>handler</code>, <code>service</code>, <code>repository</code>:<ul><li><code>handler</code> interacts directly with gRPC or REST using specific codes (cookies,...)<li><code>service</code> is where we write business/logic codes, and only business/logic codes is written here.<li><code>repository</code> is where we write codes which interacts with database/cache like MySQL, Redis, ...</ul><p><code>handler</code> must exist inside <code>grpc</code>, <code>http</code>.<br>But <code>service</code>, <code>repository</code>, <code>models</code> can exist directly inside <code>business</code> if both <code>grpc</code>, <code>http</code> has same business/logic.<h2>Do not repeat!</h2><p>If we have too many services, some of the logic will be overlapped.<p>For example, service A and service B both need to make POST call API to service C.<br>If service A and service B both have libs to call service C to do that API, we need to move the libs to some common pkg libs.<br>So in the future, service D which needs to call C will not need to copy libs to handle service C api but only need to import from common pkg libs.<p>Another bad practice is adapter service.<br>No need to write a new service if what we need is just common pkg libs.<h2>External libs</h2><h3>Don't use cli libs (<a href=https://github.com/spf13/cobra>spf13/cobra</a>, <a href=https://github.com/urfave/cli>urfave/cli</a>) just for Go service</h3><p>What is the point to pass many params (<code>--abc</code>, <code>--xyz</code>) when what we only need is start service?<p>In my case, service starts with only config, and config should be read from file or environment like <a href=https://12factor.net/>The Twelve Factors</a> guide.<h3>Don't use <a href=https://github.com/grpc-ecosystem/grpc-gateway>grpc-ecosystem/grpc-gateway</a></h3><p>Just don't.<p>Use <a href=https://github.com/protocolbuffers/protobuf-go>protocolbuffers/protobuf-go</a>, <a href=https://github.com/grpc/grpc-go>grpc/grpc-go</a> for gRPC.<p>Write 1 for both gRPC, REST sounds good, but in the end, it is not worth it.<h3>Don't use <a href=https://github.com/uber/prototool>uber/prototool</a>, use <a href=https://github.com/bufbuild/buf>bufbuild/buf</a></h3><p>prototool is deprecated, and buf can generate, lint, format as good as prototool.<h3>Use <a href=https://github.com/gin-gonic/gin>gin-gonic/gin</a> for REST.</h3><p>Don't use <code>gin.Context</code> when pass context from handler layer to service layer, use <code>gin.Context.Request.Context()</code> instead.<h3>If you want log, just use <a href=https://github.com/uber-go/zap>uber-go/zap</a></h3><p>It is fast!<ul><li><p>Don't overuse <code>func (*Logger) With</code>. Because if log line is too long, there is a possibility that we can lost it.<li><p>Use <code>MarshalLogObject</code> when we need to hide some field of object when log (field has long or sensitive value)<li><p>Don't use <code>Panic</code>. Use <code>Fatal</code> for errors when start service to check dependencies. If you really need panic level, use <code>DPanic</code>.<li><p>Use <code>contextID</code> or <code>traceID</code> in every log lines for easily debug.</ul><h3>Don't overuse ORM libs, no need to handle another layer above SQL.</h3><p>Each ORM libs has each different syntax.<br>To learn and use those libs correctly is time consuming.<br>So just stick to plain SQL.<br>It is easier to debug when something is wrong.<p>But <code>database/sql</code> has its own limit.<br>For example, it is hard to get primary key after insert/update.<br>So may be you want to use ORM for those cases.<h3>If you want test, just use <a href=https://github.com/stretchr/testify>stretchr/testify</a>.</h3><p>It is easy to write a suite test, thanks to testify.<br>Also, for mocking, there are many options out there.<br>Pick 1 then sleep peacefully.<h3>Replace <code>go fmt</code>, <code>goimports</code> with <a href=https://github.com/mvdan/gofumpt>mvdan/gofumpt</a>.</h3><p><code>gofumpt</code> provides more rules when format Go codes.<h3>Use <a href=https://github.com/golangci/golangci-lint>golangci/golangci-lint</a>.</h3><p>No need to say more.<br>Lint or get the f out!
|
||||||
|
|
||||||
<p>All business codes are inside <code>internal</code>.
|
|
||||||
Each business has a different directory (<code>business_1</code>, <code>business_2</code>).</p>
|
|
||||||
|
|
||||||
<p>Inside each business, there are 2 handlers: <code>http</code>, <code>grpc</code>:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><code>http</code> is for public APIs (Android, iOS,… are clients).</li>
|
|
||||||
<li><code>grpc</code> is for internal APIs (other services are clients).</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Inside each handler, there are usually 3 layers: <code>handler</code>, <code>service</code>, <code>repository</code>:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><code>handler</code> interacts directly with gRPC or REST using specific codes (cookies,…)</li>
|
|
||||||
<li><code>service</code> is where we write business/logic codes, and only business/logic codes is written here.</li>
|
|
||||||
<li><code>repository</code> is where we write codes which interacts with database/cache like MySQL, Redis, …</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p><code>handler</code> must exist inside <code>grpc</code>, <code>http</code>.
|
|
||||||
But <code>service</code>, <code>repository</code>, <code>models</code> can exist directly inside <code>business</code> if both <code>grpc</code>, <code>http</code> has same business/logic.</p>
|
|
||||||
|
|
||||||
<h2 id="toc_2">Do not repeat!</h2>
|
|
||||||
|
|
||||||
<p>If we have too many services, some of the logic will be overlapped.</p>
|
|
||||||
|
|
||||||
<p>For example, service A and service B both need to make POST call API to service C.
|
|
||||||
If service A and service B both have libs to call service C to do that API, we need to move the libs to some common pkg libs.
|
|
||||||
So in the future, service D which needs to call C will not need to copy libs to handle service C api but only need to import from common pkg libs.</p>
|
|
||||||
|
|
||||||
<p>Another bad practice is adapter service.
|
|
||||||
No need to write a new service if what we need is just common pkg libs.</p>
|
|
||||||
|
|
||||||
<h2 id="toc_3">External libs</h2>
|
|
||||||
|
|
||||||
<h3 id="toc_4">Don’t use cli libs (<a href="https://github.com/spf13/cobra">spf13/cobra</a>, <a href="https://github.com/urfave/cli">urfave/cli</a>) just for Go service</h3>
|
|
||||||
|
|
||||||
<p>What is the point to pass many params (<code>--abc</code>, <code>--xyz</code>) when what we only need is start service?</p>
|
|
||||||
|
|
||||||
<p>In my case, service starts with only config, and config should be read from file or environment like <a href="https://12factor.net/">The Twelve Factors</a> guide.</p>
|
|
||||||
|
|
||||||
<h3 id="toc_5">Don’t use <a href="https://github.com/grpc-ecosystem/grpc-gateway">grpc-ecosystem/grpc-gateway</a></h3>
|
|
||||||
|
|
||||||
<p>Just don’t.</p>
|
|
||||||
|
|
||||||
<p>Use <a href="https://github.com/protocolbuffers/protobuf-go">protocolbuffers/protobuf-go</a>, <a href="https://github.com/grpc/grpc-go">grpc/grpc-go</a> for gRPC.</p>
|
|
||||||
|
|
||||||
<p>Write 1 for both gRPC, REST sounds good, but in the end, it is not worth it.</p>
|
|
||||||
|
|
||||||
<h3 id="toc_6">Don’t use <a href="https://github.com/uber/prototool">uber/prototool</a>, use <a href="https://github.com/bufbuild/buf">bufbuild/buf</a></h3>
|
|
||||||
|
|
||||||
<p>prototool is deprecated, and buf can generate, lint, format as good as prototool.</p>
|
|
||||||
|
|
||||||
<h3 id="toc_7">Use <a href="https://github.com/gin-gonic/gin">gin-gonic/gin</a> for REST.</h3>
|
|
||||||
|
|
||||||
<p>Don’t use <code>gin.Context</code> when pass context from handler layer to service layer, use <code>gin.Context.Request.Context()</code> instead.</p>
|
|
||||||
|
|
||||||
<h3 id="toc_8">If you want log, just use <a href="https://github.com/uber-go/zap">uber-go/zap</a></h3>
|
|
||||||
|
|
||||||
<p>It is fast!</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><p>Don’t overuse <code>func (*Logger) With</code>. Because if log line is too long, there is a possibility that we can lost it.</p></li>
|
|
||||||
|
|
||||||
<li><p>Use <code>MarshalLogObject</code> when we need to hide some field of object when log (field has long or sensitive value)</p></li>
|
|
||||||
|
|
||||||
<li><p>Don’t use <code>Panic</code>. Use <code>Fatal</code> for errors when start service to check dependencies. If you really need panic level, use <code>DPanic</code>.</p></li>
|
|
||||||
|
|
||||||
<li><p>Use <code>contextID</code> or <code>traceID</code> in every log lines for easily debug.</p></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h3 id="toc_9">Don’t overuse ORM libs, no need to handle another layer above SQL.</h3>
|
|
||||||
|
|
||||||
<p>Each ORM libs has each different syntax.
|
|
||||||
To learn and use those libs correctly is time consuming.
|
|
||||||
So just stick to plain SQL.
|
|
||||||
It is easier to debug when something is wrong.</p>
|
|
||||||
|
|
||||||
<p>But <code>database/sql</code> has its own limit.
|
|
||||||
For example, it is hard to get primary key after insert/update.
|
|
||||||
So may be you want to use ORM for those cases.</p>
|
|
||||||
|
|
||||||
<h3 id="toc_10">If you want test, just use <a href="https://github.com/stretchr/testify">stretchr/testify</a>.</h3>
|
|
||||||
|
|
||||||
<p>It is easy to write a suite test, thanks to testify.
|
|
||||||
Also, for mocking, there are many options out there.
|
|
||||||
Pick 1 then sleep peacefully.</p>
|
|
||||||
|
|
||||||
<h3 id="toc_11">Replace <code>go fmt</code>, <code>goimports</code> with <a href="https://github.com/mvdan/gofumpt">mvdan/gofumpt</a>.</h3>
|
|
||||||
|
|
||||||
<p><code>gofumpt</code> provides more rules when format Go codes.</p>
|
|
||||||
|
|
||||||
<h3 id="toc_12">Use <a href="https://github.com/golangci/golangci-lint">golangci/golangci-lint</a>.</h3>
|
|
||||||
|
|
||||||
<p>No need to say more.
|
|
||||||
Lint or get the f out!</p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,49 +1 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html><meta charset=utf-8><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap" rel=stylesheet><link rel=stylesheet href=styles.css><h1>Index</h1><p>This is where I dump my thoughts.<ul><li><a href=2022-06-08-backup>Backup my way</a><li><a href=2022-06-08-dockerfile-go>Dockerfile for Go</a><li><a href=2022-07-10-bootstrap-go>Bootstrap Go</a></ul>
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>index.md</title>
|
|
||||||
<meta name="GENERATOR" content="github.com/gomarkdown/markdown markdown processor for Go">
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: "Recursive", sans-serif;
|
|
||||||
font-variation-settings: "MONO" 0, "CASL" 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: "Recursive", monospace;
|
|
||||||
font-variation-settings: "MONO" 1, "CASL" 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<a href="index">Back to index</a>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="#toc_0">Index</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<h1 id="toc_0">Index</h1>
|
|
||||||
|
|
||||||
<p>This is where I dump my thoughts.</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="2022-06-08-backup">Backup my way</a></li>
|
|
||||||
<li><a href="2022-06-08-dockerfile-go">Dockerfile for Go</a></li>
|
|
||||||
<li><a href="2022-07-10-bootstrap-go">Bootstrap Go</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -0,0 +1 @@
|
||||||
|
body{font-family:recursive,sans-serif;font-variation-settings:"MONO" 0,"CASL" 1}code{font-family:recursive,monospace;font-variation-settings:"MONO" 1,"CASL" 1}
|
7
go.mod
7
go.mod
|
@ -2,4 +2,9 @@ module github.com/haunt98/posts-go
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require github.com/gomarkdown/markdown v0.0.0-20220627144906-e9a81102ebeb
|
require (
|
||||||
|
github.com/tdewolff/minify/v2 v2.12.0
|
||||||
|
github.com/yuin/goldmark v1.4.13
|
||||||
|
)
|
||||||
|
|
||||||
|
require github.com/tdewolff/parse/v2 v2.6.1 // indirect
|
||||||
|
|
18
go.sum
18
go.sum
|
@ -1,2 +1,16 @@
|
||||||
github.com/gomarkdown/markdown v0.0.0-20220627144906-e9a81102ebeb h1:5b/eFaSaKPFG9ygDBaPKkydKU5nFJYk08g9jPIVogMg=
|
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||||
github.com/gomarkdown/markdown v0.0.0-20220627144906-e9a81102ebeb/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
|
||||||
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||||
|
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/tdewolff/minify/v2 v2.12.0 h1:ZyvMKeciyR3vzJrK/oHyBcSmpttQ/V+ah7qOqTZclaU=
|
||||||
|
github.com/tdewolff/minify/v2 v2.12.0/go.mod h1:8mvf+KglD7XurfvvFZDUYvVURy6bA/r0oTvmakXMnyg=
|
||||||
|
github.com/tdewolff/parse/v2 v2.6.1 h1:RIfy1erADkO90ynJWvty8VIkqqKYRzf2iLp8ObG174I=
|
||||||
|
github.com/tdewolff/parse/v2 v2.6.1/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||||
|
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||||
|
github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM=
|
||||||
|
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||||
|
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
135
main.go
135
main.go
|
@ -1,22 +1,41 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/gomarkdown/markdown"
|
"github.com/tdewolff/minify/v2"
|
||||||
"github.com/gomarkdown/markdown/html"
|
minify_css "github.com/tdewolff/minify/v2/css"
|
||||||
|
minify_html "github.com/tdewolff/minify/v2/html"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
gm_extension "github.com/yuin/goldmark/extension"
|
||||||
|
gm_html "github.com/yuin/goldmark/renderer/html"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
postsPath = "posts"
|
postFilesPath = "posts"
|
||||||
headHTMLPath = "custom/head.html"
|
templatePostPath = "templates/post.html"
|
||||||
|
|
||||||
|
templateCSSPath = "templates/styles.css"
|
||||||
|
cssFilename = "styles.css"
|
||||||
|
|
||||||
generatedPath = "docs"
|
generatedPath = "docs"
|
||||||
htmlExt = ".html"
|
|
||||||
|
extHTML = ".html"
|
||||||
|
|
||||||
|
mimeTypeHTML = "text/html"
|
||||||
|
mimeTypeCSS = "text/css"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type templatePostData struct {
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Cleanup generated path
|
// Cleanup generated path
|
||||||
if err := os.RemoveAll(generatedPath); err != nil {
|
if err := os.RemoveAll(generatedPath); err != nil {
|
||||||
|
@ -27,47 +46,99 @@ func main() {
|
||||||
log.Fatalln("Failed to mkdir all", generatedPath)
|
log.Fatalln("Failed to mkdir all", generatedPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read needed files
|
// Read post files directory
|
||||||
headHTML, err := os.ReadFile(headHTMLPath)
|
postFiles, err := os.ReadDir(postFilesPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to read file", headHTML)
|
log.Fatalln("Failed to read dir", postFilesPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
files, err := os.ReadDir(postsPath)
|
// Prepare template
|
||||||
|
templatePostBytes, err := os.ReadFile(templatePostPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to read dir", postsPath)
|
log.Fatalln("Failed to read file", templatePostPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
templatePost, err := template.New("post").Parse(string(templatePostBytes))
|
||||||
if file.IsDir() {
|
if err != nil {
|
||||||
|
log.Fatalln("Failed to parse template", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare parse markdown
|
||||||
|
gm := goldmark.New(
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
gm_extension.GFM,
|
||||||
|
),
|
||||||
|
goldmark.WithRendererOptions(
|
||||||
|
gm_html.WithHardWraps(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Prepare minify
|
||||||
|
m := minify.New()
|
||||||
|
m.AddFunc(mimeTypeHTML, minify_html.Minify)
|
||||||
|
m.AddFunc(mimeTypeCSS, minify_css.Minify)
|
||||||
|
|
||||||
|
// Generate post files
|
||||||
|
for _, postFile := range postFiles {
|
||||||
|
if postFile.IsDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate HTML
|
// Prepare post file
|
||||||
filePath := filepath.Join(postsPath, file.Name())
|
mdFilename := filepath.Join(postFilesPath, postFile.Name())
|
||||||
md, err := os.ReadFile(filePath)
|
mdFileBytes, err := os.ReadFile(mdFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to read file", filePath)
|
log.Fatalln("Failed to read file", mdFilename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlFlags := html.CommonFlags |
|
// Prepare html file
|
||||||
html.CompletePage |
|
htmlFilename := strings.TrimSuffix(postFile.Name(), filepath.Ext(postFile.Name())) + extHTML
|
||||||
html.TOC |
|
htmlFilepath := filepath.Join(generatedPath, htmlFilename)
|
||||||
html.LazyLoadImages
|
|
||||||
htmlRendererOtps := html.RendererOptions{
|
htmlFile, err := os.OpenFile(htmlFilepath, os.O_RDWR|os.O_CREATE, 0o600)
|
||||||
Title: file.Name(),
|
if err != nil {
|
||||||
Head: headHTML,
|
log.Fatalln("Failed to open file", htmlFilepath, err)
|
||||||
Flags: htmlFlags,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlRenderer := html.NewRenderer(htmlRendererOtps)
|
// Parse markdown
|
||||||
generatedHTML := markdown.ToHTML(md, nil, htmlRenderer)
|
var markdownBuf bytes.Buffer
|
||||||
|
if err := gm.Convert(mdFileBytes, &markdownBuf); err != nil {
|
||||||
generatedFileName := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) + htmlExt
|
log.Fatalln("Failed to convert markdown", err)
|
||||||
generatedFilePath := filepath.Join(generatedPath, generatedFileName)
|
|
||||||
|
|
||||||
if err := os.WriteFile(generatedFilePath, generatedHTML, 0o666); err != nil {
|
|
||||||
log.Fatalln("Failed to write file", generatedFilePath, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tmpReader, tmpWriter := io.Pipe()
|
||||||
|
|
||||||
|
// Template
|
||||||
|
go func() {
|
||||||
|
if err := templatePost.Execute(tmpWriter, templatePostData{
|
||||||
|
Body: markdownBuf.String(),
|
||||||
|
}); err != nil {
|
||||||
|
log.Fatalln("Failed to execute html template", err)
|
||||||
|
}
|
||||||
|
tmpWriter.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Minify
|
||||||
|
if err := m.Minify(mimeTypeHTML, htmlFile, tmpReader); err != nil {
|
||||||
|
log.Fatalln("Failed to minify html", err)
|
||||||
|
}
|
||||||
|
tmpReader.Close()
|
||||||
|
htmlFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy css file
|
||||||
|
templateCSSFile, err := os.OpenFile(templateCSSPath, os.O_RDONLY, 0o600)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Failed to open file", templateCSSPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cssFilename := filepath.Join(generatedPath, cssFilename)
|
||||||
|
cssFile, err := os.OpenFile(cssFilename, os.O_RDWR|os.O_CREATE, 0o600)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Failed to open file", cssFilename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.Minify(mimeTypeCSS, cssFile, templateCSSFile); err != nil {
|
||||||
|
log.Fatalln("Failed to minify css", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link rel="stylesheet" href="styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{.Body}}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,9 @@
|
||||||
|
body {
|
||||||
|
font-family: "Recursive", sans-serif;
|
||||||
|
font-variation-settings: "MONO" 0, "CASL" 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: "Recursive", monospace;
|
||||||
|
font-variation-settings: "MONO" 1, "CASL" 1;
|
||||||
|
}
|
Loading…
Reference in New Issue