blog: Zola site, nginx image, Forgejo deploy pipeline
This commit is contained in:
commit
a4b677f898
23 changed files with 561 additions and 0 deletions
104
.forgejo/workflows/deploy.yaml
Normal file
104
.forgejo/workflows/deploy.yaml
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
name: Build and Deploy to Production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
# Same job as in test.yaml (non-main branches); duplicated so a broken
|
||||
# build blocks deploy — Forgejo has no cross-workflow needs.
|
||||
check:
|
||||
runs-on: nix-latest
|
||||
steps:
|
||||
- name: Prepare container for actions
|
||||
run: |
|
||||
echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf
|
||||
nix-env -iA nixpkgs.nodejs_22
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build site
|
||||
run: nix develop -c zola build
|
||||
|
||||
build:
|
||||
runs-on: arch-latest
|
||||
needs: check
|
||||
container:
|
||||
image: gcr.io/kaniko-project/executor:debug
|
||||
outputs:
|
||||
image_tag: ${{ steps.build_image.outputs.image_tag }}
|
||||
steps:
|
||||
- name: Build and push Docker Image using Kaniko
|
||||
id: build_image
|
||||
run: |
|
||||
# Tags
|
||||
SHORT_SHA=${GITHUB_SHA::8}
|
||||
IMAGE_BASE=${{ secrets.FORGEJO_REGISTRY }}/${{ secrets.FORGEJO_USER }}/blog
|
||||
IMAGE_TAG=${IMAGE_BASE}:${SHORT_SHA}
|
||||
IMAGE_LATEST=${IMAGE_BASE}:latest
|
||||
|
||||
# Auth Conf
|
||||
mkdir -p /kaniko/.docker
|
||||
echo "{\"auths\":{\"${{ secrets.FORGEJO_REGISTRY }}\":{\"auth\":\"$(echo -n ${{ secrets.FORGEJO_USER }}:${{ secrets.FORGEJO_TOKEN }} | base64)\"}}}" > /kaniko/.docker/config.json
|
||||
|
||||
# Build & push
|
||||
/kaniko/executor \
|
||||
--context=git://${{ secrets.FORGEJO_REGISTRY }}/${{ github.repository }}.git \
|
||||
--git=branch=${{ github.ref_name }} \
|
||||
--destination=$IMAGE_TAG \
|
||||
--destination=$IMAGE_LATEST
|
||||
|
||||
# Output the specific tag for deployment
|
||||
echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT
|
||||
|
||||
deploy:
|
||||
runs-on: arch-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pacman -Syu --noconfirm nodejs openssh rsync
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Deploy docker-compose.yml
|
||||
uses: easingthemes/ssh-deploy@v5.1.0
|
||||
with:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
REMOTE_HOST: ${{ secrets.DEPLOY_IP }}
|
||||
REMOTE_USER: ${{ secrets.DEPLOY_USER }}
|
||||
SOURCE: "docker-compose.yml"
|
||||
TARGET: "/opt/blog/"
|
||||
|
||||
- name: Deploy and update container
|
||||
uses: appleboy/ssh-action@v1.2.3
|
||||
env:
|
||||
NEW_IMAGE: ${{ needs.build.outputs.image_tag }}
|
||||
with:
|
||||
host: ${{ secrets.DEPLOY_IP }}
|
||||
username: ${{ secrets.DEPLOY_USER }}
|
||||
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
envs: NEW_IMAGE
|
||||
script: |
|
||||
cd /opt/blog
|
||||
|
||||
sed -i "s|image:.*|image: $NEW_IMAGE|" docker-compose.yml
|
||||
|
||||
docker pull $NEW_IMAGE
|
||||
docker compose up -d
|
||||
|
||||
# Wait for the image HEALTHCHECK to report healthy.
|
||||
for i in $(seq 1 18); do
|
||||
status=$(docker inspect --format '{{.State.Health.Status}}' blog 2>/dev/null || echo missing)
|
||||
if [ "$status" = "healthy" ]; then
|
||||
echo "Deployment successful!"
|
||||
exit 0
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
echo "blog never became healthy (last status: $status)" >&2
|
||||
docker logs --tail 50 blog
|
||||
exit 1
|
||||
21
.forgejo/workflows/test.yaml
Normal file
21
.forgejo/workflows/test.yaml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: nix-latest
|
||||
steps:
|
||||
- name: Prepare container for actions
|
||||
run: |
|
||||
echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf
|
||||
nix-env -iA nixpkgs.nodejs_22
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build site
|
||||
run: nix develop -c zola build
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
public/
|
||||
.env
|
||||
21
CLAUDE.md
Normal file
21
CLAUDE.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# blog.asxp.io — static blog
|
||||
|
||||
Zola static site, built in CI into an nginx container, served behind Traefik on the same prod host as asxp.io. Mirrors the conventions of the sibling `../asxpio` repo (Kaniko build, SSH compose deploy, health-wait); see that repo's CLAUDE.md for the pipeline details.
|
||||
|
||||
## Writing a post
|
||||
|
||||
Add `content/<slug>.md` with TOML front matter (`title`, `date`, optional `description`), push to `main`. CI rebuilds and deploys. Local preview: `nix develop -c zola serve` → http://127.0.0.1:1111.
|
||||
|
||||
## Stack
|
||||
|
||||
- **Zola** — version pinned in `Dockerfile` (`ghcr.io/getzola/zola`, no `:latest` tag exists); the `flake.nix` dev shell tracks nixpkgs independently. Bump the Dockerfile pin manually.
|
||||
- Multi-stage `Dockerfile`: `zola build` → `nginx:alpine` serving the generated `public/`. nginx config in `nginx.conf` (404 page + short static cache).
|
||||
- `docker-compose.yml`: Traefik labels for `blog.asxp.io`, joins only the external `traefik` network — no storage-stack dependency, fully static, no `.env`.
|
||||
- Deploy dir on prod: `/opt/blog`. Secrets used (shared with asxpio): `DEPLOY_IP/USER/SSH_KEY`, `FORGEJO_REGISTRY/USER/TOKEN`. Nothing else.
|
||||
- `templates/` are hand-written Tera (no theme); `static/style.css` reuses the asxp.io dark palette (`#121212` bg, Open Sans + Hack).
|
||||
|
||||
## Rules
|
||||
|
||||
- Same hard rule as the main site: **never use the words "consulting" or "advisory"** in posts or copy — "services", "engineering", "implementation" instead.
|
||||
- Blog content is fully public; no entity details beyond what asxp.io already shows (no banking, no registration number).
|
||||
- Atom feed at `/atom.xml` (`generate_feeds = true` in `config.toml`).
|
||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Zola publishes no :latest tag; bump the pin manually (flake.nix tracks nixpkgs separately).
|
||||
FROM ghcr.io/getzola/zola:v0.22.1 AS build
|
||||
|
||||
COPY . /project
|
||||
WORKDIR /project
|
||||
RUN ["zola", "build"]
|
||||
|
||||
FROM nginx:1.28-alpine
|
||||
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /project/public /usr/share/nginx/html
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||
CMD wget -qO /dev/null http://127.0.0.1/ || exit 1
|
||||
14
config.toml
Normal file
14
config.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
base_url = "https://blog.asxp.io"
|
||||
title = "asxp.io · blog"
|
||||
description = "Notes on DevOps and infrastructure engineering by Sergei Poljanski."
|
||||
default_language = "en"
|
||||
author = "Sergei Poljanski"
|
||||
|
||||
compile_sass = false
|
||||
build_search_index = false
|
||||
generate_feeds = true
|
||||
feed_filenames = ["atom.xml"]
|
||||
|
||||
[markdown.highlighting]
|
||||
enabled = true
|
||||
theme = "ayu-dark"
|
||||
5
content/_index.md
Normal file
5
content/_index.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
+++
|
||||
sort_by = "date"
|
||||
template = "index.html"
|
||||
page_template = "page.html"
|
||||
+++
|
||||
11
content/hello-world.md
Normal file
11
content/hello-world.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
+++
|
||||
title = "Hello, world"
|
||||
date = 2026-07-16
|
||||
description = "First post: what this blog is for."
|
||||
+++
|
||||
|
||||
Short posts on DevOps and infrastructure engineering: NixOS, Kubernetes, Docker,
|
||||
CI/CD, and the occasional war story from running small self-hosted stacks.
|
||||
|
||||
The blog itself is [Zola](https://www.getzola.org/), built in CI into an nginx
|
||||
container and served behind Traefik, next to [asxp.io](https://asxp.io).
|
||||
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
services:
|
||||
blog:
|
||||
# Image will be dynamically updated during deployment
|
||||
# Format: registry.example.com/user/blog:sha_tag
|
||||
build: .
|
||||
image: blog:latest
|
||||
container_name: blog
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- traefik
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.blog.rule=Host(`blog.asxp.io`)"
|
||||
- "traefik.http.routers.blog.entrypoints=websecure"
|
||||
- "traefik.http.routers.blog.tls=true"
|
||||
- "traefik.http.routers.blog.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.blog.loadbalancer.server.port=80"
|
||||
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1784120854,
|
||||
"narHash": "sha256-KesHgItiZPgGX740axSiQLcIQ8D24MDqNpkKYWIek8k=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "753cc8a3a87467296ddd1fa93f0cc3e81120ee46",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
25
flake.nix
Normal file
25
flake.nix
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
description = "blog.asxp.io — static blog (Zola)";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.zola
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
echo "blog dev shell ready"
|
||||
echo " zola serve - live-reload preview on http://127.0.0.1:1111"
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
15
nginx.conf
Normal file
15
nginx.conf
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
error_page 404 /404.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location ~* \.(css|png|svg|xml|ico)$ {
|
||||
expires 1h;
|
||||
}
|
||||
}
|
||||
BIN
static/fonts/ibm-plex-mono-latin-400-normal.woff2
Normal file
BIN
static/fonts/ibm-plex-mono-latin-400-normal.woff2
Normal file
Binary file not shown.
BIN
static/fonts/ibm-plex-sans-latin-400-italic.woff2
Normal file
BIN
static/fonts/ibm-plex-sans-latin-400-italic.woff2
Normal file
Binary file not shown.
BIN
static/fonts/ibm-plex-sans-latin-400-normal.woff2
Normal file
BIN
static/fonts/ibm-plex-sans-latin-400-normal.woff2
Normal file
Binary file not shown.
BIN
static/fonts/ibm-plex-sans-latin-600-normal.woff2
Normal file
BIN
static/fonts/ibm-plex-sans-latin-600-normal.woff2
Normal file
Binary file not shown.
BIN
static/fonts/ibm-plex-sans-latin-700-normal.woff2
Normal file
BIN
static/fonts/ibm-plex-sans-latin-700-normal.woff2
Normal file
Binary file not shown.
BIN
static/hedgehog.png
Normal file
BIN
static/hedgehog.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
183
static/style.css
Normal file
183
static/style.css
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/* Self-hosted fonts (no CDN). Latin subsets, same files as asxp.io. */
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans';
|
||||
src: url('/fonts/ibm-plex-sans-latin-400-normal.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans';
|
||||
src: url('/fonts/ibm-plex-sans-latin-400-italic.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans';
|
||||
src: url('/fonts/ibm-plex-sans-latin-600-normal.woff2') format('woff2');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Sans';
|
||||
src: url('/fonts/ibm-plex-sans-latin-700-normal.woff2') format('woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Mono';
|
||||
src: url('/fonts/ibm-plex-mono-latin-400-normal.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Design tokens — kept in sync with asxp.io's public/style.css (source of truth) */
|
||||
:root {
|
||||
--bg: #121212;
|
||||
--surface: #1A1A1A;
|
||||
--surface-alt: #222222;
|
||||
--surface-hover: #333333;
|
||||
--border-subtle: #1E1E1E;
|
||||
--border: #2A2A2A;
|
||||
--border-strong: #3A3A3A;
|
||||
--text: #AAAAAA;
|
||||
--text-bright: #EEEEEE;
|
||||
--text-strong: #DDDDDD;
|
||||
--text-soft: #CCCCCC;
|
||||
--text-link: #BBBBBB;
|
||||
--text-dim: #999999;
|
||||
--text-dimmer: #888888;
|
||||
--text-faint: #777777;
|
||||
--text-muted: #666666;
|
||||
--text-ghost: #555555;
|
||||
--text-hover: #FFFFFF;
|
||||
--link-underline: #444444;
|
||||
--danger: #C97070;
|
||||
--danger-border: #4A1F1F;
|
||||
--danger-bg: #2A1414;
|
||||
--pending: #C9A86A;
|
||||
--pending-border: #4A3E1F;
|
||||
--pending-bg: #2A2414;
|
||||
--paid: #7AB97A;
|
||||
--paid-border: #234A23;
|
||||
--paid-bg: #142A14;
|
||||
--font-sans: 'IBM Plex Sans', sans-serif;
|
||||
--font-mono: 'IBM Plex Mono', monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: var(--font-sans);
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
main {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px 80px;
|
||||
}
|
||||
.site-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
.site-title {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-bright);
|
||||
text-decoration: none;
|
||||
}
|
||||
.site-header nav a {
|
||||
color: var(--text-faint);
|
||||
text-decoration: none;
|
||||
margin-left: 18px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.site-header nav a:hover,
|
||||
.site-title:hover {
|
||||
color: var(--text-bright);
|
||||
}
|
||||
a {
|
||||
color: var(--text-soft);
|
||||
}
|
||||
a:hover {
|
||||
color: var(--text-hover);
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-bright);
|
||||
letter-spacing: -0.01em;
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
article h2 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-strong);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding-bottom: 6px;
|
||||
margin: 32px 0 14px;
|
||||
}
|
||||
.post-meta {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85rem;
|
||||
margin: 0 0 28px;
|
||||
}
|
||||
ul.post-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
ul.post-list li {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: baseline;
|
||||
padding: 6px 0;
|
||||
}
|
||||
ul.post-list time {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.92em;
|
||||
background: var(--surface-alt);
|
||||
padding: 1px 5px;
|
||||
}
|
||||
pre {
|
||||
background: #1B1B1B;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
border: 1px solid #020202;
|
||||
margin: 14px 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 3px solid var(--border);
|
||||
margin: 14px 0;
|
||||
padding: 0 0 0 16px;
|
||||
color: var(--text-dimmer);
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: var(--text-ghost);
|
||||
font-size: 0.8rem;
|
||||
padding: 20px 0 40px;
|
||||
}
|
||||
8
templates/404.html
Normal file
8
templates/404.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}404 · {{ config.title }}{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<h1>404</h1>
|
||||
<p>No such page. <a href="/">Back to the index.</a></p>
|
||||
{% endblock content %}
|
||||
59
templates/base.html
Normal file
59
templates/base.html
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>{% block title %}{{ config.title }}{% endblock title %}</title>
|
||||
<meta name="description" content="{% block description %}{{ config.description }}{% endblock description %}" />
|
||||
<meta name="author" content="Sergei Poljanski" />
|
||||
<link rel="canonical" href="{{ current_url | default(value=config.base_url) }}" />
|
||||
|
||||
<meta property="og:type" content="{% block og_type %}website{% endblock og_type %}" />
|
||||
<meta property="og:site_name" content="{{ config.title }}" />
|
||||
<meta property="og:title" content="{% block og_title %}{{ config.title }}{% endblock og_title %}" />
|
||||
<meta property="og:url" content="{{ current_url | default(value=config.base_url) }}" />
|
||||
{% block extra_head %}{% endblock extra_head %}
|
||||
|
||||
<link rel="icon" type="image/png" href="/hedgehog.png" sizes="32x32" />
|
||||
<link rel="apple-touch-icon" href="/hedgehog.png" />
|
||||
<link rel="alternate" type="application/atom+xml" title="{{ config.title }}" href="{{ get_url(path='atom.xml') }}" />
|
||||
|
||||
<!-- Fonts are self-hosted; see @font-face in style.css -->
|
||||
<link rel="preload" href="/fonts/ibm-plex-sans-latin-400-normal.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="/fonts/ibm-plex-sans-latin-600-normal.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Blog",
|
||||
"@id": "{{ config.base_url | safe }}/#blog",
|
||||
"url": "{{ config.base_url | safe }}",
|
||||
"name": "{{ config.title }}",
|
||||
"description": "{{ config.description }}",
|
||||
"inLanguage": "en",
|
||||
"author": { "@id": "https://asxp.io/#person" },
|
||||
"publisher": { "@id": "https://asxp.io/#organization" }
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header class="site-header">
|
||||
<a class="site-title" href="/">blog.asxp.io</a>
|
||||
<nav>
|
||||
<a href="https://asxp.io">asxp.io</a>
|
||||
<a href="{{ get_url(path='atom.xml') }}">feed</a>
|
||||
</nav>
|
||||
</header>
|
||||
{% block content %}{% endblock content %}
|
||||
</main>
|
||||
|
||||
<div class="footer">
|
||||
<div class="footer-line">
|
||||
IE Sergei Poljanski · Tbilisi, Georgia
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
12
templates/index.html
Normal file
12
templates/index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<ul class="post-list">
|
||||
{% for page in section.pages %}
|
||||
<li>
|
||||
<time datetime="{{ page.date }}">{{ page.date | date(format="%Y-%m-%d") }}</time>
|
||||
<a href="{{ page.permalink }}">{{ page.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock content %}
|
||||
19
templates/page.html
Normal file
19
templates/page.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ page.title }} · {{ config.title }}{% endblock title %}
|
||||
{% block og_type %}article{% endblock og_type %}
|
||||
{% block og_title %}{{ page.title }}{% endblock og_title %}
|
||||
{% block description %}{{ page.description | default(value=config.description) }}{% endblock description %}
|
||||
{% block extra_head %}
|
||||
<meta property="article:published_time" content="{{ page.date }}" />
|
||||
{% endblock extra_head %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1>{{ page.title }}</h1>
|
||||
<p class="post-meta">
|
||||
<time datetime="{{ page.date }}">{{ page.date | date(format="%Y-%m-%d") }}</time>
|
||||
</p>
|
||||
{{ page.content | safe }}
|
||||
</article>
|
||||
{% endblock content %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue