Compare commits
25 Commits
b805a9ef6d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 02ee9aea80 | |||
| 23ec1fdf80 | |||
| ef8a22d55b | |||
| 5aa3612cdd | |||
| 9adf6f55a2 | |||
| 604d301a1c | |||
| 956b284d4b | |||
| 0a4b1a24f7 | |||
| de2044343d | |||
| b8b7baee0f | |||
| 162fbabd26 | |||
| 9702741bbe | |||
| 9c9db094a0 | |||
| ac62e53698 | |||
| 5d34c5f815 | |||
| 4177e53284 | |||
| aeb85fc7cc | |||
| d88df0413e | |||
| 69a5fab541 | |||
| 77242246e1 | |||
| f1ba956126 | |||
| fbec385422 | |||
| f775ed5800 | |||
| 2bb5b50ded | |||
| 635aa4dfc7 |
52
.gitea/workflows/deploy.yml
Normal file
52
.gitea/workflows/deploy.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Build and Publish Web
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Hugo Setup
|
||||
uses: peaceiris/actions-hugo@v2
|
||||
with:
|
||||
hugo-version: 'latest'
|
||||
extended: true
|
||||
|
||||
- name: Web Build
|
||||
run: |
|
||||
cd themes/minimal-black
|
||||
npm install
|
||||
cd ../..
|
||||
hugo --minify -b "https://jirkabuilds.dev/"
|
||||
|
||||
- name: Packaging ZIP
|
||||
run: |
|
||||
cd public
|
||||
zip -r ../build.zip ./*
|
||||
|
||||
- name: Delete old package (if exists)
|
||||
run: |
|
||||
curl -X DELETE --user "${{ github.actor }}:${{ secrets.PACKAGE_TOKEN }}" \
|
||||
"https://git.jirkabuilds.dev/api/packages/${{ github.repository_owner }}/generic/hugo-build/latest/web.zip" || true
|
||||
|
||||
- name: Uploading package back to gitea
|
||||
run: |
|
||||
curl -f --user "${{ github.actor }}:${{ secrets.PACKAGE_TOKEN }}" \
|
||||
--upload-file build.zip \
|
||||
"https://git.jirkabuilds.dev/api/packages/${{ github.repository_owner }}/generic/hugo-build/latest/web.zip"
|
||||
|
||||
- name: Trigger Webhook
|
||||
run: |
|
||||
curl -X POST "https://hooks.jirkabuilds.dev/hooks/deploy-web" \
|
||||
-H "X-Deploy-Token: ${{ secrets.WEBHOOK_SECRET }}"
|
||||
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
public/
|
||||
0
.hugo_build.lock
Normal file
0
.hugo_build.lock
Normal file
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 JirkaBuilds
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
11
README.md
11
README.md
@@ -1,8 +1,8 @@
|
||||
# JirkaBuilds.dev
|
||||
|
||||
This repository holds whole entry side to [JirkaBuilds.dev](https://jirkabuilds.dev).
|
||||
This repository holds whole entry site to [JirkaBuilds.dev](https://jirkabuilds.dev).
|
||||
|
||||
Template, settings and all blog and project articles are store in this repository.
|
||||
Template, settings and all blog and project articles are stored in this repository.
|
||||
|
||||
## Adding new content
|
||||
|
||||
@@ -13,3 +13,10 @@ hugo new projects/{article_name}.md
|
||||
```
|
||||
|
||||
Then just edit `*/{article_name}.md` and add content.
|
||||
|
||||
|
||||
|
||||
## License
|
||||
Unless otherwise stated, the source code of this website (HTML, CSS, JavaScript, Hugo configuration) is licensed under the **MIT License** (see the `LICENSE` file).
|
||||
|
||||
All blog content (article texts in the `/content` directory and photographs/images created by the author) is licensed under the **Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)**.
|
||||
|
||||
3
content/_index.md
Normal file
3
content/_index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
{}
|
||||
---
|
||||
151
content/blog/01-blog.md
Normal file
151
content/blog/01-blog.md
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
date: '2026-03-23T17:27:50+01:00'
|
||||
draft: false
|
||||
title: "Let's start a blog!"
|
||||
author: "Jirka"
|
||||
tags: ["hugo", "idea", "guide", "tutorial"]
|
||||
categories: ["Blog setup", "idea"]
|
||||
description: "Behind the scenes of the idea and setup."
|
||||
---
|
||||
|
||||
This blog has been created as one of my personal projects, first one released on this page. (Actually, this article, about creating this blog, is being written before creation of the blog and this page).
|
||||
|
||||
> [!WARNING]
|
||||
> If you decide to follow my steps, please, be careful and use your head. I do not guarantee they will work for you and do so at your own risk.
|
||||
|
||||
|
||||
## How it started
|
||||
|
||||
The original idea was simple: **I would like to share my projects and knowledge with others.** But how?
|
||||
|
||||
I am big fan of self-hosting and in the same time I hate JavaScript, CSS and anything connected to web development. (A bit embarrassing I know).
|
||||
|
||||
So how to self-host simple web/blog without need to write the HTML and all the web things on my own?
|
||||
|
||||
The answer is [Hugo](https://gohugo.io/)! I do not feel knowledgeable enough to describe what Hugo is, but I will share with you, how I am going to use it.
|
||||
|
||||
## Dreamed solution
|
||||
|
||||
Before finding out what Hugo is, I had a pretty complete idea of how this should work:
|
||||
|
||||
- Every project shared here should be shared with source files, so anyone can reuse and replicate my journey.
|
||||
- I am using [Markdown](https://www.markdownguide.org/) for all my notes, so it would be best, if I can write and article in Markdown and then convert it to static page. **And that is exactly what Hugo is going to do for me!**
|
||||
|
||||
Those two things connects together like this:
|
||||
- Self host a Git with project files for Hugo
|
||||
- Write new articles in `.md` and upload them to the server via git.
|
||||
- When pushing to `master` branch, build/update the page files and automatically reflect it to the website.
|
||||
|
||||
This article is just about how to set up the Hugo for my purpose and how the content in associated repository has been created. More about the self-hosting in the future articles.
|
||||
|
||||
## Hugo setup
|
||||
|
||||
Work with Hugo is super simple, I've start with their official [guide](https://gohugo.io/getting-started/quick-start/), but I will cover basic steps here.
|
||||
|
||||
To create empty Hugo project (after installing Hugo), start with:
|
||||
```bash
|
||||
hugo new project name_of_new_directory
|
||||
```
|
||||
|
||||
This creates initial `hugo.toml`. Right now, you should know which theme for your side you would like to use. List of example theme can be found (here)[https://themes.gohugo.io/]. As you can see, I have chosen (Minimal Black)[https://themes.gohugo.io/themes/hugo-minimal-black/].
|
||||
|
||||
Next step is to download the theme and add it to the `themes` directory. You can clone it from Git repository, or download it. What is right depends just on your target usage.
|
||||
|
||||
I am going to download the theme as `.zip`, because I am going to edit some files and would like to reflect the changes in my own repository.
|
||||
|
||||
```bash
|
||||
cd themes
|
||||
wget 'https://gitlab.com/jimchr12/hugo-minimal-black/-/archive/main/hugo-minimal-black-main.zip?ref_type=heads' -O theme.zip
|
||||
unzip theme.zip
|
||||
mv hugo-minimal-black-main minimal-black
|
||||
rm theme.zip
|
||||
```
|
||||
|
||||
Right now, you should have your theme installed in right directory and follow the directions from theme author.
|
||||
|
||||
For me this means I should install `npm` dependencies and copy example `hugo.toml`.
|
||||
```bash
|
||||
cd minimal-black/
|
||||
npm install
|
||||
cd ../..
|
||||
cp themes/minimal-black/exampleSite/hugo.toml ./hugo.toml
|
||||
```
|
||||
|
||||
From now on, you should be good to go. Go through the `hugo.toml` file, edit it to your likeness and create some content.
|
||||
|
||||
I am not going to explain what changes I've done (you can see them in the repo (Once it is set up #TODO)), neither I am going to talk about writing this how to guide, but I will add changes that I've done to the project.
|
||||
|
||||
## Work with Hugo
|
||||
|
||||
When you want to write new article, you create new `.md` file in `content` directory with:
|
||||
```bash
|
||||
hugo new blog/01-blog.md
|
||||
```
|
||||
|
||||
This will automatically add header to your file. How the header is going to look like is based on your `archetypes`. Those are `.md` files located in `archetypes` folder.
|
||||
|
||||
`default.md` is created by default, and you can look what it contains on your own. I recommend copy it to your specialized `.md` files and add lines which you need (probably based on your theme selection).
|
||||
|
||||
For example, I've created `blog.md` file, and it is going to be used for files created under `blog` directory.
|
||||
|
||||
It looks like this:
|
||||
```toml
|
||||
---
|
||||
date: '{{ .Date }}'
|
||||
draft: true
|
||||
title: '{{ replace .File.ContentBaseName "-" " " | title }}'
|
||||
author: "Jirka"
|
||||
tags: ["tag"]
|
||||
categories: ["documentation"]
|
||||
description: "..."
|
||||
---
|
||||
```
|
||||
As you can see, it is going to prefill date, author, ... for me and prepares fields where I can specify description, categories, etc. for the article.
|
||||
|
||||
> [!NOTE]
|
||||
> I've switched to `yaml` format, because it looks better (by me) in raw `.md` files rendered in Gitea for example.
|
||||
|
||||
Now, when you created your `.md` files (not just archetypes, but some testing article also), you can start your development server and see how it looks like by:
|
||||
```bash
|
||||
hugo server # or hugo server -D for preview of draft files
|
||||
```
|
||||
|
||||
And that's it! You've just created new web!
|
||||
|
||||
For publication, right now the web can be prepared for publishing just by executing `hugo`. It will generate static files in `public` folder. On how to make web online, you can check [official documentation](https://gohugo.io/host-and-deploy/). I will cover how this website is going to be published in next articles.
|
||||
|
||||
|
||||
## Advanced changes
|
||||
|
||||
You do not have to do anything more. I will just cover changes I've made to make publishing for me just a bit easier.
|
||||
|
||||
### Use natural Markdown linking between files
|
||||
|
||||
Right now, Hugo does not expect me to link articles just how I would link them in plain `.md` files. I do not like that, because it would not work in repo that way.
|
||||
So I've created `layouts/_default/_markup/render-link.html` will follow content:
|
||||
|
||||
```html
|
||||
{{- $link := .Destination -}}
|
||||
{{- $isRemote := or (strings.HasPrefix $link "http") (strings.HasPrefix $link "mailto") -}}
|
||||
|
||||
{{- if not $isRemote -}}
|
||||
{{- $url := urls.Parse .Destination -}}
|
||||
{{- $fragment := "" -}}
|
||||
{{- with $url.Fragment }}{{ $fragment = printf "#%s" . }}{{ end -}}
|
||||
|
||||
{{- /* Try to find the page by its path */ -}}
|
||||
{{- with .Page.GetPage $url.Path -}}
|
||||
{{- $link = printf "%s%s" .RelPermalink $fragment -}}
|
||||
{{- else -}}
|
||||
{{- /* If the page is not found, throw a warning during build. */ -}}
|
||||
{{- warnf "Broken link to page: '%s' found in file %s" .Destination .Page.File.Path -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<a href="{{ $link | safeURL }}"{{ with .Title }} title="{{ . }}"{{ end }}>{{ .Text | safeHTML }}</a>
|
||||
```
|
||||
|
||||
It creates correct links just from file links and will warn me if anything will go wrong.
|
||||
|
||||
---
|
||||
This article is one from series about this blog and self-hosting. All connected articles can found [here](categories/blog-setup).
|
||||
269
content/blog/02-hosting-blog.md
Normal file
269
content/blog/02-hosting-blog.md
Normal file
@@ -0,0 +1,269 @@
|
||||
---
|
||||
date: '2026-03-24T10:17:21+01:00'
|
||||
draft: false
|
||||
title: 'How to self-host a blog'
|
||||
author: "Jirka"
|
||||
tags: ["caddy", "docker", "wireguard", "vps", "guide", "tutorial", "self-hosted"]
|
||||
categories: ["Blog setup"]
|
||||
description: "How this blog came online without public IP."
|
||||
---
|
||||
|
||||
In last post [Let's start a blog!](./01-blog.md) I've introduced idea and how-to guide how I will write this blog, but left it offline.
|
||||
That is going to change in this post.
|
||||
|
||||
> [!WARNING]
|
||||
> If you decide to follow my steps, please, be careful and use your head. I do not guarantee they will work for you and do so at your own risk.
|
||||
|
||||
## Requirements
|
||||
|
||||
This whole blog/website project is about self-hosting. That said, I will host this page my on server/PC at home. For that, you usually have public IP address.
|
||||
|
||||
In this setup, I will act like I do not have one (or you do not want to use it) and setup proxy with VPS in cloud.
|
||||
You do not have to copy my setup. If you have public IP address, you can use it and skip whole VPS and WireGuard setup. Or you can host the entire web on VPS. The path I have chosen is the most complicated, so you should be able to make it easier for your self.
|
||||
|
||||
That said, here are requirements for my setup:
|
||||
|
||||
- Home server with Docker ([Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/))
|
||||
- VPS with public IPv4 address
|
||||
- Domain (Like [JirkaBuilds.dev](https://jirkabuilds.dev))
|
||||
- The desire to set it all up
|
||||
|
||||
## Target solution
|
||||
|
||||
Static web files are generated by Hugo in `public` folder, how to set up Hugo I've explained [here](./01-blog.md). We will look on how to automate everything in the future posts, but for now, just assume you can always build Hugo files and copy them to the web folder.
|
||||
|
||||
Those web files you need to somehow serve, for that purpose there are web servers like [Apache](https://httpd.apache.org/), [Nginx](https://nginx.org/en/), etc. I will use [Caddy](https://caddyserver.com/), it is (at least by me) the easiest one to set up, and it will handle HTTPS certificates for us automatically.
|
||||
|
||||
For connection between VPS and our home server, we will use WireGuard and HAProxy for correct source address pass-through via VPN to Caddy.
|
||||
|
||||
And that is all, pretty straight forward, isn't it?
|
||||
|
||||
When you will connect to [JirkaBuilds.dev](https://jirkabuilds.dev), you will type the URL into the browser, it will find DNS entry for the domain and return IP address of the VPS. Then your browser will connect to VPS, which will wrap the request via HAProxy and sent it via WireGuard to my home server. There, the caddy will serve the static web for you and send it via WireGuard via VPS back to your browser.
|
||||
|
||||
As you can see, there is actually no way for you to know, that the web is running in different place than VPS, and that is the purpose.
|
||||
|
||||
## Set up VPS
|
||||
|
||||
I assume that you've already purchased some domain. Setting up the VPS will be similar, you have to find a provider which will rent you a server and public IPv4 address. Which one is best depends on your location and how powerful you want your VPS to be.
|
||||
For my purpose, I've purchased the cheapest one possible. I do not need much power and 1 CPU with 1 GB of RAM will work just fine. Remember that the only purpose of VPS is to redirect the connection. All the heavy lifting will be done on my home server.
|
||||
|
||||
From this point, I assume that you do have set up domain and DNS to your IPv4 address (how to do so depends on from who you bought the domain), and you do have SSH access to your VPS.
|
||||
|
||||
Also, I am using VPS with Ubuntu 24, so all commands count with that.
|
||||
|
||||
### Set up SSH
|
||||
|
||||
This should be your first step. You do not want to enable connection to your VPS just via password. You should generate keys and set up your VPS to accept SSH connection only via them. Good guide how to do from DigitalOcean is [here](https://www.digitalocean.com/community/tutorials/how-to-configure-ssh-key-based-authentication-on-a-linux-server).
|
||||
|
||||
Second thing I suggest is to change ssh port, it is not mandatory, but it is generally better to change from 22, so no one can just try to connect to port 22.
|
||||
Before changing port, you should open it in your firewall first. I suppose `ufw` is active and only open port is ssh.
|
||||
It beginning it should look like this:
|
||||
```text
|
||||
root@server:~# ufw status
|
||||
Status: active
|
||||
To Action From
|
||||
-- ------ ----
|
||||
OpenSSH ALLOW Anywhere
|
||||
OpenSSH (v6) ALLOW Anywhere (v6)
|
||||
```
|
||||
|
||||
You should open your new port with:
|
||||
|
||||
```bash
|
||||
ufw allow 2222/tcp # or any port you desire
|
||||
```
|
||||
|
||||
Change ssh port in `/etc/ssh/sshd_config`. Find line with `#Port 22` and change it to `Port 2222` (or any port you choose).
|
||||
Now restart ssh `systemctl restart ssh` or server and try to connect via new port. If it works, then you can remove port 22 from `ufs` via:
|
||||
|
||||
```bash
|
||||
ufw delete allow OpenSSH
|
||||
```
|
||||
|
||||
If it doesn't work... well, you have to somehow regain access to your server. You should be able to connect via some kind of console provided by you VPS provider(I cannot really help you with that). And correct your setup.
|
||||
|
||||
|
||||
### Set up WireGuard
|
||||
|
||||
WireGuard is our next step.
|
||||
|
||||
First install WireGuard tools on VPS:
|
||||
```bash
|
||||
apt install wireguard-tools
|
||||
```
|
||||
|
||||
Now, generate key pairs. It does not really matter where you do so, but I do prefer store them somehow locally.
|
||||
|
||||
```bash
|
||||
wg genkey | tee vps_private | wg pubkey > vps_public
|
||||
wg genkey | tee home_private | wg pubkey > home_public
|
||||
```
|
||||
|
||||
Those keys will be used for authentication between VPS and our server, you should keep them for your self.
|
||||
|
||||
Now, let's describe our networks on VPS. Create file `/etc/wireguard/wg0.conf` will follow content:
|
||||
```toml
|
||||
[Interface]
|
||||
Address = 10.0.0.1/24
|
||||
ListenPort = 51820
|
||||
PrivateKey = <VPS_PRIVATE_KEY>
|
||||
|
||||
[Peer]
|
||||
# Home Server
|
||||
PublicKey = <HOME_PUBLIC_KEY>
|
||||
AllowedIPs = 10.0.0.2/32
|
||||
```
|
||||
|
||||
What that means. We are creating new network, where VPS have address `10.0.0.1` and Peer (our home server) will get address `10.0.0.2` (We will set up mirror config on home server later).
|
||||
|
||||
Now we can enable the network by:
|
||||
```bash
|
||||
systemctl enable --now wg-quick@wg0
|
||||
```
|
||||
|
||||
### Set up HAProxy
|
||||
|
||||
Now the HAProxy. I think there is good to say a few words why. If we would not set up HAProxy, then from point of view of our home server, all connections would have origin IP of our VPS. We would not be able to check from where did they come from.
|
||||
What HAProxy will do for us is that it will add right origin IP address in front of the requests and Caddy will read it and correctly replace the IP address in the requests.
|
||||
|
||||
That said, we can move to the set-up. Start with installing the HAProxy:
|
||||
```bash
|
||||
apt install haproxy
|
||||
```
|
||||
I should automatically enable HAProxy service.
|
||||
|
||||
Now add following line to the end of `/etc/haproxy/haproxy.cfg`
|
||||
```text
|
||||
frontend http_front
|
||||
bind *:80
|
||||
mode tcp
|
||||
option tcplog
|
||||
default_backend caddy_http
|
||||
|
||||
frontend https_front
|
||||
bind *:443
|
||||
mode tcp
|
||||
option tcplog
|
||||
default_backend caddy_https
|
||||
|
||||
backend caddy_http
|
||||
mode tcp
|
||||
server home_caddy 10.0.0.2:80 send-proxy-v2
|
||||
|
||||
backend caddy_https
|
||||
mode tcp
|
||||
server home_caddy 10.0.0.2:443 send-proxy-v2
|
||||
```
|
||||
|
||||
What does it mean? Frontend configuration specifies on which port listen and to what backend passes the packets. And the backend just specifies where to send packets.
|
||||
|
||||
You can see that we will be listening on port 80 and 443 (for HTTP and HTTPS) and the communication will be passed via WireGuard network to our home server.
|
||||
|
||||
Now restart the HAProxy.
|
||||
```bash
|
||||
systemctl restart haproxy
|
||||
```
|
||||
|
||||
### Set up firewall
|
||||
|
||||
Last step on side of VPS is to open firewall for HAProxy and WireGuard. This is done similarly as for ssh earlier:
|
||||
```bash
|
||||
# Allow HTTP and HTTPS for HAProxy
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 443/tcp
|
||||
|
||||
# Allow WireGuard connection via UDP
|
||||
sudo ufw allow 51820/udp
|
||||
```
|
||||
|
||||
And that's it! VPS is all set up.
|
||||
|
||||
## Set up home server
|
||||
|
||||
Only requirement for our home server is running docker.
|
||||
|
||||
Let's start by creating `docker-compose.yml`:
|
||||
```yaml docker-compose.yml
|
||||
# Shared network between containers (for later use)
|
||||
networks:
|
||||
proxy_network:
|
||||
|
||||
services:
|
||||
wireguard:
|
||||
image: linuxserver/wireguard:latest
|
||||
container_name: wireguard
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
volumes:
|
||||
- ./wg-config:/config
|
||||
- /lib/modules:/lib/modules:ro
|
||||
restart: unless-stopped
|
||||
# We will add wireguard to this network, but limit access from WG itself
|
||||
networks:
|
||||
- proxy_network
|
||||
|
||||
caddy:
|
||||
image: caddy:latest
|
||||
container_name: caddy
|
||||
# Caddy shares all networks with WireGuard
|
||||
network_mode: "service:wireguard"
|
||||
volumes:
|
||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile
|
||||
- ./site_data:/srv
|
||||
- ./caddy/data:/data
|
||||
- ./caddy/config:/config
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
Now, in same directory copy and rename `public` folder generated by Hugo to `site_data`. Also create folders `caddy` and `wg-config`.
|
||||
|
||||
Now setup Caddy to host web via WireGuard and use data from HAProxy. Create `caddy/Caddyfile` with following content:
|
||||
```text
|
||||
{
|
||||
servers {
|
||||
listener_wrappers {
|
||||
proxy_protocol {
|
||||
timeout 2s
|
||||
allow 10.0.0.1/32
|
||||
}
|
||||
tls
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jirkabuilds.dev {
|
||||
root * /srv
|
||||
file_server
|
||||
}
|
||||
```
|
||||
|
||||
And final setup for WireGuard. Create `wg-config/wg0.conf` with following content:
|
||||
```toml
|
||||
[Interface]
|
||||
PrivateKey = <HOME_PRIVATE_KEY>
|
||||
Address = 10.0.0.2/24
|
||||
# Do not allow access to anything outside wg0 network
|
||||
PostUp = iptables -I FORWARD -i wg0 -j DROP
|
||||
PreDown = iptables -D FORWARD -i wg0 -j DROP
|
||||
|
||||
[Peer]
|
||||
PublicKey = <VPS_PUBLIC_KEY>
|
||||
Endpoint = <PUBLIC_IP_VPS_IP>:51820
|
||||
# Only requests for VPS are routed
|
||||
AllowedIPs = 10.0.0.1/32
|
||||
# This will send packet every 25 seconds from home server to keep active connection with VPS
|
||||
# Remember we do not have public IP to connect to from VPS
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
And that's it! All should be good to go! Just start the containers with:
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
And your web should be online.
|
||||
|
||||
---
|
||||
This article is one from series about this blog and self-hosting. All connected articles can found [here](categories/blog-setup).
|
||||
162
content/blog/03-gitea.md
Normal file
162
content/blog/03-gitea.md
Normal file
@@ -0,0 +1,162 @@
|
||||
---
|
||||
date: '2026-03-24T18:48:40+01:00'
|
||||
draft: false
|
||||
title: 'Time for version control'
|
||||
author: "Jirka"
|
||||
tags: ["gitea", "self-hosted", "docker", "guide", "tutorial"]
|
||||
categories: ["Blog setup"]
|
||||
description: "Let's look at how to self host gitea on our infrastructure."
|
||||
---
|
||||
|
||||
How would I share my projects with you, if I do not have any git hosting server? I don't know...
|
||||
|
||||
That is a reason, why we need to set up one! You can choose from more variant, but I decided for [Gitea](https://about.gitea.com/).
|
||||
|
||||
> [!WARNING]
|
||||
> If you decide to follow my steps, please, be careful and use your head. I do not guarantee they will work for you and do so at your own risk.
|
||||
|
||||
## Self-hosting Gitea
|
||||
|
||||
Thanks to our earlier setup, next step is pretty simple.
|
||||
|
||||
All what we need to do is expand our docker setup, but I will do one more step, because we will need to share network between docker containers, I will add one more layer of docker compose, for holding the networks alive.
|
||||
|
||||
That said, I will continue with setup from [last article](./02-hosting-blog.md) about setting up whole server and WireGuard.
|
||||
|
||||
### Change docker layout
|
||||
|
||||
Let's start with stopping our containers for a while (execute commands in same folder as docker compose).
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
Create new folder for all the files, for example `web` and move all files into it.
|
||||
```bash
|
||||
mkdir web
|
||||
mv * web
|
||||
```
|
||||
|
||||
Now create new `docker-compose.yml` file with following content:
|
||||
```yaml
|
||||
networks:
|
||||
proxy_network:
|
||||
|
||||
include:
|
||||
- web/docker-compose.yml
|
||||
```
|
||||
|
||||
What you can see is, that we used same network as in `docker-compose.yml` file, and included it. We will add all future `docker-compose.yml` files here. Thanks to that we can control all containers by one command and share network between them, without need to manually create persistent docker networks.
|
||||
|
||||
You can start containers again by:
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
And you should be back online.
|
||||
|
||||
### Add Gitea to stack
|
||||
|
||||
Create new directory `gitea` for Gitea and it's data an into it create new `docker-compose.yml` file with following content:
|
||||
```yaml
|
||||
networks:
|
||||
gitea_internal:
|
||||
proxy_network:
|
||||
|
||||
services:
|
||||
gitea:
|
||||
image: docker.gitea.com/gitea:latest
|
||||
container_name: gitea
|
||||
environment:
|
||||
- USER_UID=${APP_UID}
|
||||
- USER_GID=${APP_GID}
|
||||
- GITEA__database__DB_TYPE=postgres
|
||||
- GITEA__database__HOST=gitea_db:5432
|
||||
- GITEA__database__NAME=${DB_NAME}
|
||||
- GITEA__database__USER=${DB_USER}
|
||||
- GITEA__database__PASSWD=${DB_PASSWORD}
|
||||
restart: always
|
||||
networks:
|
||||
- gitea_internal
|
||||
volumes:
|
||||
- ./gitea:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "222:22"
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
gitea_db:
|
||||
image: docker.io/library/postgres:14
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=${DB_USER}
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||
- POSTGRES_DB=${DB_NAME}
|
||||
networks:
|
||||
- gitea_internal
|
||||
volumes:
|
||||
- ./postgres:/var/lib/postgresql/data
|
||||
```
|
||||
|
||||
And create file `.env` with secrets:
|
||||
```text
|
||||
DB_NAME=gitea
|
||||
DB_USER=gitea_user
|
||||
DB_PASSWORD="super secret password"
|
||||
|
||||
APP_UID=1000
|
||||
APP_GID=1000
|
||||
```
|
||||
|
||||
Note that we specified proxy network in compose file but did not use it yet. That is because we will do first Gitea setup locally and after that we will connect it to our web.
|
||||
|
||||
Now you can `cd ..` to upper directory and add new `docker-compose.yml` to upper `docker-compose.yml`. Just add following line:
|
||||
```yaml
|
||||
- gitea/docker-compose.yml
|
||||
```
|
||||
|
||||
Now you can start Gitea by `docker compose up -d`.
|
||||
|
||||
After that, Gitea should be online. You should visit it, by going to `local_ip_address_of_server:3000` and finish first setup. This step is just up to you.
|
||||
|
||||
When you finish initial setup, we can add Gitea to our stack. Start by editing Gitea `docker-compose.yml`.
|
||||
|
||||
There you should remove line with ports and port definition under Gitea service and add proxy network under networks. Changed part of the file should look like this:
|
||||
```yaml
|
||||
gitea:
|
||||
image: docker.gitea.com/gitea:latest
|
||||
container_name: gitea
|
||||
environment:
|
||||
- USER_UID=${APP_UID}
|
||||
- USER_GID=${APP_GID}
|
||||
- GITEA__database__DB_TYPE=postgres
|
||||
- GITEA__database__HOST=gitea_db:5432
|
||||
- GITEA__database__NAME=${DB_NAME}
|
||||
- GITEA__database__USER=${DB_USER}
|
||||
- GITEA__database__PASSWD=${DB_PASSWORD}
|
||||
restart: always
|
||||
networks:
|
||||
- gitea_internal
|
||||
- proxy_network
|
||||
volumes:
|
||||
- ./gitea:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
depends_on:
|
||||
- gitea_db
|
||||
```
|
||||
|
||||
Last step is to change `Caddyfile` in `web` folder. All you need to do is add following lines:
|
||||
```
|
||||
git.jirkabuilds.dev {
|
||||
reverse_proxy gitea:3000
|
||||
}
|
||||
```
|
||||
If you have set up DNS correctly, after shutting the stack down and up the Gitea should be online.
|
||||
|
||||
And that's it!
|
||||
|
||||
---
|
||||
This article is one from series about this blog and self-hosting. All connected articles can found [here](categories/blog-setup).
|
||||
286
content/blog/04-automatic-release.md
Normal file
286
content/blog/04-automatic-release.md
Normal file
@@ -0,0 +1,286 @@
|
||||
---
|
||||
date: '2026-03-24T20:36:26+01:00'
|
||||
draft: false
|
||||
title: 'Automatic web deployment'
|
||||
author: "Jirka"
|
||||
tags: ["self-hosted", "gitea", "automation", "docker"]
|
||||
categories: ["Blog setup"]
|
||||
description: "Last step, how to relase changes to web automatically"
|
||||
---
|
||||
|
||||
Okay, this should be the last step of this article series. Automatic releases to web.
|
||||
|
||||
The idea is simple, push changes to `master` branch of repo holding this web and automatically release it.
|
||||
|
||||
We will split this problem into two: Building web with Hugo and second, updating files with webhook. Let's start.
|
||||
|
||||
> [!WARNING]
|
||||
> If you decide to follow my steps, please, be careful and use your head. I do not guarantee they will work for you and do so at your own risk.
|
||||
|
||||
## Set up Gitea runner
|
||||
|
||||
Gitea does support actions like GitHub, but you have to provide it runner to execute you tasks. We are going to use the most secured way, runner in docker, which will start another docker container in another docker.
|
||||
|
||||
I know, it sounds silly, it has a reason. You do not want to have container with privileges to run docker containers. It is even more true, when you consider you can control this container via Gitea actions. So, we will start docker in container and then give Gitea runner rights to that. It is actually much simpler to do, then it may seem like.
|
||||
|
||||
First, let's get registration runner token. Where you can obtain one depends on scope, from which the runner should be accessible. I want the runner to be usable only by me, so I will get registration token from Gitea → Profile settings → Actions → Runners → Create new Runner.
|
||||
|
||||
I will store this token into `.env` file inside my `gitea` directory created in [last](./03-gitea.md) article with `RUNNER_TOKEN` name.
|
||||
|
||||
Next we modify Gitea `docker-compose.yml` file to look as follows:
|
||||
```yml
|
||||
networks:
|
||||
gitea_internal:
|
||||
proxy_network:
|
||||
gitea_runner_net: # For runner dind comunication
|
||||
gitea_net: # For runner gitea comunication, isolated from db
|
||||
|
||||
|
||||
services:
|
||||
gitea:
|
||||
image: docker.gitea.com/gitea:latest
|
||||
container_name: gitea
|
||||
environment:
|
||||
- USER_UID=${APP_UID}
|
||||
- USER_GID=${APP_GID}
|
||||
- GITEA__database__DB_TYPE=postgres
|
||||
- GITEA__database__HOST=gitea_db:5432
|
||||
- GITEA__database__NAME=${DB_NAME}
|
||||
- GITEA__database__USER=${DB_USER}
|
||||
- GITEA__database__PASSWD=${DB_PASSWORD}
|
||||
restart: always
|
||||
networks:
|
||||
- gitea_internal
|
||||
- proxy_network
|
||||
- gitea_net
|
||||
volumes:
|
||||
- ./gitea:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
depends_on:
|
||||
- gitea_db
|
||||
|
||||
gitea_db:
|
||||
image: docker.io/library/postgres:14
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=${DB_USER}
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||
- POSTGRES_DB=${DB_NAME}
|
||||
networks:
|
||||
- gitea_internal
|
||||
volumes:
|
||||
- ./postgres:/var/lib/postgresql/data
|
||||
|
||||
# New
|
||||
dind:
|
||||
image: docker:dind
|
||||
container_name: gitea-dind
|
||||
restart: always
|
||||
privileged: true
|
||||
environment:
|
||||
- DOCKER_TLS_CERTDIR=/certs
|
||||
volumes:
|
||||
- dind_certs:/certs
|
||||
- dind_data:/var/lib/docker
|
||||
networks:
|
||||
gitea_runner_net:
|
||||
aliases:
|
||||
- docker
|
||||
|
||||
runner:
|
||||
image: gitea/act_runner:latest
|
||||
container_name: gitea-runner
|
||||
restart: always
|
||||
depends_on:
|
||||
- dind
|
||||
- gitea
|
||||
environment:
|
||||
- GITEA_INSTANCE_URL=https://git.jirkabuilds.dev
|
||||
- GITEA_RUNNER_REGISTRATION_TOKEN=${RUNNER_TOKEN}
|
||||
- GITEA_RUNNER_NAME=docker-runner-dind
|
||||
- DOCKER_HOST=tcp://docker:2376
|
||||
- DOCKER_CERT_PATH=/certs/client
|
||||
- DOCKER_TLS_VERIFY=1
|
||||
volumes:
|
||||
- runner_data:/data
|
||||
- dind_certs:/certs:ro
|
||||
networks:
|
||||
- gitea_net
|
||||
- gitea_runner_net
|
||||
|
||||
volumes:
|
||||
dind_certs:
|
||||
dind_data:
|
||||
runner_data:
|
||||
```
|
||||
|
||||
And that is all, after `docker compose up -d`, runner should show up in the registry.
|
||||
|
||||
## Set up workflow
|
||||
|
||||
Next step is to set up workflow inside repository. Before we can do that, we need token for workload, to add access for package creation.
|
||||
|
||||
To do so, in Gitea got to Settings → Applications → Generate New Token. Give it permission to create package and copy token.
|
||||
|
||||
Now go to desired repo Settings → Actions → Secrets → Add secret and save token to Value. Name it `PACKAGE_TOKEN`.
|
||||
|
||||
Now we can use the token in our workflow. Workflow will be defined in `.gitea/workflows/deploy.yml` inside repository. And should contain something like this:
|
||||
```yaml
|
||||
name: Build and Publish Web
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Hugo Setup
|
||||
uses: peaceiris/actions-hugo@v2
|
||||
with:
|
||||
hugo-version: 'latest'
|
||||
extended: true
|
||||
|
||||
- name: Web Build
|
||||
run: |
|
||||
cd themes/minimal-black
|
||||
npm install
|
||||
cd ../..
|
||||
hugo --minify
|
||||
|
||||
- name: Packaging ZIP
|
||||
run: |
|
||||
cd public
|
||||
zip -r ../build.zip ./*
|
||||
|
||||
# Need to delete old package first, otherwise new will not replace it
|
||||
- name: Delete old package (if exists)
|
||||
run: |
|
||||
curl -X DELETE --user "${{ github.actor }}:${{ secrets.PACKAGE_TOKEN }}" \
|
||||
"https://git.jirkabuilds.dev/api/packages/${{ github.repository_owner }}/generic/hugo-build/latest/web.zip" || true
|
||||
|
||||
- name: Uploading package back to gitea
|
||||
run: |
|
||||
curl -f --user "${{ github.actor }}:${{ secrets.PACKAGE_TOKEN }}" \
|
||||
--upload-file build.zip \
|
||||
"https://git.jirkabuilds.dev/api/packages/${{ github.repository_owner }}/generic/hugo-build/latest/web.zip"
|
||||
|
||||
# TODO - Webhook
|
||||
```
|
||||
|
||||
This will automatically create package on push to master.
|
||||
|
||||
Now, once the package was successfully built, we can move onto the webhook.
|
||||
|
||||
## Webhook set up
|
||||
|
||||
For webhook, we will use `almir/webhook:latest` image, But because we need curl and zip, we will build it on our own.
|
||||
Created `webhook` directory next to `web` directory and create `Dockerfile`:
|
||||
```
|
||||
FROM almir/webhook:latest
|
||||
|
||||
USER root
|
||||
|
||||
RUN apk add --no-cache curl unzip
|
||||
|
||||
WORKDIR /app
|
||||
```
|
||||
|
||||
Next we will specify script, which will be executed on webhook trigger. Create file `deploy.sh` with `+x` privileges to be executable:
|
||||
```bash
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
ZIP_PATH="$TEMP_DIR/web.zip"
|
||||
|
||||
curl -fL -o "$ZIP_PATH" "https://git.jirkabuilds.dev/api/packages/jirka/generic/hugo-build/latest/web.zip"
|
||||
|
||||
# Delete old web data
|
||||
rm -rf /site_data/*
|
||||
rm -rf /site_data/.[!.]* 2>/dev/null || true
|
||||
|
||||
unzip -q "$ZIP_PATH" -d /site_data/
|
||||
|
||||
rm -rf "$TEMP_DIR"
|
||||
```
|
||||
|
||||
As you can see, we are downloading created package and replacing all site content.
|
||||
|
||||
Next step is to define hook itself. Create `hooks.json` file with following content:
|
||||
```
|
||||
[
|
||||
{
|
||||
"id": "deploy-web",
|
||||
"execute-command": "/app/deploy.sh",
|
||||
"command-working-directory": "/app",
|
||||
"trigger-rule": {
|
||||
"match": {
|
||||
"type": "value",
|
||||
"value": "secret token",
|
||||
"parameter": {
|
||||
"source": "header",
|
||||
"name": "X-Deploy-Token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
You should create `secret`, which you will store to Gitea, same as with token earlier` with name `WEBHOOK_SECRET`. Thanks to it, only the workflow can trigger this webhook.
|
||||
|
||||
And last step is to create `docker-compose.yml`:
|
||||
```
|
||||
services:
|
||||
webhook:
|
||||
build: .
|
||||
container_name: release_web_webhook
|
||||
volumes:
|
||||
- ./hooks.json:/etc/webhook/hooks.json:ro
|
||||
- ./deploy.sh:/app/deploy.sh:ro
|
||||
- ./../web/site_data:/site_data
|
||||
command: ["-verbose", "-hooks=/etc/webhook/hooks.json", "-hotreload"]
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- proxy_network
|
||||
|
||||
networks:
|
||||
proxy_network:
|
||||
```
|
||||
|
||||
You can see, we are using the same proxy network as for everything. Next step is to add following block to `Caddyfile`:
|
||||
```
|
||||
hooks.jirkabuilds.dev {
|
||||
reverse_proxy release_web_webhook:9000
|
||||
}
|
||||
```
|
||||
|
||||
And following line to our root `docker-compose.yml`
|
||||
```
|
||||
- webhook/docker-compose.yml
|
||||
```
|
||||
|
||||
Now you can restart the stack and webhook should work.
|
||||
|
||||
Final last step is to replace #TODO in our workflow with following:
|
||||
```
|
||||
- name: Trigger Webhook
|
||||
run: |
|
||||
curl -X POST "https://hooks.jirkabuilds.dev/hooks/deploy-web" \
|
||||
-H "X-Deploy-Token: ${{ secrets.WEBHOOK_SECRET }}"
|
||||
```
|
||||
|
||||
And that is it. On next push, web should update itself automatically.
|
||||
|
||||
---
|
||||
This article is one from series about this blog and self-hosting. All connected articles can found [here](categories/blog-setup).
|
||||
19
content/projects/01-blog.md
Normal file
19
content/projects/01-blog.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
date: '2026-03-25T10:21:48+01:00'
|
||||
draft: false
|
||||
title: 'This webpage'
|
||||
author: "Jirka"
|
||||
tags: ["self-hosted", "docker"]
|
||||
categories: ["projects"]
|
||||
description: "This whole webpage is my project!"
|
||||
git: "https://git.jirkabuilds.dev/jirka/web"
|
||||
---
|
||||
|
||||
This is The first project shared on this webpage, and it is the webpage itself!
|
||||
|
||||
|
||||
There are several articles connected to how this page was build [here](categories/blog-setup).
|
||||
|
||||
Source files for Hugo can be found on my Gitea [here](https://git.jirkabuilds.dev/jirka/web).
|
||||
|
||||
And that is it. Feel free to explore and learn something.
|
||||
12
hugo.toml
12
hugo.toml
@@ -52,8 +52,8 @@ theme = "minimal-black"
|
||||
showNowSection = true
|
||||
showFeaturedProjects = true
|
||||
showLatestPosts = true
|
||||
featuredProjectsLimit = 3
|
||||
latestPostsLimit = 3
|
||||
featuredProjectsLimit = 4
|
||||
latestPostsLimit = 4
|
||||
projectsTitle = "Latest projects"
|
||||
projectsSubtitle = ""
|
||||
blogTitle = "Latest articles"
|
||||
@@ -86,10 +86,10 @@ theme = "minimal-black"
|
||||
useDevicon = true
|
||||
|
||||
# Social Links
|
||||
# [[params.social]] # TODO
|
||||
# label = "Gitea"
|
||||
# url = "https://git.jirkabuilds.dev/jirka"
|
||||
# icon = "fa-solid fa-code-branch"
|
||||
[[params.social]]
|
||||
label = "Gitea"
|
||||
url = "https://git.jirkabuilds.dev/jirka"
|
||||
icon = "fa-solid fa-code-branch"
|
||||
|
||||
# Navigation Menu
|
||||
[menu]
|
||||
|
||||
1
layouts/_default/_markup/render-heading.html
Normal file
1
layouts/_default/_markup/render-heading.html
Normal file
@@ -0,0 +1 @@
|
||||
<h{{ .Level }} id="{{ .Anchor | safeURL }}">{{ .Text | safeHTML }}</h{{ .Level }}>
|
||||
18
layouts/_default/_markup/render-link.html
Normal file
18
layouts/_default/_markup/render-link.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{{- $link := .Destination -}}
|
||||
{{- $isRemote := or (strings.HasPrefix $link "http") (strings.HasPrefix $link "mailto") -}}
|
||||
|
||||
{{- if not $isRemote -}}
|
||||
{{- $url := urls.Parse .Destination -}}
|
||||
{{- $fragment := "" -}}
|
||||
{{- with $url.Fragment }}{{ $fragment = printf "#%s" . }}{{ end -}}
|
||||
|
||||
{{- /* Try to find the page by its path */ -}}
|
||||
{{- with .Page.GetPage $url.Path -}}
|
||||
{{- $link = printf "%s%s" .RelPermalink $fragment -}}
|
||||
{{- else -}}
|
||||
{{- /* If the page is not found, throw a warning during build. */ -}}
|
||||
{{- warnf "Broken link to page: '%s' found in file %s" .Destination .Page.File.Path -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<a href="{{ $link | safeURL }}"{{ with .Title }} title="{{ . }}"{{ end }}>{{ .Text | safeHTML }}</a>
|
||||
37
layouts/_default/list.html
Normal file
37
layouts/_default/list.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{{ define "main" }}
|
||||
<section class="layout-page">
|
||||
<div class="page-int section-stack">
|
||||
<header class="space-y-2">
|
||||
<h1 class="heading-page text-2xl sm:text-3xl">{{ .Title }}</h1>
|
||||
</header>
|
||||
|
||||
{{ $paginator := .Paginate .Pages }}
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
{{ range $paginator.Pages.ByDate.Reverse }}
|
||||
{{ partial "components/post-card.html" (dict "Page" . "Root" $) }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{ if gt $paginator.TotalPages 1 }}
|
||||
<nav class="mt-6 flex items-center justify-between text-xs text-muted">
|
||||
{{ if $paginator.HasPrev }}
|
||||
<a href="{{ $paginator.Prev.URL }}" class="link-underline">
|
||||
← Novější
|
||||
</a>
|
||||
{{ else }}
|
||||
<span></span>
|
||||
{{ end }}
|
||||
|
||||
{{ if $paginator.HasNext }}
|
||||
<a href="{{ $paginator.Next.URL }}" class="link-underline">
|
||||
Starší →
|
||||
</a>
|
||||
{{ else }}
|
||||
<span></span>
|
||||
{{ end }}
|
||||
</nav>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
{{ end }}
|
||||
19
layouts/_default/terms.html
Normal file
19
layouts/_default/terms.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{ define "main" }}
|
||||
<section class="layout-page">
|
||||
<div class="page-int section-stack">
|
||||
<header class="space-y-2">
|
||||
<h1 class="heading-page text-2xl sm:text-3xl">{{ .Title }}</h1>
|
||||
</header>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{{ range .Data.Terms }}
|
||||
<a href="{{ .Page.Permalink }}" class="flex items-baseline gap-1.5 px-3 py-1 bg-white/5 border border-white/10 rounded-full hover:bg-white/10 hover:border-white/20 transition-all text-sm">
|
||||
{{/* Název tagu */}}
|
||||
<span class="font-medium text-white/90">{{ .Page.Title }}</span>
|
||||
<span class="text-xs text-muted">({{ .Count }})</span>
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{ end }}
|
||||
BIN
static/logo.png
Normal file
BIN
static/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
1
static/logo.svg
Normal file
1
static/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
@@ -15,7 +15,7 @@
|
||||
--color-text: #111827;
|
||||
--color-text-muted: #6b7280;
|
||||
--color-border: #e5e7eb;
|
||||
--color-accent: #a855f7;
|
||||
--color-accent: #555555;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] {
|
||||
@@ -25,7 +25,7 @@ html[data-theme="dark"] {
|
||||
--color-text: #f9fafb;
|
||||
--color-text-muted: #9ca3af;
|
||||
--color-border: #27272a;
|
||||
--color-accent: #c084fc;
|
||||
--color-accent: #707070;
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
--tw-contain-style: ;
|
||||
}
|
||||
|
||||
/* ! tailwindcss v3.4.18 | MIT License | https://tailwindcss.com */
|
||||
/* ! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com */
|
||||
|
||||
/*
|
||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||
@@ -746,10 +746,6 @@ video {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
visibility: collapse;
|
||||
}
|
||||
@@ -1193,10 +1189,6 @@ video {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.antialiased {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
@@ -1242,7 +1234,7 @@ video {
|
||||
--color-text: #111827;
|
||||
--color-text-muted: #6b7280;
|
||||
--color-border: #e5e7eb;
|
||||
--color-accent: #a855f7;
|
||||
--color-accent: #555555;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] {
|
||||
@@ -1253,7 +1245,7 @@ html[data-theme="dark"] {
|
||||
--color-text: #f9fafb;
|
||||
--color-text-muted: #9ca3af;
|
||||
--color-border: #27272a;
|
||||
--color-accent: #c084fc;
|
||||
--color-accent: #707070;
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
|
||||
Reference in New Issue
Block a user