Migrate to Zola
14
.github/workflows/pages.yml
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
on: push
|
||||
name: Build and deploy to Pages
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: build_and_deploy
|
||||
uses: shalzz/zola-deploy-action@v0.17.2
|
||||
env:
|
||||
PAGES_BRANCH: gh-pages
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
public
|
||||
ideas
|
||||
|
|
|
|||
14
README.md
|
|
@ -1,18 +1,18 @@
|
|||
A bare-bones personal website relying only on simple scripts.
|
||||
|
||||
## Next
|
||||
- [ ] Light mode
|
||||
- [ ] Improve responsiveness
|
||||
- [x] Light mode
|
||||
- [x] Set page titles dynamically
|
||||
- [x] Handle header a11y
|
||||
- [x] Add post date
|
||||
- [x] Add post author
|
||||
- [ ] Add source code link on homepage
|
||||
- [ ] Update data object programmatically on HTML post generation
|
||||
- [ ] Style inline `<code>` tags
|
||||
- [ ] Create a custom syntax highlighting style
|
||||
- [x] Style inline `<code>` tags
|
||||
- [x] Create a custom syntax highlighting style
|
||||
- See: <https://pandoc.org/chunkedhtml-demo/13-syntax-highlighting.html>
|
||||
- [x] Add an RSS feed
|
||||
- [ ] Links RSS feed
|
||||
- [x] Links RSS feed
|
||||
- [ ] Add tags
|
||||
- [ ] No JavaScript browsing support
|
||||
- [ ] Add a 404 page
|
||||
- [x] No JavaScript browsing support
|
||||
- [x] Add a 404 page
|
||||
|
|
|
|||
|
|
@ -1,183 +0,0 @@
|
|||
* { margin: 0px; }
|
||||
|
||||
@font-face {
|
||||
font-family: 'Mononoki';
|
||||
src: url('Mononoki.ttf'), format('truetype');
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #222222;
|
||||
font-family: 'Mononoki', monospace;
|
||||
color: #F1E9E5;
|
||||
}
|
||||
|
||||
.header-art {
|
||||
color: #888
|
||||
}
|
||||
|
||||
#a11y-skip {
|
||||
position: absolute;
|
||||
left: -1000%;
|
||||
}
|
||||
|
||||
#a11y-skip:focus {
|
||||
left: 5%;
|
||||
}
|
||||
|
||||
#nav-menu li {
|
||||
display: inline-block;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#nav-posts {
|
||||
margin-left: 40px;
|
||||
margin-top: 40px;
|
||||
color: #F1E9E5;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 15px;
|
||||
line-height: 2em;
|
||||
}
|
||||
ul { margin-top: 15px; }
|
||||
|
||||
p {
|
||||
margin: 30px;
|
||||
line-height: 1.7em;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline dotted #888;
|
||||
color: #AEDBCE;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #39AEA9;
|
||||
text-decoration: underline dotted #fff;
|
||||
transition: 1500ms;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 25px;
|
||||
font-weight: bold;
|
||||
color: #ccc;
|
||||
margin: 40px 0px 10px 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 25px;
|
||||
font-weight: bold;
|
||||
color: #ccc;
|
||||
margin: 20px 0px 10px 0px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 21px;
|
||||
font-weight: bold;
|
||||
color: #ccc;
|
||||
margin: 20px 0px 10px 0px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 21px;
|
||||
font-weight: bold;
|
||||
color: #6a978b;
|
||||
margin: 20px 0px 10px 0px;
|
||||
}
|
||||
|
||||
.article p {
|
||||
margin: 25px 0px;
|
||||
}
|
||||
|
||||
.article h1 {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.sourceCode pre {
|
||||
color: #ccc;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
p code {
|
||||
background-color: #444;
|
||||
padding: 5px 6.5px;
|
||||
margin-right: 2px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
li code {
|
||||
background-color: #444;
|
||||
padding: 5px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
color: #888;
|
||||
background-color: #1b1b1b;
|
||||
padding: 10px 40px;
|
||||
font-style: italic;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
blockquote p code {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
padding: 20px 20px;
|
||||
}
|
||||
|
||||
.footer-back {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* tablet */
|
||||
@media (min-width: 401px) {
|
||||
.article {
|
||||
margin: 0 45px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-left: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
/* widescreen */
|
||||
@media (min-width: 1000px) {
|
||||
.article {
|
||||
margin: 0 20%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-left: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
/* fullscreen */
|
||||
@media (min-width: 1500px) {
|
||||
.article {
|
||||
margin: 0 30%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-left: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
/* phone */
|
||||
@media (max-width: 400px) {
|
||||
.article {
|
||||
margin: 0 20px;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 15 KiB |
|
|
@ -1,112 +0,0 @@
|
|||
function get_data() {
|
||||
return {
|
||||
"posts": {
|
||||
"pt": [
|
||||
{
|
||||
"title": "Scripts em OCaml",
|
||||
"slug": "scripts-em-ocaml",
|
||||
"date": "23/03/2024"
|
||||
}
|
||||
],
|
||||
"en": [
|
||||
{
|
||||
"title": "Giving up simplicity",
|
||||
"slug": "unwinding",
|
||||
"date": "August 10, 2024"
|
||||
},
|
||||
{
|
||||
"title": "Introducing tori",
|
||||
"slug": "introducing-tori",
|
||||
"date": "July 6, 2024"
|
||||
},
|
||||
{
|
||||
"title": "Void on ZFS",
|
||||
"slug": "void-on-zfs",
|
||||
"date": "June 9, 2024"
|
||||
},
|
||||
{
|
||||
"title": "Meeting the BSD Family",
|
||||
"slug": "meeting-the-bsd-family",
|
||||
"date": "May 20, 2024"
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "mrusme/reader: for your command line what the 'readability view' is for browsers",
|
||||
"url": "https://github.com/mrusme/reader",
|
||||
"date": "06/08/2024",
|
||||
},
|
||||
{
|
||||
"title": "A handful of reasons JavaScript won’t be available - Piccalilli",
|
||||
"url": "https://piccalil.li/blog/a-handful-of-reasons-javascript-wont-be-available/",
|
||||
"date": "01/08/2024",
|
||||
},
|
||||
{
|
||||
"title": "jgs font - Adel Faure",
|
||||
"url": "https://adelfaure.net/tools/jgs/",
|
||||
"date": "02/07/2024"
|
||||
},
|
||||
{
|
||||
"title": "Differential Analysis: A Summary",
|
||||
"url": "https://blog.brownplt.org/2024/06/27/differential-analysis.html",
|
||||
"date": "28/06/2024"
|
||||
},
|
||||
{
|
||||
"title": "aryak/mozhi: Mozhi is an alternative-frontend for many translation engines. - Codeberg.org",
|
||||
"url": "https://codeberg.org/aryak/mozhi",
|
||||
"date": "27/06/2024"
|
||||
},
|
||||
{
|
||||
"title": "GitHub - JFryy/qq: jq multi-configuration format tool with interactive REPL.",
|
||||
"url": "https://github.com/JFryy/qq/",
|
||||
"date": "23/06/2024"
|
||||
},
|
||||
{
|
||||
"title": "UserLAnd | F-Droid - Free and Open Source Android App Repository",
|
||||
"url": "https://f-droid.org/en/packages/tech.ula/",
|
||||
"date": "21/06/2024"
|
||||
},
|
||||
{
|
||||
"title": "phyphox | F-Droid - Free and Open Source Android App Repository",
|
||||
"url": "https://f-droid.org/packages/de.rwth_aachen.phyphox/",
|
||||
"date": "21/06/2024"
|
||||
},
|
||||
{
|
||||
"title": "Sounds of the Forest - Soundmap Timber Festival",
|
||||
"url": "https://timberfestival.org.uk/soundsoftheforest-soundmap/",
|
||||
"date": "14/06/2024"
|
||||
},
|
||||
{
|
||||
"title": "Piku",
|
||||
"url": "https://piku.github.io/",
|
||||
"date": "10/06/2024"
|
||||
},
|
||||
{
|
||||
"title": "Effects Showroom - TerminalTextEffects Docs",
|
||||
"url": "https://chrisbuilds.github.io/terminaltexteffects/showroom/",
|
||||
"date": "10/06/2024"
|
||||
},
|
||||
{
|
||||
"title": "Share Paste O2 | F-Droid - Free and Open Source Android App Repository",
|
||||
"url": "https://f-droid.org/en/packages/alt.nainapps.sharepaste.fdroid/",
|
||||
"date": "08/06/2024"
|
||||
},
|
||||
{
|
||||
"title": "Moving Beyond Type Systems | Vhyrro's Digital Garden",
|
||||
"url": "https://vhyrro.github.io/posts/effect-systems/",
|
||||
"date": "06/06/2024"
|
||||
},
|
||||
{
|
||||
"title": "hackerb9/lsix Like ls, but for images. Shows thumbnails in terminal using sixel graphics.",
|
||||
"url": "https://github.com/hackerb9/lsix",
|
||||
"date": "06/06/2024"
|
||||
},
|
||||
{
|
||||
"title": "Capital Offense How to Handle Abbreviations in CamelCase - Approxion",
|
||||
"url": "https://www.approxion.com/capital-offenses-how-to-handle-abbreviations-in-camelcase/",
|
||||
"date": "05/06/2024"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
const data = get_data()
|
||||
|
||||
function populate_links() {
|
||||
const links = document.getElementById('links')
|
||||
links.innerHTML = ''
|
||||
|
||||
data.links.forEach(link => {
|
||||
const item = document.createElement('li')
|
||||
const anchor = document.createElement('a')
|
||||
const date = document.createElement('span')
|
||||
anchor.innerText = link.title
|
||||
anchor.href = link.url
|
||||
date.innerText = `${parse_date(link.date)} - `
|
||||
item.appendChild(date)
|
||||
item.appendChild(anchor)
|
||||
links.appendChild(item)
|
||||
})
|
||||
}
|
||||
|
||||
function parse_date(date) {
|
||||
current_year = new Date().getFullYear()
|
||||
return date.replace(`/${current_year}`, '')
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
populate_links()
|
||||
})
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
const userLang = navigator.language || navigator.userLanguage
|
||||
let lang = userLang.includes('pt') ? 'pt' : 'en'
|
||||
|
||||
function localize_menu() {
|
||||
const home = document.getElementById('nav-menu-home')
|
||||
const rss = document.getElementById('nav-menu-rss')
|
||||
|
||||
if (lang == 'pt') {
|
||||
if (home) home.innerText = 'Início'
|
||||
if (rss) rss.setAttribute('href', 'assets/rss/pt.rss')
|
||||
} else {
|
||||
if (home) home.innerText = 'Home'
|
||||
if (rss) rss.setAttribute('href', 'assets/rss/en.rss')
|
||||
}
|
||||
}
|
||||
|
||||
function localize_navigation() {
|
||||
const skip = document.getElementById('a11y-skip')
|
||||
const post_list = document.getElementById('nav-posts')
|
||||
|
||||
if (lang == 'pt') {
|
||||
if (skip) skip.innerText = 'Saltar para o conteúdo principal'
|
||||
if (post_list) post_list.ariaLabel = 'Listagem de todas as postagens'
|
||||
} else {
|
||||
if (skip) skip.innerText = 'Skip to main content'
|
||||
if (post_list) post_list.ariaLabel = 'Listing of all posts'
|
||||
}
|
||||
}
|
||||
|
||||
function localize_footer() {
|
||||
const footer_back = document.getElementById('footer-back-link')
|
||||
|
||||
if (footer_back) {
|
||||
if (lang == 'pt') {
|
||||
footer_back.innerText = 'Voltar'
|
||||
} else {
|
||||
footer_back.innerText = 'Back'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const en_lang_swapper = document.getElementById('lang-swap-en')
|
||||
const pt_lang_swapper = document.getElementById('lang-swap-pt')
|
||||
|
||||
if (en_lang_swapper) {
|
||||
en_lang_swapper.addEventListener("click", () => {
|
||||
lang = 'en'
|
||||
populate_posts()
|
||||
localize_navigation()
|
||||
localize_menu()
|
||||
})
|
||||
}
|
||||
|
||||
if (pt_lang_swapper) {
|
||||
pt_lang_swapper.addEventListener("click", () => {
|
||||
lang = 'pt'
|
||||
populate_posts()
|
||||
localize_navigation()
|
||||
localize_menu()
|
||||
})
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
localize_navigation()
|
||||
localize_menu()
|
||||
localize_footer()
|
||||
})
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
const data = get_data()
|
||||
|
||||
function populate_posts() {
|
||||
const posts = document.getElementById('nav-posts')
|
||||
posts.innerHTML = ''
|
||||
|
||||
data.posts[lang].forEach(post => {
|
||||
const item = document.createElement('li')
|
||||
const anchor = document.createElement('a')
|
||||
const date = document.createElement('span')
|
||||
anchor.innerText = post.title
|
||||
anchor.href = 'posts/' + post.slug + '.html'
|
||||
date.innerText = `${parse_date(post.date)} - `
|
||||
item.appendChild(date)
|
||||
item.appendChild(anchor)
|
||||
posts.appendChild(item)
|
||||
})
|
||||
}
|
||||
|
||||
function parse_date(date) {
|
||||
current_year = new Date().getFullYear()
|
||||
return date.replace(`, ${current_year}`, '')
|
||||
.replace(`/${current_year}`, '')
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
populate_posts()
|
||||
})
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
const userLang = navigator.language || navigator.userLanguage
|
||||
let lang = userLang.includes('pt') ? 'pt' : 'en'
|
||||
|
||||
if (lang == 'pt') {
|
||||
const footer_back = document.getElementById('footer-back-link')
|
||||
const author_pre = document.getElementById('author-pre-text')
|
||||
const date_pre = document.getElementById('date-pre-text')
|
||||
footer_back.innerText = 'Voltar'
|
||||
author_pre.innerText = 'Postado por'
|
||||
date_pre.innerText = 'em'
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf8"?>
|
||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
|
||||
<channel>
|
||||
<title>jutty.dev: Blog</title>
|
||||
<link>http://blog.jutty.dev/</link>
|
||||
<atom:link href="https://blog.jutty.dev/feed.rss" rel="self" type="application/rss+xml"/>
|
||||
<description>jutty.dev: Blog</description>
|
||||
<language>en-us</language>
|
||||
<lastBuildDate>Sun, 9 Jun 2024 17:36:20 -0300</lastBuildDate>
|
||||
<docs>https://www.rssboard.org/rss-specification</docs>
|
||||
<item>
|
||||
<title>Meeting the BSD Family</title>
|
||||
<pubDate>Mon, 20 May 2024 14:35:27 -0300</pubDate>
|
||||
<link>https://blog.jutty.dev/posts/meeting-the-bsd-family.html</link>
|
||||
<guid>https://blog.jutty.dev/posts/meeting-the-bsd-family.html</guid>
|
||||
<author>juno@jutty.dev (Juno Takano)</author>
|
||||
</item>
|
||||
<item>
|
||||
<title>Void on ZFS</title>
|
||||
<pubDate>Sun, 9 Jun 2024 17:36:20 -0300</pubDate>
|
||||
<link>https://blog.jutty.dev/posts/void-on-zfs.html</link>
|
||||
<guid>https://blog.jutty.dev/posts/void-on-zfs.html</guid>
|
||||
<author>juno@jutty.dev (Juno Takano)</author>
|
||||
</item>
|
||||
<item>
|
||||
<title>Introducing tori</title>
|
||||
<pubDate>Sat, 6 Jul 2024 11:16:39 -0300</pubDate>
|
||||
<link>https://blog.jutty.dev/posts/introducing-tori.html</link>
|
||||
<guid>https://blog.jutty.dev/posts/introducing-tori.html</guid>
|
||||
<author>juno@jutty.dev (Juno Takano)</author>
|
||||
</item>
|
||||
<item>
|
||||
<title>Giving up simplicity</title>
|
||||
<pubDate>Sun, 11 Aug 2024 00:02:44 -0300</pubDate>
|
||||
<link>https://blog.jutty.dev/posts/unwinding.html</link>
|
||||
<guid>https://blog.jutty.dev/posts/unwinding.html</guid>
|
||||
<author>juno@jutty.dev (Juno Takano)</author>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf8"?>
|
||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
|
||||
<channel>
|
||||
<title>jutty.dev: Blog</title>
|
||||
<link>http://blog.jutty.dev/</link>
|
||||
<atom:link href="https://blog.jutty.dev/feed.rss" rel="self" type="application/rss+xml"/>
|
||||
<description>jutty.dev: Blog</description>
|
||||
<language>pt-br</language>
|
||||
<lastBuildDate>Mon, 20 May 2024 12:06:59 -0300</lastBuildDate>
|
||||
<docs>https://www.rssboard.org/rss-specification</docs>
|
||||
<item>
|
||||
<title>Scripts em OCaml</title>
|
||||
<pubDate>Fri, 23 Feb 2024 21:23:00 -0300</pubDate>
|
||||
<link>https://blog.jutty.dev/posts/scripts-em-ocaml.html</link>
|
||||
<guid>https://blog.jutty.dev/posts/scripts-em-ocaml.html</guid>
|
||||
<author>juno@jutty.dev (Juno Takano)</author>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
37
config.toml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
title = "jutty.dev"
|
||||
description = "Just computer nerd things"
|
||||
author = "Juno Takano"
|
||||
base_url = "https://jultty.github.io/blog-zola"
|
||||
default_language = "en"
|
||||
|
||||
generate_feeds = true
|
||||
feed_filenames = ["rss.xml", "atom.xml"]
|
||||
|
||||
taxonomies = [
|
||||
{ name = "tags", feed = true },
|
||||
]
|
||||
|
||||
[translations]
|
||||
[languages]
|
||||
|
||||
[languages.pt]
|
||||
generate_feeds = true
|
||||
feed_filenames = ["rss.xml", "atom.xml"]
|
||||
title = "jutty.dev"
|
||||
description = "Nerdices, unix, computarias"
|
||||
|
||||
[markdown]
|
||||
smart_punctuation = true
|
||||
bottom_footnotes = true
|
||||
|
||||
highlight_code = true
|
||||
highlight_theme = "css"
|
||||
# highlight_themes_css = [
|
||||
# # base16-ocean-dark, material-dark, nord
|
||||
# { theme = "material-dark", filename = "assets/css/syntax-dark.css" },
|
||||
# # dimmed-fluid, subway-moscow
|
||||
# { theme = "dimmed-fluid", filename = "assets/css/syntax-light.css" },
|
||||
# ]
|
||||
|
||||
[extra]
|
||||
title_separator = "-"
|
||||
5
content/feeds.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
+++
|
||||
+++
|
||||
|
||||
- Posts: [RSS](https://blog.jutty.dev/rss.xml) [Atom](https://blog.jutty.dev/atom.xml)
|
||||
- Links: [RSS](https://blog.jutty.dev/links/rss.xml) [Atom](https://blog.jutty.dev/links/atom.xml)
|
||||
5
content/feeds.pt.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
+++
|
||||
+++
|
||||
|
||||
- Posts: [RSS](https://blog.jutty.dev/pt/rss.xml) [Atom](https://blog.jutty.dev/pt/atom.xml)
|
||||
- Links: [RSS](https://blog.jutty.dev/pt/links/rss.xml) [Atom](https://blog.jutty.dev/pt/links/atom.xml)
|
||||
7
content/links/_index.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
+++
|
||||
title = "Links"
|
||||
sort_by = "date"
|
||||
template = "links.html"
|
||||
page_template = "link-page.html"
|
||||
generate_feeds = true
|
||||
+++
|
||||
6
content/links/_index.pt.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
+++
|
||||
title = "Links"
|
||||
sort_by = "date"
|
||||
template = "links.html"
|
||||
page_template = "link-page.html"
|
||||
+++
|
||||
8
content/links/mrusme-reader.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
+++
|
||||
title = "mrusme/reader: for your command line what the 'readability view' is for browsers"
|
||||
date = 2024-08-06
|
||||
[extra]
|
||||
url = "https://github.com/mrusme/reader"
|
||||
+++
|
||||
|
||||
Happy to see *any* tool that reduces having to open a browser.
|
||||
8
content/links/mrusme-reader.pt.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
+++
|
||||
title = "mrusme/reader: pra sua linha de comando o que o 'readability view' é para navegadores"
|
||||
date = 2024-08-06
|
||||
[extra]
|
||||
url = "https://github.com/mrusme/reader"
|
||||
+++
|
||||
|
||||
Bom ver *qualquer* ferramenta que evita precisar abrir um navegador.
|
||||
6
content/posts/_index.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
+++
|
||||
title = "Posts"
|
||||
sort_by = "date"
|
||||
template = "posts.html"
|
||||
page_template = "post-page.html"
|
||||
+++
|
||||
6
content/posts/_index.pt.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
+++
|
||||
title = "Posts"
|
||||
sort_by = "date"
|
||||
template = "posts.html"
|
||||
page_template = "post-page.html"
|
||||
+++
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: Introducing tori
|
||||
author: Juno Takano
|
||||
date: June 30, 2024
|
||||
---
|
||||
+++
|
||||
title = "Introducing tori"
|
||||
date = 2024-06-30
|
||||
aliases = ["posts/introducing-tori.html"]
|
||||
+++
|
||||
|
||||
**[tori](https://tori.jutty.dev/)** is a tool to track your personal systems' configurations and replicate them.
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: Meeting the BSD family
|
||||
author: Juno Takano
|
||||
date: May 20, 2024
|
||||
---
|
||||
+++
|
||||
title = "Meeting the BSD family"
|
||||
date = 2024-05-20
|
||||
aliases = ["posts/meeting-the-bsd-family.html"]
|
||||
+++
|
||||
|
||||
During this year I have been delving deeper and deeper in the BSD realm. Switching my home server to FreeBSD, trying NetBSD and OpenBSD on my backup machine, getting a cheap SSD to see how they'd all run on my main one, all beaming with the joy of tinkering and learning.
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: Scripts em OCaml
|
||||
author: Juno Takano
|
||||
date: 23 de fevereiro de 2024
|
||||
---
|
||||
+++
|
||||
title = "Scripts em OCaml"
|
||||
date = 2024-02-23
|
||||
aliases = ["posts/scripts-em-ocaml.html"]
|
||||
+++
|
||||
|
||||
Este blog gera suas postagens com o Pandoc, mas quem faz o trabalho lógico de identificar os arquivos e montar os comandos é um pequeno script em OCaml.
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: Giving up simplicity
|
||||
author: Juno Takano
|
||||
date: August 10, 2024
|
||||
---
|
||||
+++
|
||||
title = "Giving up simplicity"
|
||||
date = 2024-08-10
|
||||
aliases = ["posts/unwinding.html"]
|
||||
+++
|
||||
|
||||
_or_
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
---
|
||||
title: Void on ZFS
|
||||
author: Juno Takano
|
||||
date: June 9, 2024
|
||||
---
|
||||
+++
|
||||
title = "Void on ZFS"
|
||||
date = 2024-06-09
|
||||
aliases = ["posts/void-on-zfs.html"]
|
||||
+++
|
||||
|
||||

|
||||

|
||||
|
||||
June is here. It brings the usual cold weather and some extra rhinitis complications. With that I find myself in a recovery mood Sunday, wrapped in a blanket with a mug of tea, a screwdriver, some notes on paper, a flash drive, a couple of NVMe cards and the trio of Unix-powered machines that will help me get this done.
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ The mission is to get a root-on-ZFS EFI installation of Void Linux with ZFSBootM
|
|||
|
||||
To my left, a ceiling-collapse-survivor Sony VAIO is running NetBSD with spectrwm. It's split with sakura and tmux on a terminal to one side, where Neovim is storing these words, and Firefox on the other, ready to fetch all the docs. In the middle, the object of today's operation. And to my right, a headless PC board runs FreeBSD with ZFS, holding all the backups needed for the post-install tasks.
|
||||
|
||||

|
||||

|
||||
|
||||
This lengthy post, written not after the fact but during it, is my way of documenting and also sharing how it all went. Additionally, it's a way to delve deeper into many of the things the ZFSBootMenu docs leave unexplained, an urge I already had yesterday as I just tried it out without much modification.
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ While a bit dusty, the machine was overall in good state. The release date for t
|
|||
|
||||
It has a single NVMe slot, one 16 GB RAM stick and one unused RAM slot. If you look closely, you can notice a dent on the vent tube connecting the cooler to the CPU. Despite this, it very rarely heats up.
|
||||
|
||||

|
||||

|
||||
|
||||
Next up is to boot up [hrmpf](https://github.com/leahneukirchen/hrmpf) in EFI mode.
|
||||
|
||||
|
|
@ -172,7 +172,7 @@ My target device is an NVMe at `nvme0n1`, so I'll have:
|
|||
- `BOOT_PART="1"`
|
||||
- `BOOT_DEVICE="${BOOT_DISK}p${BOOT_PART}"`
|
||||
- which evaluates to `/dev/nvme0n1p1`
|
||||
|
||||
<br/><br/>
|
||||
- `POOL_DISK="/dev/nvme0n1"`
|
||||
- `POOL_PART="2"`
|
||||
- `POOL_DEVICE="${POOL_DISK}p${POOL_PART}"`
|
||||
|
|
@ -187,10 +187,13 @@ At this point, we should be able to print something like this in our environment
|
|||
```sh
|
||||
# env | grep ID
|
||||
ID=void
|
||||
|
||||
# hostid
|
||||
00bab10c
|
||||
|
||||
# echo $BOOT_DEVICE
|
||||
/dev/nvme0n1p1
|
||||
|
||||
# echo $POOL_DEVICE
|
||||
/dev/nvme0n1p2
|
||||
```
|
||||
|
|
@ -462,7 +465,7 @@ xchroot /mnt
|
|||
|
||||
This is a good time to get back to the notes I mentioned taking the day before.
|
||||
|
||||

|
||||

|
||||
|
||||
#### Reconfiguring packages
|
||||
|
||||
48
index.html
|
|
@ -1,48 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>jutty.dev</title>
|
||||
<link href="assets/css/style.css" rel="stylesheet">
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (English)" href="https://blog.jutty.dev/assets/rss/en.rss"/>
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (Português)" href="https://blog.jutty.dev/assets/rss/pt.rss"/>
|
||||
<link rel="icon" type="image/ico" href="assets/img/favicon.ico">
|
||||
<script src="assets/js/data.js"></script>
|
||||
<script src="assets/js/main-l10n.js" defer></script>
|
||||
<script src="assets/js/main.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<a id="a11y-skip" href="#main">Skip to main content</a>
|
||||
<header>
|
||||
<pre class="header-art"
|
||||
role="img" aria-label="ASCII art with jutty.dev in a 3D, slanted font">
|
||||
|
||||
|
||||
__ __ __
|
||||
__ /\ \__/\ \__ /\ \
|
||||
/\_\ __ __\ \ ,_\ \ ,_\ __ __ \_\ \ __ __ __
|
||||
\/\ \/\ \/\ \\ \ \/\ \ \/ /\ \/\ \ /'_` \ /'__`\/\ \/\ \
|
||||
\ \ \ \ \_\ \\ \ \_\ \ \_\ \ \_\ \ __/\ \L\ \/\ __/\ \ \_/ |
|
||||
_\ \ \ \____/ \ \__\\ \__\\/`____ \/\_\ \___,_\ \____\\ \___/
|
||||
/\ \_\ \/___/ \/__/ \/__/ `/___/> \/_/\/__,_ /\/____/ \/__/
|
||||
\ \____/ /\___/
|
||||
\/___/ \/__/
|
||||
|
||||
</pre>
|
||||
</header>
|
||||
<nav>
|
||||
<ul id="nav-menu">
|
||||
<li><a id="nav-menu-home" href="https://jutty.dev">Home</a></li>
|
||||
<li><a id="lang-swap-en" href="#">English</a></li>
|
||||
<li><a id="lang-swap-pt" href="#">Português</a></li>
|
||||
<li><a id="nav-menu-links" href="pages/links.html">Links</a></li>
|
||||
<li><a id="nav-menu-rss" href="https://blog.jutty.dev/assets/rss/en.rss">RSS</a></li>
|
||||
<li><a href="https://mastodon.bsd.cafe/@jutty">Mastodon</a></li>
|
||||
</ul>
|
||||
<main id="main">
|
||||
<ul id="nav-posts" aria-label="Listing of all posts"></ul>
|
||||
</main>
|
||||
</nav>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Links • jutty.dev</title>
|
||||
<link href="../assets/css/style.css" rel="stylesheet">
|
||||
<!--link rel="alternate" type="application/rss+xml" title="RSS (Links)" href="https://blog.jutty.dev/assets/rss/links.rss"/-->
|
||||
<link rel="icon" type="image/ico" href="../assets/img/favicon.ico">
|
||||
<script src="../assets/js/data.js"></script>
|
||||
<script src="../assets/js/main-l10n.js"></script>
|
||||
<script src="../assets/js/links.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<main id="main" style="margin-top: 50px">
|
||||
<ul id="links"></ul>
|
||||
</main>
|
||||
</body>
|
||||
<footer>
|
||||
<hr/>
|
||||
<div class="footer-text">
|
||||
<span class="footer-back" aria-role="nav" aria-label="Navigate back">
|
||||
↩ <a id="footer-back-link" href="../index.html">Back</a>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
||||
1
posts/.gitattributes
vendored
|
|
@ -1 +0,0 @@
|
|||
*.html linguist-generated
|
||||
17
posts/gen.ml
|
|
@ -1,17 +0,0 @@
|
|||
#!/usr/bin/env utop
|
||||
|
||||
let args = "--css ../assets/css/style.css --standalone " ^
|
||||
"--from markdown+yaml_metadata_block-implicit_figures " ^ "--to html5 " ^
|
||||
"--highlight-style zenburn " ^ "--template=template.html "
|
||||
|
||||
|
||||
let vert md = begin
|
||||
let filename_split = String.split_on_char '.' md in
|
||||
Sys.command (
|
||||
"pandoc " ^ args ^ " md/" ^ md ^ " -o " ^ List.nth filename_split 0 ^ ".html"
|
||||
)
|
||||
end ;;
|
||||
|
||||
let contents = Sys.readdir "./md/"
|
||||
|> Array.to_list
|
||||
|> List.map vert
|
||||
|
|
@ -1,315 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
<meta name="author" content="Juno Takano" />
|
||||
<meta name="dcterms.date" content="2024-06-30" />
|
||||
<title>Introducing tori • jutty.dev</title>
|
||||
<style>
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
div.columns{display: flex; gap: min(4vw, 1.5em);}
|
||||
div.column{flex: auto; overflow-x: auto;}
|
||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||||
/* The extra [class] is a hack that increases specificity enough to
|
||||
override a similar rule in reveal.js */
|
||||
ul.task-list[class]{list-style: none;}
|
||||
ul.task-list li input[type="checkbox"] {
|
||||
font-size: inherit;
|
||||
width: 0.8em;
|
||||
margin: 0 0.8em 0.2em -1.6em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
|
||||
/* CSS for syntax highlighting */
|
||||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||||
pre > code.sourceCode > span { line-height: 1.25; }
|
||||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||||
.sourceCode { overflow: visible; }
|
||||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||||
div.sourceCode { margin: 1em 0; }
|
||||
pre.sourceCode { margin: 0; }
|
||||
@media screen {
|
||||
div.sourceCode { overflow: auto; }
|
||||
}
|
||||
@media print {
|
||||
pre > code.sourceCode { white-space: pre-wrap; }
|
||||
pre > code.sourceCode > span { display: inline-block; text-indent: -5em; padding-left: 5em; }
|
||||
}
|
||||
pre.numberSource code
|
||||
{ counter-reset: source-line 0; }
|
||||
pre.numberSource code > span
|
||||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||||
pre.numberSource code > span > a:first-child::before
|
||||
{ content: counter(source-line);
|
||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||
border: none; display: inline-block;
|
||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||
-khtml-user-select: none; -moz-user-select: none;
|
||||
-ms-user-select: none; user-select: none;
|
||||
padding: 0 4px; width: 4em;
|
||||
}
|
||||
pre.numberSource { margin-left: 3em; padding-left: 4px; }
|
||||
div.sourceCode
|
||||
{ color: #cccccc; background-color: #303030; }
|
||||
@media screen {
|
||||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||||
}
|
||||
code span.al { color: #ffcfaf; } /* Alert */
|
||||
code span.an { color: #7f9f7f; font-weight: bold; } /* Annotation */
|
||||
code span.at { } /* Attribute */
|
||||
code span.bn { color: #dca3a3; } /* BaseN */
|
||||
code span.bu { } /* BuiltIn */
|
||||
code span.cf { color: #f0dfaf; } /* ControlFlow */
|
||||
code span.ch { color: #dca3a3; } /* Char */
|
||||
code span.cn { color: #dca3a3; font-weight: bold; } /* Constant */
|
||||
code span.co { color: #7f9f7f; } /* Comment */
|
||||
code span.cv { color: #7f9f7f; font-weight: bold; } /* CommentVar */
|
||||
code span.do { color: #7f9f7f; } /* Documentation */
|
||||
code span.dt { color: #dfdfbf; } /* DataType */
|
||||
code span.dv { color: #dcdccc; } /* DecVal */
|
||||
code span.er { color: #c3bf9f; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #c0bed1; } /* Float */
|
||||
code span.fu { color: #efef8f; } /* Function */
|
||||
code span.im { } /* Import */
|
||||
code span.in { color: #7f9f7f; font-weight: bold; } /* Information */
|
||||
code span.kw { color: #f0dfaf; } /* Keyword */
|
||||
code span.op { color: #f0efd0; } /* Operator */
|
||||
code span.ot { color: #efef8f; } /* Other */
|
||||
code span.pp { color: #ffcfaf; font-weight: bold; } /* Preprocessor */
|
||||
code span.sc { color: #dca3a3; } /* SpecialChar */
|
||||
code span.ss { color: #cc9393; } /* SpecialString */
|
||||
code span.st { color: #cc9393; } /* String */
|
||||
code span.va { } /* Variable */
|
||||
code span.vs { color: #cc9393; } /* VerbatimString */
|
||||
code span.wa { color: #7f9f7f; font-weight: bold; } /* Warning */
|
||||
</style>
|
||||
<link rel="stylesheet" href="../assets/css/style.css" />
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (English)" href="https://blog.jutty.dev/assets/rss/en.rss"/>
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (Português)" href="https://blog.jutty.dev/assets/rss/pt.rss"/>
|
||||
<link rel="icon" type="image/ico" href="../assets/img/favicon.ico">
|
||||
<script src="../assets/js/post-l10n.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header id="title-block-header">
|
||||
<div id="title-container">
|
||||
<h1 class="title">Introducing tori</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main class="article">
|
||||
<p><strong><a href="https://tori.jutty.dev/">tori</a></strong> is a tool
|
||||
to track your personal systems’ configurations and replicate them.</p>
|
||||
<p>For the past 5 months, I’ve been simultaneously using and writing it
|
||||
to manage my main machine’s configuration. By “manage the configuration”
|
||||
what I mean is keeping track of installed packages, configuration files,
|
||||
symlinks, and other settings that can be queried and set through command
|
||||
line interfaces.</p>
|
||||
<p>After installing a given system, I wanted to get it to the same
|
||||
configuration state I was used to, or to a certain configuration
|
||||
specific to its purpose. Just copying backups would certainly be a very
|
||||
manual task, namely because:</p>
|
||||
<ul>
|
||||
<li>Not all settings live in <code>/etc</code></li>
|
||||
<li>Some settings must be set using a specific CLI utility</li>
|
||||
<li>Backups usually carry an overwhelming amount of redundant default
|
||||
configuration you never even touched</li>
|
||||
<li>It does not track what is changing as you are still using the
|
||||
system</li>
|
||||
<li>I actually wanted to <em>know</em> what I was tracking</li>
|
||||
</ul>
|
||||
<p>Configuring a system can become a very vague process as you start to
|
||||
lose track of where the changes are being made and what is the specific
|
||||
configuration needed for something to work.</p>
|
||||
<p>Every time you change some configuration file, every time you create
|
||||
a symlink somewhere, that’s all having effects on the system that you
|
||||
may expect to be there in the future, but you may not remember how to
|
||||
accomplish that. This drift between what you have and what you are able
|
||||
to replicate only grows as you keep using your system.</p>
|
||||
<p>To get a better idea, see the code snippet below. It’s from the main
|
||||
file that I use to manage all function calls:</p>
|
||||
<div class="sourceCode" id="cb1"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a> <span class="ex">xbps_get_many</span> packages</span>
|
||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="ex">check_service</span> dbus</span>
|
||||
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">check_group</span> audio</span>
|
||||
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">check_group</span> video</span>
|
||||
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">copy</span> dhcpcd.conf /etc/dhcpcd.conf</span>
|
||||
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">place</span> kernel-cmd-line.conf /etc/dracut.conf.d</span>
|
||||
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a> <span class="ex">check_link</span> /etc/localtime /usr/share/zoneinfo/America/Sao_Paulo</span>
|
||||
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a> <span class="ex">get_nix</span> tailspin tspin</span>
|
||||
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a> <span class="ex">get_cargo</span> taplo-cli taplo <span class="at">--locked</span></span>
|
||||
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a> <span class="ex">get_bun</span> bash-language-server</span>
|
||||
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a> <span class="ex">get_bin</span> <span class="st">'https://raw.githubusercontent.com/hackerb9/lsix/master/lsix'</span> lsix</span>
|
||||
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a> <span class="ex">check_gsettings</span> gtk-theme <span class="st">'Plata-Noir'</span></span>
|
||||
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a> <span class="ex">check_gsettings</span> font-name <span class="st">'Mononoki Nerd Font Regular'</span></span></code></pre></div>
|
||||
<p>What is happening here:</p>
|
||||
<ol type="1">
|
||||
<li>A file named <code>packages</code> containing package names is
|
||||
parsed and all packages are queried and installed by the
|
||||
<code>xbps</code> package manager, if not already installed</li>
|
||||
<li>Service <code>dbus</code> is enabled if not already enabled</li>
|
||||
<li>The user is added to groups <code>audio</code> and
|
||||
<code>video</code>, unless already in them</li>
|
||||
<li>File <code>dhcpcd.conf</code> from the configuration directory’s
|
||||
<code>base</code> directory is checked against the one in the passed
|
||||
path and overwrites it if the user chooses to do so</li>
|
||||
<li>File <code>kernel-cmd-line.conf</code> from the configuration
|
||||
directory’s <code>base</code> directory is copied into the passed path.
|
||||
If the file already exists or differs, tori will present an error</li>
|
||||
<li>A symlink is checked to be on <code>/etc/localtime</code> pointing
|
||||
to the passed path. If it doesn’t, it is created or fixed</li>
|
||||
<li>If not installed, a few packages are installed using different
|
||||
package managers: <code>tailspin</code>, <code>taplo</code> and
|
||||
<code>bash-language-server</code></li>
|
||||
<li>If absent, an executable for <code>lsix</code> is downloaded from a
|
||||
URL and placed at <code>~/.local/bin</code></li>
|
||||
<li>Some <code>gsettings</code> values are read and set if they differ:
|
||||
<code>gtk-theme</code> and <code>font-name</code></li>
|
||||
</ol>
|
||||
<p>Notice how everything is conditioned to the system not already
|
||||
presenting that state? tori aims to be idempotent. Running it twice
|
||||
should do nothing the second time it runs so you can run it multiple
|
||||
times while making changes without any doubled effects.</p>
|
||||
<p>I mentioned a <code>base</code> directory. This is what a sample tori
|
||||
directory would look like in its present state:</p>
|
||||
<pre><code>.
|
||||
├── base
|
||||
│ ├── dhcpcd.conf
|
||||
│ ├── kernel-cmd-line.conf
|
||||
│ ├── packages
|
||||
│ └── vars.sh
|
||||
├── .bkp
|
||||
│ ├── canonical
|
||||
│ │ ├── etc
|
||||
│ │ └── opt
|
||||
│ └── ephemeral
|
||||
│ └── etc
|
||||
├── src
|
||||
│ ├── checks.sh
|
||||
│ ├── copy.sh
|
||||
│ └── get.sh
|
||||
└── strap</code></pre>
|
||||
<p>What you are seeing in this sample of the directory are the following
|
||||
files and directories:</p>
|
||||
<ul>
|
||||
<li><code>base</code>: Where you place the configuration files that
|
||||
functions like <code>copy</code> and <code>place</code> will look for
|
||||
and copy into the desired locations</li>
|
||||
<li><code>.bkp/canonical</code>: Where tori will look for initial
|
||||
backups and create them if none exists</li>
|
||||
<li><code>.bkp/ephemeral</code>: Where tori will place timestamped
|
||||
backups every time a file is modified or overwritten</li>
|
||||
<li><code>src</code>: Where the source files live, mostly containing
|
||||
function definitions</li>
|
||||
<li><code>strap</code>: The main file used to call the functions</li>
|
||||
</ul>
|
||||
<p>Because I developed tori for my own purposes initially, I didn’t
|
||||
really care to separate the actual source files from the
|
||||
context-sensitive data. While a mistake from a higher level, it allowed
|
||||
me to just keep developing the whole system configuration and the code
|
||||
that tracked it from a single, version-controlled location, amounting to
|
||||
very little complexity. I can’t deny to have enjoyed it so far, but
|
||||
going forward that is going to change.</p>
|
||||
<p>Currently, tori is able to install several package managers and their
|
||||
packages, including xbps, apt, nix, opam, stack, cargo, go, sdkmanager,
|
||||
npm, flatpak and pipx.</p>
|
||||
<p>It can also perform several other tasks:</p>
|
||||
<ul>
|
||||
<li>setup programming runtimes for OCaml, Scala (via Coursier), Go,
|
||||
JavaScript (node and bun), Rust (via rustup)</li>
|
||||
<li>generate GPG certificates</li>
|
||||
<li>query and set options with <code>update-alternatives</code> and
|
||||
<code>gsettings</code></li>
|
||||
<li>change the user shell</li>
|
||||
<li>check and enable services (systemd and runit)</li>
|
||||
<li>download pre-built binaries from tarballs and (g)zip files,
|
||||
unpacking and making them executable</li>
|
||||
<li>get files through the network using rsync</li>
|
||||
<li>several other things likely not worth mentioning</li>
|
||||
</ul>
|
||||
<p>The application slowly grew to accommodate many of my needs, but I
|
||||
also made it very hard to share with the world in the process, since I
|
||||
never really meant to go public with it.</p>
|
||||
<h2 id="portability-issues">Portability issues</h2>
|
||||
<p>Despite it being very useful to me in its current state and still
|
||||
being something I actively use every day, a lot of it is hard-coded for
|
||||
my very personal use. It was not written with portability in mind and
|
||||
therefore requires a lot of source-code editing to use in a different
|
||||
system.</p>
|
||||
<p>For instance, when I switched from Debian to Void Linux, most of it
|
||||
broke. I certainly would not expect the package list to be compatible
|
||||
between them, but I realized at that point how tightly it was coupled to
|
||||
Debian.</p>
|
||||
<p>When I started delving deeper into FreeBSD and setting up the system,
|
||||
I kept reaching out to something like tori. But it wasn’t there, and it
|
||||
wouldn’t work even if it were.</p>
|
||||
<p>Something that certainly influenced my desire to write tori was my
|
||||
experience using NixOS, which was full of mixed feelings, but undeniably
|
||||
had good feelings that stuck.</p>
|
||||
<p>I really liked being able to manage the system configuration and
|
||||
packages from a single file. But, at the same time, I felt it was
|
||||
overkill. It was limiting because most of the time you were
|
||||
<strong>forced</strong> to configure things through its interfaces. It
|
||||
was basically incompatible with what every other Unix system expects,
|
||||
and therefore what people who write software for these systems also
|
||||
expect.</p>
|
||||
<p>I appreciated bringing the system configuration to a centralized
|
||||
file, but I certainly did not want to manage all my
|
||||
<code>~/.config</code> configuration files from that same place. After
|
||||
writing tori, I can choose what to place under its tracking and what not
|
||||
to. Third-party software still works as both me and its creators expect
|
||||
it to, instead of my system breaking things and needing them to work the
|
||||
way <em>it</em> expects.</p>
|
||||
<h2 id="glad-to-reinvent-the-wheel">Glad to reinvent the wheel</h2>
|
||||
<p>While I understand there are very mature and powerful tools to manage
|
||||
a system’s state and reproduce it, I am aiming here for a much simpler
|
||||
use case. I have no intention to see it used in enterprise or
|
||||
distributed systems. It is all about managing how your personal
|
||||
computing is set up and having a backup of how you did it.</p>
|
||||
<p>What I need is not a tool that can orchestrate a fleet of containers
|
||||
running a given configuration. What I need is a tool that can run in a
|
||||
bare system that just got installed and get it to a state that feels
|
||||
useful to me. I do not want it to run instructions over a range of IPs,
|
||||
I just want to be able to check at any time if the system state has
|
||||
diverged from the configuration I am using to track it. I wanted a tool
|
||||
that would help me develop a different habit when I need to make
|
||||
system-level changes.</p>
|
||||
<p>And finally, I suppose I just really wanted to build this. I really
|
||||
enjoy the process of configuring operating systems and learning how they
|
||||
work and differ. And I really wanted to learn more about portable,
|
||||
POSIX-compatible shell scripting.</p>
|
||||
<p>So I decided to rewrite it with portability in mind. I am doing this
|
||||
rewrite in FreeBSD, to put the portability to the test. Once some basic
|
||||
functionality is done, the next step will be bringing it to Void Linux,
|
||||
Debian and NetBSD.</p>
|
||||
<p>tori is a bird that has just hatched, so everything is still very,
|
||||
very crude. At this stage, the docs often show intentions rather than
|
||||
implemented functionality. Still, because it is something I’ve come to
|
||||
depend on, it has this rewarding sense of usefulness behind it.</p>
|
||||
<p>If it sounds interesting to you, take a look. You can follow
|
||||
development at the main <a href="https://brew.bsd.cafe/jutty/tori">Git
|
||||
repository</a> in BSD.Cafe’s Git forge or through its mirrors on <a
|
||||
href="https://github.com/jultty/tori">GitHub</a> and <a
|
||||
href="https://codeberg.org/jutty/tori">Codeberg</a>. Going forward, I
|
||||
will also probably be talking a lot about it on my <a
|
||||
href="https://mastodon.bsd.cafe/@jutty">Mastodon profile</a>.</p>
|
||||
</main class="article">
|
||||
</body>
|
||||
<footer>
|
||||
<hr/>
|
||||
<div class="footer-text">
|
||||
<span id="author-pre-text">posted by</span>
|
||||
<span class="author">Juno Takano</span>
|
||||
<span id="date-pre-text">on</span>
|
||||
<span class="date">June 30, 2024</span>
|
||||
<span class="footer-back" aria-role="nav" aria-label="Navigate back">
|
||||
↩ <a id="footer-back-link" href="../index.html">Back</a>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
<meta name="author" content="Juno Takano" />
|
||||
<meta name="dcterms.date" content="2024-05-20" />
|
||||
<title>Meeting the BSD family • jutty.dev</title>
|
||||
<style>
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
div.columns{display: flex; gap: min(4vw, 1.5em);}
|
||||
div.column{flex: auto; overflow-x: auto;}
|
||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||||
/* The extra [class] is a hack that increases specificity enough to
|
||||
override a similar rule in reveal.js */
|
||||
ul.task-list[class]{list-style: none;}
|
||||
ul.task-list li input[type="checkbox"] {
|
||||
font-size: inherit;
|
||||
width: 0.8em;
|
||||
margin: 0 0.8em 0.2em -1.6em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
|
||||
</style>
|
||||
<link rel="stylesheet" href="../assets/css/style.css" />
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (English)" href="https://blog.jutty.dev/assets/rss/en.rss"/>
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (Português)" href="https://blog.jutty.dev/assets/rss/pt.rss"/>
|
||||
<link rel="icon" type="image/ico" href="../assets/img/favicon.ico">
|
||||
<script src="../assets/js/post-l10n.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header id="title-block-header">
|
||||
<div id="title-container">
|
||||
<h1 class="title">Meeting the BSD family</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main class="article">
|
||||
<p>During this year I have been delving deeper and deeper in the BSD
|
||||
realm. Switching my home server to FreeBSD, trying NetBSD and OpenBSD on
|
||||
my backup machine, getting a cheap SSD to see how they’d all run on my
|
||||
main one, all beaming with the joy of tinkering and learning.</p>
|
||||
<p>As a nerd who delights in reading documentation, manuals and
|
||||
handbooks, I feel like I have found a gigantic library to lose myself
|
||||
in. And to me the delight of such reading is in that it’s never a
|
||||
passive learning experience, but something you can act on and bring to
|
||||
fruition yourself.</p>
|
||||
<p>While Linux-based operating systems, with all the popularity they
|
||||
have gained, have developed into a complex and extremely active
|
||||
ecosystem, the BSD operating systems feel less bloated and more focused
|
||||
on whatever their specialty is.</p>
|
||||
<p>You can’t really complain about software availability, given the
|
||||
amount of pre-packaged binaries you will find. When trying FreeBSD, I
|
||||
could not miss anything I needed. More recently, on NetBSD, I also found
|
||||
most of the tools I reached for.</p>
|
||||
<p>Though I have a mostly text-driven workflow, doing almost all things
|
||||
with a browser and a terminal alone – which certainly helps in making
|
||||
your stack more portable – I do rely on some GUI applications for the
|
||||
domains where they excel.</p>
|
||||
<p>What you might experience is a slower pace of change for major
|
||||
things, such as on Wayland adoption, which like it or not is coming for
|
||||
all of us with X deprecation looming.</p>
|
||||
<p>Running BSD is an incredible opportunity to really learn about
|
||||
UNIX-like systems and operating systems in general.</p>
|
||||
<p>Recently, I’ve been learning more about NetBSD after spending some
|
||||
time with FreeBSD. And this inner diversity of fully-independent
|
||||
operating systems with their own kernels and perks keeps multiplying the
|
||||
learning opportunities.</p>
|
||||
<p>If you already learned a lot about whatever OS you currently use, I’d
|
||||
say particularly if that OS is Linux-based, when you start to play with
|
||||
a BSD system you are able to realize what is similar and what is
|
||||
not.</p>
|
||||
<p>Whatever is different is likely teaching you the more portable, UNIX
|
||||
way of doing things. Even if it isn’t, it’s teaching you how a different
|
||||
OS is designed and behaves.</p>
|
||||
<p>Things that are the same, which are not few, also offer learning
|
||||
opportunities. You get to see what parts of a Linux-based OS perhaps
|
||||
didn’t really originate there, or aren’t in any way an exclusive feature
|
||||
of it.</p>
|
||||
<p>Now, to lay any zealousness aside and not make this a saccharine
|
||||
one-sided tale, I’d also like to mention a certain social phenomenon
|
||||
that this endeavour reminded me of.</p>
|
||||
<p>This is certainly not something specific to BSD, but because it has
|
||||
such an engaged and savvy community, you definitely get to notice it
|
||||
sometimes. I’m talking about the tendency to identify with and then
|
||||
indiscriminately defend the software you use.</p>
|
||||
<p>One common meme you’ll find is people complaining about lack of
|
||||
hardware support, especially wifi. In response, I’ve seen people stating
|
||||
with little nuance that any difficulty to getting your hardware to work
|
||||
on <insert a BSD OS here> is to be explained by poor skills or
|
||||
lack of dedication in reading the documentation.</p>
|
||||
<p>I see that as denial. When everyone around is just defending
|
||||
something to no end, no critiques allowed, it starts to feel… awkward,
|
||||
to say the least.</p>
|
||||
<p>Conversely, when I see people openly pointing out weaknesses in
|
||||
something I value and that I can tell they also care for, I feel relief
|
||||
and admiration for that person and that community at large. And
|
||||
thankfully I have also found a lot of this among the BSD folks.</p>
|
||||
<p>Because running a given operating system on a machine you rely on is
|
||||
such a big commitment, it intensifies this phenomenon where users start
|
||||
to identify with the software they use and defend it beyond reason.</p>
|
||||
<p>It happens with frameworks, desktop environments and window managers,
|
||||
but operating systems require you to commit even more because you can’t
|
||||
just swap them as easily, so my guess is we identify to compensate this
|
||||
sense of being tied to it. And from this identification comes an urge to
|
||||
deny any defect.</p>
|
||||
<p>If you are cognizant of the perils, identifying with something is not
|
||||
necessarily a bad thing, though. To some extent, it is inevitable, and
|
||||
being really into something, caring about it, nurturing immense
|
||||
curiosity and a desire to discuss it, are all sources of pleasure I do
|
||||
not excuse myself from.</p>
|
||||
<p>Software wars aside, getting to know this family of operating systems
|
||||
better has been a joy. It opened up whole new avenues and perspectives
|
||||
to understanding operating systems as a whole, and how beyond
|
||||
Linux-based OSs there are numerous other free and open source operating
|
||||
systems that strengthen the diversity in this field.</p>
|
||||
</main class="article">
|
||||
</body>
|
||||
<footer>
|
||||
<hr/>
|
||||
<div class="footer-text">
|
||||
<span id="author-pre-text">posted by</span>
|
||||
<span class="author">Juno Takano</span>
|
||||
<span id="date-pre-text">on</span>
|
||||
<span class="date">May 20, 2024</span>
|
||||
<span class="footer-back" aria-role="nav" aria-label="Navigate back">
|
||||
↩ <a id="footer-back-link" href="../index.html">Back</a>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
<meta name="author" content="Juno Takano" />
|
||||
<title>Scripts em OCaml • jutty.dev</title>
|
||||
<style>
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
div.columns{display: flex; gap: min(4vw, 1.5em);}
|
||||
div.column{flex: auto; overflow-x: auto;}
|
||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||||
/* The extra [class] is a hack that increases specificity enough to
|
||||
override a similar rule in reveal.js */
|
||||
ul.task-list[class]{list-style: none;}
|
||||
ul.task-list li input[type="checkbox"] {
|
||||
font-size: inherit;
|
||||
width: 0.8em;
|
||||
margin: 0 0.8em 0.2em -1.6em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
|
||||
/* CSS for syntax highlighting */
|
||||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||||
pre > code.sourceCode > span { line-height: 1.25; }
|
||||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||||
.sourceCode { overflow: visible; }
|
||||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||||
div.sourceCode { margin: 1em 0; }
|
||||
pre.sourceCode { margin: 0; }
|
||||
@media screen {
|
||||
div.sourceCode { overflow: auto; }
|
||||
}
|
||||
@media print {
|
||||
pre > code.sourceCode { white-space: pre-wrap; }
|
||||
pre > code.sourceCode > span { display: inline-block; text-indent: -5em; padding-left: 5em; }
|
||||
}
|
||||
pre.numberSource code
|
||||
{ counter-reset: source-line 0; }
|
||||
pre.numberSource code > span
|
||||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||||
pre.numberSource code > span > a:first-child::before
|
||||
{ content: counter(source-line);
|
||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||
border: none; display: inline-block;
|
||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||
-khtml-user-select: none; -moz-user-select: none;
|
||||
-ms-user-select: none; user-select: none;
|
||||
padding: 0 4px; width: 4em;
|
||||
}
|
||||
pre.numberSource { margin-left: 3em; padding-left: 4px; }
|
||||
div.sourceCode
|
||||
{ color: #cccccc; background-color: #303030; }
|
||||
@media screen {
|
||||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||||
}
|
||||
code span.al { color: #ffcfaf; } /* Alert */
|
||||
code span.an { color: #7f9f7f; font-weight: bold; } /* Annotation */
|
||||
code span.at { } /* Attribute */
|
||||
code span.bn { color: #dca3a3; } /* BaseN */
|
||||
code span.bu { } /* BuiltIn */
|
||||
code span.cf { color: #f0dfaf; } /* ControlFlow */
|
||||
code span.ch { color: #dca3a3; } /* Char */
|
||||
code span.cn { color: #dca3a3; font-weight: bold; } /* Constant */
|
||||
code span.co { color: #7f9f7f; } /* Comment */
|
||||
code span.cv { color: #7f9f7f; font-weight: bold; } /* CommentVar */
|
||||
code span.do { color: #7f9f7f; } /* Documentation */
|
||||
code span.dt { color: #dfdfbf; } /* DataType */
|
||||
code span.dv { color: #dcdccc; } /* DecVal */
|
||||
code span.er { color: #c3bf9f; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #c0bed1; } /* Float */
|
||||
code span.fu { color: #efef8f; } /* Function */
|
||||
code span.im { } /* Import */
|
||||
code span.in { color: #7f9f7f; font-weight: bold; } /* Information */
|
||||
code span.kw { color: #f0dfaf; } /* Keyword */
|
||||
code span.op { color: #f0efd0; } /* Operator */
|
||||
code span.ot { color: #efef8f; } /* Other */
|
||||
code span.pp { color: #ffcfaf; font-weight: bold; } /* Preprocessor */
|
||||
code span.sc { color: #dca3a3; } /* SpecialChar */
|
||||
code span.ss { color: #cc9393; } /* SpecialString */
|
||||
code span.st { color: #cc9393; } /* String */
|
||||
code span.va { } /* Variable */
|
||||
code span.vs { color: #cc9393; } /* VerbatimString */
|
||||
code span.wa { color: #7f9f7f; font-weight: bold; } /* Warning */
|
||||
</style>
|
||||
<link rel="stylesheet" href="../assets/css/style.css" />
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (English)" href="https://blog.jutty.dev/assets/rss/en.rss"/>
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (Português)" href="https://blog.jutty.dev/assets/rss/pt.rss"/>
|
||||
<link rel="icon" type="image/ico" href="../assets/img/favicon.ico">
|
||||
<script src="../assets/js/post-l10n.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header id="title-block-header">
|
||||
<div id="title-container">
|
||||
<h1 class="title">Scripts em OCaml</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main class="article">
|
||||
<p>Este blog gera suas postagens com o Pandoc, mas quem faz o trabalho
|
||||
lógico de identificar os arquivos e montar os comandos é um pequeno
|
||||
script em OCaml.</p>
|
||||
<p>Descobri essa linguagem quando comecei a investigar como poderia
|
||||
substituir o Bash como uma linguagem para scripts. Eu acho que o Bash é
|
||||
excelente para cumprir essa função, apesar de bastante inseguro e
|
||||
propenso a erros. O que me incomodava mesmo era ver que todo aquele
|
||||
tempo escrevendo scripts com Bash poderia ser tempo aprendendo uma
|
||||
linguagem cujo conhecimento se transferisse para muitas outras
|
||||
aplicações.</p>
|
||||
<p>O script se resume a:</p>
|
||||
<div class="sourceCode" id="cb1"><pre
|
||||
class="sourceCode ocaml"><code class="sourceCode ocaml"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> pandoc_params = </span>
|
||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> <span class="st">"--css ../../assets/style.css -s --to html5 --metadata pagetitle='basename' "</span></span>
|
||||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> vert md = <span class="kw">begin</span></span>
|
||||
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">if</span> md <> <span class="st">"gen.ml"</span> && md <> <span class="st">"html"</span> <span class="kw">then</span></span>
|
||||
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">ignore</span>( <span class="dt">Sys</span>.command (</span>
|
||||
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="st">"pandoc "</span> ^ pandoc_params ^ md ^ <span class="st">" -o html/"</span> ^ md ^ <span class="st">".html "</span></span>
|
||||
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> ) )</span>
|
||||
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="kw">end</span> ;;</span>
|
||||
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> contents = <span class="dt">Array</span>.to_list (<span class="dt">Sys</span>.readdir <span class="st">"."</span>) <span class="kw">in</span></span>
|
||||
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="dt">List</span>.map vert contents ;;</span></code></pre></div>
|
||||
<p>Primeiro, armazeno em <code>pandoc_params</code> os parâmetros comuns
|
||||
que serão passados ao Pandoc para gerar os arquivos HTML a partir dos
|
||||
arquivos em Markdown.</p>
|
||||
<p>Em seguida, defino a função <code>vert</code>, que, caso o arquivo
|
||||
não se chame “gen.ml” (o próprio script) nem “html” (o diretório onde os
|
||||
arquivos gerados ficam), o comando do Pandoc é chamado com os nomes dos
|
||||
arquivos de entrada e saída.</p>
|
||||
<p>Essa função então é aplicada com <code>List.map vert contents</code>
|
||||
a cada arquivo do diretório atual.</p>
|
||||
<h2 id="referências">Referências</h2>
|
||||
<ul>
|
||||
<li><a
|
||||
href="https://rosettacode.org/wiki/Walk_a_directory/Non-recursively#OCaml">Walk
|
||||
a directory/Non-recursively - Rosetta Code</a></li>
|
||||
<li><a href="https://jez.io/pandoc-markdown-css-theme/">Pandoc Markdown
|
||||
CSS Theme</a></li>
|
||||
</ul>
|
||||
</main class="article">
|
||||
</body>
|
||||
<footer>
|
||||
<hr/>
|
||||
<div class="footer-text">
|
||||
<span id="author-pre-text">posted by</span>
|
||||
<span class="author">Juno Takano</span>
|
||||
<span id="date-pre-text">on</span>
|
||||
<span class="date">23 de fevereiro de 2024</span>
|
||||
<span class="footer-back" aria-role="nav" aria-label="Navigate back">
|
||||
↩ <a id="footer-back-link" href="../index.html">Back</a>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="$lang$" xml:lang="$lang$"$if(dir)$ dir="$dir$"$endif$>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
$for(author-meta)$
|
||||
<meta name="author" content="$author-meta$" />
|
||||
$endfor$
|
||||
$if(date-meta)$
|
||||
<meta name="dcterms.date" content="$date-meta$" />
|
||||
$endif$
|
||||
$if(keywords)$
|
||||
<meta name="keywords" content="$for(keywords)$$keywords$$sep$, $endfor$" />
|
||||
$endif$
|
||||
$if(description-meta)$
|
||||
<meta name="description" content="$description-meta$" />
|
||||
$endif$
|
||||
<title>$if(title-prefix)$$title-prefix$ – $endif$$pagetitle$ • jutty.dev</title>
|
||||
<style>
|
||||
$styles.html()$
|
||||
</style>
|
||||
$for(css)$
|
||||
<link rel="stylesheet" href="$css$" />
|
||||
$endfor$
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (English)" href="https://blog.jutty.dev/assets/rss/en.rss"/>
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (Português)" href="https://blog.jutty.dev/assets/rss/pt.rss"/>
|
||||
<link rel="icon" type="image/ico" href="../assets/img/favicon.ico">
|
||||
<script src="../assets/js/post-l10n.js" defer></script>
|
||||
$for(header-includes)$
|
||||
$header-includes$
|
||||
$endfor$
|
||||
$if(math)$
|
||||
$math$
|
||||
$endif$
|
||||
</head>
|
||||
<body>
|
||||
$for(include-before)$
|
||||
$include-before$
|
||||
$endfor$
|
||||
$if(title)$
|
||||
<header id="title-block-header">
|
||||
<div id="title-container">
|
||||
<h1 class="title">$title$</h1>
|
||||
</div>
|
||||
$if(subtitle)$
|
||||
<p class="subtitle">$subtitle$</p>
|
||||
$endif$
|
||||
$if(abstract)$
|
||||
<div class="abstract">
|
||||
<div class="abstract-title">$abstract-title$</div>
|
||||
$abstract$
|
||||
</div>
|
||||
$endif$
|
||||
</header>
|
||||
$endif$
|
||||
$if(toc)$
|
||||
<nav id="$idprefix$TOC" role="doc-toc">
|
||||
$if(toc-title)$
|
||||
<h2 id="$idprefix$toc-title">$toc-title$</h2>
|
||||
$endif$
|
||||
$table-of-contents$
|
||||
</nav>
|
||||
$endif$
|
||||
<main class="article">
|
||||
$body$
|
||||
</main class="article">
|
||||
$for(include-after)$
|
||||
$include-after$
|
||||
$endfor$
|
||||
</body>
|
||||
<footer>
|
||||
<hr/>
|
||||
<div class="footer-text">
|
||||
<span id="author-pre-text">posted by</span>
|
||||
$for(author)$
|
||||
<span class="author">$author$</span>
|
||||
$endfor$
|
||||
<span id="date-pre-text">on</span>
|
||||
$if(date)$
|
||||
<span class="date">$date$</span>
|
||||
<span class="footer-back" aria-role="nav" aria-label="Navigate back">
|
||||
↩ <a id="footer-back-link" href="../index.html">Back</a>
|
||||
</span>
|
||||
$endif$
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
||||
|
|
@ -1,226 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
<meta name="author" content="Juno Takano" />
|
||||
<meta name="dcterms.date" content="2024-08-10" />
|
||||
<title>Giving up simplicity • jutty.dev</title>
|
||||
<style>
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
div.columns{display: flex; gap: min(4vw, 1.5em);}
|
||||
div.column{flex: auto; overflow-x: auto;}
|
||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||||
/* The extra [class] is a hack that increases specificity enough to
|
||||
override a similar rule in reveal.js */
|
||||
ul.task-list[class]{list-style: none;}
|
||||
ul.task-list li input[type="checkbox"] {
|
||||
font-size: inherit;
|
||||
width: 0.8em;
|
||||
margin: 0 0.8em 0.2em -1.6em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
|
||||
/* CSS for syntax highlighting */
|
||||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||||
pre > code.sourceCode > span { line-height: 1.25; }
|
||||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||||
.sourceCode { overflow: visible; }
|
||||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||||
div.sourceCode { margin: 1em 0; }
|
||||
pre.sourceCode { margin: 0; }
|
||||
@media screen {
|
||||
div.sourceCode { overflow: auto; }
|
||||
}
|
||||
@media print {
|
||||
pre > code.sourceCode { white-space: pre-wrap; }
|
||||
pre > code.sourceCode > span { display: inline-block; text-indent: -5em; padding-left: 5em; }
|
||||
}
|
||||
pre.numberSource code
|
||||
{ counter-reset: source-line 0; }
|
||||
pre.numberSource code > span
|
||||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||||
pre.numberSource code > span > a:first-child::before
|
||||
{ content: counter(source-line);
|
||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||
border: none; display: inline-block;
|
||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||
-khtml-user-select: none; -moz-user-select: none;
|
||||
-ms-user-select: none; user-select: none;
|
||||
padding: 0 4px; width: 4em;
|
||||
}
|
||||
pre.numberSource { margin-left: 3em; padding-left: 4px; }
|
||||
div.sourceCode
|
||||
{ color: #cccccc; background-color: #303030; }
|
||||
@media screen {
|
||||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||||
}
|
||||
code span.al { color: #ffcfaf; } /* Alert */
|
||||
code span.an { color: #7f9f7f; font-weight: bold; } /* Annotation */
|
||||
code span.at { } /* Attribute */
|
||||
code span.bn { color: #dca3a3; } /* BaseN */
|
||||
code span.bu { } /* BuiltIn */
|
||||
code span.cf { color: #f0dfaf; } /* ControlFlow */
|
||||
code span.ch { color: #dca3a3; } /* Char */
|
||||
code span.cn { color: #dca3a3; font-weight: bold; } /* Constant */
|
||||
code span.co { color: #7f9f7f; } /* Comment */
|
||||
code span.cv { color: #7f9f7f; font-weight: bold; } /* CommentVar */
|
||||
code span.do { color: #7f9f7f; } /* Documentation */
|
||||
code span.dt { color: #dfdfbf; } /* DataType */
|
||||
code span.dv { color: #dcdccc; } /* DecVal */
|
||||
code span.er { color: #c3bf9f; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #c0bed1; } /* Float */
|
||||
code span.fu { color: #efef8f; } /* Function */
|
||||
code span.im { } /* Import */
|
||||
code span.in { color: #7f9f7f; font-weight: bold; } /* Information */
|
||||
code span.kw { color: #f0dfaf; } /* Keyword */
|
||||
code span.op { color: #f0efd0; } /* Operator */
|
||||
code span.ot { color: #efef8f; } /* Other */
|
||||
code span.pp { color: #ffcfaf; font-weight: bold; } /* Preprocessor */
|
||||
code span.sc { color: #dca3a3; } /* SpecialChar */
|
||||
code span.ss { color: #cc9393; } /* SpecialString */
|
||||
code span.st { color: #cc9393; } /* String */
|
||||
code span.va { } /* Variable */
|
||||
code span.vs { color: #cc9393; } /* VerbatimString */
|
||||
code span.wa { color: #7f9f7f; font-weight: bold; } /* Warning */
|
||||
</style>
|
||||
<link rel="stylesheet" href="../assets/css/style.css" />
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (English)" href="https://blog.jutty.dev/assets/rss/en.rss"/>
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (Português)" href="https://blog.jutty.dev/assets/rss/pt.rss"/>
|
||||
<link rel="icon" type="image/ico" href="../assets/img/favicon.ico">
|
||||
<script src="../assets/js/post-l10n.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header id="title-block-header">
|
||||
<div id="title-container">
|
||||
<h1 class="title">Giving up simplicity</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main class="article">
|
||||
<p><em>or</em></p>
|
||||
<h1 id="unwinding">Unwinding</h1>
|
||||
<p>Due to a worker strike, the university semester ended late this year.
|
||||
That meant everything had to be compressed into little more than a month
|
||||
in order to wrap up what would have taken four. Now that we’re almost
|
||||
through it, my mind wanders to writing. It is almost always what springs
|
||||
from the void in me, what has been winded up loosens and the scattered
|
||||
meaning starts to recollect into the drain of language and swirl through
|
||||
the piping of my nervous system.</p>
|
||||
<p><em>Wind</em> is air that has been somehow compressed. If there was
|
||||
no pressure pushing it anywhere, it would be just expansive air,
|
||||
floating in peace with the atmosphere.</p>
|
||||
<p>If you’d entertain this thought further, consider a work of visual
|
||||
art. It can be more figurative, clearly depicting shapes that mean
|
||||
something, and therefore able to convey an array of ideas to whatever
|
||||
extent of detail the artist wants. Conversely, it can be more abstract,
|
||||
where ideas will be a lot more sparse, possibly to the point where
|
||||
nothing at all is conveyed other than the appearance, whatever aesthetic
|
||||
is employed being the whole message in itself. Very little is packed
|
||||
into the work, just like the air you can’t even feel weighing on
|
||||
you.</p>
|
||||
<p>In computing, and more specifically in the realm of programming – a
|
||||
craft presently overshadowed by the semantically starved jargon of
|
||||
whatever the department responsible for inflating public perception
|
||||
numbers is called these days – simplicity is often emphasized. Code is
|
||||
supposed to be clear, expressive and clean. A software application is
|
||||
supposed to have as few dependencies as possible, and strive to keep it
|
||||
simple, or risk stupidity.</p>
|
||||
<p>While that is a lofty goal, and while clear, expressive and clean
|
||||
code is a refreshing and tranquilizing sight, more often than not
|
||||
software just can’t be simple.</p>
|
||||
<p>Not having dependencies means implementing more and more yourself.
|
||||
There are corner cases to cover, tests to run, different architectures
|
||||
and operating systems to support. Even the simplest of software ideas,
|
||||
say, a calculator, a program that prints back a sentence in reverse,
|
||||
that displays a picture you give it, whatever you conceive as the
|
||||
simplest use case, is hardly ever implemented with simplicity in the
|
||||
naïve sense of something that is, quite literally, simplistic.</p>
|
||||
<p>More often than not, simplicity is actually abstraction. The breaking
|
||||
apart of complexity behind a simpler facade. More so a way to manage
|
||||
complexity by conveying it simply than to enact simplicity in its actual
|
||||
sense. Each step in abstraction is actually a layer deeper into
|
||||
intricacy. And yet, it makes things immensely easier to manage and
|
||||
understand.</p>
|
||||
<p>For some reason, I have always felt very drawn to abstract works.
|
||||
Staring into them, there is no expectation to understand, get, or argue
|
||||
about. Interestingly, to me that also means they couldn’t be any more
|
||||
clear. It does not mean a specific, intelligible message is conveyed
|
||||
from the artist to me, rather, it means whatever impression is caused on
|
||||
the viewer was never intended to reach too deep anyways. It never
|
||||
intended to carry that much through its medium.</p>
|
||||
<p>Sometimes, too much detail, no matter how specific, can draw an idea
|
||||
so far out that it becomes harder and harder to grasp. In contrast to
|
||||
that, a brisk exposition can get the message across like lightning.</p>
|
||||
<p>So detail does not always convey meaning, although it can convey a
|
||||
specific meaning to someone who will bear with you as you build the
|
||||
context for it. Otherwise, you could convey your message just as
|
||||
effectively in an abstract manner if your receiver already has that
|
||||
context from the outset.</p>
|
||||
<p>In computing, such possibility is simply absent from us. No context
|
||||
whatsoever can be assumed, and if it is present, that is because some
|
||||
other structure is providing it.</p>
|
||||
<p>Complexity produces confusion, confusion produces frustration, and
|
||||
frustration can lead to either surrender or a rebound. So the
|
||||
interesting thing about this pressurizing of ideas is that it springs
|
||||
back into action a process that may reverse it or deflect into something
|
||||
else entirely.</p>
|
||||
<p>Instead of surrendering to the frustration of complexity, sometimes I
|
||||
actually take the time to recollect myself and analyze it into
|
||||
understanding. This feeling of winning over something that had me on my
|
||||
knees and ready to give up is a very gratifying one. It convinces me I
|
||||
can squeeze grit from despair if I bet on it and willingly risk
|
||||
desperation in order to see it through.</p>
|
||||
<p>Yet, after chasing the deadline of effort all of it relaxes back into
|
||||
this state. In it, the only way to rest is to embrace complexity as the
|
||||
whole, and simplicity as one of its manifestations. It is so cold right
|
||||
now in the south, but in the north it is not. Four days and nine hours
|
||||
ago it was the peak of the winter. I’m wrapped in blankets and the room
|
||||
is dark like the depths of a submarine cave, LED lights here and there
|
||||
like fluorescent eyes blinking in silence.</p>
|
||||
<p>The professor had us build games and then present them to the whole
|
||||
faculty. Among the outputs, one algorithm I produced stuck with me for
|
||||
what I deem is simplicity. It is responsible for causing an enemy to
|
||||
chase the player by finding the difference between their positions and
|
||||
lowering it:</p>
|
||||
<div class="sourceCode" id="cb1"><pre
|
||||
class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>func move_to(x, y):</span>
|
||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> var x_difference <span class="op">=</span> <span class="va">self</span>.global_position.x <span class="op">-</span> x</span>
|
||||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> var y_difference <span class="op">=</span> <span class="va">self</span>.global_position.y <span class="op">-</span> y</span>
|
||||
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> </span>
|
||||
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="bu">abs</span>(x_difference) <span class="op">></span> chase_speed:</span>
|
||||
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> x_difference <span class="op">></span> <span class="dv">0</span>:</span>
|
||||
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.global_position.x <span class="op">-=</span> chase_speed</span>
|
||||
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
|
||||
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.global_position.x <span class="op">+=</span> chase_speed</span>
|
||||
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
|
||||
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.global_position.x <span class="op">+=</span> x_difference</span>
|
||||
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="bu">abs</span>(y_difference) <span class="op">></span> chase_speed:</span>
|
||||
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> y_difference <span class="op">></span> <span class="dv">0</span>:</span>
|
||||
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.global_position.y <span class="op">-=</span> chase_speed</span>
|
||||
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
|
||||
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.global_position.y <span class="op">+=</span> chase_speed</span>
|
||||
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span>:</span>
|
||||
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a> <span class="va">self</span>.global_position.y <span class="op">+=</span> y_difference</span></code></pre></div>
|
||||
<p>It could be further abstracted. The logic is repetitive. The math
|
||||
could be condensed. But should it? Would that make it harder or easier
|
||||
to understand? And to whom? There is no single answer, and yet I would
|
||||
really appreciate knowing.</p>
|
||||
</main class="article">
|
||||
</body>
|
||||
<footer>
|
||||
<hr/>
|
||||
<div class="footer-text">
|
||||
<span id="author-pre-text">posted by</span>
|
||||
<span class="author">Juno Takano</span>
|
||||
<span id="date-pre-text">on</span>
|
||||
<span class="date">August 10, 2024</span>
|
||||
<span class="footer-back" aria-role="nav" aria-label="Navigate back">
|
||||
↩ <a id="footer-back-link" href="../index.html">Back</a>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
||||
|
|
@ -1,849 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
<meta name="author" content="Juno Takano" />
|
||||
<meta name="dcterms.date" content="2024-06-09" />
|
||||
<title>Void on ZFS • jutty.dev</title>
|
||||
<style>
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
div.columns{display: flex; gap: min(4vw, 1.5em);}
|
||||
div.column{flex: auto; overflow-x: auto;}
|
||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||||
/* The extra [class] is a hack that increases specificity enough to
|
||||
override a similar rule in reveal.js */
|
||||
ul.task-list[class]{list-style: none;}
|
||||
ul.task-list li input[type="checkbox"] {
|
||||
font-size: inherit;
|
||||
width: 0.8em;
|
||||
margin: 0 0.8em 0.2em -1.6em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
|
||||
/* CSS for syntax highlighting */
|
||||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||||
pre > code.sourceCode > span { line-height: 1.25; }
|
||||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||||
.sourceCode { overflow: visible; }
|
||||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||||
div.sourceCode { margin: 1em 0; }
|
||||
pre.sourceCode { margin: 0; }
|
||||
@media screen {
|
||||
div.sourceCode { overflow: auto; }
|
||||
}
|
||||
@media print {
|
||||
pre > code.sourceCode { white-space: pre-wrap; }
|
||||
pre > code.sourceCode > span { display: inline-block; text-indent: -5em; padding-left: 5em; }
|
||||
}
|
||||
pre.numberSource code
|
||||
{ counter-reset: source-line 0; }
|
||||
pre.numberSource code > span
|
||||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||||
pre.numberSource code > span > a:first-child::before
|
||||
{ content: counter(source-line);
|
||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||
border: none; display: inline-block;
|
||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||
-khtml-user-select: none; -moz-user-select: none;
|
||||
-ms-user-select: none; user-select: none;
|
||||
padding: 0 4px; width: 4em;
|
||||
}
|
||||
pre.numberSource { margin-left: 3em; padding-left: 4px; }
|
||||
div.sourceCode
|
||||
{ color: #cccccc; background-color: #303030; }
|
||||
@media screen {
|
||||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||||
}
|
||||
code span.al { color: #ffcfaf; } /* Alert */
|
||||
code span.an { color: #7f9f7f; font-weight: bold; } /* Annotation */
|
||||
code span.at { } /* Attribute */
|
||||
code span.bn { color: #dca3a3; } /* BaseN */
|
||||
code span.bu { } /* BuiltIn */
|
||||
code span.cf { color: #f0dfaf; } /* ControlFlow */
|
||||
code span.ch { color: #dca3a3; } /* Char */
|
||||
code span.cn { color: #dca3a3; font-weight: bold; } /* Constant */
|
||||
code span.co { color: #7f9f7f; } /* Comment */
|
||||
code span.cv { color: #7f9f7f; font-weight: bold; } /* CommentVar */
|
||||
code span.do { color: #7f9f7f; } /* Documentation */
|
||||
code span.dt { color: #dfdfbf; } /* DataType */
|
||||
code span.dv { color: #dcdccc; } /* DecVal */
|
||||
code span.er { color: #c3bf9f; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #c0bed1; } /* Float */
|
||||
code span.fu { color: #efef8f; } /* Function */
|
||||
code span.im { } /* Import */
|
||||
code span.in { color: #7f9f7f; font-weight: bold; } /* Information */
|
||||
code span.kw { color: #f0dfaf; } /* Keyword */
|
||||
code span.op { color: #f0efd0; } /* Operator */
|
||||
code span.ot { color: #efef8f; } /* Other */
|
||||
code span.pp { color: #ffcfaf; font-weight: bold; } /* Preprocessor */
|
||||
code span.sc { color: #dca3a3; } /* SpecialChar */
|
||||
code span.ss { color: #cc9393; } /* SpecialString */
|
||||
code span.st { color: #cc9393; } /* String */
|
||||
code span.va { } /* Variable */
|
||||
code span.vs { color: #cc9393; } /* VerbatimString */
|
||||
code span.wa { color: #7f9f7f; font-weight: bold; } /* Warning */
|
||||
</style>
|
||||
<link rel="stylesheet" href="../assets/css/style.css" />
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (English)" href="https://blog.jutty.dev/assets/rss/en.rss"/>
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS (Português)" href="https://blog.jutty.dev/assets/rss/pt.rss"/>
|
||||
<link rel="icon" type="image/ico" href="../assets/img/favicon.ico">
|
||||
<script src="../assets/js/post-l10n.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header id="title-block-header">
|
||||
<div id="title-container">
|
||||
<h1 class="title">Void on ZFS</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main class="article">
|
||||
<p><img src="../assets/img/posts/void-on-zfs/desk.jpg"
|
||||
alt="An L-shaped desk with two laptops, an external monitor, a router and a third headless computer in a tower case with several power cables connected to a power strip on top of it. Next to the power strip are two cellphones, a long red box, and a charging case for Bluetooth headphones with a red LED on. The tower case has three stickers on it: one with the machine specifications, one the FreeBSD logo, and one reading “platform feodalism (sic) is so 1492”. Scattered around the machines are some office supplies, medicine containers, a screwdriver, a notepad, a small notebook, an NVMe SSD card on top of its packaging, a mug, a water bottle and a scarf. Hanging on the wall are a small painting of a dead tree before the twilight, prayer beads, a sunflower-pattern keychain and a calendar. Between the desk and the camera, a green plastic chair has a pillow and blanket on top of it. A few wires and cardboard boxes are visible under the desk." /></p>
|
||||
<p>June is here. It brings the usual cold weather and some extra
|
||||
rhinitis complications. With that I find myself in a recovery mood
|
||||
Sunday, wrapped in a blanket with a mug of tea, a screwdriver, some
|
||||
notes on paper, a flash drive, a couple of NVMe cards and the trio of
|
||||
Unix-powered machines that will help me get this done.</p>
|
||||
<p>The mission is to get a root-on-ZFS EFI installation of Void Linux
|
||||
with ZFSBootMenu on a Dell Latitude 7480.</p>
|
||||
<p>To my left, a ceiling-collapse-survivor Sony VAIO is running NetBSD
|
||||
with spectrwm. It’s split with sakura and tmux on a terminal to one
|
||||
side, where Neovim is storing these words, and Firefox on the other,
|
||||
ready to fetch all the docs. In the middle, the object of today’s
|
||||
operation. And to my right, a headless PC board runs FreeBSD with ZFS,
|
||||
holding all the backups needed for the post-install tasks.</p>
|
||||
<p><img src="../assets/img/posts/void-on-zfs/duo.jpg"
|
||||
alt="Two laptops side-by-side on a desk, each with a USB keyboard plugged in. Writing utensils inside a holder and post-its are between the two. A sunflower-patterned keychain and prayer beads hang from the wall. The computer on the left has a brown cloth for a wrist rest in front of its USB keyboard, to the left of which lies a small blue Campus notebook." /></p>
|
||||
<p>This lengthy post, written not after the fact but during it, is my
|
||||
way of documenting and also sharing how it all went. Additionally, it’s
|
||||
a way to delve deeper into many of the things the ZFSBootMenu docs leave
|
||||
unexplained, an urge I already had yesterday as I just tried it out
|
||||
without much modification.</p>
|
||||
<p>Last night, I ran through the <a
|
||||
href="https://docs.zfsbootmenu.org/en/latest/guides/void-linux/uefi.html">ZFSBootMenu
|
||||
documentation guide for Void</a> and followed it both on a VM and then
|
||||
on an external SATA HDD plugged through a USB case, taking some notes
|
||||
and getting a general idea of the process.</p>
|
||||
<p>The Void installer does not support ZFS out of the box, so the <a
|
||||
href="https://docs.voidlinux.org/">Void Handbook</a> itself recommends
|
||||
the ZFSBootMenu documentation before <a
|
||||
href="https://docs.voidlinux.org/installation/guides/zfs.html">its
|
||||
own</a> (a manual chroot installation) when it comes to doing a
|
||||
ZFS-on-root install. This guide from ZFSBootMenu is what we’ll be
|
||||
following throughout this post.</p>
|
||||
<p>Do note that, while comprehensive, my account is no replacement for
|
||||
<a
|
||||
href="https://docs.zfsbootmenu.org/en/latest/guides/void-linux/uefi.html">the
|
||||
original guide</a>. Although more concise, it contains certain notes not
|
||||
included in this post and covers a larger set of possibilities than I
|
||||
did here. Some of the code blocks you’ll see here are identical to the
|
||||
ones from the guide, but many others are specific to how I did things,
|
||||
so keep that in mind and try things before going with your final
|
||||
installation.</p>
|
||||
<h2 id="why-void">Why Void?</h2>
|
||||
<p>I don’t really enjoy distro-hopping. I usually will spend a few years
|
||||
on the same OS and only switch for good reason and after some thorough
|
||||
testing. And after some Debian time, I felt interested in trying Void
|
||||
for a few reasons:</p>
|
||||
<ul>
|
||||
<li>rolling, but stable</li>
|
||||
<li>runit init system</li>
|
||||
<li>BSD-like rc files</li>
|
||||
<li>BSD-like handbook documentation</li>
|
||||
<li>numerous, up to date, but stable packages</li>
|
||||
</ul>
|
||||
<p>After trying it, some other features made me settle:</p>
|
||||
<ul>
|
||||
<li>fast and feature-packed package manager</li>
|
||||
<li>very fast startup time (kudos to runit)</li>
|
||||
<li>first-class support in ZFSBootMenu</li>
|
||||
</ul>
|
||||
<p>The Void package manager, <a
|
||||
href="https://docs.voidlinux.org/xbps/index.html">xbps</a>, has several
|
||||
interesting features. One of my favorites, for a taste, is
|
||||
<code>xbps-query --cat</code>, which shows the original contents of a
|
||||
given file in a package.</p>
|
||||
<p>For example,
|
||||
<code>xpbps-query --cat /etc/zfsbootmenu/config.yaml zfsbootmenu</code>
|
||||
will show you the original content of the <code>config.yaml</code> file
|
||||
in the <code>zfsbootmenu</code> package. You can use it for very core
|
||||
packages like <code>base-system</code> or <code>runit-void</code> to
|
||||
determine the original version of files shipped by them.</p>
|
||||
<h2 id="and-why-zfs">And why ZFS?</h2>
|
||||
<p>My first contact with ZFS was when using FreeBSD, which provides it
|
||||
as an option in its installer, making it a bit too easy not to try.
|
||||
Having a server on ZFS means all the data it holds can be safeguarded
|
||||
and transferred in robust ways, and mistakes are also easier to recover
|
||||
from.</p>
|
||||
<p>Aside from all the data integrity features and flexibility it brings,
|
||||
the features that interest me the most are the ones for managing
|
||||
snapshots.</p>
|
||||
<p>ZFS snapshots allow you to store the filesystem state at a given
|
||||
point in time, and to compare against, access the content of, and fully
|
||||
revert to this state. After the guide has been followed throughout, an
|
||||
extra section at the end of this post has some snapshot basics.</p>
|
||||
<h2 id="getting-in">Getting in</h2>
|
||||
<p>So, first things first, open the machine up and swap the NVMe cards.
|
||||
For me, that means getting my 128 GB NVMe stick, which I use basically
|
||||
for tests, and replace it with the 256 GB one which currently has Debian
|
||||
on it. Yes, I get by just fine with that much.</p>
|
||||
<p>While a bit dusty, the machine was overall in good state. The release
|
||||
date for the model is 2017, which for my computing standards is very
|
||||
recent.</p>
|
||||
<p>It has a single NVMe slot, one 16 GB RAM stick and one unused RAM
|
||||
slot. If you look closely, you can notice a dent on the vent tube
|
||||
connecting the cooler to the CPU. Despite this, it very rarely heats
|
||||
up.</p>
|
||||
<p><img src="../assets/img/posts/void-on-zfs/karu-inside.jpg"
|
||||
alt="The Dell laptop seen from above, lid closed, with the screen against the desk and the bottom cover removed, exposing the motherboard." /></p>
|
||||
<p>Next up is to boot up <a
|
||||
href="https://github.com/leahneukirchen/hrmpf">hrmpf</a> in EFI
|
||||
mode.</p>
|
||||
<p>hrmpf is a Void-based rescue system maintained by a Void team member
|
||||
and distributed as a bootable image that can accomplish many things,
|
||||
some of them being a full Void installation, entering a proper chroot,
|
||||
and being ZFS-ready with the needed drivers and tools.</p>
|
||||
<p>Once booted into it, EFI support can be confirmed by filtering the
|
||||
output of <code>dmesg</code>:</p>
|
||||
<div class="sourceCode" id="cb1"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="fu">dmesg</span> <span class="kw">|</span> <span class="fu">grep</span> <span class="at">-i</span> efivars</span></code></pre></div>
|
||||
<p>The output should contain “Registered efivars operations”.</p>
|
||||
<p>Make sure you have an Internet connection at this point. Most of the
|
||||
following steps will run fine without one, but closer to the end, when
|
||||
installing the Void base system, it will all go to waste if we can’t
|
||||
reach a package mirror.</p>
|
||||
<h2 id="setting-up-the-installation-environment">Setting up the
|
||||
installation environment</h2>
|
||||
<p>The ZFSBootMenu guide uses some variables in order to avoid mistakes
|
||||
and make the instructions more portable across the different storage
|
||||
types and supported operating systems.</p>
|
||||
<h3 id="etcos-release"><code>/etc/os-release</code></h3>
|
||||
<p>The <code>/etc/os-release</code> file typically contains information
|
||||
on the operating system you are running.</p>
|
||||
<p>In the hrmpf live system, these are its contents:</p>
|
||||
<div class="sourceCode" id="cb2"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="va">NAME</span><span class="op">=</span><span class="st">"Void"</span></span>
|
||||
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="va">ID</span><span class="op">=</span><span class="st">"void"</span></span>
|
||||
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="va">PRETTY_NAME</span><span class="op">=</span><span class="st">"Void Linux"</span></span>
|
||||
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="va">HOME_URL</span><span class="op">=</span><span class="st">"https://voidlinux.org/"</span></span>
|
||||
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="va">DOCUMENTATION</span><span class="op">=</span><span class="st">"https://docs.voidlinux.org/"</span></span>
|
||||
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="va">LOGO</span><span class="op">=</span><span class="st">"void-logo"</span></span>
|
||||
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="va">ANSI_COLOR</span><span class="op">=</span><span class="st">"0;38;2;71;128;97"</span></span>
|
||||
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="va">DISTRIB_ID</span><span class="op">=</span><span class="st">"void</span></span></code></pre></div>
|
||||
<p>For comparison, here is FreeBSD’s <code>os-release</code> file:</p>
|
||||
<div class="sourceCode" id="cb3"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="va">NAME</span><span class="op">=</span>FreeBSD</span>
|
||||
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="va">VERSION</span><span class="op">=</span><span class="st">"14.0-RELEASE"</span></span>
|
||||
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="va">VERSION_ID</span><span class="op">=</span><span class="st">"14.0"</span></span>
|
||||
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="va">ID</span><span class="op">=</span>freebsd</span>
|
||||
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="va">ANSI_COLOR</span><span class="op">=</span><span class="st">"0;31"</span></span>
|
||||
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="va">PRETTY_NAME</span><span class="op">=</span><span class="st">"FreeBSD 14.0-RELEASE"</span></span>
|
||||
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="va">CPE_NAME</span><span class="op">=</span><span class="st">"cpe:/o:freebsd:freebsd:14.0"</span></span>
|
||||
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="va">HOME_URL</span><span class="op">=</span><span class="st">"https://FreeBSD.org/"</span></span>
|
||||
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="va">BUG_REPORT_URL</span><span class="op">=</span><span class="st">"https://bugs.FreeBSD.org/"</span></span></code></pre></div>
|
||||
<p>In contrast, NetBSD has no such file.</p>
|
||||
<p>For the purposes of the ZFSBootMenu guide, only the <code>$ID</code>
|
||||
value appears to be used. And because the file already is structured as
|
||||
shell-compatible variable assignments, we just source it:</p>
|
||||
<div class="sourceCode" id="cb4"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="bu">source</span> /etc/os-release</span>
|
||||
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="bu">export</span> <span class="va">ID</span></span></code></pre></div>
|
||||
<h3 id="hostid"><code>hostid</code></h3>
|
||||
<p>Required by ZFS intallations, a host ID is a 32-bit hexadecimal value
|
||||
that, supposedly, will uniquely identify a machine. Considering the
|
||||
number of existing machines and the 32-bit range, you might guess why I
|
||||
say <em>supposedly</em>.</p>
|
||||
<p>If your machine has the <code>hostid</code> utilities, you can see
|
||||
the host ID by simply running <code>hostid</code>. Prior to generation,
|
||||
my hrmpf live system reports <code>00000000</code>.</p>
|
||||
<p>It can’t provide a real guarantee that it will be unique, so it’s up
|
||||
to you to take care that it is unique among <em>your</em> machines. Read
|
||||
on for why that’s hardly an issue.</p>
|
||||
<p>From the <code>gethostid(3)</code> man page:</p>
|
||||
<blockquote>
|
||||
<p>[…] a unique 32-bit identifier for the current machine. The 32-bit
|
||||
identifier was intended to be unique among all UNIX systems in
|
||||
existence. This normally resembles the Internet address for the local
|
||||
machine, as returned by <code>gethostbyname(3)</code>, and thus usually
|
||||
never needs to be set.</p>
|
||||
</blockquote>
|
||||
<p>This seems to be more or less a legacy feature. In Void’s man page
|
||||
for <code>gethostid(3)</code>, you see this in the history section:</p>
|
||||
<blockquote>
|
||||
<p>4.2BSD; dropped in 4.4BSD. SVr4 and POSIX.1-2001 include gethostid()
|
||||
but not sethostid().</p>
|
||||
</blockquote>
|
||||
<p>Still, it is something that OpenZFS requires to be set:</p>
|
||||
<blockquote>
|
||||
<p>At time of import or creation, the pool stores the system’s unique
|
||||
host ID and for the purposes of supporting multipath, import into other
|
||||
systems will fail unless forced. <br/><br/> — <a
|
||||
href="https://openzfs.readthedocs.io/en/latest/introduction.html">OpenZFS
|
||||
docs, Introduction to ZFS: Storage pools</a></p>
|
||||
</blockquote>
|
||||
<p><code>zgenhostid</code>, which is shipped by OpenZFS, according to
|
||||
its man page “emulates the <code>genhostid(1)</code> utility and is
|
||||
provided for use on systems which do not include the utility or do not
|
||||
provide the <code>sethostid(3)</code> function.”</p>
|
||||
<p>When used without arguments, these commands will generate a random
|
||||
host ID. But they can also be passed a hexadecimal value, which gets
|
||||
stored by default in <code>/etc/hostid</code> unless another path is
|
||||
given with <code>-o</code>.</p>
|
||||
<p>Considering this information, it threw me off a bit that the
|
||||
ZFSBootMenu guide tells you to specify an arbitrary host ID rather than
|
||||
generate a random one:</p>
|
||||
<div class="sourceCode" id="cb5"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zgenhostid</span> <span class="at">-f</span> 0x00bab10c</span></code></pre></div>
|
||||
<p>If they must be unique, that seems odd.</p>
|
||||
<p>The value <code>0x00bab10c</code> actually has significance in the
|
||||
context of OpenZFS as an identifier (and leetspeak) for its uberblock.
|
||||
However, it apparently is totally unrelated to host IDs.</p>
|
||||
<p>Should you be curious still, you can refer to <a
|
||||
href="https://github.com/zbm-dev/zfsbootmenu/discussions/465">this
|
||||
GitHub discussion</a> where a ZFSBootMenu user brought this exact
|
||||
question to the developers.</p>
|
||||
<p>According to the answer given above, the uniqueness of host IDs is
|
||||
useful for “multipathed SAS enclosures with two discrete head unis
|
||||
attached”, which is an enterprise-grade storage solution.</p>
|
||||
<p>The value <code>0x00bab10c</code> is indeed unrelated and chosen for
|
||||
easy identification. Any value may be used, but when using the pre-built
|
||||
ZFSBootMenu images it may make the process slightly slower (around
|
||||
250ms) as ZFSBootMenu will have to “discover the hostid every boot”.</p>
|
||||
<h3 id="disk-variables">Disk variables</h3>
|
||||
<p>Here too, the ZFSBootMenu guide <a
|
||||
href="https://docs.zfsbootmenu.org/en/latest/guides/void-linux/uefi.html#define-disk-variables">works
|
||||
with a set of variables</a> to make it easier covering different
|
||||
possible storage types:</p>
|
||||
<ul>
|
||||
<li><code>BOOT_DISK</code>, <code>BOOT_PART</code> and
|
||||
<code>BOOT_DEVICE</code></li>
|
||||
<li><code>POOL_DISK</code>, <code>POOL_PART</code> and
|
||||
<code>POOL_DEVICE</code></li>
|
||||
</ul>
|
||||
<p>My target device is an NVMe at <code>nvme0n1</code>, so I’ll
|
||||
have:</p>
|
||||
<ul>
|
||||
<li><code>BOOT_DISK="/dev/nvme0n1"</code></li>
|
||||
<li><code>BOOT_PART="1"</code></li>
|
||||
<li><code>BOOT_DEVICE="${BOOT_DISK}p${BOOT_PART}"</code>
|
||||
<ul>
|
||||
<li>which evaluates to <code>/dev/nvme0n1p1</code></li>
|
||||
</ul></li>
|
||||
<li><code>POOL_DISK="/dev/nvme0n1"</code></li>
|
||||
<li><code>POOL_PART="2"</code></li>
|
||||
<li><code>POOL_DEVICE="${POOL_DISK}p${POOL_PART}"</code>
|
||||
<ul>
|
||||
<li>which evaluates to <code>/dev/nvme0n1p2</code></li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
<p>While this may seem silly at first, it allows using the values
|
||||
separately in the next steps. It also makes the docs a lot more concise
|
||||
while covering several possible disk setups.</p>
|
||||
<h3 id="confirming-the-environment-setup">Confirming the environment
|
||||
setup</h3>
|
||||
<p>At this point, we should be able to print something like this in our
|
||||
environment:</p>
|
||||
<div class="sourceCode" id="cb6"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co"># env | grep ID</span></span>
|
||||
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="va">ID</span><span class="op">=</span>void</span>
|
||||
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co"># hostid</span></span>
|
||||
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="ex">00bab10c</span></span>
|
||||
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="co"># echo $BOOT_DEVICE</span></span>
|
||||
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="ex">/dev/nvme0n1p1</span></span>
|
||||
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="co"># echo $POOL_DEVICE</span></span>
|
||||
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="ex">/dev/nvme0n1p2</span></span></code></pre></div>
|
||||
<p>Take care to keep this same environment for all the next steps as
|
||||
they depend on it. For instance, the hrmpf live system ships tmux. While
|
||||
that is great and I have used it throughout, you must be careful to use
|
||||
a single pane for all the actual steps, and the other panes just for
|
||||
secondary things like looking up man pages or checking file
|
||||
contents.</p>
|
||||
<h2 id="filesystem-setup">Filesystem setup</h2>
|
||||
<h3 id="wiping">Wiping</h3>
|
||||
<p>The first step is to clear the current ZFS label information from the
|
||||
device:</p>
|
||||
<div class="sourceCode" id="cb7"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zpool</span> labelclear <span class="at">-f</span> <span class="st">"</span><span class="va">$POOL_DISK</span><span class="st">"</span></span></code></pre></div>
|
||||
<p>The <code>-f</code> option will “treat exported or foreign devices as
|
||||
inactive”, per the man page.</p>
|
||||
<p>This step fails consistenly for me, which I assume is because the
|
||||
previous filesystem was not ZFS to begin with.</p>
|
||||
<p>Next, we will use <code>wipefs</code> to erase the current filesystem
|
||||
signature.</p>
|
||||
<p>This command is not ZFS-specific, but part of the kernel utilities.
|
||||
It does not erase the filesystems themselves, nor their content, but the
|
||||
signatures that aid in their detection.</p>
|
||||
<p>Without any options, it will list all the filesystems that are still
|
||||
visible:</p>
|
||||
<div class="sourceCode" id="cb8"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="co"># wipefs "$BOOT_DISK"</span></span>
|
||||
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="ex">DEVICE</span> OFFSET TYPE UUID LABEL</span>
|
||||
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="ex">nvme0n1</span> 0x200 gpt</span>
|
||||
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="ex">nvme0n1</span> 0x3d9e655e00 gpt</span>
|
||||
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="ex">nvme0n1</span> 0x1fe PMBR</span></code></pre></div>
|
||||
<p>The <code>-a</code> option is for erasing all signatures. This means
|
||||
it will “scan the device again after each modification until no magic
|
||||
string [signature] is found”, as per its man page.</p>
|
||||
<p>In my case:</p>
|
||||
<div class="sourceCode" id="cb9"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="ex">wipefs</span> <span class="at">-a</span> <span class="st">"</span><span class="va">$POOL_DISK</span><span class="st">"</span></span>
|
||||
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="ex">wipefs</span> <span class="at">-a</span> <span class="st">"</span><span class="va">$BOOT_DISK</span><span class="st">"</span></span></code></pre></div>
|
||||
<p>Along the guide, commands are sometimes repeated for both
|
||||
<code>$POOL_DISK</code> and <code>$BOOT_DISK</code>. If you are using
|
||||
the same disk for both, this may be redundant, although also
|
||||
harmless.</p>
|
||||
<p>This is my case, so I am not typically running it twice. I’ll still
|
||||
leave it as is however, so as not to mislead the reader.</p>
|
||||
<p>Now, when listing the signatures again with
|
||||
<code>wipefs "$BOOT_DISK"</code>, there should be no output.</p>
|
||||
<p>Finally, the current MBR and GPT tables must be destroyed. For this,
|
||||
the ZFSBootMenu guide uses <code>sgdisk</code>. This is also not
|
||||
ZFS-specific.</p>
|
||||
<div class="sourceCode" id="cb10"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="ex">sgdisk</span> <span class="at">--zap-all</span> <span class="st">"</span><span class="va">$POOL_DISK</span><span class="st">"</span></span>
|
||||
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="ex">sgdisk</span> <span class="at">--zap-all</span> <span class="st">"</span><span class="va">$BOOT_DISK</span><span class="st">"</span></span></code></pre></div>
|
||||
<p>The <code>--zap-all</code> option contrasts with <code>--zap</code>
|
||||
in that it will destroy both MBR and GPT partition tables.</p>
|
||||
<h3 id="partitioning">Partitioning</h3>
|
||||
<p>In the ZFSBootMenu guide, <code>sgdisk</code> is used again for
|
||||
creating the partitions:</p>
|
||||
<div class="sourceCode" id="cb11"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="ex">sgdisk</span> <span class="dt">\</span></span>
|
||||
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a> <span class="at">-n</span> <span class="st">"</span><span class="va">${BOOT_PART}</span><span class="st">:1m:+512m"</span> <span class="dt">\</span></span>
|
||||
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a> <span class="at">-t</span> <span class="st">"</span><span class="va">${BOOT_PART}</span><span class="st">:ef00"</span> <span class="st">"</span><span class="va">$BOOT_DISK</span><span class="st">"</span></span>
|
||||
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a><span class="ex">sgdisk</span> <span class="dt">\</span></span>
|
||||
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a> <span class="at">-n</span> <span class="st">"</span><span class="va">${POOL_PART}</span><span class="st">:0:-10m"</span> <span class="dt">\</span></span>
|
||||
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a> <span class="at">-t</span> <span class="st">"</span><span class="va">${POOL_PART}</span><span class="st">:bf00"</span> <span class="st">"</span><span class="va">$POOL_DISK</span><span class="st">"</span></span></code></pre></div>
|
||||
<p>In the commands above, option <code>-n</code> is short for
|
||||
<code>--new</code>, and is specifying the start and end sectors by using
|
||||
relative kibibyte measures. The format is
|
||||
<code>--new partnum:start:end</code>.</p>
|
||||
<p>Breaking it down:</p>
|
||||
<ul>
|
||||
<li><code>1m</code> 1 mebibyte from the start of the disk</li>
|
||||
<li><code>+512m</code> 512 mebibytes after the default start sector</li>
|
||||
<li><code>-10m</code> 10 mebibytes before the last available sector</li>
|
||||
<li><code>0</code> the default value</li>
|
||||
</ul>
|
||||
<p>In the list above, “default” is “the start of the largest available
|
||||
block for the start sector and the end of the same block for the end
|
||||
sector”, as per the <code>sgdisk</code> man page.</p>
|
||||
<p><code>1:1m:+512m</code>, therefore, means that partition 1 will start
|
||||
1 mebibyte from the start of the disk and end 512 mebibytes after the
|
||||
start of the largest available block.</p>
|
||||
<p><code>2:0:-10m</code>, in turn, means partition 2 will begin at the
|
||||
start of the largest available block and end 10 mebibytes before the
|
||||
last available sector.</p>
|
||||
<p>Option <code>-t</code> is for setting the typecode for each
|
||||
partition. Typecode <code>ef00</code> is for the EFI system partition,
|
||||
and typecode <code>bf00</code> is for “Solaris root”, the Unix system
|
||||
upon whose ZFS implementation OpenZFS was based.</p>
|
||||
<p>For a list of typecodes, see <code>sgdisk -L</code>.</p>
|
||||
<p>While just running these commands as-is is your safest option, you
|
||||
might have a different layout in mind or prefer an interactive UI.</p>
|
||||
<p>For one thing, I’ve had issues in the past with the boot partition
|
||||
being too small, so I’ll be using <code>2g</code> instead of
|
||||
<code>512m</code> for it.</p>
|
||||
<p><code>sgdisk</code> has a friendlier counterpart named
|
||||
<code>gdisk</code>, which you can use just by passing it the disk path,
|
||||
as in <code>gdisk /dev/sda</code>.</p>
|
||||
<p>At this point, you should be safe to try partitioning and going back
|
||||
to wiping as needed until you are satisfied.</p>
|
||||
<p>When you are done, you can use <code>lsblk</code> to confirm the
|
||||
results. The following will show you the options just configured:</p>
|
||||
<div class="sourceCode" id="cb12"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ex">lsblk</span> <span class="at">-o</span> NAME,SIZE,TYPE,PARTTYPENAME</span></code></pre></div>
|
||||
<h3 id="creating-the-pool">Creating the pool</h3>
|
||||
<p>This part of the guide was the one that actually made me want to
|
||||
delve deeper and understand what each option meant.</p>
|
||||
<p>With little knowledge about ZFS still, I wanted to understand
|
||||
precisely what was happening here, but also what a pool even is and what
|
||||
its creation meant.</p>
|
||||
<p>Here’s the <code>zpool(8)</code> man page:</p>
|
||||
<blockquote>
|
||||
<p>A storage pool is a collection of devices that provides physical
|
||||
storage and data replication for ZFS datasets. All datasets within a
|
||||
storage pool share the same space.</p>
|
||||
</blockquote>
|
||||
<p>The definition of a dataset is then indicated to be at
|
||||
<code>zfs(8)</code>:</p>
|
||||
<blockquote>
|
||||
<p>A dataset is identified by a unique path within the ZFS namespace:
|
||||
<br/> <code>pool[/component]/component</code> for example:
|
||||
<code>rpool/var/log</code></p>
|
||||
</blockquote>
|
||||
<p>Here, it’s also explained that a dataset can be a file system,
|
||||
logical volume, snapshot or bookmark.</p>
|
||||
<p>Further information is also hinted to be found at
|
||||
<code>zpoolconcepts(7)</code>.</p>
|
||||
<p>At this point you start to notice the breadth of knowledge available
|
||||
in the documentation. The man pages are not only comprehensible, but
|
||||
sometimes contain several examples on how to apply their concepts. Each
|
||||
command has their own man page named with a hyphen for separation, as in
|
||||
<code>zpool-create</code>.</p>
|
||||
<p>We’ll be exploring only the <code>zpool-create(8)</code> command in
|
||||
depth, in particular the options used in the ZFSBootMenu guide:</p>
|
||||
<ul>
|
||||
<li><code>-f</code> force the use of virtual devices, even if they
|
||||
appear in use</li>
|
||||
<li><code>-o feature=value</code> set a pool feature</li>
|
||||
<li><code>-O property=value</code> set a file system property in the
|
||||
root file system of the pool</li>
|
||||
<li><code>-o compatibility=off|legacy|file[,file]</code> specify a
|
||||
compatibility feature set</li>
|
||||
<li><code>-m mountpoint</code> the mountpoint (default:
|
||||
<code>/pool</code>)</li>
|
||||
<li><code>pool</code> the pool</li>
|
||||
<li><code>vdev</code> the virtual device</li>
|
||||
</ul>
|
||||
<p>The listing with pool features (including compatibility feature sets)
|
||||
is at <code>zpool-features(7)</code>. Pool properties are at
|
||||
<code>zpoolprops(7)</code> and file system properties at
|
||||
<code>zfsprops(7)</code>.</p>
|
||||
<p>In the guide, these are the options given:</p>
|
||||
<div class="sourceCode" id="cb13"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zpool</span> create <span class="at">-f</span> <span class="dt">\</span></span>
|
||||
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a> <span class="at">-o</span> ashift=12 <span class="dt">\</span></span>
|
||||
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a> <span class="at">-O</span> compression=lz4 <span class="dt">\</span></span>
|
||||
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a> <span class="at">-O</span> acltype=posixacl <span class="dt">\</span></span>
|
||||
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a> <span class="at">-O</span> xattr=sa <span class="dt">\</span></span>
|
||||
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a> <span class="at">-O</span> relatime=on <span class="dt">\</span></span>
|
||||
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a> <span class="at">-o</span> autotrim=on <span class="dt">\</span></span>
|
||||
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a> <span class="at">-o</span> compatibility=openzfs-2.1-linux <span class="dt">\</span></span>
|
||||
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a> <span class="at">-m</span> none zroot <span class="st">"</span><span class="va">$POOL_DEVICE</span><span class="st">"</span></span></code></pre></div>
|
||||
<p>Among the options above, several pool features and system properties
|
||||
are set:</p>
|
||||
<ul>
|
||||
<li><code>-o ashift=12</code> “Alignment shift”, used to calculate
|
||||
physical sector sizes.This is discussed at greater length in the <a
|
||||
href="https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Workload%20Tuning.html#alignment-shift-ashift">online
|
||||
documentation on Workload Tuning</a></li>
|
||||
<li><code>-O compression=lz4</code> Sets the compression algorithm used
|
||||
(<a
|
||||
href="https://en.wikipedia.org/wiki/LZ4_(compression_algorithm)">LZ4</a>)</li>
|
||||
<li><code>-O acltype=posixacl</code> Whether <a
|
||||
href="https://en.wikipedia.org/wiki/Access-control_list">ACLs</a> are
|
||||
enabled and what type to use. The value <code>posixacl</code> is
|
||||
equivalent to <code>posix</code> (default on Linux: off)</li>
|
||||
<li><code>-O xattr=sa</code> Enables extended attributes. If value is
|
||||
<code>on</code>, uses directory-based extended attributes, while
|
||||
<code>sa</code> uses system-attribute-based. The latter has performance
|
||||
benefits, and is important for ACLs and SELinux usage</li>
|
||||
<li><code>-O relatime=on</code> “Causes the access time to be updated
|
||||
relative to the modify or change time.” Also, “access time is only
|
||||
updated if the previous access time was earlier than the current modify
|
||||
or change time or if the existing access time hasn’t been updated within
|
||||
the past 24hours”</li>
|
||||
<li><code>-o autotrim=on</code> Automatically reclaims unused blocks
|
||||
from time to time. Can put the filesystem under some stress</li>
|
||||
</ul>
|
||||
<p>The last option, the compatibility feature set, specifies in this
|
||||
case a relative filename to
|
||||
<code>/usr/share/zfs/compatibility.d</code>:</p>
|
||||
<ul>
|
||||
<li><code>-o compatibility=openzfs-2.1-linux</code></li>
|
||||
</ul>
|
||||
<p><code>zpool-create(8)</code> also states:</p>
|
||||
<blockquote>
|
||||
<p>By default, all supported features are enabled on the new pool. The
|
||||
<code>-d</code> option and the <code>-o</code> compatibility property
|
||||
[…] can be used to restrict the features that are enabled, so that the
|
||||
pool can be imported on other releases of ZFS.</p>
|
||||
</blockquote>
|
||||
<p>The compatibility option <code>openzfs-2.1-linux</code> is described
|
||||
as a “conservative” choice in the ZFSBootMenu guide and in my tests had
|
||||
little impact, so I decided to not use it for this installation.</p>
|
||||
<h3 id="creating-the-filesystems">Creating the filesystems</h3>
|
||||
<p>Once the pool is ready, the filesystems can be created.</p>
|
||||
<p>For this task, the <code>zfs</code> command is used with
|
||||
<code>create</code>:</p>
|
||||
<div class="sourceCode" id="cb14"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> create <span class="at">-o</span> mountpoint=none zroot/ROOT</span>
|
||||
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> create <span class="at">-o</span> mountpoint=/ <span class="at">-o</span> canmount=noauto zroot/ROOT/<span class="va">${ID}</span></span>
|
||||
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> create <span class="at">-o</span> mountpoint=/home zroot/home</span></code></pre></div>
|
||||
<p>The ZFSBootMenu guide explains at this point that if
|
||||
<code>canmount=noauto</code> is not set on file systems with the
|
||||
<code>/</code> mountpoint, the OS will try to mount them all and fail.
|
||||
It goes on to say:</p>
|
||||
<blockquote>
|
||||
<p>Automatic mounting of / is not required because the root file system
|
||||
is explicitly mounted in the boot process.</p>
|
||||
</blockquote>
|
||||
<p>After the filesystems have been created, the boot file system must be
|
||||
set.</p>
|
||||
<div class="sourceCode" id="cb15"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zpool</span> set bootfs=zroot/ROOT/<span class="va">${ID}</span> zroot</span></code></pre></div>
|
||||
<p>Essentially, this is saying “set <code>zroot</code>’s
|
||||
<code>bootfs</code> property to <code>zroot/ROOT/void</code>”</p>
|
||||
<h3 id="export-reimport-and-mount">Export, reimport and mount</h3>
|
||||
<p>The next steps consist in exporting and then importing the pool with
|
||||
a given mountpoint.</p>
|
||||
<div class="sourceCode" id="cb16"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zpool</span> export zroot</span>
|
||||
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a><span class="ex">zpool</span> import <span class="at">-N</span> <span class="at">-R</span> /mnt zroot</span></code></pre></div>
|
||||
<p>From what I gather, exporting means putting the pool in a more
|
||||
portable state. According to the <code>zpool-export(8)</code> man page,
|
||||
“the devices [marked as exported] can be moved between systems […] and
|
||||
imported as long as a sufficient number of devices are present.”</p>
|
||||
<p>If <code>zfs import</code> is used without any arguments, it will
|
||||
list the exported pools available to be imported.</p>
|
||||
<p>The <code>-N root</code> option imports the pool without mounting any
|
||||
of its file systems, the <code>-R</code> option “sets the
|
||||
<code>cachefile</code> property to <code>none</code> and the
|
||||
<code>altroot</code> property to <code>root</code>”. In this case, that
|
||||
<code>root</code> will be <code>/mnt</code>.</p>
|
||||
<p><code>altroot</code> stands for the alternate root directory. In
|
||||
<code>zpoolprops(7)</code>, this becomes clearer when it is stated that
|
||||
“this directory is prepended to any mount points within the pool.”</p>
|
||||
<p>Once re-imported, we can mount:</p>
|
||||
<div class="sourceCode" id="cb17"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> mount zroot/ROOT/<span class="va">${ID}</span></span>
|
||||
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> mount zroot/home</span></code></pre></div>
|
||||
<p>And verify that all is mounted correctly with
|
||||
<code>mount | grep mnt</code>:</p>
|
||||
<div class="sourceCode" id="cb18"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="co"># mount | grep mnt</span></span>
|
||||
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a><span class="ex">zroot/ROOT/void</span> on /mnt type zfs <span class="er">(</span><span class="ex">rw,relatime,xattr,posixacl,casesensitive</span><span class="kw">)</span></span>
|
||||
<span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a><span class="ex">zroot/home</span> on /mnt/home type zfs <span class="er">(</span><span class="ex">rw,relatime,xattr,posixacl,casesensitive</span><span class="kw">)</span></span></code></pre></div>
|
||||
<p>Lastly, we request the device events from the kernel to update the
|
||||
device symlinks:</p>
|
||||
<div class="sourceCode" id="cb19"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="ex">udevadm</span> trigger</span></code></pre></div>
|
||||
<h2 id="setting-up-void">Setting up Void</h2>
|
||||
<h3 id="installation">Installation</h3>
|
||||
<p>So far, not much here was Void-specific. This is when we start
|
||||
bootstrapping the void system into the filesystem we laid out.</p>
|
||||
<div class="sourceCode" id="cb20"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="va">XBPS_ARCH</span><span class="op">=</span>x86_64 <span class="ex">xbps-install</span> <span class="dt">\</span></span>
|
||||
<span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a> <span class="at">-S</span> <span class="at">-R</span> https://repo-fastly.voidlinux.org/current <span class="dt">\</span></span>
|
||||
<span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a> <span class="at">-r</span> /mnt base-system</span></code></pre></div>
|
||||
<p>Here, we are passing an environment variable to set the architecture
|
||||
to <code>x86_64</code>, then use <code>xbps-install</code> from the xbps
|
||||
package manager to fetch the Void base system.</p>
|
||||
<p><code>-S</code> takes care of synchronizing the data from the mirror
|
||||
so that package data is fetched, <code>-R</code> allows us to manually
|
||||
specify the repository for this run, and <code>-r</code> allows choosing
|
||||
a different root directory to act upon.</p>
|
||||
<p>Here, I chose the Fastly mirror over the ServerCentral one. <a
|
||||
href="https://xmirror.voidlinux.org/">Any working mirror</a> should
|
||||
do.</p>
|
||||
<p>Note that not all mirrors have the same content at the root of their
|
||||
URL. Some point directly to a Void repo, some don’t. You can access the
|
||||
mirror in a browser or otherwise inspect it to find the path to the
|
||||
<code>current</code> directory.</p>
|
||||
<p>With this done, we can copy the host ID file, which will also be
|
||||
required in our final system, and we are ready to chroot.</p>
|
||||
<div class="sourceCode" id="cb21"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true" tabindex="-1"></a><span class="fu">cp</span> /etc/hostid /mnt/etc</span></code></pre></div>
|
||||
<h3 id="chrooting">chrooting</h3>
|
||||
<p>We will chroot into the system mounted at the <code>/mnt</code>
|
||||
directory using <code>xchroot</code>, which is part of the xbps
|
||||
<code>xtools</code> package and should already be available on hrmpf. It
|
||||
provides a <a
|
||||
href="https://docs.voidlinux.org/config/containers-and-vms/chroot.html#chroot-usage">more
|
||||
sane</a> chroot than the plain one, in particular regarding the required
|
||||
mountpoints:</p>
|
||||
<div class="sourceCode" id="cb22"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true" tabindex="-1"></a><span class="ex">xchroot</span> /mnt</span></code></pre></div>
|
||||
<p>This is a good time to get back to the notes I mentioned taking the
|
||||
day before.</p>
|
||||
<p><img src="../assets/img/posts/void-on-zfs/notes.jpg"
|
||||
alt="A block of paper with some notes scribbled: “check connection first of all”, “reconfigure after chroot”, “see /usr/share/doc/efibootmgr/README.voidlinux for automatic EFI entry management”, “superb docs”, “take the first snapshot ASAP”. An arrow points from the last note to “on chroot?” Visible above the block of paper is a keyboard. To the right, the tip of a notebook and a piece of brown cloth are visible. On top of the block, there is a mechanical pencil and a Tombow MONO One plastic eraser." /></p>
|
||||
<h4 id="reconfiguring-packages">Reconfiguring packages</h4>
|
||||
<p>After chrooting, it may be a good idea to run
|
||||
<code>xbps-reconfigure</code> to make sure packages are properly
|
||||
configured. This is because in the bootstrap process some packets may
|
||||
have tried to configure themselves while relying on directories that
|
||||
were not mounted anywhere.</p>
|
||||
<p>This is particularly true for <a
|
||||
href="https://github.com/OSInside/kiwi/issues/1867"><code>dracut</code></a>,
|
||||
which is a tool that generates initramfs and initrd images, therefore
|
||||
being critical to the early boot process. You might see error messages
|
||||
related to it in your first run of xbps outside of the chroot, when
|
||||
installing the base system.</p>
|
||||
<p>To reconfigure <strong>all</strong> packages, just run
|
||||
<code>xbps-reconfigure -fa</code>. If you’d rather only reconfigure
|
||||
<code>dracut</code>, go with
|
||||
<code>xpbs-reconfigure -f dracut</code>.</p>
|
||||
<h4 id="root-password">root password</h4>
|
||||
<p>As early as possible is a good time to run <code>passwd</code> and
|
||||
set the root password.</p>
|
||||
<h4 id="rc.conf"><code>rc.conf</code></h4>
|
||||
<p><code>runit</code> reads the <code>/etc/rc.conf</code> file during
|
||||
startup to configure the system, setting up things like the keymap,
|
||||
hardware clock and terminal font.</p>
|
||||
<p>For your reference, here is what I added to mine during the
|
||||
installation:</p>
|
||||
<div class="sourceCode" id="cb23"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb23-1"><a href="#cb23-1" aria-hidden="true" tabindex="-1"></a><span class="va">HARDWARECLOCK</span><span class="op">=</span><span class="st">"UTC"</span></span>
|
||||
<span id="cb23-2"><a href="#cb23-2" aria-hidden="true" tabindex="-1"></a><span class="va">KEYMAP</span><span class="op">=</span><span class="st">"br-abnt2"</span></span>
|
||||
<span id="cb23-3"><a href="#cb23-3" aria-hidden="true" tabindex="-1"></a><span class="va">FONT</span><span class="op">=</span><span class="st">"ter-120n"</span></span></code></pre></div>
|
||||
<h4 id="time-zone-and-locale">Time zone and locale</h4>
|
||||
<p>To configure your local time zone, create a symlink at
|
||||
<code>/etc/localtime</code> that points to the corresponding time zone
|
||||
in the <code>/usr/share/zoneinfo</code> directory.</p>
|
||||
<div class="sourceCode" id="cb24"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb24-1"><a href="#cb24-1" aria-hidden="true" tabindex="-1"></a><span class="fu">ln</span> <span class="at">-sf</span> /usr/share/zoneinfo/<span class="op"><</span>timezone<span class="op">></span> /etc/localtime</span></code></pre></div>
|
||||
<p>Unless you are using <code>musl</code>, you also want to set and
|
||||
generate the <code>glibc</code> locales. Edit
|
||||
<code>/etc/default/libc-locales</code> and uncomment the desired
|
||||
locales, then run <code>xbps-reconfigure -f glibc-locales</code>.</p>
|
||||
<h4 id="dracut">dracut</h4>
|
||||
<p><code>dracut</code> generates file system images used by the kernel
|
||||
at the very early stages of boot. We have to make it able to identify
|
||||
our ZFS root filesystem by enabling the proper modules. This is
|
||||
accomplished by creating <code>/etc/dracut.conf.d/zol.conf</code>
|
||||
with:</p>
|
||||
<div class="sourceCode" id="cb25"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb25-1"><a href="#cb25-1" aria-hidden="true" tabindex="-1"></a><span class="va">nofsck</span><span class="op">=</span><span class="st">"yes"</span></span>
|
||||
<span id="cb25-2"><a href="#cb25-2" aria-hidden="true" tabindex="-1"></a><span class="va">add_dracutmodules</span><span class="op">+=</span><span class="st">" zfs "</span></span>
|
||||
<span id="cb25-3"><a href="#cb25-3" aria-hidden="true" tabindex="-1"></a><span class="va">omit_dracutmodules</span><span class="op">+=</span><span class="st">" btrfs "</span></span></code></pre></div>
|
||||
<p>Notice the spaces surrounding the module names.</p>
|
||||
<h2 id="installing-and-configuring-zfsbootmenu">Installing and
|
||||
configuring ZFSBootMenu</h2>
|
||||
<p>We are now ready to install both ZFS and ZFSBootMenu. Let’s start
|
||||
with ZFS:</p>
|
||||
<div class="sourceCode" id="cb26"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb26-1"><a href="#cb26-1" aria-hidden="true" tabindex="-1"></a><span class="ex">xbps-install</span> <span class="at">-R</span> https://repo-fastly.voidlinux.org/current zfs</span></code></pre></div>
|
||||
<p>Now, before installing ZFSBootMenu, we set the kernel commandline.
|
||||
This is the command line that will be used by the Linux kernel, so any
|
||||
options you are used to go here.</p>
|
||||
<p>The ZFSBootMenu guide has only the <code>quiet</code> option. In my
|
||||
case, I added <code>net.ifnames=0</code> to have the classic
|
||||
<code>eth0</code>, <code>wlan0</code> network interface names, and
|
||||
<code>fbcon=nodefer video=efifb:nobgrt</code>, which prevents the
|
||||
manufacturer’s logo from showing after boot and sometimes obscuring the
|
||||
boot process output.</p>
|
||||
<div class="sourceCode" id="cb27"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb27-1"><a href="#cb27-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> set org.zfsbootmenu:commandline=<span class="st">"quiet net.ifnames=0 fbcon=nodefer video=efifb:nobgrt"</span> zroot/ROOT</span></code></pre></div>
|
||||
<p>We also need a <code>vfat</code> filesystem on our boot device:</p>
|
||||
<div class="sourceCode" id="cb28"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb28-1"><a href="#cb28-1" aria-hidden="true" tabindex="-1"></a><span class="ex">mkfs.vfat</span> <span class="at">-F32</span> <span class="st">"</span><span class="va">$BOOT_DEVICE</span><span class="st">"</span></span></code></pre></div>
|
||||
<p>Now we can add an <code>/etc/fstab</code> entry pointing to it, and
|
||||
mount:</p>
|
||||
<div class="sourceCode" id="cb29"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb29-1"><a href="#cb29-1" aria-hidden="true" tabindex="-1"></a><span class="bu">echo</span> <span class="st">"</span><span class="va">$(</span><span class="ex">blkid</span> <span class="kw">|</span> <span class="fu">grep</span> <span class="st">"</span><span class="va">$BOOT_DEVICE</span><span class="st">"</span> <span class="kw">|</span> <span class="fu">cut</span> <span class="at">-d</span> <span class="st">' '</span> <span class="at">-f</span> 2<span class="va">)</span><span class="st"> /boot/efi vfat defaults 0 0"</span> <span class="op">>></span> /etc/fstab</span>
|
||||
<span id="cb29-2"><a href="#cb29-2" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb29-3"><a href="#cb29-3" aria-hidden="true" tabindex="-1"></a><span class="fu">mkdir</span> <span class="at">-p</span> /boot/efi</span>
|
||||
<span id="cb29-4"><a href="#cb29-4" aria-hidden="true" tabindex="-1"></a><span class="fu">mount</span> /boot/efi</span></code></pre></div>
|
||||
<p>Into this directory we just mounted, we can now install
|
||||
ZFSBootMenu.</p>
|
||||
<p>The guide provides two different paths here: a prebuilt image or the
|
||||
Void package, which you can get through xbps.</p>
|
||||
<p>While there are advantages to both, I decided to go with the prebuilt
|
||||
image since I’d rather the package manager not touch the boot manager on
|
||||
updating. This has the downside of you having to take care of being
|
||||
aware of any relevant versions and when to upgrade to them.</p>
|
||||
<div class="sourceCode" id="cb30"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb30-1"><a href="#cb30-1" aria-hidden="true" tabindex="-1"></a><span class="ex">xbps-install</span> curl</span>
|
||||
<span id="cb30-2"><a href="#cb30-2" aria-hidden="true" tabindex="-1"></a><span class="fu">mkdir</span> <span class="at">-p</span> /boot/efi/EFI/ZBM</span>
|
||||
<span id="cb30-3"><a href="#cb30-3" aria-hidden="true" tabindex="-1"></a><span class="ex">curl</span> <span class="at">-o</span> /boot/efi/EFI/ZBM/VMLINUZ.EFI <span class="at">-L</span> https://get.zfsbootmenu.org/efi</span>
|
||||
<span id="cb30-4"><a href="#cb30-4" aria-hidden="true" tabindex="-1"></a><span class="fu">cp</span> /boot/efi/EFI/ZBM/VMLINUZ.EFI /boot/efi/EFI/ZBM/VMLINUZ-BACKUP.EFI</span></code></pre></div>
|
||||
<p>If you’d rather use the repository package, see the <a
|
||||
href="https://docs.zfsbootmenu.org/en/latest/guides/void-linux/uefi.html#install-zfsbootmenu">corresponding
|
||||
instructions in the guide</a>.</p>
|
||||
<p>Finally, a second choice has to be made between <code>rEFind</code>
|
||||
or plain <code>efibootmgr</code> for managing the boot entries. I prefer
|
||||
to go with the simpler one, but you may find <code>rEFind</code> more
|
||||
feature-packed.</p>
|
||||
<p>First, install <code>efibootmgr</code> using
|
||||
<code>xbps-install efibootmgr</code>, then run the following
|
||||
commands:</p>
|
||||
<div class="sourceCode" id="cb31"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb31-1"><a href="#cb31-1" aria-hidden="true" tabindex="-1"></a><span class="ex">efibootmgr</span> <span class="at">-c</span> <span class="at">-d</span> <span class="st">"</span><span class="va">$BOOT_DISK</span><span class="st">"</span> <span class="at">-p</span> <span class="st">"</span><span class="va">$BOOT_PART</span><span class="st">"</span> <span class="dt">\</span></span>
|
||||
<span id="cb31-2"><a href="#cb31-2" aria-hidden="true" tabindex="-1"></a> <span class="at">-L</span> <span class="st">"ZFSBootMenu (Backup)"</span> <span class="dt">\</span></span>
|
||||
<span id="cb31-3"><a href="#cb31-3" aria-hidden="true" tabindex="-1"></a> <span class="at">-l</span> <span class="st">'\EFI\ZBM\VMLINUZ-BACKUP.EFI'</span></span>
|
||||
<span id="cb31-4"><a href="#cb31-4" aria-hidden="true" tabindex="-1"></a></span>
|
||||
<span id="cb31-5"><a href="#cb31-5" aria-hidden="true" tabindex="-1"></a><span class="ex">efibootmgr</span> <span class="at">-c</span> <span class="at">-d</span> <span class="st">"</span><span class="va">$BOOT_DISK</span><span class="st">"</span> <span class="at">-p</span> <span class="st">"</span><span class="va">$BOOT_PART</span><span class="st">"</span> <span class="dt">\</span></span>
|
||||
<span id="cb31-6"><a href="#cb31-6" aria-hidden="true" tabindex="-1"></a> <span class="at">-L</span> <span class="st">"ZFSBootMenu"</span> <span class="dt">\</span></span>
|
||||
<span id="cb31-7"><a href="#cb31-7" aria-hidden="true" tabindex="-1"></a> <span class="at">-l</span> <span class="st">'\EFI\ZBM\VMLINUZ.EFI'</span></span></code></pre></div>
|
||||
<p>If you’d prefer to use rEFInd, see the <a
|
||||
href="https://docs.zfsbootmenu.org/en/latest/guides/void-linux/uefi.html#configure-efi-boot-entries">guide’s
|
||||
relevant section</a>.</p>
|
||||
<p><code>zbm-kcl</code> is mentioned here in passing. This utility
|
||||
allows you, among other things, to set ZFSBootMenu options, such as the
|
||||
delay before automatically booting. I am not sure if it comes included
|
||||
with the ZFSBootMenu package, as I went for the pre-built image, but you
|
||||
can nonetheless get it from GitHub:</p>
|
||||
<div class="sourceCode" id="cb32"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb32-1"><a href="#cb32-1" aria-hidden="true" tabindex="-1"></a><span class="ex">curl</span> <span class="at">-O</span> https://raw.githubusercontent.com/zbm-dev/zfsbootmenu/master/bin/zbm-kcl</span>
|
||||
<span id="cb32-2"><a href="#cb32-2" aria-hidden="true" tabindex="-1"></a><span class="fu">chmod</span> +x zbm-kcl</span></code></pre></div>
|
||||
<p>Now, if you want to change an option, you can use its <code>-a</code>
|
||||
option to append an argument to the target image’s command line:</p>
|
||||
<div class="sourceCode" id="cb33"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb33-1"><a href="#cb33-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zbm-kcl</span> <span class="at">-a</span> <span class="st">'zbm.timeout=2'</span> /boot/efi/EFI/ZBM/VMLINUZ.EFI</span></code></pre></div>
|
||||
<p>In the example above, the timeout before automatically booting is set
|
||||
from its 10 seconds default to 2 seconds.</p>
|
||||
<h2 id="getting-out">Getting out</h2>
|
||||
<p>We are all done. It’s time to exit the chroot, unmount and export the
|
||||
pool.</p>
|
||||
<div class="sourceCode" id="cb34"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb34-1"><a href="#cb34-1" aria-hidden="true" tabindex="-1"></a><span class="bu">exit</span></span>
|
||||
<span id="cb34-2"><a href="#cb34-2" aria-hidden="true" tabindex="-1"></a><span class="fu">umount</span> <span class="at">-n</span> <span class="at">-R</span> /mnt</span>
|
||||
<span id="cb34-3"><a href="#cb34-3" aria-hidden="true" tabindex="-1"></a><span class="ex">zpool</span> export zroot</span></code></pre></div>
|
||||
<p>If all above went well, we can now run <code>reboot</code>, remove
|
||||
the flash drive used for installation, and log in for the first time
|
||||
into our new system.</p>
|
||||
<h2 id="zfs-snapshot-basics">ZFS snapshot basics</h2>
|
||||
<p>Something you might want to do at this point is to take a snapshot of
|
||||
the current state, since it can serve as a baseline before any further
|
||||
tweaking, allowing you to go back or access the files in this state as
|
||||
you make important changes that could potentially break the system.</p>
|
||||
<div class="sourceCode" id="cb35"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb35-1"><a href="#cb35-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> snapshot <span class="at">-r</span> zroot/ROOT/void@baseline</span></code></pre></div>
|
||||
<p>Note that, if you followed the ZFSBootMenu guide in creating a
|
||||
separate dataset for your home directory, this snapshot will not include
|
||||
the contents inside and under <code>/home</code> (which at this point
|
||||
should be empty anyways).</p>
|
||||
<p>You can access the contents of a snapshot at any time in the
|
||||
<code>.zfs</code> directory at the root of a given dataset. For the ones
|
||||
we previously set up, those would be <code>/.zfs</code> and
|
||||
<code>/home/.zfs</code>. Note that these directories are not only hidden
|
||||
in the traditional way, but they won’t show up even if you use
|
||||
<code>ls -a</code>. You need to actually <code>cd</code> into the
|
||||
apparently absent directory for it to work.</p>
|
||||
<p>ZFS snapshots start taking virtually no space at all, but grow with
|
||||
time as the snapshot drifts from the present system state. For that
|
||||
reason, keeping a snapshot of the very first moment of your system can
|
||||
take up significant space. Depending on your storage resources, you
|
||||
might eventually decide to destroy this snapshot:</p>
|
||||
<div class="sourceCode" id="cb36"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb36-1"><a href="#cb36-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> destroy <span class="at">-r</span> zroot/ROOT/void@baseline</span></code></pre></div>
|
||||
<p>You may also want to list your current snapshots. While typically you
|
||||
can use <code>zfs list -t snap</code>, I tend to use the following
|
||||
command in order to get more relevant output:</p>
|
||||
<div class="sourceCode" id="cb37"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb37-1"><a href="#cb37-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> list <span class="at">-t</span> snap <span class="at">-o</span> creation,name,used,written,referenced,refcompressratio <span class="at">-S</span> creation</span></code></pre></div>
|
||||
<p>Finally, you might want to rename a snapshot:</p>
|
||||
<div class="sourceCode" id="cb38"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb38-1"><a href="#cb38-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> rename <span class="at">-r</span> zroot/ROOT/void@baseline @day0</span></code></pre></div>
|
||||
<p>Combined, these commands can get you as far as an automatic, rolling
|
||||
snapshot system. Say, for instance you add the following to
|
||||
<code>rc.local</code>:</p>
|
||||
<div class="sourceCode" id="cb39"><pre
|
||||
class="sourceCode sh"><code class="sourceCode bash"><span id="cb39-1"><a href="#cb39-1" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> destroy <span class="at">-r</span> zroot/ROOT/void@fallbackBoot</span>
|
||||
<span id="cb39-2"><a href="#cb39-2" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> rename <span class="at">-r</span> zroot/ROOT/void@previousBoot @fallbackBoot</span>
|
||||
<span id="cb39-3"><a href="#cb39-3" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> rename <span class="at">-r</span> zroot/ROOT/void@currentBoot @previousBoot</span>
|
||||
<span id="cb39-4"><a href="#cb39-4" aria-hidden="true" tabindex="-1"></a><span class="ex">zfs</span> snapshot <span class="at">-r</span> zroot/ROOT/void@currentBoot</span></code></pre></div>
|
||||
<p>This would give you a per-boot snapshot trail to rely on.</p>
|
||||
<p>The <code>zfs-snapshot(8)</code> man page provides a similar example
|
||||
for daily snapshots. Considering how simple the zfs CLI is, scripting
|
||||
several snapshot schemes can be quite easy, be them per boot, daily, or
|
||||
even every so many minutes using cron. Because ZFS snapshots grow as
|
||||
they drift from the present state, rotating them is optimal when storage
|
||||
space is a concern.</p>
|
||||
<p>That’s it! I hope this was helpful to you in either learning about
|
||||
ZFS or about Void installations with Root on ZFS.</p>
|
||||
<hr />
|
||||
<p><em>Originally written June 2nd, 2024</em></p>
|
||||
</main class="article">
|
||||
</body>
|
||||
<footer>
|
||||
<hr/>
|
||||
<div class="footer-text">
|
||||
<span id="author-pre-text">posted by</span>
|
||||
<span class="author">Juno Takano</span>
|
||||
<span id="date-pre-text">on</span>
|
||||
<span class="date">June 9, 2024</span>
|
||||
<span class="footer-back" aria-role="nav" aria-label="Navigate back">
|
||||
↩ <a id="footer-back-link" href="../index.html">Back</a>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
while true; do
|
||||
./gen.ml
|
||||
echo
|
||||
inotifywait -e modify -r md
|
||||
done
|
||||
288
static/assets/css/style.css
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
@font-face {
|
||||
font-family: 'Mononoki';
|
||||
src: url('../fonts/Mononoki.ttf'), format('truetype');
|
||||
}
|
||||
|
||||
#theme {
|
||||
background-color: #f1e9e5;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: 'Mononoki', monospace;
|
||||
}
|
||||
|
||||
* { margin: 0px; }
|
||||
|
||||
.header-art {
|
||||
color: #888
|
||||
}
|
||||
|
||||
#user-controls {
|
||||
display: grid;
|
||||
grid-template-columns: 8fr 1fr;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
nav {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
#language-selector {
|
||||
justify-self: end;
|
||||
margin-top: 7px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#nav-menu #home {
|
||||
color: #353;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#nav-menu li {
|
||||
display: inline-block;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#skip-to-main {
|
||||
position: absolute;
|
||||
left: -1000%;
|
||||
}
|
||||
|
||||
#skip-to-main:focus {
|
||||
left: 40%;
|
||||
background-color: #333;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#posts {
|
||||
margin-left: 20px;
|
||||
margin-top: 20px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.index-posts h2 {
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-top: 5px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
ol {
|
||||
margin-top: 5px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 10px;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
ul li:before {
|
||||
content: "▫";
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
nav ul li:before {
|
||||
content: "";
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 30px;
|
||||
line-height: 1.7em;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline dotted #999;
|
||||
text-decoration-thickness: 1.5px;
|
||||
color: #276E6B;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #007F80;
|
||||
text-decoration: underline dotted #39959A;
|
||||
transition: 1500ms;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 25px;
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
margin: 40px 0px 10px 0px;
|
||||
}
|
||||
|
||||
h2,h3,h4 {
|
||||
font-weight: bold;
|
||||
margin: 20px 0px 10px 30px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
h2 { font-size: 25px; }
|
||||
h3 { font-size: 21px; }
|
||||
h4 { font-size: 21px; color: #59978b; }
|
||||
|
||||
.article p {
|
||||
margin: 25px 0px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
p code {
|
||||
background-color: #ccc;
|
||||
padding: 5px 6.5px;
|
||||
margin-right: 2px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
li code {
|
||||
background-color: #ccc;
|
||||
padding: 5px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
color: #333;
|
||||
background-color: #ddd;
|
||||
padding: 10px 10px;
|
||||
margin: 10px 20px;
|
||||
font-style: italic;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
main pre {
|
||||
margin: 30px;
|
||||
border-color: #fff;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
blockquote p code {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
table {
|
||||
text-align: center;
|
||||
margin: 30px;
|
||||
border: 1px solid;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table th, td {
|
||||
border: 1px dotted;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
padding: 20px 20px;
|
||||
}
|
||||
|
||||
.footer-back {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* tablet */
|
||||
@media (min-width: 401px) {
|
||||
.article {
|
||||
margin: 0 45px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.post-date {
|
||||
color: #777;
|
||||
font-size: 14px;
|
||||
margin-top: 0px;
|
||||
text-decoration-thickness: 0.1px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#theme {
|
||||
background-color: #222222;
|
||||
color: #F1E9E5;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #AEDBCE;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #39AEA9;
|
||||
}
|
||||
|
||||
h1,h2,h3 {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: #6a978b;
|
||||
}
|
||||
|
||||
p code {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
li code {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
color: #888;
|
||||
background-color: #1b1b1b;
|
||||
}
|
||||
|
||||
blockquote p code {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.post-date {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#nav-menu #home {
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
/* widescreen */
|
||||
@media (min-width: 1000px) {
|
||||
.article {
|
||||
margin: 0 20%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
/* fullscreen */
|
||||
@media (min-width: 1500px) {
|
||||
.article {
|
||||
margin: 0 30%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
/* phone */
|
||||
@media (max-width: 400px) {
|
||||
.article {
|
||||
margin: 0 20px;
|
||||
}
|
||||
}
|
||||
265
static/assets/css/syntax-dark.css
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* theme "Material-Theme-Darker" generated by syntect
|
||||
*/
|
||||
|
||||
.z-code {
|
||||
color: #eeffff;
|
||||
background-color: #292929;
|
||||
}
|
||||
|
||||
.z-comment, .z-punctuation.z-definition.z-comment {
|
||||
color: #7f7f7f;
|
||||
font-style: italic;
|
||||
}
|
||||
.z-variable, .z-string .z-constant.z-other.z-placeholder {
|
||||
color: #eeffff;
|
||||
}
|
||||
.z-constant.z-other.z-color {
|
||||
color: #ffffff;
|
||||
}
|
||||
.z-invalid, .z-invalid.z-illegal, .z-invalid.z-broken {
|
||||
color: #ffffff;
|
||||
background-color: #ff5370;
|
||||
}
|
||||
.z-invalid.z-unimplemented {
|
||||
color: #ffffff;
|
||||
background-color: #aacaba;
|
||||
}
|
||||
.z-invalid.z-deprecated {
|
||||
color: #ffffff;
|
||||
background-color: #c792ea;
|
||||
}
|
||||
.z-keyword, .z-storage.z-type, .z-storage.z-modifier {
|
||||
color: #c792ea;
|
||||
}
|
||||
.z-storage.z-type, .z-keyword.z-control {
|
||||
font-style: italic;
|
||||
}
|
||||
.z-keyword.z-operator, .z-constant.z-other.z-color, .z-punctuation, .z-meta.z-tag, .z-punctuation.z-definition.z-tag, .z-punctuation.z-separator.z-inheritance.z-php, .z-punctuation.z-definition.z-tag.z-html, .z-punctuation.z-definition.z-tag.z-begin.z-html, .z-punctuation.z-definition.z-tag.z-end.z-html, .z-punctuation.z-section.z-embedded, .z-keyword.z-other.z-template, .z-keyword.z-other.z-substitution {
|
||||
color: #aaaaff;
|
||||
}
|
||||
.z-entity.z-name.z-tag, .z-meta.z-tag.z-sgml, .z-markup.z-deleted.z-git_gutter {
|
||||
color: #f07178;
|
||||
}
|
||||
.z-entity.z-name.z-function, .z-meta.z-function-call, .z-variable.z-function, .z-support.z-function, .z-keyword.z-other.z-special-method, .z-meta.z-block-level {
|
||||
color: #aaaaaf;
|
||||
}
|
||||
.z-support.z-other.z-variable, .z-string.z-other.z-link {
|
||||
color: #f07178;
|
||||
}
|
||||
.z-constant.z-numeric, .z-constant.z-language, .z-support.z-constant, .z-constant.z-character, .z-variable.z-parameter, .z-keyword.z-other.z-unit {
|
||||
color: #6f67fe;
|
||||
}
|
||||
.z-string, .z-constant.z-other.z-symbol, .z-constant.z-other.z-key, .z-entity.z-other.z-inherited-class, .z-markup.z-heading, .z-markup.z-inserted.z-git_gutter, .z-meta.z-group.z-braces.z-curly .z-constant.z-other.z-object.z-key.z-js .z-string.z-unquoted.z-label.z-js {
|
||||
color: #aacaba;
|
||||
}
|
||||
.z-entity.z-name.z-class, .z-entity.z-name.z-type.z-class, .z-support.z-type, .z-support.z-class, .z-support.z-orther.z-namespace.z-use.z-php, .z-meta.z-use.z-php, .z-support.z-other.z-namespace.z-php, .z-markup.z-changed.z-git_gutter, .z-support.z-type.z-sys-types {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
.z-source.z-css .z-support.z-type, .z-source.z-sass .z-support.z-type, .z-source.z-scss .z-support.z-type, .z-source.z-less .z-support.z-type, .z-source.z-stylus .z-support.z-type {
|
||||
color: #b2ccd6;
|
||||
}
|
||||
.z-entity.z-name.z-module.z-js, .z-variable.z-import.z-parameter.z-js, .z-variable.z-other.z-class.z-js {
|
||||
color: #ff5370;
|
||||
}
|
||||
.z-variable.z-language {
|
||||
color: #ff5370;
|
||||
font-style: italic;
|
||||
}
|
||||
.z-entity.z-name.z-method.z-js {
|
||||
color: #82aaff;
|
||||
}
|
||||
.z-meta.z-class-method.z-js .z-entity.z-name.z-function.z-js, .z-variable.z-function.z-constructor {
|
||||
color: #82aaff;
|
||||
}
|
||||
.z-entity.z-other.z-attribute-name {
|
||||
color: #c792ea;
|
||||
}
|
||||
.z-text.z-html.z-basic .z-entity.z-other.z-attribute-name.z-html, .z-text.z-html.z-basic .z-entity.z-other.z-attribute-name {
|
||||
color: #ffcb6b;
|
||||
font-style: italic;
|
||||
}
|
||||
.z-entity.z-other.z-attribute-name.z-class {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
.z-source.z-sass .z-keyword.z-control {
|
||||
color: #82aaff;
|
||||
}
|
||||
.z-markup.z-inserted {
|
||||
color: #aacaba;
|
||||
}
|
||||
.z-markup.z-deleted {
|
||||
color: #ff5370;
|
||||
}
|
||||
.z-markup.z-changed {
|
||||
color: #c792ea;
|
||||
}
|
||||
.z-string.z-regexp {
|
||||
color: #89ddff;
|
||||
}
|
||||
.z-constant.z-character.z-escape {
|
||||
color: #89ddff;
|
||||
}
|
||||
.z-*url*, .z-*link*, .z-*uri* {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.z-constant.z-numeric.z-line-number.z-find-in-files {
|
||||
color: #c17e70;
|
||||
}
|
||||
.z-entity.z-name.z-filename.z-find-in-files {
|
||||
color: #aacaba;
|
||||
}
|
||||
.z-tag.z-decorator.z-js .z-entity.z-name.z-tag.z-js, .z-tag.z-decorator.z-js .z-punctuation.z-definition.z-tag.z-js {
|
||||
color: #82aaff;
|
||||
font-style: italic;
|
||||
}
|
||||
.z-source.z-js .z-constant.z-other.z-object.z-key.z-js .z-string.z-unquoted.z-label.z-js {
|
||||
color: #ff5370;
|
||||
font-style: italic;
|
||||
}
|
||||
.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
|
||||
color: #aacaba;
|
||||
}
|
||||
.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
|
||||
color: #c792ea;
|
||||
}
|
||||
.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
|
||||
color: #f07178;
|
||||
}
|
||||
.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
|
||||
color: #82aaff;
|
||||
}
|
||||
.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
|
||||
color: #c17e70;
|
||||
}
|
||||
.z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
|
||||
color: #ff5370;
|
||||
}
|
||||
.z-source.z-json .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
|
||||
color: #f78c6c;
|
||||
}
|
||||
.z-source.z-json .z-meta .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
.z-source.z-json .z-meta.z-structure.z-dictionary.z-json .z-string.z-quoted.z-double.z-json, .z-source.z-json .z-meta.z-structure.z-dictionary.z-json .z-punctuation.z-definition.z-string {
|
||||
color: #c792ea;
|
||||
}
|
||||
.z-text.z-html.z-markdown, .z-punctuation.z-definition.z-list_item.z-markdown {
|
||||
color: #eeffff;
|
||||
}
|
||||
.z-text.z-html.z-markdown .z-markup.z-raw.z-inline {
|
||||
color: #c792ea;
|
||||
}
|
||||
.z-text.z-html.z-markdown .z-punctuation.z-definition.z-raw.z-markdown {
|
||||
color: #65737e;
|
||||
}
|
||||
.z-markdown.z-heading, .z-markup.z-heading, .z-markup.z-heading .z-entity.z-name, .z-markup.z-heading.z-markdown .z-punctuation.z-definition.z-heading.z-markdown {
|
||||
color: #aacaba;
|
||||
}
|
||||
.z-markup.z-italic {
|
||||
color: #f07178;
|
||||
font-style: italic;
|
||||
}
|
||||
.z-markup.z-bold, .z-markup.z-bold .z-string {
|
||||
color: #f07178;
|
||||
font-weight: bold;
|
||||
}
|
||||
.z-markup.z-bold .z-markup.z-italic, .z-markup.z-italic .z-markup.z-bold, .z-markup.z-quote .z-markup.z-bold, .z-markup.z-bold .z-markup.z-italic .z-string, .z-markup.z-italic .z-markup.z-bold .z-string, .z-markup.z-quote .z-markup.z-bold .z-string {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
.z-markup.z-underline {
|
||||
color: #f78c6c;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.z-markup.z-quote .z-punctuation.z-definition.z-blockquote.z-markdown {
|
||||
color: #65737e;
|
||||
background-color: #65737e;
|
||||
}
|
||||
.z-string.z-other.z-link.z-title.z-markdown {
|
||||
color: #82aaff;
|
||||
}
|
||||
.z-string.z-other.z-link.z-description.z-title.z-markdown {
|
||||
color: #c792ea;
|
||||
}
|
||||
.z-constant.z-other.z-reference.z-link.z-markdown {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
.z-markup.z-raw.z-block {
|
||||
color: #c792ea;
|
||||
}
|
||||
.z-markup.z-raw.z-block.z-fenced.z-markdown {
|
||||
background-color: #000000;
|
||||
}
|
||||
.z-punctuation.z-definition.z-fenced.z-markdown {
|
||||
background-color: #000000;
|
||||
}
|
||||
.z-markup.z-raw.z-block.z-fenced.z-markdown, .z-variable.z-language.z-fenced.z-markdown, .z-punctuation.z-section.z-class.z-end {
|
||||
color: #eeffff;
|
||||
}
|
||||
.z-variable.z-language.z-fenced.z-markdown {
|
||||
color: #65737e;
|
||||
}
|
||||
.z-text.z-html.z-markdown .z-punctuation.z-definition {
|
||||
color: #4a4a4a;
|
||||
}
|
||||
.z-text.z-html.z-markdown .z-meta.z-disable-markdown .z-punctuation.z-definition {
|
||||
color: #89ddff;
|
||||
}
|
||||
.z-meta.z-separator {
|
||||
color: #65737e;
|
||||
background-color: #000000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.z-acejump.z-label.z-blue {
|
||||
color: #ffffff;
|
||||
background-color: #82aaff;
|
||||
}
|
||||
.z-acejump.z-label.z-green {
|
||||
color: #ffffff;
|
||||
background-color: #aacaba;
|
||||
}
|
||||
.z-acejump.z-label.z-orange {
|
||||
color: #ffffff;
|
||||
background-color: #f78c6c;
|
||||
}
|
||||
.z-acejump.z-label.z-purple {
|
||||
color: #ffffff;
|
||||
background-color: #c792ea;
|
||||
}
|
||||
.z-sublimelinter.z-mark.z-warning {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
.z-sublimelinter.z-gutter-mark {
|
||||
color: #ffffff;
|
||||
}
|
||||
.z-sublimelinter.z-mark.z-error {
|
||||
color: #ff5370;
|
||||
}
|
||||
.z-sublimelinter.z-annotations {
|
||||
background-color: #c17e70;
|
||||
}
|
||||
.z-markup.z-ignored.z-git_gutter {
|
||||
color: #65737e;
|
||||
}
|
||||
.z-markup.z-untracked.z-git_gutter {
|
||||
color: #65737e;
|
||||
}
|
||||
.z-markup.z-inserted.z-git_gutter {
|
||||
color: #aacaba;
|
||||
}
|
||||
.z-markup.z-changed.z-git_gutter {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
.z-markup.z-deleted.z-git_gutter {
|
||||
color: #ff5370;
|
||||
}
|
||||
.z-brackethighlighter.z-default {
|
||||
color: #b2ccd6;
|
||||
}
|
||||
.z-brackethighlighter.z-quote {
|
||||
color: #aacaba;
|
||||
}
|
||||
.z-brackethighlighter.z-unmatched {
|
||||
color: #ff5370;
|
||||
}
|
||||
61
static/assets/css/syntax-light.css
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* theme "Dimmed Fluid" generated by syntect
|
||||
*/
|
||||
|
||||
.z-code {
|
||||
color: #4d4d4c;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.z-comment {
|
||||
color: #999999;
|
||||
}
|
||||
.z-keyword.z-operator.z-class, .z-constant.z-other, .z-source.z-php.z-embedded.z-line {
|
||||
color: #666666;
|
||||
}
|
||||
.z-variable, .z-support.z-other.z-variable, .z-string.z-other.z-link, .z-string.z-regexp, .z-entity.z-name.z-tag, .z-entity.z-other.z-attribute-name, .z-meta.z-tag, .z-declaration.z-tag {
|
||||
color: #77c;
|
||||
}
|
||||
.z-constant.z-numeric, .z-constant.z-language, .z-support.z-constant, .z-constant.z-character, .z-variable.z-parameter, .z-punctuation.z-section.z-embedded, .z-keyword.z-other.z-unit {
|
||||
color: #b77;
|
||||
}
|
||||
.z-entity.z-name.z-class, .z-entity.z-name.z-type.z-class, .z-support.z-type, .z-support.z-class {
|
||||
color: #f0ae00;
|
||||
}
|
||||
.z-string, .z-constant.z-other.z-symbol, .z-entity.z-other.z-inherited-class, .z-markup.z-heading {
|
||||
color: #699200;
|
||||
}
|
||||
.z-keyword.z-operator, .z-constant.z-other.z-color {
|
||||
color: #1aa7b0;
|
||||
}
|
||||
.z-entity.z-name.z-function, .z-meta.z-function-call, .z-support.z-function, .z-keyword.z-other.z-special-method, .z-meta.z-block-level {
|
||||
color: #777;
|
||||
}
|
||||
.z-keyword, .z-storage, .z-storage.z-type {
|
||||
color: #8e44be;
|
||||
}
|
||||
.z-meta.z-separator {
|
||||
color: #ffffff;
|
||||
background-color: #2169c7;
|
||||
}
|
||||
.z-invalid.z-deprecated {
|
||||
color: #ffffff;
|
||||
background-color: #8e44be;
|
||||
}
|
||||
.z-markup.z-inserted.z-diff, .z-markup.z-deleted.z-diff, .z-meta.z-diff.z-header.z-to-file, .z-meta.z-diff.z-header.z-from-file {
|
||||
color: #ffffff;
|
||||
}
|
||||
.z-markup.z-inserted.z-diff, .z-meta.z-diff.z-header.z-to-file {
|
||||
background-color: #008f00;
|
||||
}
|
||||
.z-markup.z-deleted.z-diff, .z-meta.z-diff.z-header.z-from-file {
|
||||
background-color: #ef0000;
|
||||
}
|
||||
.z-meta.z-diff.z-header.z-from-file, .z-meta.z-diff.z-header.z-to-file {
|
||||
color: #ffffff;
|
||||
background-color: #333333;
|
||||
}
|
||||
.z-meta.z-diff.z-range {
|
||||
color: #333333;
|
||||
font-style: italic;
|
||||
}
|
||||
51
static/assets/img/icon/icon.svg
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 6 6"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="favicon.svg"
|
||||
inkscape:export-filename="favicon.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.8780367"
|
||||
inkscape:cx="56.44192"
|
||||
inkscape:cy="47.922385"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="741"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<circle
|
||||
style="fill:#483737;fill-rule:evenodd;stroke-width:0.046875"
|
||||
id="path897"
|
||||
cx="3"
|
||||
cy="3"
|
||||
r="2.901917" />
|
||||
<path
|
||||
fill="#000000"
|
||||
fill-rule="evenodd"
|
||||
d="m 3.0016204,1.6398895 c -0.330319,0 -0.363351,0.035041 -0.5343133,0.035041 -0.1423944,0 -0.5700235,-0.3488437 -0.7836149,-0.3488437 -0.2138146,0 -0.4631162,0.1256552 -0.4631162,0.4883365 V 2.2329015 C 1.2210224,2.34271 1.2607503,2.6792784 1.4169819,2.5893335 1.2324051,2.8076119 1.2138804,3.0622699 1.2163354,3.3086701 1.1665645,3.3229539 1.1159003,3.3392468 1.066576,3.3559863 0.91391508,3.408212 0.75188026,3.4747226 0.67889763,3.5220385 A 0.16739867,0.16739867 0 0 0 0.86057297,3.8032557 C 0.89539086,3.7807138 1.021492,3.7251395 1.1748224,3.6726904 l 0.050887,-0.016739 c 0.010262,0.096641 0.035934,0.1852464 0.074099,0.2655943 l -0.00535,0.0029 C 1.2029445,3.972656 1.1181325,4.0282296 1.064121,4.0634934 L 1.0389,4.0800084 a 0.16739867,0.16739867 0 1 0 0.1816753,0.2812175 l 0.029238,-0.019194 c 0.054681,-0.03571 0.1247624,-0.081464 0.2010929,-0.1216376 0.017856,-0.0096 0.035041,-0.018078 0.051556,-0.025891 0.3861161,0.3622348 1.0795628,0.4794089 1.4991573,0.4794089 0.4195944,0 1.113041,-0.1171741 1.4991571,-0.4794089 0.016515,0.00781 0.033478,0.016293 0.051556,0.025891 0.07633,0.040173 0.1464117,0.085928 0.2010928,0.1216376 l 0.029238,0.019194 A 0.16739867,0.16739867 0 0 0 4.96434,4.0800095 L 4.93912,4.0634935 A 2.9032361,2.9032361 0 0 0 4.7087892,3.9244471 l -0.00535,-0.0029 c 0.038158,-0.080347 0.063824,-0.1689531 0.074091,-0.2655938 l 0.050887,0.016739 C 4.9817492,3.7251405 5.1078507,3.780714 5.1428913,3.803256 A 0.16739138,0.16739138 0 0 0 5.324343,3.5220382 C 5.2513604,3.4747222 5.0893256,3.408212 4.9368879,3.355986 A 3.6475698,3.6475698 0 0 0 4.7869052,3.3084468 C 4.7895834,3.0620466 4.7708356,2.8076118 4.5860356,2.5893334 4.7422675,2.6792783 4.7824415,2.3427101 4.7826646,2.2326782 V 1.8144229 c 0,-0.3626813 -0.2493016,-0.4883365 -0.4631161,-0.4883365 -0.2135914,0 -0.6412205,0.3488437 -0.7833917,0.3488437 -0.1711855,0 -0.2042175,-0.035041 -0.5345364,-0.035041 z M 2.8509681,3.6918846 c 0.045085,-0.0154 0.098426,-0.022319 0.1506523,-0.022319 0.052226,0 0.1055685,0.00691 0.1508754,0.022319 0.022319,0.00759 0.049101,0.019641 0.073205,0.038835 a 0.13815369,0.13815369 0 0 1 0.054905,0.1062378 c 0,0.051334 -0.031023,0.087043 -0.054905,0.1062378 -0.023881,0.019194 -0.051109,0.031246 -0.073205,0.038835 A 0.47985529,0.47985529 0 0 1 3.0016204,4.0043485 0.4776234,0.4776234 0 0 1 2.8509681,3.9820296 0.2405972,0.2405972 0 0 1 2.7775391,3.9431948 0.13815369,0.13815369 0 0 1 2.7226348,3.8369571 c 0,-0.051334 0.031023,-0.087043 0.054905,-0.1062378 0.02388,-0.019194 0.051334,-0.031246 0.073428,-0.038835 z M 3.4859394,3.0000003 c 0.030577,-0.050887 0.090614,-0.1115943 0.1807827,-0.1115943 0.090169,0 0.150429,0.060707 0.1807827,0.1115943 0.031693,0.053342 0.04687,0.1176203 0.04687,0.1814522 0,0.063609 -0.015177,0.127887 -0.04687,0.1810059 -0.030354,0.051109 -0.090614,0.1118174 -0.1807827,0.1118174 -0.090169,0 -0.1502059,-0.060707 -0.1807827,-0.1115942 a 0.35620886,0.35620886 0 0 1 -0.04687,-0.1814523 c 0,-0.063608 0.015177,-0.127887 0.04687,-0.1810058 z m -1.3302035,0 c 0.030577,-0.050887 0.090614,-0.1115943 0.1807827,-0.1115943 0.090169,0 0.1504291,0.060707 0.1807828,0.1115943 0.031693,0.053342 0.04687,0.1176203 0.04687,0.1814522 0,0.063609 -0.015177,0.127887 -0.04687,0.1810059 -0.030354,0.051109 -0.090614,0.1118174 -0.1807828,0.1118174 -0.090168,0 -0.1502058,-0.060707 -0.1807827,-0.1115942 a 0.35620886,0.35620886 0 0 1 -0.04687,-0.1814523 c 0,-0.063608 0.015177,-0.127887 0.04687,-0.1810058 z"
|
||||
clip-rule="evenodd"
|
||||
id="path2"
|
||||
style="stroke-width:0.223188;fill:#ffffff" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
BIN
static/assets/img/icon/icon_16.png
Normal file
|
After Width: | Height: | Size: 449 B |
BIN
static/assets/img/icon/icon_192.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
static/assets/img/icon/icon_260.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
static/assets/img/icon/icon_32.png
Normal file
|
After Width: | Height: | Size: 845 B |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB |
19
static/assets/rss/en.rss
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
|
||||
<channel>
|
||||
<title>jutty.dev</title>
|
||||
<link>http://blog.jutty.dev</link>
|
||||
<description>Just computer nerd things</description>
|
||||
<language>en</language>
|
||||
<atom:link href="http://blog.jutty.dev/assets/rss/en.xml" rel="self" type="application/rss+xml"/>
|
||||
<lastBuildDate>Sun, 18 Aug 2024 00:00:00 +0000</lastBuildDate>
|
||||
<item>
|
||||
<title>The jutty.dev feed has moved</title>
|
||||
<pubDate>Sun, 18 Aug 2024 15:52:11 +0000</pubDate>
|
||||
<author>Juno Takano</author>
|
||||
<link>http://blog.jutty.dev/en/rss.xml</link>
|
||||
<guid>http://blog.jutty.dev/en/rss.xml</guid>
|
||||
<description xml:base="http://blog.jutty.dev/en/rss.xml">This RSS feed has moved to: blog.jutty.dev/rss.xml. More feed options are available at blog.jutty.dev/feeds. Update your RSS reader to continue receiving new entries.</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
19
static/assets/rss/pt.rss
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
|
||||
<channel>
|
||||
<title>jutty.dev</title>
|
||||
<link>http://blog.jutty.dev</link>
|
||||
<description>Nerdices, unix, computarias</description>
|
||||
<language>pt</language>
|
||||
<atom:link href="http://blog.jutty.dev/pt/rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<lastBuildDate>Sun, 18 Aug 2024 00:00:00 +0000</lastBuildDate>
|
||||
<item>
|
||||
<title>O feed para jutty.dev mudou</title>
|
||||
<pubDate>Sun, 18 Aug 2024 15:52:11 +0000</pubDate>
|
||||
<author>Juno Takano</author>
|
||||
<link>http://blog.jutty.dev/pt/rss.xml</link>
|
||||
<guid>http://blog.jutty.dev/pt/rss.xml</guid>
|
||||
<description xml:base="http://blog.jutty.dev/pt/rss.xml">O endereço deste feed RSS mudou para: blog.jutty.dev/pt/rss.xml. Mais opções de feeds estão disponíveis em blog.jutty.dev/pt/feeds (Português) e blog.jutty.dev/feeds (Inglês). Atualize seu leitor de RSS para continuar recebendo atualizações.</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
BIN
static/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
6
templates/404.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{% block content %}
|
||||
|
||||
<h1>404</h1>
|
||||
<p>Page not found</p>
|
||||
|
||||
{% endblock content %}
|
||||
80
templates/base.html
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<!DOCTYPE html>
|
||||
{% if lang == "pt" %}
|
||||
<html lang="pt-BR" id="theme">
|
||||
{% else %}
|
||||
<html lang="{{ lang }}" id="theme">
|
||||
{% endif %}
|
||||
|
||||
<head prefix="og: https://ogp.me/ns/website#">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
|
||||
{%- set title_tail = " ~ " ~ config.title %}
|
||||
|
||||
{%- if page.title %}
|
||||
{%- set title = page.title ~ title_tail %}
|
||||
{%- elif section.title %}
|
||||
{%- set title = section.title ~ title_tail %}
|
||||
{%- else %}
|
||||
{%- set title = config.title %}
|
||||
{%- endif %}
|
||||
|
||||
<title>{{ title }}</title>
|
||||
|
||||
<link href="{{ get_url(path="assets/css/style.css", trailing_slash=false) }}" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="{{ get_url(path="assets/css/syntax-dark.css", trailing_slash=false) }}" media="(prefers-color-scheme: dark)" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ get_url(path="assets/css/syntax-light.css", trailing_slash=false) }}" media="(prefers-color-scheme: light)" />
|
||||
|
||||
<link rel="icon" type="image/png" sizes="36x36" href="{{ get_url(path="assets/img/icon/icon_32.png", trailing_slash=false) }}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ get_url(path="assets/img/icon/icon_16.png", trailing_slash=false) }}">
|
||||
<link rel="icon" type="image/png" sizes="128x128" href="{{ get_url(path="assets/img/icon/icon_128.png", trailing_slash=false) }}">
|
||||
|
||||
{%- set description = page.content | default(value=config.description) | striptags | truncate(length=120) %}
|
||||
|
||||
<meta property="og:title" content="{{ title }}" />
|
||||
<meta property="og:description" content="{{ description }}" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="{{ current_url | default(value= get_url(path="/")) | safe }}" />
|
||||
<meta property="og:image" content="{{ get_url(path="assets/img/logo/logo_192.png") }}" />
|
||||
|
||||
{% block rss %}<link rel="alternate" type="application/rss+xml" title="RSS ({{ lang }})" href="{{ get_url(path="rss.xml", lang=lang, trailing_slash=false) }}">
|
||||
<link rel="alternate" type="application/rss+xml" title="Atom ({{ lang }})" href="{{ get_url(path="atom.xml", lang=lang, trailing_slash=false) }}">{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{% if lang == "pt" %}
|
||||
<a id="skip-to-main" href="#main">Saltar para o conteúdo</a>
|
||||
{% else %}
|
||||
<a id="skip-to-main" href="#main">Skip to content</a>
|
||||
{% endif %}
|
||||
|
||||
{% block header %}{% endblock %}
|
||||
|
||||
<div id="user-controls">
|
||||
<nav>
|
||||
<ul id="nav-menu">
|
||||
<li><a id="home" href="{{ get_url(path="/") }}">Home</a></li>
|
||||
<li><a href="{{ get_url(path="posts", lang=lang) }}">Posts</a></li>
|
||||
<li><a href="{{ get_url(path="links", lang=lang) }}">Links</a></li>
|
||||
<li><a href="{{ get_url(path="feeds", lang=lang) }}">RSS</a></li>
|
||||
<li><a href="https://mastodon.bsd.cafe/@jutty">Mastodon</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div id="language-selector">
|
||||
{% set prepared_path = current_path | replace(from="pt/", to="") %}
|
||||
{% if lang == "pt" %}
|
||||
<a href="{{ get_url(path=prepared_path) }}">English</a>
|
||||
{% else %}
|
||||
<a href="{{ get_url(path="pt" ~ prepared_path) }}">Português</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main id="main">
|
||||
{% block content %} {% endblock %}
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
42
templates/index.html
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block header %}
|
||||
<header>
|
||||
<pre class="header-art"
|
||||
role="img" aria-label="ASCII art with 'jutty.dev' in a 3D, slanted font">
|
||||
|
||||
|
||||
__ __ __
|
||||
__ /\ \__/\ \__ /\ \
|
||||
/\_\ __ __\ \ ,_\ \ ,_\ __ __ \_\ \ __ __ __
|
||||
\/\ \/\ \/\ \\ \ \/\ \ \/ /\ \/\ \ /'_` \ /'__`\/\ \/\ \
|
||||
\ \ \ \ \_\ \\ \ \_\ \ \_\ \ \_\ \ __/\ \L\ \/\ __/\ \ \_/ |
|
||||
_\ \ \ \____/ \ \__\\ \__\\/`____ \/\_\ \___,_\ \____\\ \___/
|
||||
/\ \_\ \/___/ \/__/ \/__/ `/___/> \/_/\/__,_ /\/____/ \/__/
|
||||
\ \____/ /\___/
|
||||
\/___/ \/__/
|
||||
|
||||
</pre>
|
||||
</header>
|
||||
{% endblock header %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if lang == "pt" %}
|
||||
{% set posts = get_section(path="posts/_index.pt.md") %}
|
||||
{% else %}
|
||||
{% set posts = get_section(path="posts/_index.md") %}
|
||||
{% endif %}
|
||||
|
||||
<nav class="index-posts">
|
||||
<ul id="posts">
|
||||
{% for post in posts.pages %}
|
||||
<li>
|
||||
<span class="post-date">{{ post.date }}</span>
|
||||
<a href="{{ post.permalink | safe }}">{{ post.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{% endblock content %}
|
||||
14
templates/link-page.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 id="post-title">
|
||||
<a href="{{ page.extra.url }}">{{ page.title }}</a>
|
||||
</h1>
|
||||
<p class="post-date"><strong>Linked {{ page.date }}</strong></p>
|
||||
{% if page.extra.comment %}
|
||||
<p>{{ page.extra.comment }}</p>
|
||||
{% endif %}
|
||||
{{ page.content | safe }}
|
||||
|
||||
{% endblock content %}
|
||||
25
templates/links.html
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% set english_section = get_section(path="links/_index.md", lang="en") %}
|
||||
|
||||
<h1 class="title">
|
||||
{{ section.title }}
|
||||
</h1>
|
||||
<ul id="posts">
|
||||
{% for page in english_section.pages %}
|
||||
<li>
|
||||
<a class="post-date" href="{{ page.permalink }}">{{ page.date }}</a>
|
||||
<a href="{{ page.extra.url | safe }}">{{ page.title }}</a>
|
||||
{% if page.content %}
|
||||
{% set portuguese_page = get_page(path="links/" ~ page.slug ~ ".pt.md") %}
|
||||
{% if lang == "pt" %}
|
||||
<span>{{ portuguese_page.content | replace(from="<p>", to="") | safe }}</span>
|
||||
{% else %}
|
||||
<span>{{ page.content | replace(from="<p>", to="") | safe }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock content %}
|
||||
5
templates/page.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
{{ page.content | safe }}
|
||||
{% endblock content %}
|
||||
11
templates/post-page.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 id="post-title">
|
||||
{{ page.title }}
|
||||
</h1>
|
||||
<p class="post-date"><strong>{{ page.date }}</strong></p>
|
||||
{{ page.content | safe }}
|
||||
|
||||
{% endblock content %}
|
||||
15
templates/posts.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="title">
|
||||
{{ section.title }}
|
||||
</h1>
|
||||
<ul id="posts">
|
||||
{% for page in section.pages %}
|
||||
<li>
|
||||
<span class="post-date">{{ page.date }}</span>
|
||||
<a href="{{ page.permalink | safe }}">{{ page.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock content %}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
#!/usr/bin/env zsh
|
||||
# dependencies: zsh, xmlstarlet, xmllint
|
||||
|
||||
date="$(date +'%a, %d %b %Y %H:%M:%S %z')"
|
||||
data='assets/js/data.js'
|
||||
|
||||
function subnode {
|
||||
local -r tag="${1}"
|
||||
local -r value="${2}"
|
||||
local path="${3:-rss/channel/new}"
|
||||
local -r file="${4:-tmp.rss}"
|
||||
|
||||
if [[ -n $value ]]; then
|
||||
/usr/bin/xmlstarlet ed -L --subnode "$path" -t elem -n "$tag" -v "$value" "$file"
|
||||
else
|
||||
/usr/bin/xmlstarlet ed -L --subnode "$path" -t elem -n "$tag" "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
function insert {
|
||||
local -r title="$1"
|
||||
local -r slug="${2}.html"
|
||||
local -r file="${3}"
|
||||
local -r url_root='https://blog.jutty.dev/posts'
|
||||
local -r author='juno@jutty.dev (Juno Takano)'
|
||||
|
||||
if grep -q "$url_root/$slug" "$file"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
xmlstarlet ed --update rss/channel/lastBuildDate --value "$date" "$file" > tmp.rss
|
||||
|
||||
subnode new '' rss/channel
|
||||
subnode title "$title"
|
||||
subnode pubDate "$date"
|
||||
subnode link "$url_root/$slug"
|
||||
subnode guid "$url_root/$slug"
|
||||
subnode author "$author"
|
||||
|
||||
sed -i 's/<new>/<item>/g' tmp.rss
|
||||
sed -i 's/<\/new>/<\/item>/g' tmp.rss
|
||||
|
||||
if xmllint --noout tmp.rss; then
|
||||
mv -v tmp.rss "$file"
|
||||
else
|
||||
echo 'Generated an invalid feed, not overwriting'
|
||||
rm -v tmp.rss
|
||||
return 1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
function assemble_index {
|
||||
local -r lang="$1"
|
||||
|
||||
cat "$data" |
|
||||
grep -v function |
|
||||
sed 's/ *return //' |
|
||||
sed -E ':a;N;$!ba; s/(.*)\}(.*)/\1\2/' |
|
||||
jq ".$lang" |
|
||||
grep 'title\|slug' |
|
||||
grep '.'
|
||||
}
|
||||
|
||||
index_en=$(assemble_index en)
|
||||
index_pt=$(assemble_index pt)
|
||||
|
||||
function insert_from {
|
||||
local -r index="$1"
|
||||
local -r file="$2"
|
||||
local -r total=$(echo $index | wc -l)
|
||||
local title
|
||||
local slug
|
||||
|
||||
current=2
|
||||
while [[ $current -le $total ]]; do
|
||||
lines=$(echo $index | tail -$((current)) | head -2)
|
||||
title=$(echo $lines | grep title | cut -d'"' -f 4 | xargs)
|
||||
slug=$(echo $lines | grep slug | cut -d'"' -f 4 | xargs)
|
||||
insert "$title" "$slug" "$file"
|
||||
((current+=2))
|
||||
done
|
||||
}
|
||||
|
||||
insert_from "$index_pt" assets/rss/pt.rss
|
||||
insert_from "$index_en" assets/rss/en.rss
|
||||