Switched to Shifu.
0
_old/.gitignore
vendored
Normal file
BIN
_old/assets/avatar.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 4.9 MiB |
BIN
_old/assets/favicon/android-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
_old/assets/favicon/android-icon-192x192.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
_old/assets/favicon/android-icon-36x36.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
_old/assets/favicon/android-icon-48x48.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
_old/assets/favicon/android-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
_old/assets/favicon/android-icon-96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
_old/assets/favicon/apple-icon-114x114.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
_old/assets/favicon/apple-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
_old/assets/favicon/apple-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
_old/assets/favicon/apple-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
_old/assets/favicon/apple-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
_old/assets/favicon/apple-icon-57x57.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
_old/assets/favicon/apple-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
_old/assets/favicon/apple-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
_old/assets/favicon/apple-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
_old/assets/favicon/apple-icon-precomposed.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
_old/assets/favicon/apple-icon.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
2
_old/assets/favicon/browserconfig.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
|
||||
BIN
_old/assets/favicon/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 816 B |
BIN
_old/assets/favicon/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
_old/assets/favicon/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
_old/assets/favicon/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
41
_old/assets/favicon/manifest.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/android-icon-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
_old/assets/favicon/ms-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
_old/assets/favicon/ms-icon-150x150.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
_old/assets/favicon/ms-icon-310x310.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
_old/assets/favicon/ms-icon-70x70.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
_old/assets/font/DMSans-Bold.ttf
Normal file
BIN
_old/assets/font/DMSans-BoldItalic.ttf
Normal file
BIN
_old/assets/font/DMSans-Italic.ttf
Normal file
BIN
_old/assets/font/DMSans-Regular.ttf
Normal file
3
_old/assets/icons/arrow.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 11L1 6L6 1" stroke="#808080" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 190 B |
4
_old/assets/icons/cv.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="8" fill="#212121"/>
|
||||
<path d="M11.096 22.192C9.99733 22.192 9.048 21.952 8.248 21.472C7.45867 20.9813 6.84533 20.304 6.408 19.44C5.97067 18.5653 5.752 17.552 5.752 16.4C5.752 15.2587 5.97067 14.256 6.408 13.392C6.84533 12.5173 7.45867 11.8347 8.248 11.344C9.048 10.8533 9.99733 10.608 11.096 10.608C12.376 10.608 13.416 10.9173 14.216 11.536C15.0267 12.144 15.544 12.9973 15.768 14.096H14.28C14.1093 13.4027 13.7573 12.848 13.224 12.432C12.7013 12.0053 11.992 11.792 11.096 11.792C10.296 11.792 9.59733 11.9787 9 12.352C8.40267 12.7147 7.93867 13.2427 7.608 13.936C7.288 14.6187 7.128 15.44 7.128 16.4C7.128 17.36 7.288 18.1867 7.608 18.88C7.93867 19.5627 8.40267 20.0907 9 20.464C9.59733 20.8267 10.296 21.008 11.096 21.008C11.992 21.008 12.7013 20.8053 13.224 20.4C13.7573 19.984 14.1093 19.4347 14.28 18.752H15.768C15.544 19.8293 15.0267 20.672 14.216 21.28C13.416 21.888 12.376 22.192 11.096 22.192ZM21.0756 22L16.8676 10.8H18.3236L21.8596 20.576L25.4276 10.8H26.8516L22.6436 22H21.0756Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
_old/assets/icons/github.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.0001 0C7.16466 0 0 7.33526 0 16.3841C0 23.6231 4.58452 29.7645 10.9419 31.931C11.7415 32.0827 12.0351 31.5756 12.0351 31.1428C12.0351 30.7521 12.0202 29.4615 12.0133 28.0924C7.56209 29.0836 6.62283 26.1593 6.62283 26.1593C5.89499 24.2655 4.8463 23.762 4.8463 23.762C3.39461 22.7451 4.95573 22.766 4.95573 22.766C6.56242 22.8816 7.40842 24.4544 7.40842 24.4544C8.83547 26.9592 11.1514 26.235 12.0645 25.8164C12.2081 24.7574 12.6227 24.0347 13.0803 23.6255C9.52647 23.2112 5.7906 21.8064 5.7906 15.5284C5.7906 13.7396 6.41563 12.2781 7.43916 11.1307C7.27303 10.718 6.72537 9.05159 7.59415 6.7948C7.59415 6.7948 8.93774 6.35445 11.9953 8.47423C13.2716 8.11122 14.6404 7.92916 16.0001 7.92292C17.3599 7.92916 18.7297 8.11122 20.0084 8.47423C23.0623 6.35445 24.404 6.7948 24.404 6.7948C25.2749 9.05159 24.727 10.718 24.5608 11.1307C25.5868 12.2781 26.2075 13.7396 26.2075 15.5284C26.2075 21.8213 22.4645 23.2069 18.9017 23.6125C19.4756 24.1209 19.9869 25.118 19.9869 26.6466C19.9869 28.8388 19.9684 30.6032 19.9684 31.1428C19.9684 31.5788 20.2564 32.0897 21.0674 31.9288C27.4213 29.7599 32 23.6206 32 16.3841C32 7.33526 24.8364 0 16.0001 0ZM5.99257 23.3395C5.95733 23.4209 5.83227 23.4454 5.71834 23.3895C5.60229 23.336 5.53711 23.2251 5.57474 23.1434C5.60918 23.0596 5.7345 23.0362 5.85029 23.0924C5.9666 23.1458 6.03284 23.2579 5.99257 23.3395ZM6.7796 24.0587C6.70329 24.1311 6.55412 24.0975 6.45291 23.983C6.34825 23.8687 6.32864 23.716 6.40601 23.6425C6.4847 23.57 6.62937 23.6039 6.73429 23.7182C6.83895 23.8337 6.85935 23.9854 6.7796 24.0587ZM7.31953 24.9787C7.2215 25.0485 7.0612 24.9831 6.96211 24.8374C6.86407 24.6917 6.86407 24.517 6.96422 24.447C7.06358 24.377 7.2215 24.4399 7.32191 24.5845C7.41968 24.7327 7.41968 24.9074 7.31953 24.9787ZM8.23267 26.0443C8.14497 26.1433 7.95818 26.1168 7.82146 25.9816C7.68156 25.8495 7.64261 25.662 7.73058 25.563C7.81934 25.4637 8.00719 25.4916 8.14497 25.6257C8.28381 25.7575 8.3262 25.9464 8.23267 26.0443ZM9.41281 26.404C9.37413 26.5324 9.19423 26.5907 9.013 26.5362C8.83203 26.48 8.7136 26.3297 8.75016 26.2C8.78778 26.0709 8.96848 26.0101 9.15104 26.0684C9.33174 26.1243 9.45044 26.2735 9.41281 26.404ZM10.7559 26.5566C10.7604 26.6917 10.6067 26.8038 10.4165 26.8062C10.2252 26.8106 10.0704 26.7012 10.0683 26.5683C10.0683 26.4318 10.2185 26.3209 10.4098 26.3176C10.6001 26.3138 10.7559 26.4223 10.7559 26.5566ZM12.0753 26.5048C12.0981 26.6367 11.9658 26.7721 11.7769 26.8082C11.5912 26.8429 11.4192 26.7615 11.3957 26.6307C11.3726 26.4956 11.5072 26.3602 11.6927 26.3252C11.8819 26.2916 12.0512 26.3708 12.0753 26.5048Z" fill="#212121"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
8
_old/assets/icons/indiehackers.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" fill="#212121"/>
|
||||
<path d="M32 0H0V32H32V0Z" fill="#212121"/>
|
||||
<path d="M10.4 9.06665H7.2V22.9333H10.4V9.06665Z" fill="white"/>
|
||||
<path d="M16.8 9.06665H13.6V22.9333H16.8V9.06665Z" fill="white"/>
|
||||
<path d="M22.1333 14.4H16.2667V17.6H22.1333V14.4Z" fill="white"/>
|
||||
<path d="M24.8 9.06665H21.6V22.9333H24.8V9.06665Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 455 B |
3
_old/assets/icons/twitter.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0669 29C22.1394 29 28.7444 18.9957 28.7444 10.3226C28.7444 10.0413 28.7381 9.75382 28.7256 9.47256C30.0105 8.54336 31.1193 7.39242 32 6.0738C30.8033 6.6062 29.5329 6.95389 28.2319 7.10502C29.6017 6.28394 30.6274 4.99402 31.1187 3.47442C29.8301 4.23814 28.4208 4.77688 26.9513 5.06754C25.9611 4.01548 24.652 3.31888 23.2263 3.08546C21.8005 2.85204 20.3376 3.0948 19.0637 3.77619C17.7897 4.45759 16.7757 5.53967 16.1785 6.85515C15.5812 8.17062 15.4339 9.64622 15.7594 11.0538C13.15 10.9228 10.5973 10.245 8.26667 9.06422C5.93603 7.88336 3.87959 6.22598 2.23063 4.19942C1.39253 5.64439 1.13607 7.35429 1.51337 8.98149C1.89067 10.6088 2.87342 12.0314 4.26187 12.96C3.21951 12.927 2.19997 12.6463 1.2875 12.1413V12.2226C1.28657 13.739 1.8108 15.2088 2.77108 16.3824C3.73136 17.556 5.06843 18.3608 6.555 18.66C5.58941 18.9242 4.57597 18.9628 3.59313 18.7726C4.01261 20.0766 4.82877 21.2172 5.92769 22.0352C7.0266 22.8531 8.35347 23.3076 9.72313 23.335C7.39787 25.1616 4.52557 26.1522 1.56875 26.1476C1.04438 26.1468 0.520532 26.1146 0 26.0513C3.00381 27.9784 6.49804 29.0019 10.0669 29Z" fill="#212121"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
BIN
_old/assets/marvin.jpg
Normal file
|
After Width: | Height: | Size: 292 KiB |
230
_old/assets/style.css
Normal file
@@ -0,0 +1,230 @@
|
||||
:root {
|
||||
--dark-gray: #212121;
|
||||
--light-gray: #808080;
|
||||
--lighter-gray: rgb(238, 238, 238);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "DM Sans";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("/assets/font/DMSans-Regular.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "DM Sans";
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("/assets/font/DMSans-Italic.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "DM Sans";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("/assets/font/DMSans-Bold.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "DM Sans";
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
font-display: 700;
|
||||
src: url("/assets/font/DMSans-BoldItalic.ttf");
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "DM Sans", sans-serif;
|
||||
color: var(--dark-gray);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 60px;
|
||||
line-height: 78px;
|
||||
font-weight: bold;
|
||||
margin: 0 0 32px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: bold;
|
||||
font-size: 32px;
|
||||
line-height: 42px;
|
||||
margin: 0 0 16px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h2.name {
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 16px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
margin: 0 0 28px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: circle;
|
||||
margin: 0 0 28px 0;
|
||||
padding: 0 0 0 32px;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
background: var(--lighter-gray);
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0 0 28px 0;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
button, .button {
|
||||
width: 192px;
|
||||
height: 54px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
font-size: 22px;
|
||||
line-height: 29px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: var(--dark-gray);
|
||||
border-width: 0;
|
||||
border-radius: 8px;
|
||||
margin: 64px 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.1s linear;
|
||||
}
|
||||
|
||||
button:hover, .button:hover {
|
||||
color: #fff;
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.1s linear;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--dark-gray);
|
||||
transition: color 0.1s linear;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--light-gray);
|
||||
transition: color 0.1s linear;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: var(--light-gray);
|
||||
}
|
||||
|
||||
article a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 640px;
|
||||
margin: 128px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
max-width: 540px;
|
||||
border-width: 1px 0 0 0;
|
||||
border-style: solid;
|
||||
border-color: var(--light-gray);
|
||||
margin: 64px 0 0 0;
|
||||
padding: 32px 0 0 0;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 0 0 18px 0;
|
||||
}
|
||||
|
||||
.footer-icons {
|
||||
display: inline-grid;
|
||||
gap: 16px;
|
||||
grid-auto-flow: column;
|
||||
margin: 0 0 32px 0;
|
||||
}
|
||||
|
||||
.footer-icons img {
|
||||
transition: opacity 0.1s linear;
|
||||
}
|
||||
|
||||
.footer-icons img:hover {
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.1s linear;
|
||||
}
|
||||
|
||||
.back-to-homepage {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: var(--light-gray);
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.back-to-homepage:hover {
|
||||
color: var(--dark-gray);
|
||||
}
|
||||
|
||||
.back-to-homepage img {
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.cv {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.cv div:first-child {
|
||||
width: 128px;
|
||||
color: var(--light-gray);
|
||||
}
|
||||
|
||||
.fullpage-img {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: 896px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: var(--light-gray);
|
||||
margin: 0 0 24px 0;
|
||||
}
|
||||
3
_old/config.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[server]
|
||||
host = "localhost"
|
||||
port = 8080
|
||||
@@ -0,0 +1,89 @@
|
||||
{{partial "head" .}}
|
||||
|
||||
<div class="content">
|
||||
{{partial "back-to-homepage" .}}
|
||||
<h1 style="margin-bottom: 16px;">How I Built My Website Using Emvi as a Headless CMS</h1>
|
||||
<div class="date">14. June 2020</div>
|
||||
<article>
|
||||
<p>Welcome to my blog! My name is Marvin, I'm a software engineer and entrepreneur. I write about programming, servers, my work and everything I'm interested in. In my first blog post, I would like to show you how I build my website and the tools I used. You already guessed that from the title I suppose.</p>
|
||||
<p>You can find the full source code for my website on GitHub. It's MIT licensed, so you can build your own on top of it or just reuse parts of the code.</p>
|
||||
<h2>Goals</h2>
|
||||
<p>So first of all, here are the goals I set when I started:</p>
|
||||
<ul>
|
||||
<li>the page must be self hosted, I do like to have full control</li>
|
||||
<li>it must be fast and have a small footprint</li>
|
||||
<li>easy to deploy on cheap hardware</li>
|
||||
<li>I don't want to put too much thought and time into styling</li>
|
||||
<li>enable me to write articles without having to change the page itself, but don't require me to install (and update!) a full fledged CMS at the same time</li>
|
||||
</ul>
|
||||
<p>The last point is probably the most important one to me. My page won't change very frequently and I don't want to maintain a CMS. I also don't want to write a template for any CMS out there, as that quickly gets out of hand and is not worth the effort. Static HTML won't do it neither, as the blog articles need to be updated as soon as I release a new one or change an existing one.</p>
|
||||
<p>Lets go through the bullet points and the choices I made. The most interesting part is probably how I build the blog.</p>
|
||||
<h2>Server and Deployment</h2>
|
||||
<p>For hosting, I chose Hetzner as a cloud provider. The Hetzner cloud offers virtual machines, block storage and networking (subnets, floating IPs, ...). There is an API too, which can be used to automate things.</p>
|
||||
<p>My website is hosted on the smallest VM (CX11-CEPH) for 2,96 €/month, which is insanely cheap. It provides a single vCPU, 2GB RAM and 20GB storage. Which is sufficient for my simple page. I chose a CEPH machine, as this will store all data on block storage rather than on the machine itself, which decouples it from the hardware. In case of a hardware fault, Hetzner will boot up my server on a different machine and I won't have to do anything. I'm not sure if it assigns a different IP to the server in that case. For the OS I chose Ubuntu as I use that on my computer and I'm familiar with it.</p>
|
||||
<p>The software running my page is a custom server I build using Go (golang), as it is an excellent programming language and offers high performance. I will go into more detail about the code in a second.</p>
|
||||
<p>I use Docker and Compose to deploy my page. Both are well established tools to package and deploy software. These are the only tools I installed on the VM, so I just need to update the systems packages through apt from time to time. Within the docker-compose.yml I added Traefik as a reverse proxy to schedule a SSL certificate from Letsencrypt.</p>
|
||||
<p>Deploying my page is now as simple as building and pushing the Docker image, pulling it on the server and restarting the container. Of course you could automate that whole process so that the page updates itself, but again: I won't change the content frequently. So that's good enough.</p>
|
||||
<h2>Structure and Static Content</h2>
|
||||
<p>Lets taking a look at the directory structure:</p>
|
||||
<ul>
|
||||
<li>blog - code to load and cache blog articles</li>
|
||||
<li>static - static files (my picture, stylesheets, ...) and used to cache blog article attachments (more on that later)</li>
|
||||
<li>template - contains the HTML files to build the page</li>
|
||||
<li>tpl - code to load and build the page from the template files</li>
|
||||
</ul>
|
||||
<p>The root directory contains the <code>main.go</code> to wire everything up and set up the router, as well as the <code>Dockerfile</code>, <code>docker-compose.yml</code> and the Go dependencies (<code>go.mod</code>). Everything within the <code>static</code> directory is served as static content on the <code>/static/...</code> route. Each page has it's own handler function which assembles the HTML using the template files.</p>
|
||||
<p>Another point worth mentioning is gzip compression. I added the <code>gziphandler.GzipHandler</code> on the static route to compress files. The middleware is build by the New York Times and easy to integrate. You can check it out <a href="https://github.com/nytimes/gziphandler" target="_blank" rel="noreferrer">here</a>.</p>
|
||||
<h2>Styling</h2>
|
||||
<p>As I do like to keep things simple, I chose a micro CSS framework so that I don't have to bother with styling too much. Namely <a href="https://concrete.style/" target="_blank" rel="noreferrer">concrete</a>, which I adjusted a bit, to narrow the layout and add a header with my picture. Apart from that I'm quite pleased with the look of it. As a bonus, it also switches to dark mode automatically if you set that in your (OS) preferences.</p>
|
||||
<h2>Templating</h2>
|
||||
<p>To prevent writing the same HTML over and over again I made use of Go's template system. It's simple but powerful enough for most websites and you can extend it using function maps. Here is an example for the blog article page (the one you're looking at right now):</p>
|
||||
<pre><code language="text/html">{{`{{template "head.html"}}`}}
|
||||
{{`{{template "menu.html"}}`}}
|
||||
|
||||
<section>
|
||||
<h1>{{"{{.Title}}"}}</h1>
|
||||
<small>Published on {{`{{format .Published "2. January 2006"}}`}}</small>
|
||||
{{"{{.Content}}"}}
|
||||
</section>
|
||||
|
||||
{{`{{template "end.html"}}`}}</code></pre>
|
||||
<p><code>head</code>, <code>menu</code> and <code>end</code> are reused on all pages.</p>
|
||||
<p>I've added two functions to format dates and build the blog article slug from the title:</p>
|
||||
<pre><code language="text/x-go">var funcMap = template.FuncMap{
|
||||
"slug": slug.Make,
|
||||
"format": func(t time.Time, layout string) string {
|
||||
return t.Format(layout)
|
||||
},
|
||||
}</code></pre>
|
||||
<h2>Blog</h2>
|
||||
<p><a href="https://emvi.com/" target="_blank" rel="noreferrer">Emvi</a> offers an API which allows anyone to use it as a headless CMS. The main advantage of it is, that I can use its editor to write my blog articles, upload images/files and don't need to worry about hosting my own CMS. Apart from that I'm using Emvi for note taking and documentation anyways, so I can stay on the same platform.</p>
|
||||
<p>To read articles, I make use of the <a href="https://github.com/emvi/api-go" target="_blank" rel="noreferrer">Go client library</a>. It isn't complete yet, as Emvi is still in beta, but provides everything required to build a blog. On top of it I build my own type to cache articles and files and sort them into maps, which are rendered on my page later. You could just use the client to do all of that without caching, but to reduce latency and serve articles in case Emvi goes down for some reason, I thought that would be a good idea.</p>
|
||||
<pre><code language="text/x-go">type Blog struct {
|
||||
client *emvi.Client
|
||||
articles map[string]emvi.Article // id -> article
|
||||
articlesYear map[int][]emvi.Article // year -> articles
|
||||
nextUpdate time.Time
|
||||
}</code></pre>
|
||||
<p>The <code>client</code> is initialized with the client ID and secret I generated within Emvi, as well as the name of my organization. These are configured using environment variables, so that I can put them into the <code>docker-compose.yml</code>. <code>nextUpdate</code> is used to refresh the cache after some time. Articles and files will only be updated in case they have changed since the last time they have been accessed. The article content itself is cached in memory, files are stored on disk.</p>
|
||||
<p>Articles are put into two different maps. The first one is used to access any article by ID. The ID is read from the slug within the URL to render an article. The second map groups all articles by year, which is used to display them on the blogs overview page.</p>
|
||||
<p>Note that you need to set an article to "external" within Emvi to allow it to be read through the API. To prevent reading articles which do not belong to my blog, I filtered the results by the tag "blog" and sort them in descending order:</p>
|
||||
<pre><code language="text/x-go">results, _, err = blog.client.FindArticles("", &emvi.ArticleFilter{
|
||||
BaseSearch: emvi.BaseSearch{Offset: offset},
|
||||
Tags: "blog",
|
||||
SortPublished: emvi.SortDescending,
|
||||
})</code></pre>
|
||||
<p>The offset is provided to read articles in a loop, as you can only read a fixed amount of results in one call. Afterwards, the content and files are read and cached for all results. I also added some regex to replace the paths within the content of each article to read images and files from my page instead of accessing Emvi.</p>
|
||||
<p>And that's pretty much it. If you now visit my website, it will extract the ID from the URL, look up the cache, update it if required and return the result to you.</p>
|
||||
<h2>Conclusion</h2>
|
||||
<p>Personal blogging is something I love about the internet and I now started my own blog. In terms of cost, running this page costs me 2,96 €/month for the server and 5$/month for Emvi (also I'm not paying for it as I'm the co-founder) plus something for the domain, which is insignificant. The solution I chose is fun and easy to implement, but certainly not suitable for non-programmers. I hope I can provide a plug and play solution in the future. It will most likely also use Emvi, as we are turning it into a platform for all sorts of different applications.</p>
|
||||
<p>In case you would like to send me feedback or have a question, you can contact me by <a href="mailto:marvin@marvinblum.de" target="_blank" rel="noreferrer">mail</a> or on <a href="https://twitter.com/m5blum" target="_blank" rel="noreferrer">Twitter</a>.</p>
|
||||
</article>
|
||||
|
||||
{{partial "footer" .}}
|
||||
</div>
|
||||
<div class="fullpage-img">
|
||||
<img src="assets/blog/how-i-built-my-website-using-emvi-as-a-headless-cms/test.png" alt="Test" />
|
||||
</div>
|
||||
|
||||
{{partial "end" .}}
|
||||
41
_old/content/cv/index.html
Normal file
@@ -0,0 +1,41 @@
|
||||
{{partial "head" .}}
|
||||
|
||||
<div class="content">
|
||||
{{partial "back-to-homepage" .}}
|
||||
<h1>Curriculum Vitae</h1>
|
||||
<h2>Work Experience</h2>
|
||||
<div class="cv">
|
||||
<div>
|
||||
2020 — Now
|
||||
</div>
|
||||
<div>
|
||||
Software Developer<br />
|
||||
skalar GmbH
|
||||
</div>
|
||||
</div>
|
||||
<div class="cv">
|
||||
<div>
|
||||
2018 — Now
|
||||
</div>
|
||||
<div>
|
||||
Co-Founder<br />
|
||||
Emvi Software GmbH
|
||||
</div>
|
||||
</div>
|
||||
<div class="cv">
|
||||
<div>
|
||||
2014 — 2020
|
||||
</div>
|
||||
<div>
|
||||
Software Developer<br />
|
||||
arvato business support GmbH
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{partial "footer" .}}
|
||||
</div>
|
||||
<div class="fullpage-img">
|
||||
<img src="assets/marvin.jpg" alt="Marvin" />
|
||||
</div>
|
||||
|
||||
{{partial "end" .}}
|
||||
13
_old/content/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{{partial "head" .}}
|
||||
|
||||
<div class="content">
|
||||
<h2 class="name">Marvin Blum <span style="color: #808080;">— Rheda-Wiedenbrück, Germany</span></h2>
|
||||
<h1>Full-Stack<br />Software Developer</h1>
|
||||
<p>Hi, my name is Marvin. I'm a full-stack software engineer from Germany, open-source and Linux enthusiast, and co-founder of Emvi, where we build and maintain software projects for clients, and work on existing software.</p>
|
||||
<p>I’m building Pirsch Analytics — a privacy-friendly, cookie-less web analytics solution made for personal websites, freelancers, and agencies.</p>
|
||||
<a class="button" href="mailto:marvin@marvinblum.de">Get in touch</a>
|
||||
|
||||
{{partial "footer" .}}
|
||||
</div>
|
||||
|
||||
{{partial "end" .}}
|
||||
19
_old/content/legal/index.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{partial "head" .}}
|
||||
|
||||
<div class="content">
|
||||
{{partial "back-to-homepage" .}}
|
||||
<h1>Legal</h1>
|
||||
<h2>According to §5 TMG</h2>
|
||||
<p>
|
||||
Marvin Blum<br />
|
||||
Gerhard-Hauptmannstraße 3<br />
|
||||
33378 Rheda-Wiedenbrück, Germany<br />
|
||||
marvin@marvinblum.de
|
||||
</p>
|
||||
<h2>Cookie Policy</h2>
|
||||
<p>This page does not use cookies.</p>
|
||||
|
||||
{{partial "footer" .}}
|
||||
</div>
|
||||
|
||||
{{partial "end" .}}
|
||||
4
_old/partials/back-to-homepage.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<a href="/" class="back-to-homepage">
|
||||
<img src="assets/icons/arrow.svg" alt="<" />
|
||||
Back to Homepage
|
||||
</a>
|
||||
2
_old/partials/end.html
Normal file
@@ -0,0 +1,2 @@
|
||||
</body>
|
||||
</html>
|
||||
24
_old/partials/footer.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<div class="footer">
|
||||
<div class="footer-icons">
|
||||
<a href="https://github.com/Kugelschieber" target="_blank">
|
||||
<img src="assets/icons/github.svg" alt="GitHub" />
|
||||
</a>
|
||||
<a href="https://twitter.com/m5blum" target="_blank">
|
||||
<img src="assets/icons/twitter.svg" alt="Twitter" />
|
||||
</a>
|
||||
<a href="https://www.indiehackers.com/m5blum" target="_blank">
|
||||
<img src="assets/icons/indiehackers.svg" alt="IndieHackers" />
|
||||
</a>
|
||||
<a href="/cv">
|
||||
<img src="assets/icons/cv.svg" alt="GitHub" />
|
||||
</a>
|
||||
</div>
|
||||
<p>
|
||||
<small>© 2021 Marvin Blum. This page does not use cookies. Website Analytics by <a href="https://pirsch.io/" target="_blank">Pirsch</a>.</small>
|
||||
</p>
|
||||
<p>
|
||||
<small>
|
||||
<a href="/legal">Legal</a>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
45
_old/partials/head.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/" />
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="copyright" content="Marvin Blum" />
|
||||
<meta name="author" content="Marvin Blum" />
|
||||
<meta name="title" content="Marvin Blum — Full-Stack Software Developer" />
|
||||
<meta name="description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
||||
<meta name="twitter:card" content="profile" />
|
||||
<meta name="twitter:site" content="@m5blum" />
|
||||
<meta name="twitter:title" content="Marvin Blum" />
|
||||
<meta name="twitter:description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
||||
<meta name="twitter:image" content="https://marvinblum.de/assets/avatar.png" />
|
||||
<meta property="og:url" content="https://marvinblum.de/" />
|
||||
<meta property="og:title" content="Marvin Blum" />
|
||||
<meta property="og:description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
||||
<meta property="og:image" content="https://marvinblum.de/assets/avatar.png" />
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="assets/favicon/ms-icon-144x144.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="assets/favicon/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="assets/favicon/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="assets/favicon/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="assets/favicon/apple-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="assets/favicon/apple-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="assets/favicon/apple-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="assets/favicon/apple-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="assets/favicon/apple-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="assets/favicon/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="assets/favicon/manifest.json">
|
||||
<link rel="stylesheet" type="text/css" href="assets/style.css" />
|
||||
|
||||
<title>Marvin Blum — Full-Stack Software Developer</title>
|
||||
|
||||
<script defer type="text/javascript" src="https://api.pirsch.io/pirsch.js"
|
||||
id="pirschjs"
|
||||
data-code="1vNLFfXQ6qdYSaa9x1CGNwLwFDbD19zT"></script>
|
||||
</head>
|
||||
<body>
|
||||