Owen Young's Blog - All Blog Posts
Mainly focus on technology, reading, excerpts, miscellaneous, article review, tool sharing, workflow, inspiration, English learning, attention management, deep work and other directions
Zola
2022-09-12T13:47:59+08:00
https://www.owenyoung.com/en/blog/atom.xml
2022.09.12: Assorted Links
2022-09-12T13:47:59+08:00
2022-09-12T00:00:00+00:00
https://www.owenyoung.com/en/blog/journals/2022-09-12/
<ul>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://www.sanejs.dev/docs/overview">Sanejs - a starter-kit for server-rendered templates built with HTMX, Express, and Mongoose</a></li>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://www.foreignaffairs.com/ukraine/ukraine-war-democracy-nihilism-timothy-snyder">Ukraine Holds the Future - The War Between Democracy and Nihilism</a></li>
</ul>
Simple bash to generate template markdown file for the initial blog post
2022-07-25T10:50:28+08:00
2022-07-25T00:00:00+00:00
https://www.owenyoung.com/en/blog/simple-bash-to-generate-template-markdown-file-for-the-initial-blog-post/
<p>When you’re using markdown to write your blog, you can use the following template to generate a new markdown file, you may use some template like this to generate a new markdown file:</p>
<span id="continue-reading"></span><pre data-lang="yaml" style="background-color:#2b303b;color:#c0c5ce;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span>---
</span><span style="color:#bf616a;">title</span><span>:
</span><span style="color:#bf616a;">date</span><span>: </span><span style="color:#a3be8c;">${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}T${CURRENT_HOUR}:${CURRENT_MINUTE}:${CURRENT_SECOND}+08:00
</span><span style="color:#bf616a;">updated</span><span>: </span><span style="color:#a3be8c;">${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}
</span><span style="color:#bf616a;">draft</span><span>: </span><span style="color:#d08770;">true
</span><span style="color:#bf616a;">taxonomies</span><span>:
</span><span> </span><span style="color:#bf616a;">categories</span><span>:
</span><span> - </span><span style="color:#a3be8c;">Random
</span><span> </span><span style="color:#bf616a;">tags</span><span>:
</span><span> -
</span><span>---
</span></code></pre>
<p>I used to generate these markdown articles with VSCode extention <a rel="noopener nofollow noreferrer" target="_blank" href="https://marketplace.visualstudio.com/items?itemName=theowenyoung.foam-lite-vscode">foam-lite</a>, which is like this:</p>
<p><img src="https://www.owenyoung.com/en/blog/simple-bash-to-generate-template-markdown-file-for-the-initial-blog-post/./vscode-foam.gif" alt="" /></p>
<p>The problem is that it’s an VSCode extention, not an general tool, and the tool also has a little limitations.
for example, the default generated file path is based on the current directory, so I always need to navigate to the correct directory to generate the markdown file.</p>
<p>And recent days, I started to use <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/dotfiles">Dotfiles</a> to manage all my configuration files, and I used a lot of bash scripts to manage my dotfiles. So why not to use bash scripts to generate the initial markdown files? It’s general enough, and it’s easy to use, and it can be reused in any editors.</p>
<p>Here is the final workflow for creating a daily markdown file:</p>
<p><img src="https://www.owenyoung.com/en/blog/simple-bash-to-generate-template-markdown-file-for-the-initial-blog-post/./bash-daily.gif" alt="" /></p>
<p>Here is the workflow for creating a normal post:</p>
<p><img src="https://www.owenyoung.com/en/blog/simple-bash-to-generate-template-markdown-file-for-the-initial-blog-post/./bash-random.gif" alt="" /></p>
<h2 id="how-to-do-this">How to do this?<a class="zola-anchor" href="#how-to-do-this" aria-label="Anchor link for: how-to-do-this">🔗</a></h2>
<p>I use <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/tests-always-included/mo">Bash Mustache</a> as the template engine, it’s simple but powerful, and it’s just a simple bash file, I place it in my blog repo <code>./scripts/mo.sh</code>, then I write an function in <code>./scripts/common.sh</code></p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#65737e;">#!/bin/bash
</span><span style="color:#b48ead;">export </span><span style="color:#bf616a;">CURRENT_YEAR</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">date</span><span style="color:#a3be8c;"> +</span><span>%</span><span style="color:#bf616a;">Y</span><span style="color:#a3be8c;">)
</span><span style="color:#b48ead;">export </span><span style="color:#bf616a;">CURRENT_MONTH</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">date</span><span style="color:#a3be8c;"> +</span><span>%</span><span style="color:#bf616a;">m</span><span style="color:#a3be8c;">)
</span><span style="color:#b48ead;">export </span><span style="color:#bf616a;">CURRENT_DATE</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">date</span><span style="color:#a3be8c;"> +</span><span>%</span><span style="color:#bf616a;">d</span><span style="color:#a3be8c;">)
</span><span style="color:#b48ead;">export </span><span style="color:#bf616a;">CURRENT_HOUR</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">date</span><span style="color:#a3be8c;"> +</span><span>%</span><span style="color:#bf616a;">H</span><span style="color:#a3be8c;">)
</span><span style="color:#b48ead;">export </span><span style="color:#bf616a;">CURRENT_MINUTE</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">date</span><span style="color:#a3be8c;"> +</span><span>%</span><span style="color:#bf616a;">M</span><span style="color:#a3be8c;">)
</span><span style="color:#b48ead;">export </span><span style="color:#bf616a;">CURRENT_SECOND</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">date</span><span style="color:#a3be8c;"> +</span><span>%</span><span style="color:#bf616a;">S</span><span style="color:#a3be8c;">)
</span><span>
</span><span style="color:#65737e;"># template functions
</span><span style="color:#b48ead;">function </span><span style="color:#8fa1b3;">template</span><span>() {
</span><span> </span><span style="color:#bf616a;">template_path</span><span>="$</span><span style="color:#bf616a;">1</span><span>"
</span><span> </span><span style="color:#bf616a;">target_path</span><span>="$</span><span style="color:#bf616a;">2</span><span>"
</span><span> </span><span style="color:#bf616a;">target_dir</span><span>="$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">dirname </span><span>$</span><span style="color:#bf616a;">target_path</span><span style="color:#a3be8c;">)</span><span>"
</span><span> </span><span style="color:#65737e;"># check target dir is exist, if not, create it
</span><span> </span><span style="color:#b48ead;">if </span><span style="color:#96b5b4;">[ </span><span>! </span><span style="color:#bf616a;">-d </span><span>"$</span><span style="color:#bf616a;">target_dir</span><span>" </span><span style="color:#96b5b4;">]</span><span>; </span><span style="color:#b48ead;">then
</span><span> </span><span style="color:#bf616a;">mkdir -p </span><span>$</span><span style="color:#bf616a;">target_dir
</span><span> </span><span style="color:#b48ead;">fi
</span><span>
</span><span> </span><span style="color:#65737e;"># check target file is exists
</span><span> </span><span style="color:#b48ead;">if </span><span style="color:#96b5b4;">[ </span><span style="color:#bf616a;">-f </span><span>$</span><span style="color:#bf616a;">target_path </span><span style="color:#96b5b4;">]</span><span>; </span><span style="color:#b48ead;">then
</span><span> </span><span style="color:#96b5b4;">echo </span><span>"$</span><span style="color:#bf616a;">target_path</span><span style="color:#a3be8c;"> file exists</span><span>"
</span><span> </span><span style="color:#96b5b4;">exit</span><span> 1
</span><span> </span><span style="color:#b48ead;">fi
</span><span> </span><span style="color:#bf616a;">cat </span><span>$</span><span style="color:#bf616a;">template_path </span><span>| </span><span style="color:#bf616a;">./scripts/mo.sh </span><span>> $</span><span style="color:#bf616a;">target_path
</span><span> </span><span style="color:#96b5b4;">echo </span><span>"$</span><span style="color:#bf616a;">target_path</span><span style="color:#a3be8c;"> created!</span><span>"
</span><span>}
</span></code></pre>
<p>For daily journal article(<code>./scripts/daily.sh</code>):</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#65737e;">#!/bin/bash
</span><span style="color:#96b5b4;">source</span><span> ./scripts/common.sh
</span><span style="color:#bf616a;">template_path</span><span>="</span><span style="color:#a3be8c;">./scripts/templates/daily.md.tmpl</span><span>"
</span><span style="color:#bf616a;">target_path</span><span>="</span><span style="color:#a3be8c;">./content/blog/journals/</span><span>$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">date</span><span style="color:#a3be8c;"> +</span><span>%</span><span style="color:#bf616a;">Y</span><span style="color:#a3be8c;">-</span><span>%</span><span style="color:#bf616a;">m</span><span style="color:#a3be8c;">-</span><span>%</span><span style="color:#bf616a;">d</span><span style="color:#a3be8c;">).md</span><span>"
</span><span>
</span><span style="color:#bf616a;">template </span><span>$</span><span style="color:#bf616a;">template_path </span><span>$</span><span style="color:#bf616a;">target_path</span><span>;
</span><span style="color:#bf616a;">code </span><span>$</span><span style="color:#bf616a;">target_path</span><span>;
</span></code></pre>
<p>This script will create a file named <code>./content/blog/journals/2022-07-25.md</code> with the <code>./scripts/templates/daily.md.tmpl</code> template:</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">---
</span><span style="color:#bf616a;">title: </span><span>{{CURRENT_YEAR}}-{{CURRENT_MONTH}}-{{CURRENT_DATE}}的各种链接
</span><span style="color:#bf616a;">date: </span><span>{{CURRENT_YEAR}}-{{CURRENT_MONTH}}-{{CURRENT_DATE}}T{{CURRENT_HOUR}}:{{CURRENT_MINUTE}}:{{CURRENT_SECOND}}+08:00
</span><span style="color:#bf616a;">updated: </span><span>{{CURRENT_YEAR}}-{{CURRENT_MONTH}}-{{CURRENT_DATE}}
</span><span style="color:#bf616a;">draft:</span><span> false
</span><span style="color:#bf616a;">taxonomies:
</span><span> </span><span style="color:#bf616a;">categories:
</span><span> </span><span style="color:#bf616a;">-</span><span> Journal
</span><span> </span><span style="color:#bf616a;">tags:
</span><span> </span><span style="color:#bf616a;">-</span><span> Journal
</span><span style="color:#bf616a;">---
</span></code></pre>
<p>For convinient to use, I also add a alias in <code>Makefile</code>:</p>
<pre data-lang="Makefile" style="background-color:#2b303b;color:#c0c5ce;" class="language-Makefile "><code class="language-Makefile" data-lang="Makefile"><span style="color:#8fa1b3;">.Phony</span><span>: </span><span style="color:#a3be8c;">daily
</span><span style="color:#8fa1b3;">daily</span><span>:
</span><span> </span><span style="color:#bf616a;">./scripts/daily.sh
</span></code></pre>
<p>So if I want to generate a new daily article, I just need to run <code>make daily</code></p>
<p>Or, If I’m in VSCode, I created a task for VSCode in <code>.vscode/tasks.json</code>:</p>
<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
</span><span> "</span><span style="color:#a3be8c;">version</span><span>": "</span><span style="color:#a3be8c;">2.0.0</span><span>",
</span><span> "</span><span style="color:#a3be8c;">tasks</span><span>": [
</span><span> {
</span><span> "</span><span style="color:#a3be8c;">label</span><span>": "</span><span style="color:#a3be8c;">daily</span><span>",
</span><span> "</span><span style="color:#a3be8c;">type</span><span>": "</span><span style="color:#a3be8c;">shell</span><span>",
</span><span> "</span><span style="color:#a3be8c;">command</span><span>": "</span><span style="color:#a3be8c;">make daily</span><span>",
</span><span> "</span><span style="color:#a3be8c;">presentation</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">reveal</span><span>": "</span><span style="color:#a3be8c;">silent</span><span>",
</span><span> "</span><span style="color:#a3be8c;">close</span><span>": </span><span style="color:#d08770;">true
</span><span> }
</span><span> },
</span><span> {
</span><span> "</span><span style="color:#a3be8c;">label</span><span>": "</span><span style="color:#a3be8c;">random</span><span>",
</span><span> "</span><span style="color:#a3be8c;">type</span><span>": "</span><span style="color:#a3be8c;">shell</span><span>",
</span><span> "</span><span style="color:#a3be8c;">command</span><span>": "</span><span style="color:#a3be8c;">./scripts/random.sh ${input:filename}</span><span>",
</span><span> "</span><span style="color:#a3be8c;">presentation</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">reveal</span><span>": "</span><span style="color:#a3be8c;">silent</span><span>",
</span><span> "</span><span style="color:#a3be8c;">close</span><span>": </span><span style="color:#d08770;">true
</span><span> }
</span><span> },
</span><span> {
</span><span> "</span><span style="color:#a3be8c;">label</span><span>": "</span><span style="color:#a3be8c;">notes</span><span>",
</span><span> "</span><span style="color:#a3be8c;">type</span><span>": "</span><span style="color:#a3be8c;">shell</span><span>",
</span><span> "</span><span style="color:#a3be8c;">command</span><span>": "</span><span style="color:#a3be8c;">./scripts/notes.sh ${input:filename}</span><span>",
</span><span> "</span><span style="color:#a3be8c;">presentation</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">reveal</span><span>": "</span><span style="color:#a3be8c;">silent</span><span>",
</span><span> "</span><span style="color:#a3be8c;">close</span><span>": </span><span style="color:#d08770;">true
</span><span> }
</span><span> },
</span><span> {
</span><span> "</span><span style="color:#a3be8c;">label</span><span>": "</span><span style="color:#a3be8c;">book</span><span>",
</span><span> "</span><span style="color:#a3be8c;">type</span><span>": "</span><span style="color:#a3be8c;">shell</span><span>",
</span><span> "</span><span style="color:#a3be8c;">command</span><span>": "</span><span style="color:#a3be8c;">./scripts/book.sh ${input:filename}</span><span>",
</span><span> "</span><span style="color:#a3be8c;">presentation</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">reveal</span><span>": "</span><span style="color:#a3be8c;">silent</span><span>",
</span><span> "</span><span style="color:#a3be8c;">close</span><span>": </span><span style="color:#d08770;">true
</span><span> }
</span><span> }
</span><span>
</span><span> ],
</span><span> "</span><span style="color:#a3be8c;">inputs</span><span>": [
</span><span> {
</span><span> "</span><span style="color:#a3be8c;">type</span><span>": "</span><span style="color:#a3be8c;">promptString</span><span>",
</span><span> "</span><span style="color:#a3be8c;">id</span><span>": "</span><span style="color:#a3be8c;">filename</span><span>",
</span><span> "</span><span style="color:#a3be8c;">description</span><span>": "</span><span style="color:#a3be8c;">You article path.</span><span>",
</span><span> "</span><span style="color:#a3be8c;">default</span><span>": "",
</span><span> }
</span><span> ]
</span><span>}
</span></code></pre>
<p>All source codes are in my blog <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog">repo</a></p>
Adding search to zola blog with meilisearch deployed on Mogenius
2022-07-17T08:50:12+08:00
2022-07-17T00:00:00+00:00
https://www.owenyoung.com/en/blog/adding-search-to-zola-blog-with-meilisearch-deployed-on-mogenius/
<p>I deployed <a rel="noopener nofollow noreferrer" target="_blank" href="https://meilisearch.com/">Meilisearch</a> on my VPS <a href="https://www.owenyoung.com/blog/add-search/">a couple of days ago</a>, but I’m panic, because I mess up my VPS a lot, and I rebuild my VPS a lot, so it’s not a stable environment to deploy meilisearch. So I started to browse <a rel="noopener nofollow noreferrer" target="_blank" href="https://www.trackawesomelist.com/ripienaar/free-for-dev/">free-for-dev</a>, see if there are some good(and cheap) solutions for me. I saw this:</p>
<blockquote>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://mogenius.com/home">mogenius</a> - A code-to-cloud platform to easily deploy any service, from static website to advanced microservice architectures. mogenius comes with fully managed hyper-scaling cloud resources, Kubernetes, CI/CD and security from Cloudflare. Free tier includes 1 CPU core, 2 GB RAM, 10 GB traffic, 4 GB SSD persistent storage.</p>
</blockquote>
<p><strong>The main point:</strong></p>
<ul>
<li>Free tier includes 1 CPU core, 2 GB RAM, 10 GB traffic, 4 GB SSD persistent storage.</li>
<li>from static website to advanced microservice architectures.</li>
</ul>
<p>I think it’s enough for my blog’s search index.</p>
<span id="continue-reading"></span>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://mogenius.com/home">Mogenius</a> said they “from static website to advanced microservice architectures”, and they also have a decent plan for free tier. I decided to try it out. After completing the registration, it requires a mandatory mobile number verification. It’s a bit annoying, but I’m glad to have a free tier, and it’s a good way to avoid abuse. (There is a bug when you verify your mobile number, I’am using firefox, it seems the country code select can not show list correctly, so I just change a vpn to fix it. (maybe I should use chrome to test it, I almost give up because of this bug))</p>
<p>For those who want to see the final results, here is the final result:</p>
<p><img src="https://www.owenyoung.com/en/blog/adding-search-to-zola-blog-with-meilisearch-deployed-on-mogenius/./meilisearch.png" alt="meilisearch" /></p>
<p>Let’s see how to set it up:</p>
<h2 id="step-1-create-cloudspace">Step 1: Create cloudspace<a class="zola-anchor" href="#step-1-create-cloudspace" aria-label="Anchor link for: step-1-create-cloudspace">🔗</a></h2>
<p>Just click ADD button, and fill in the form.</p>
<h2 id="step-2-create-a-new-servicee">Step 2: Create a new servicee<a class="zola-anchor" href="#step-2-create-a-new-servicee" aria-label="Anchor link for: step-2-create-a-new-servicee">🔗</a></h2>
<p>Enter to the cloudspace, and click the add button to create a new service with <code>Use a template</code>, and search <code>meilisearch</code></p>
<h2 id="step-3-configure-the-service">Step 3: Configure the service<a class="zola-anchor" href="#step-3-configure-the-service" aria-label="Anchor link for: step-3-configure-the-service">🔗</a></h2>
<ol>
<li>
<p>Fill the servicename, for me I just type <code>meilisearch</code></p>
</li>
<li>
<p>Connect your github account(you can give it only selected repo permissions, you must selected at least one repo, it can be any public repo)</p>
<p>Screenshot:
<img src="https://www.owenyoung.com/en/blog/adding-search-to-zola-blog-with-meilisearch-deployed-on-mogenius/0.png" alt="0" /></p>
</li>
<li>
<p>Click <code>Add repository</code>, input the repo name, I input it as <code>meili</code>, and with <code>public</code> permission.</p>
<p>Screenshot:</p>
<p><img src="https://www.owenyoung.com/en/blog/adding-search-to-zola-blog-with-meilisearch-deployed-on-mogenius/./1.png" alt="1" /></p>
</li>
<li>
<p>set resources limit (if needed, the default value is a recomendation from Mogenius).</p>
</li>
<li>
<p>set environment variables:</p>
<ol>
<li>
<p>add <code>MEILI_MASTER_KEY</code> as key vault to protect your master key. (Click <code>create new secret</code> to fill the secret)</p>
</li>
<li>
<p>the following environment variables can be plain text type.</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">MEILI_NO_ANALYTICS</span><span>=</span><span style="color:#a3be8c;">true
</span><span style="color:#bf616a;">MEILI_ENV</span><span>=</span><span style="color:#a3be8c;">production
</span></code></pre>
<p>All environment options are <a rel="noopener nofollow noreferrer" target="_blank" href="https://docs.meilisearch.com/learn/configuration/instance_options.html#environment">here</a></p>
</li>
</ol>
<p>Screenshot:</p>
<p><img src="https://www.owenyoung.com/en/blog/adding-search-to-zola-blog-with-meilisearch-deployed-on-mogenius/./2.png" alt="2" /></p>
</li>
<li>
<p>optional, set custom domain. I set it to <code>meili.owenyoung.com</code>, I set a CNAME record <code>meili.owenyoung.com</code> to <code>meilisearch-prod-meilisearch-r4efxz.mo2.mogenius.io</code>, you will get the endpoint after the service created.</p>
<p>Screenshot:</p>
<p><img src="https://www.owenyoung.com/en/blog/adding-search-to-zola-blog-with-meilisearch-deployed-on-mogenius/./4.png" alt="3" /></p>
</li>
<li>
<p>Then, click the <code>Create Service</code> button. It may take 1 minute to build and deploy the service.</p>
</li>
</ol>
<h2 id="step-5-get-your-meilisearch-admin-key-and-user-key">Step 5: Get your meilisearch admin key, and user key<a class="zola-anchor" href="#step-5-get-your-meilisearch-admin-key-and-user-key" aria-label="Anchor link for: step-5-get-your-meilisearch-admin-key-and-user-key">🔗</a></h2>
<p>After the service is created, the meilisearch is ready to use, you can visit <a rel="noopener nofollow noreferrer" target="_blank" href="https://meili.owenyoung.com/">https://meili.owenyoung.com/</a> to test if everything is worked fine. At this moment, the default meilisearch template version is <a rel="noopener nofollow noreferrer" target="_blank" href="https://docs.mogenius.com/services/various/meilisearch"><code>v0.27.0rc1</code></a>, the following is the success output:</p>
<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{ "</span><span style="color:#a3be8c;">status</span><span>": "</span><span style="color:#a3be8c;">Meilisearch is running</span><span>" }
</span></code></pre>
<p>You need to run the bash script to get the admin key and user key:</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">curl </span><span>\
</span><span style="color:#bf616a;"> -X</span><span> GET '</span><span style="color:#a3be8c;">https://meili.owenyoung.com/keys</span><span>' \
</span><span style="color:#bf616a;"> -H </span><span>"</span><span style="color:#a3be8c;">Authorization: Bearer you-master-key</span><span>" \
</span><span> | </span><span style="color:#bf616a;">json_pp
</span></code></pre>
<p>We’ll use them in the next step.</p>
<h2 id="step-6-setup-doc-scraper">Step 6: Setup doc-scraper<a class="zola-anchor" href="#step-6-setup-doc-scraper" aria-label="Anchor link for: step-6-setup-doc-scraper">🔗</a></h2>
<p>We need to build search index after every updates of our blogs, I use Github Actions to build my blog, so I just add a job after the site is deployed. We’ll use <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/meilisearch/docs-scraper">docs-scraper</a> to scrape our whole site with <code>sitemap.xml</code> file. <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/meilisearch/docs-scraper">docs-scraper</a> will help us to build heading and content level search index, it’ll bring the best search experience for our readers.</p>
<blockquote>
<p>The current <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/meilisearch/docs-scraper#-compatibility-with-meilisearch">docs-scrape is only adapted with <code>0.27.x</code></a>, at this moment, the latest meilisearch version 0.28 is not supported, I have submitted <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/meilisearch/docs-scraper/issues/231">an issue</a>, so the default Mogenius meilisearch template version <code>0.27.0rc1</code> is fine, we can also upgrade it to <code>v0.27.2</code>.(I have done this by change the <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/meili/blob/main/Dockerfile">Dockerfile</a>, Mongenius will monitor the updates, and automatically deploy the updates)</p>
</blockquote>
<p>See the <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/.github/workflows/build.yml"><code>.github/workflows/build.yml</code></a></p>
<pre data-lang="yaml" style="background-color:#2b303b;color:#c0c5ce;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Deploy Site
</span><span style="color:#d08770;">on</span><span>:
</span><span> </span><span style="color:#bf616a;">workflow_dispatch</span><span>:
</span><span> </span><span style="color:#bf616a;">push</span><span>:
</span><span> </span><span style="color:#bf616a;">branches</span><span>:
</span><span> - </span><span style="color:#a3be8c;">main
</span><span style="color:#bf616a;">jobs</span><span>:
</span><span> </span><span style="color:#bf616a;">build-deploy</span><span>:
</span><span> </span><span style="color:#bf616a;">runs-on</span><span>: </span><span style="color:#a3be8c;">ubuntu-latest
</span><span> </span><span style="color:#bf616a;">steps</span><span>:
</span><span> - </span><span style="color:#bf616a;">uses</span><span>: </span><span style="color:#a3be8c;">actions/checkout@v3
</span><span> - </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">make install
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Build Site
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">make build
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Deploy
</span><span> </span><span style="color:#bf616a;">uses</span><span>: </span><span style="color:#a3be8c;">peaceiris/actions-gh-pages@v3
</span><span> </span><span style="color:#bf616a;">with</span><span>:
</span><span> </span><span style="color:#bf616a;">publish_dir</span><span>: </span><span style="color:#a3be8c;">./public
</span><span> </span><span style="color:#bf616a;">github_token</span><span>: </span><span style="color:#a3be8c;">${{ secrets.GITHUB_TOKEN }}
</span><span> </span><span style="color:#bf616a;">scrape-and-build-search-index</span><span>:
</span><span> </span><span style="color:#bf616a;">needs</span><span>: </span><span style="color:#a3be8c;">build-deploy
</span><span> </span><span style="color:#bf616a;">runs-on</span><span>: </span><span style="color:#a3be8c;">ubuntu-latest
</span><span> </span><span style="color:#bf616a;">steps</span><span>:
</span><span> </span><span style="color:#65737e;"># wait github pages publish
</span><span> - </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">sleep 1m
</span><span> - </span><span style="color:#bf616a;">uses</span><span>: </span><span style="color:#a3be8c;">actions/checkout@v3
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Run docs-scraper
</span><span> </span><span style="color:#bf616a;">env</span><span>:
</span><span> </span><span style="color:#bf616a;">HOST_URL</span><span>: "</span><span style="color:#a3be8c;">https://meili.owenyoung.com</span><span>"
</span><span> </span><span style="color:#bf616a;">API_KEY</span><span>: </span><span style="color:#a3be8c;">${{ secrets.MEILISEARCH_API_KEY }}
</span><span> </span><span style="color:#bf616a;">CONFIG_FILE_PATH</span><span>: </span><span style="color:#a3be8c;">${{ github.workspace }}/meilisearch-docs-scraper-config.json
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#b48ead;">|
</span><span style="color:#a3be8c;"> docker run -t --rm \
</span><span style="color:#a3be8c;"> -e MEILISEARCH_HOST_URL=$HOST_URL \
</span><span style="color:#a3be8c;"> -e MEILISEARCH_API_KEY=$API_KEY \
</span><span style="color:#a3be8c;"> -v $CONFIG_FILE_PATH:/docs-scraper/config.json \
</span><span style="color:#a3be8c;"> getmeili/docs-scraper:v0.12.2 pipenv run ./docs_scraper config.json
</span></code></pre>
<p><code>MEILISEARCH_API_KEY</code> will be the admin key from the previous step.</p>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/meilisearch-docs-scraper-config.json"><code>meilisearch-docs-scraper-config.json</code></a> will be placed in the root of the repository, and it will be used by <code>docs-scraper</code> to scrape the site. Here is my config file:</p>
<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
</span><span> "</span><span style="color:#a3be8c;">index_uid</span><span>": "</span><span style="color:#a3be8c;">owen-blog</span><span>",
</span><span> "</span><span style="color:#a3be8c;">sitemap_urls</span><span>": ["</span><span style="color:#a3be8c;">https://www.owenyoung.com/sitemap.xml</span><span>"],
</span><span> "</span><span style="color:#a3be8c;">start_urls</span><span>": [""],
</span><span> "</span><span style="color:#a3be8c;">scrap_start_urls</span><span>": </span><span style="color:#d08770;">false</span><span>,
</span><span> "</span><span style="color:#a3be8c;">stop_urls</span><span>": [
</span><span> "</span><span style="color:#a3be8c;">https://www.owenyoung.com/categories/</span><span>",
</span><span> "</span><span style="color:#a3be8c;">https://www.owenyoung.com/tags/</span><span>",
</span><span> "</span><span style="color:#a3be8c;">https://www.owenyoung.com/en/tags/</span><span>",
</span><span> "</span><span style="color:#a3be8c;">https://www.owenyoung.com/en/categories/</span><span>",
</span><span> "</span><span style="color:#a3be8c;">https://www.owenyoung.com/pages/</span><span>",
</span><span> "</span><span style="color:#a3be8c;">https://www.owenyoung.com/en/pages/</span><span>"
</span><span> ],
</span><span> "</span><span style="color:#a3be8c;">selectors</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">lvl0</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">selector</span><span>": "</span><span style="color:#a3be8c;">.detail-page .p-category</span><span>",
</span><span> "</span><span style="color:#a3be8c;">default_value</span><span>": "</span><span style="color:#a3be8c;">Random</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">lvl1</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">selector</span><span>": "</span><span style="color:#a3be8c;">.detail-page .p-tags</span><span>",
</span><span> "</span><span style="color:#a3be8c;">default_value</span><span>": "</span><span style="color:#a3be8c;">Notes</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">lvl2</span><span>": "</span><span style="color:#a3be8c;">.detail-page .entry-title</span><span>",
</span><span> "</span><span style="color:#a3be8c;">lvl3</span><span>": "</span><span style="color:#a3be8c;">.e-content h2</span><span>",
</span><span> "</span><span style="color:#a3be8c;">lvl4</span><span>": "</span><span style="color:#a3be8c;">.e-content h3</span><span>",
</span><span> "</span><span style="color:#a3be8c;">text</span><span>": "</span><span style="color:#a3be8c;">.e-content p, .e-content li</span><span>"
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">strip_chars</span><span>": "</span><span style="color:#a3be8c;"> .,;:#</span><span>",
</span><span> "</span><span style="color:#a3be8c;">min_indexed_level</span><span>": </span><span style="color:#d08770;">2</span><span>,
</span><span> "</span><span style="color:#a3be8c;">custom_settings</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">synonyms</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">js</span><span>": ["</span><span style="color:#a3be8c;">javascript</span><span>", "</span><span style="color:#a3be8c;">nodejs</span><span>"]
</span><span> },
</span><span> "</span><span style="color:#a3be8c;">stopWords</span><span>": ["</span><span style="color:#a3be8c;">一</span><span>", "</span><span style="color:#a3be8c;">的</span><span>", "</span><span style="color:#a3be8c;">是</span><span>"]
</span><span> }
</span><span>}
</span></code></pre>
<blockquote>
<p>Cause I’m using Chinese and English, so I added <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/meilisearch-docs-scraper-config.json">a lot of stop words of Chinese.</a></p>
</blockquote>
<p>Then the Github Actions workflow will run <code>docs-scraper</code> to scrape the site after the site is deployed.</p>
<h2 id="step-7-setup-the-frontend">Step 7: Setup the frontend<a class="zola-anchor" href="#step-7-setup-the-frontend" aria-label="Anchor link for: step-7-setup-the-frontend">🔗</a></h2>
<p>See my <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/templates/search.html">search template</a>, basically, you need to include the <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/meilisearch/docs-searchbar.js">docs-searchbar</a> css and js, and add an <code><input type="search" id="search-bar-input" placeholder="Search" /></code> to whereever you want to include the search bar. And add the following script to init:</p>
<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#8fa1b3;">docsSearchBar</span><span>({
</span><span> hostUrl: "</span><span style="color:#a3be8c;">https://meili.owenyoung.com</span><span>",
</span><span> apiKey: "</span><span style="color:#a3be8c;">your-user-api-key</span><span>",
</span><span> indexUid: "</span><span style="color:#a3be8c;">owen-blog</span><span>",
</span><span> inputSelector: "</span><span style="color:#a3be8c;">#search-bar-input</span><span>",
</span><span>});
</span></code></pre>
<h2 id="conclusion">Conclusion<a class="zola-anchor" href="#conclusion" aria-label="Anchor link for: conclusion">🔗</a></h2>
<p>You can see the all source code on <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog">my blog source code</a>.</p>
<p>As a programmer, it’s not a big deal to setup those things with <a rel="noopener nofollow noreferrer" target="_blank" href="https://mogenius.com/home">mogenius</a>, sometimes their UI prompts are not very friendly, for example:</p>
<ol>
<li>Firstly, I tried to create meilisearch without the template, the git address I entered at the beginning was <code>https://github.com/meilisearch/meilisearch</code>, then I can’t create the service, the error message is: <code>This field is required.</code>, finally I tried another git url with suffix <code>.git</code>, then the form is valid, and I can choose the git branch. I don’t know why they don’t allow me to choose a tag branch, only the normal branches are allowed, luckily, Meilisearch has <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/meilisearch/meilisearch/tree/release-v0.27.2">one normal release branch</a>, so I can choose it.</li>
<li>Their log UI is broken on Firefox browser, the edge browser is okay.</li>
<li>Even in Edge browser, the CI/CD monitor UI is broken, with an always refreshed page.</li>
</ol>
<blockquote>
<p>Update: 2. it’s not their fault, it’s my default broswer settings that disabled the canvas rendering.</p>
</blockquote>
<p>I don’t know if they will offer a config file to setup those form, like <code>vercel.json</code> or <code>netlify.toml</code>, if so, it’ll be easy for other non-programmers to setup those things.</p>
<p>Finally, I’m appreciate that they can offer a so decent free plan, I hope I don’t need to migrate it to another service. Let’s see.</p>
Now, I'm in IndieWeb?
2022-06-12T03:40:31+08:00
2022-06-12T00:00:00+00:00
https://www.owenyoung.com/en/blog/indieweb/
<p>Since I saw the concept of <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/IndieWeb">IndieWeb</a> last year, I’ve been wanting to support it on <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog">my Zola blog</a>. Before that my blog had never had a commenting system, and this time with Webmention I was finally able to get a static blog to display responses to articles on the web in plain html. I will document how I did it in this article.</p>
<span id="continue-reading"></span>
<blockquote>
<p>The <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/IndieWeb">IndieWeb</a> <strong>IndieWeb</strong> is a community of independent & personal websites connected by simple standards, based on the <strong><a rel="noopener nofollow noreferrer" target="_blank" title="principles" href="https://indieweb.org/principles">principles</a></strong> of: <strong><a rel="noopener nofollow noreferrer" target="_blank" title="personal-domain" href="https://indieweb.org/personal-domain">owning your domain</a></strong> & using it as <strong><a rel="noopener nofollow noreferrer" target="_blank" title="How to set up web sign-in on your own domain" href="https://indieweb.org/How_to_set_up_web_sign-in_on_your_own_domain">your primary identity</a></strong>, <strong><a rel="noopener nofollow noreferrer" target="_blank" title="POSSE" href="https://indieweb.org/POSSE">publishing on your own site (optionally syndicating elsewhere)</a></strong>, and <strong><a rel="noopener nofollow noreferrer" target="_blank" title="ownyourdata" href="https://indieweb.org/ownyourdata">owning your data</a></strong>.</p>
</blockquote>
<h2 id="1-support-indieweb-auth">1. Support IndieWeb Auth<a class="zola-anchor" href="#1-support-indieweb-auth" aria-label="Anchor link for: 1-support-indieweb-auth">🔗</a></h2>
<p>IndieAuth is a federated login protocol for Web sign-in, so by enabling this, we can use our own domain to sign in to other sites and services which support <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieauth.net/">IndieAuth</a>. Learn more about <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/IndieAuth">why IndieAuth</a> and <a rel="noopener nofollow noreferrer" target="_blank" href="https://indielogin.com/setup">how to IndieAuth</a>, here’s how I enabled it:</p>
<ol>
<li>By editing <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/a8b3cb3c13077b28cbcf7503518f6ed6cb8bb773/templates/base.html#L31-L33">my index template file</a>, add this to the head:</li>
</ol>
<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span><</span><span style="color:#bf616a;">link </span><span style="color:#d08770;">rel</span><span>="</span><span style="color:#a3be8c;">me</span><span>" </span><span style="color:#d08770;">href</span><span>="</span><span style="color:#a3be8c;">https://twitter.com/TheOwenYoung</span><span>" />
</span><span><</span><span style="color:#bf616a;">link </span><span style="color:#d08770;">rel</span><span>="</span><span style="color:#a3be8c;">me</span><span>" </span><span style="color:#d08770;">href</span><span>="</span><span style="color:#a3be8c;">https://github.com/theowenyoung</span><span>" />
</span><span><</span><span style="color:#bf616a;">link </span><span style="color:#d08770;">rel</span><span>="</span><span style="color:#a3be8c;">me</span><span>" </span><span style="color:#d08770;">href</span><span>="</span><span style="color:#a3be8c;">mailto:owen@owenyoung.com</span><span>" />
</span></code></pre>
<ol start="2">
<li>
<p>Editing Twitter/GitHub profile bio, add my website URL <code>https://www.owenyoung.com</code></p>
</li>
<li>
<p>Test it at <a rel="noopener nofollow noreferrer" target="_blank" href="https://indielogin.com/">https://indielogin.com/</a></p>
</li>
</ol>
<h2 id="2-add-profile-info">2. Add Profile Info<a class="zola-anchor" href="#2-add-profile-info" aria-label="Anchor link for: 2-add-profile-info">🔗</a></h2>
<p>When you sign in with IndieAuth or using <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/Webmention">web mention</a>, some sites will try to get your profile info, like name, image, and bio. So we can add this to our homepage, you can learn more about <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/h-card">h-card</a> here. Here’s how I added it:</p>
<p>By editing <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/a8b3cb3c13077b28cbcf7503518f6ed6cb8bb773/templates/base.html#L181-L187">my index template file</a>, add this in the <code>aside</code> (Cause I don’t want this to affect the layout of my site, so use css <code>display: none</code> to hide it for human, but the bot will see it):</p>
<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span><</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">display-none h-card pt</span><span>">
</span><span> <</span><span style="color:#bf616a;">img </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">u-photo icon</span><span>" </span><span style="color:#d08770;">alt</span><span>="</span><span style="color:#a3be8c;">Owen</span><span>"
</span><span> </span><span style="color:#d08770;">src</span><span>="</span><span style="color:#a3be8c;">{{get_url(path=</span><span>"</span><span style="color:#d08770;">site</span><span>/</span><span style="color:#d08770;">images</span><span>/</span><span style="color:#d08770;">favicon-96x96.png</span><span style="background-color:#bf616a;color:#2b303b;">"</span><span style="color:#d08770;">,cachebust</span><span>=</span><span style="color:#a3be8c;">true)}}</span><span style="background-color:#bf616a;color:#2b303b;">"</span><span> />
</span><span> <</span><span style="color:#bf616a;">a </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">p-name u-url</span><span>" </span><span style="color:#d08770;">href</span><span>="</span><span style="color:#a3be8c;">{{ config.base_url }}</span><span>"
</span><span> >{{ config.extra.author }}</</span><span style="color:#bf616a;">a
</span><span> >
</span><span> <</span><span style="color:#bf616a;">p </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">p-note</span><span>">{{ config.extra.bio }}</</span><span style="color:#bf616a;">p</span><span>>
</span><span></</span><span style="color:#bf616a;">div</span><span>>
</span></code></pre>
<h2 id="3-joining-indieweb-webring">3. Joining <a rel="noopener nofollow noreferrer" target="_blank" href="https://xn--sr8hvo.ws/">IndieWeb Webring</a><a class="zola-anchor" href="#3-joining-indieweb-webring" aria-label="Anchor link for: 3-joining-indieweb-webring">🔗</a></h2>
<blockquote>
<p>A webring (or web ring) is a collection of websites linked together in a circular structure, and usually organized around a specific theme, often educational or social. They were popular in the 1990s and early 2000s, particularly among amateur websites.</p>
</blockquote>
<p>Now, IndieWeb has one <a rel="noopener nofollow noreferrer" target="_blank" href="https://xn--sr8hvo.ws/">IndieWeb Webring</a>! by adding webring in our sites, so people can find (and be found by) other folks with IndieWeb building blocks on their sites!</p>
<p>So, I’m in it! By joining this, my blog has been listed in the <a rel="noopener nofollow noreferrer" target="_blank" href="https://xn--sr8hvo.ws/directory">IndieWeb Webring Directory</a>, you can see I already have <a rel="noopener nofollow noreferrer" target="_blank" href="https://xn--sr8hvo.ws/%F0%9F%93%AE%F0%9F%86%99%F0%9F%93%A9">a profile</a> there.</p>
<p>It’s easy to join the webring, just click the <a rel="noopener nofollow noreferrer" target="_blank" href="https://xn--sr8hvo.ws/">link</a>, and login with my domain <code>www.owenyoung.com</code>, then I can get my webring code, my webring code like this:</p>
<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span><</span><span style="color:#bf616a;">a </span><span style="color:#d08770;">href</span><span>="</span><span style="color:#a3be8c;">https://xn--sr8hvo.ws/%F0%9F%93%AE%F0%9F%86%99%F0%9F%93%A9/previous</span><span>"
</span><span> >←</</span><span style="color:#bf616a;">a
</span><span>>
</span><span>An IndieWeb Webring 🕸💍
</span><span><</span><span style="color:#bf616a;">a </span><span style="color:#d08770;">href</span><span>="</span><span style="color:#a3be8c;">https://xn--sr8hvo.ws/%F0%9F%93%AE%F0%9F%86%99%F0%9F%93%A9/next</span><span>">→</</span><span style="color:#bf616a;">a</span><span>>
</span></code></pre>
<p>Then, I add this code to <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/a8b3cb3c13077b28cbcf7503518f6ed6cb8bb773/templates/base.html#L216-L218">my homepage aside</a>, you can see it on the <a rel="noopener nofollow noreferrer" target="_blank" href="https://www.owenyoung.com/#bottom">aside footer</a></p>
<h2 id="4-adding-webmention-response-to-articles">4. Adding <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/Webmention">Webmention</a> Response to articles<a class="zola-anchor" href="#4-adding-webmention-response-to-articles" aria-label="Anchor link for: 4-adding-webmention-response-to-articles">🔗</a></h2>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://www.w3.org/TR/webmention/">Webmention</a> is a web standard for mentions and conversations across the web, a powerful building block that is used for a growing federated network of comments, likes, reposts, and other rich interactions across the decentralized social web.</p>
<blockquote>
<p>“An @ mention that works across websites; so that you don’t feel immovable from Twitter or Fb.” — <a rel="noopener nofollow noreferrer" target="_blank" href="https://twitter.com/rngala/status/852354426983591937">Rony Ngala</a></p>
</blockquote>
<p>Learn more about <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/Webmention">Webmention</a> and <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/Webmention-developer">How to support webmention</a></p>
<p>Basically, I use <a rel="noopener nofollow noreferrer" target="_blank" href="https://webmention.io">Webmention.io</a> to collect all webmentions about this blog, and then I use <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/denoflow/denoflow">Denoflow</a> to cache them to <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/tree/main/webmentions">my blog repo</a>, and then I use <a rel="noopener nofollow noreferrer" target="_blank" href="https://www.getzola.org/documentation/templates/overview/#load-data">Zola</a> <code>load_data</code> function to load them, and render them.</p>
<ol>
<li>
<p>First, go <a rel="noopener nofollow noreferrer" target="_blank" href="https://webmention.io">webmention.io</a> and create a new account with my domain <code>www.owenyoung.com</code>, then I can get my webmention endpoint, and I connected my twitter and github account to the service.</p>
</li>
<li>
<p>Second, let other services know your webmention endpoint. Add this to the head:</p>
</li>
</ol>
<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span><</span><span style="color:#bf616a;">link
</span><span> </span><span style="color:#d08770;">rel</span><span>="</span><span style="color:#a3be8c;">webmention</span><span>"
</span><span> </span><span style="color:#d08770;">href</span><span>="</span><span style="color:#a3be8c;">https://webmention.io/www.owenyoung.com/webmention</span><span>"
</span><span>/>
</span></code></pre>
<ol start="3">
<li>Use <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/denoflow/denoflow">Denoflow</a> to cache all webmentions to <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/tree/main/webmentions">my blog repo</a></li>
</ol>
<p>Workflow file(<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/workflows/fetch-webmention.yml"><code>workflows/fetch-webmention.yml</code></a>):</p>
<blockquote>
<p>Fetch webmention <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/aaronpk/webmention.io#api">API</a> to get updates, then save to <code>webmentions</code> directory.</p>
</blockquote>
<pre data-lang="yaml" style="background-color:#2b303b;color:#c0c5ce;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#bf616a;">sources</span><span>:
</span><span> - </span><span style="color:#bf616a;">use</span><span>: </span><span style="color:#a3be8c;">fetch
</span><span> </span><span style="color:#bf616a;">args</span><span>:
</span><span> - </span><span style="color:#a3be8c;">https://webmention.io/api/mentions.jf2?domain=www.owenyoung.com&per-page=999&token=${{ctx.env.WEBMENTION_TOKEN}}
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">return ctx.result.json()
</span><span> </span><span style="color:#bf616a;">itemsPath</span><span>: </span><span style="color:#a3be8c;">children
</span><span> </span><span style="color:#bf616a;">key</span><span>: "</span><span style="color:#a3be8c;">wm-id</span><span>"
</span><span style="color:#bf616a;">filter</span><span>:
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#b48ead;">|
</span><span style="color:#a3be8c;"> const {ensureDir} = await import("https://deno.land/std@0.121.0/fs/mod.ts");
</span><span style="color:#a3be8c;"> const { dirname } = await import("https://deno.land/std@0.121.0/path/mod.ts");
</span><span style="color:#a3be8c;"> for(const item of ctx.items){
</span><span style="color:#a3be8c;"> const id = item["wm-id"];
</span><span style="color:#a3be8c;"> const target = new URL(item["wm-target"]);
</span><span style="color:#a3be8c;"> const pathname = target.pathname;
</span><span style="color:#a3be8c;"> const filename = pathname.slice(1).replace(/\/$/, "");
</span><span style="color:#a3be8c;"> const filepath = "webmentions/"+filename+".json";
</span><span style="color:#a3be8c;"> await ensureDir(dirname(filepath));
</span><span style="color:#a3be8c;"> let webmentionData = {};
</span><span style="color:#a3be8c;"> try {
</span><span style="color:#a3be8c;"> const dataString = await Deno.readTextFile(filepath);
</span><span style="color:#a3be8c;"> webmentionData = JSON.parse(dataString);
</span><span style="color:#a3be8c;"> } catch (_e) {
</span><span style="color:#a3be8c;"> // ignore
</span><span style="color:#a3be8c;"> }
</span><span style="color:#a3be8c;"> webmentionData[id] = item;
</span><span style="color:#a3be8c;"> console.log("write file:", filepath);
</span><span style="color:#a3be8c;"> await Deno.writeTextFile(filepath, JSON.stringify(webmentionData,null,2));
</span><span style="color:#a3be8c;"> }
</span><span style="color:#a3be8c;"> return ctx.items.map(()=>true);
</span></code></pre>
<p>Github Workflow file(<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/.github/workflows/denoflow.yml"><code>.github/workflows/denoflow.yml</code></a>):</p>
<blockquote>
<p>Run denoflow every day at midnight, if there are any new updates, it will create a new pull request.</p>
</blockquote>
<pre data-lang="yaml" style="background-color:#2b303b;color:#c0c5ce;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Denoflow
</span><span style="color:#d08770;">on</span><span>:
</span><span> </span><span style="color:#bf616a;">repository_dispatch</span><span>:
</span><span> </span><span style="color:#bf616a;">workflow_dispatch</span><span>:
</span><span> </span><span style="color:#65737e;"># push:
</span><span> </span><span style="color:#65737e;"># branches:
</span><span> </span><span style="color:#65737e;"># - main
</span><span> </span><span style="color:#bf616a;">schedule</span><span>:
</span><span> - </span><span style="color:#bf616a;">cron</span><span>: "</span><span style="color:#a3be8c;">1 0 * * *</span><span>"
</span><span style="color:#bf616a;">jobs</span><span>:
</span><span> </span><span style="color:#bf616a;">denoflow</span><span>:
</span><span> </span><span style="color:#bf616a;">runs-on</span><span>: </span><span style="color:#a3be8c;">ubuntu-latest
</span><span> </span><span style="color:#bf616a;">concurrency</span><span>: </span><span style="color:#a3be8c;">denoflow
</span><span> </span><span style="color:#bf616a;">steps</span><span>:
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Check out repository code
</span><span> </span><span style="color:#bf616a;">uses</span><span>: </span><span style="color:#a3be8c;">actions/checkout@v2
</span><span> - </span><span style="color:#bf616a;">uses</span><span>: </span><span style="color:#a3be8c;">denoland/setup-deno@v1
</span><span> </span><span style="color:#bf616a;">with</span><span>:
</span><span> </span><span style="color:#bf616a;">deno-version</span><span>: </span><span style="color:#a3be8c;">v1.x
</span><span> - </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">make webmention
</span><span> </span><span style="color:#bf616a;">env</span><span>:
</span><span> </span><span style="color:#bf616a;">WEBMENTION_TOKEN</span><span>: </span><span style="color:#a3be8c;">${{secrets.WEBMENTION_TOKEN}}
</span><span> </span><span style="color:#bf616a;">continue-on-error</span><span>: </span><span style="color:#d08770;">true
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">chown
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">sudo chown -R $USER:$USER ./
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">git config
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">git config --global user.name "github-actions[bot]" && git config --global user.email github-actions-bot@users.noreply.github.com
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">git add
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">git add data && git add webmentions
</span><span> - </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">git status
</span><span> - </span><span style="color:#bf616a;">id</span><span>: </span><span style="color:#a3be8c;">isChanged
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">git diff-index --cached --quiet HEAD || echo '::set-output name=changed::true'
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Create pull request
</span><span> </span><span style="color:#bf616a;">uses</span><span>: </span><span style="color:#a3be8c;">peter-evans/create-pull-request@v3
</span><span> </span><span style="color:#bf616a;">if</span><span>: </span><span style="color:#a3be8c;">${{ steps.isChanged.outputs.changed == 'true' }}
</span><span> </span><span style="color:#bf616a;">with</span><span>:
</span><span> </span><span style="color:#bf616a;">token</span><span>: </span><span style="color:#a3be8c;">${{ secrets.PERSONAL_TOKEN }}
</span><span> </span><span style="color:#bf616a;">labels</span><span>: </span><span style="color:#a3be8c;">automerge
</span><span> </span><span style="color:#bf616a;">add-paths</span><span>: </span><span style="color:#a3be8c;">data,webmentions
</span><span> </span><span style="color:#bf616a;">commit-message</span><span>: "</span><span style="color:#a3be8c;">chore: new item</span><span>"
</span><span> </span><span style="color:#bf616a;">committer</span><span>: "</span><span style="color:#a3be8c;">github-actions[bot] <github-actions-bot@users.noreply.github.com></span><span>"
</span><span> </span><span style="color:#bf616a;">author</span><span>: "</span><span style="color:#a3be8c;">github-actions[bot] <github-actions-bot@users.noreply.github.com></span><span>"
</span><span> </span><span style="color:#bf616a;">branch</span><span>: </span><span style="color:#a3be8c;">new-item
</span><span> </span><span style="color:#bf616a;">delete-branch</span><span>: </span><span style="color:#d08770;">true
</span><span> </span><span style="color:#bf616a;">title</span><span>: </span><span style="color:#a3be8c;">New item update
</span></code></pre>
<p>Github auto merge workflow file(<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/.github/workflows/auto-merge.yml"><code>.github/workflows/auto-merge.yml</code></a>):</p>
<blockquote>
<p>When a pull request is created, the workflow will automatically merge it if the pull request is from the same author</p>
</blockquote>
<pre data-lang="yaml" style="background-color:#2b303b;color:#c0c5ce;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Auto merge
</span><span style="color:#d08770;">on</span><span>:
</span><span> </span><span style="color:#bf616a;">workflow_dispatch</span><span>:
</span><span> </span><span style="color:#bf616a;">pull_request_target</span><span>:
</span><span style="color:#bf616a;">jobs</span><span>:
</span><span> </span><span style="color:#bf616a;">auto-approve</span><span>:
</span><span> </span><span style="color:#bf616a;">runs-on</span><span>: </span><span style="color:#a3be8c;">ubuntu-latest
</span><span> </span><span style="color:#bf616a;">steps</span><span>:
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Merge
</span><span> </span><span style="color:#bf616a;">if</span><span>: </span><span style="color:#a3be8c;">(github.actor=='theowenyoung') && (startsWith(github.head_ref,'new-item'))
</span><span> </span><span style="color:#bf616a;">uses</span><span>: "</span><span style="color:#a3be8c;">pascalgn/automerge-action@v0.14.3</span><span>"
</span><span> </span><span style="color:#bf616a;">env</span><span>:
</span><span> </span><span style="color:#bf616a;">GITHUB_TOKEN</span><span>: "</span><span style="color:#a3be8c;">${{ secrets.PERSONAL_TOKEN }}</span><span>"
</span><span> </span><span style="color:#bf616a;">MERGE_DELETE_BRANCH</span><span>: </span><span style="color:#d08770;">true
</span><span> </span><span style="color:#bf616a;">MERGE_LABELS</span><span>: ""
</span></code></pre>
<ol start="4">
<li>Add response block to page tempalte</li>
</ol>
<p>This part may take a while to finish, here is <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/3034e093846f4e7e33a957e3169856407b9087c0/templates/page.html#L90-L148">the main page template code</a>, I’ll only show the main part.</p>
<p>Use <a rel="noopener nofollow noreferrer" target="_blank" href="https://www.getzola.org/documentation/templates/overview/#load-data">Zola load_data function</a> to load the webmention data JSON file.</p>
<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>{% set current_webmention_file_name = current_path | trim_end_matches(pat="/")
</span><span>%} {% set webmention_data =
</span><span>load_data(path="webmentions"~current_webmention_file_name~".json",required=false)
</span><span>%}
</span></code></pre>
<p>Show response:</p>
<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>{% if webmention_data %} {% set_global mentions = [] %} {% for ignored, item in
</span><span>webmention_data %} {% set_global mentions = mentions | concat(with=item) %} {%
</span><span>endfor %}
</span><span><</span><span style="color:#bf616a;">p </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">muted text-sm</span><span>">
</span><span> {{trans(key="label_response_description",lang=lang)| markdown(inline=true) |
</span><span> safe}}
</span><span></</span><span style="color:#bf616a;">p</span><span>>
</span><span>{% for type, items in mentions | group_by(attribute="wm-property") %}
</span><span><</span><span style="color:#bf616a;">h3</span><span>>
</span><span> {{trans(key="label_"~type,lang=lang)}} ({{items | length}})<</span><span style="color:#bf616a;">a
</span><span> </span><span style="color:#d08770;">href</span><span>="</span><span style="color:#a3be8c;">#{{type}}</span><span>"
</span><span> </span><span style="color:#8fa1b3;">id</span><span>="</span><span style="color:#a3be8c;">{{type}}</span><span>"
</span><span> </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">zola-anchor</span><span>"
</span><span> >🔗</</span><span style="color:#bf616a;">a
</span><span> >
</span><span></</span><span style="color:#bf616a;">h3</span><span>>
</span><span>{% if type == 'like-of' or type == "repost-of" or type == "bookmark-of" or type
</span><span>== "follow-of" %}
</span><span><</span><span style="color:#bf616a;">ul </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">list-none flex items-center flex-wrap</span><span>">
</span><span> {% for item in items %} {{ macro::webmention(type=type, item=item) }} {%
</span><span> endfor %}
</span><span></</span><span style="color:#bf616a;">ul</span><span>>
</span><span>{% else %}
</span><span><</span><span style="color:#bf616a;">ul </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">list-none</span><span>">
</span><span> {% for item in items %} {{ macro::webmention(type=type, item=item) }} {%
</span><span> endfor %}
</span><span></</span><span style="color:#bf616a;">ul</span><span>>
</span><span>{% endif %} {% endfor %} {% endif %}
</span></code></pre>
<p>Then, I add a webmention form, so if people want to submit their mentions, they can directly submit it.</p>
<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span><</span><span style="color:#bf616a;">form
</span><span> </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">webmention-form</span><span>"
</span><span> </span><span style="color:#d08770;">action</span><span>="</span><span style="color:#a3be8c;">https://webmention.io/www.owenyoung.com/webmention</span><span>"
</span><span> </span><span style="color:#d08770;">method</span><span>="</span><span style="color:#a3be8c;">post</span><span>"
</span><span>>
</span><span> <</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">flex items-center flex-wrap pb</span><span>">
</span><span> <</span><span style="color:#bf616a;">input </span><span style="color:#d08770;">type</span><span>="</span><span style="color:#a3be8c;">url</span><span>" </span><span style="color:#d08770;">name</span><span>="</span><span style="color:#a3be8c;">source</span><span>" </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">flex-3 mr-sm w-full py-sm px-sm</span><span>" />
</span><span> <</span><span style="color:#bf616a;">input </span><span style="color:#d08770;">type</span><span>="</span><span style="color:#a3be8c;">submit</span><span>" </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">px py-sm</span><span>" </span><span style="color:#d08770;">value</span><span>="</span><span style="color:#a3be8c;">Send Webmention</span><span>" />
</span><span> </</span><span style="color:#bf616a;">div</span><span>>
</span><span> <</span><span style="color:#bf616a;">input </span><span style="color:#d08770;">type</span><span>="</span><span style="color:#a3be8c;">hidden</span><span>" </span><span style="color:#d08770;">name</span><span>="</span><span style="color:#a3be8c;">target</span><span>" </span><span style="color:#d08770;">value</span><span>="</span><span style="color:#a3be8c;">{{current_url}}</span><span>" />
</span><span></</span><span style="color:#bf616a;">form</span><span>>
</span></code></pre>
<blockquote>
<p>Optional: I also added an activity page for aggregating all response, the activity template page is <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/templates/activity.html">here</a></p>
</blockquote>
<blockquote>
<p>Cause I don’t have too many mentions, so I use <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/sebastiandedeyne/sebastiandedeyne.com/tree/master/data/webmentions">sebastiandedeyne’s</a> mention data as this article’s webmention data.</p>
</blockquote>
<h2 id="5-send-webmention-when-you-publish-a-new-article">5. Send webmention when you publish a new article<a class="zola-anchor" href="#5-send-webmention-when-you-publish-a-new-article" aria-label="Anchor link for: 5-send-webmention-when-you-publish-a-new-article">🔗</a></h2>
<p>When we publish a new article, we want to send a webmention to the mentioned links. We can do this by using
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/denoflow/denoflow">Denoflow</a> and the
<a rel="noopener nofollow noreferrer" target="_blank" href="https://webmention.app/">Webmention.app</a> API.
<a rel="noopener nofollow noreferrer" target="_blank" href="https://webmention.app/">Webmention.app</a> can check all the mentioned links in
the new article and send all webmentions to them. Before
<a rel="noopener nofollow noreferrer" target="_blank" href="https://webmention.app/">Webmention.app</a> can recognize the mentioned links, we
need to add some extra <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/microformats2">microformats2</a> to
our article html. Basically, it’s some html tag class names. These took me quite
a few time to update my templates, so now I have supported
<a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/h-card">h-card</a>, <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/h-entry">h-entry</a>
and <a rel="noopener nofollow noreferrer" target="_blank" href="https://indieweb.org/h-feed">h-feed</a>. It looks like this:</p>
<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span><</span><span style="color:#bf616a;">article </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">h-entry</span><span>">
</span><span> <</span><span style="color:#bf616a;">h1 </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">p-name</span><span>">Microformats are amazing</</span><span style="color:#bf616a;">h1</span><span>>
</span><span> <</span><span style="color:#bf616a;">p</span><span>>
</span><span> Published by
</span><span> <</span><span style="color:#bf616a;">a </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">p-author h-card</span><span>" </span><span style="color:#d08770;">href</span><span>="</span><span style="color:#a3be8c;">http://example.com</span><span>">W. Developer</</span><span style="color:#bf616a;">a</span><span>> on
</span><span> <</span><span style="color:#bf616a;">time </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">dt-published</span><span>" </span><span style="color:#d08770;">datetime</span><span>="</span><span style="color:#a3be8c;">2013-06-13 12:00:00</span><span>"
</span><span> >13<</span><span style="color:#bf616a;">sup</span><span>>th</</span><span style="color:#bf616a;">sup</span><span>> June 2013</</span><span style="color:#bf616a;">time
</span><span> >
</span><span> </</span><span style="color:#bf616a;">p</span><span>>
</span><span> <</span><span style="color:#bf616a;">p </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">p-summary</span><span>">In which I extoll the virtues of using microformats.</</span><span style="color:#bf616a;">p</span><span>>
</span><span> <</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>="</span><span style="color:#a3be8c;">e-content</span><span>">
</span><span> <</span><span style="color:#bf616a;">p</span><span>>Blah blah blah</</span><span style="color:#bf616a;">p</span><span>>
</span><span> </</span><span style="color:#bf616a;">div</span><span>>
</span><span></</span><span style="color:#bf616a;">article</span><span>>
</span></code></pre>
<p>I have updated <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/templates/page.html">page.html</a>, <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/templates/index.html">index.html</a>, <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/templates/taxonomy_single.html">taxonomy_single.html</a> and <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/templates/section.html">section.html</a> to support these new microformats. I have to say this is the most demanding job, good luck!</p>
<p>Once finished, I went to <a rel="noopener nofollow noreferrer" target="_blank" href="https://indiewebify.me/">indiewebify</a> to test if it can recognize my new microformats. It works!</p>
<p>Next, I went to <a rel="noopener nofollow noreferrer" target="_blank" href="https://webmention.app/token">webmention.app</a> to apply for a token. Then I can add a denoflow workflow file to fetch it’s service every day.</p>
<p>Here is the workflow file(<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog/blob/main/workflows/send-webmention.yml"><code>workflows/send-webmention.yml</code></a>):</p>
<pre data-lang="yaml" style="background-color:#2b303b;color:#c0c5ce;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#bf616a;">sources</span><span>:
</span><span> - </span><span style="color:#bf616a;">from</span><span>: </span><span style="color:#a3be8c;">https://deno.land/x/denoflow@0.0.35/sources/rss.ts
</span><span> </span><span style="color:#bf616a;">args</span><span>:
</span><span> - </span><span style="color:#a3be8c;">https://www.owenyoung.com/blog/atom.xml
</span><span> - </span><span style="color:#bf616a;">from</span><span>: </span><span style="color:#a3be8c;">https://deno.land/x/denoflow@0.0.35/sources/rss.ts
</span><span> </span><span style="color:#bf616a;">args</span><span>:
</span><span> - </span><span style="color:#a3be8c;">https://www.owenyoung.com/en/blog/atom.xml
</span><span style="color:#bf616a;">steps</span><span>:
</span><span> - </span><span style="color:#bf616a;">use</span><span>: </span><span style="color:#a3be8c;">fetch
</span><span> </span><span style="color:#bf616a;">args</span><span>:
</span><span> - </span><span style="color:#a3be8c;">https://webmention.app/check?token=${{ctx.env.WEBMENTION_APP_TOKEN}}&url=${{encodeURIComponent(ctx.item.links[0].href)}}
</span><span> - </span><span style="color:#bf616a;">method</span><span>: </span><span style="color:#a3be8c;">GET
</span><span> </span><span style="color:#bf616a;">headers</span><span>:
</span><span> </span><span style="color:#bf616a;">Content-Type</span><span>: </span><span style="color:#a3be8c;">application/json
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#b48ead;">|
</span><span style="color:#a3be8c;"> console.log(ctx.item.links[0].href);
</span><span style="color:#a3be8c;"> const json = await ctx.result.json();
</span><span style="color:#a3be8c;"> console.log(json);
</span></code></pre>
<p>Don’t forget to add enviroment variables <code>WEBMENTION_APP_TOKEN</code> to your denoflow workflow file(<code>.github/workflows/denoflow.yml</code>).</p>
<pre data-lang="yaml" style="background-color:#2b303b;color:#c0c5ce;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span>- </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">deno run -A https://deno.land/x/denoflow/cli.ts run
</span><span> </span><span style="color:#bf616a;">env</span><span>:
</span><span> </span><span style="color:#bf616a;">WEBMENTION_TOKEN</span><span>: </span><span style="color:#a3be8c;">${{secrets.WEBMENTION_TOKEN}}
</span><span> </span><span style="color:#bf616a;">WEBMENTION_APP_TOKEN</span><span>: </span><span style="color:#a3be8c;">${{secrets.WEBMENTION_APP_TOKEN}}
</span></code></pre>
<h2 id="conclusion">Conclusion<a class="zola-anchor" href="#conclusion" aria-label="Anchor link for: conclusion">🔗</a></h2>
<p>I really like the concept of IndieWeb and their API design philosophy, it took some time but I still think it was worth it. It gave me renewed confidence in the Internet.</p>
<p>You can find the all source code of this blog on <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/blog">Github</a></p>
<h2 id="resources">Resources<a class="zola-anchor" href="#resources" aria-label="Anchor link for: resources">🔗</a></h2>
<ul>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://brid.gy/">Bird.gy</a> - Bridgy connects your web site to social media.
Likes, retweets, mentions, cross-posting</li>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://xn--sr8hvo.ws/">Indie Webring</a></li>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://telegraph.p3k.io/">Telegraph</a> - Easily send Webmentions from your website</li>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://webmention.app/">Webmention.app</a> - Automate your outgoing webmentions</li>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://webmention.io/">Webmention.io</a> - Webmention.io is a hosted service created to easily receive webmentions on any web page.</li>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://fediring.net/">Fediring</a></li>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://sebastiandedeyne.com/webmentions-on-a-static-site-with-github-actions/">Webmentions on a static site with GitHub Actions</a></li>
</ul>
Wiki Links to Markdown Links for VSCode
2022-03-27T00:00:00+00:00
2022-03-27T00:00:00+00:00
https://www.owenyoung.com/en/blog/wiki-links-to-markdown-links-for-vscode/
<p>Recently, I migrated my <a rel="noopener nofollow noreferrer" target="_blank" href="https://wiki.owenyoung.com">Wiki</a> to
<a rel="noopener nofollow noreferrer" target="_blank" href="https://www.getzola.org/">Zola</a>. Zola does not support relative internal links,
it only support <code>@/xxx.md</code> internal link format. This is a bit painful, I posted
a
<a rel="noopener nofollow noreferrer" target="_blank" href="https://zola.discourse.group/t/custom-content-dir-or-support-absolute-internal-link/1242/2">feature request</a>
to Zola. But for now, I will use my own edited version of Zola, this change will
allow me to use <code>/content/xxx.md</code> internal link format.</p>
<span id="continue-reading"></span>
<p>So, the second thing is to input absolute internal link quickly,
<a rel="noopener nofollow noreferrer" target="_blank" href="https://obsidian.md/">Obsidian</a> does have a feature that allows you type wiki
links, then they will be converted to markdown links. I like this feature, but
when I tried <a rel="noopener nofollow noreferrer" target="_blank" href="https://obsidian.md/">Obsidian</a> seconds time, I still can’t use to
it, and it’s also a bit slow compared to VSCode.
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/foambubble/foam">Foam</a> can only auto generate markdown
reference link, but that feature not a one-time task, it sometimes makes
mistakes, like add multiple <code>"</code> at the end of reference zone. So, I decided to
fork foam extension, and turn off the most features that I don’t need, only keep
creating notes from template, and convert wiki links to markdown links.</p>
<p><img src="https://www.owenyoung.com/en/blog/wiki-links-to-markdown-links-for-vscode/./foam-lite.gif" alt="Wiki Links to Markdown Links" /></p>
<p>I’m so lucky, Foam’s code structure is very clean, so I just changed a little
bit to achieve what I wanted. Here is my final result:
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/foam">Foam</a>, if you need this, I also publish
it on
<a rel="noopener nofollow noreferrer" target="_blank" href="https://marketplace.visualstudio.com/items?itemName=theowenyoung.foam-lite-vscode">VSCode marketplace</a>.</p>
Caddy File Server Browser with URL Scheme
2022-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/caddy-file-server-browser-with-url-scheme/
<p>Copy the <a rel="noopener nofollow noreferrer" target="_blank" href="https://gist.github.com/theowenyoung/e09cb6e2c59f247fdc3f4e6fe4401481">Template</a> to your local caddy config folder.</p>
<span id="continue-reading"></span>
<p>This template will display the follow page:
<img src="https://www.owenyoung.com/en/blog/caddy-file-server-browser-with-url-scheme/./caddy-file-template-screenshot.png" alt="screenshot" /></p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">localhost </span><span>{
</span><span> root * /root
</span><span> encode gzip
</span><span> file_server {
</span><span> browse ./file-browser-template-for-caddy.html
</span><span> hide .*
</span><span> }
</span><span>}
</span></code></pre>
Makefile Template
2022-03-26T00:00:00+00:00
2022-04-30T00:00:00+00:00
https://www.owenyoung.com/en/blog/makefile-template/
<p>Common Makefile Template.</p>
<span id="continue-reading"></span><pre data-lang="Makefile" style="background-color:#2b303b;color:#c0c5ce;" class="language-Makefile "><code class="language-Makefile" data-lang="Makefile"><span style="color:#8fa1b3;">.PHONY</span><span>: </span><span style="color:#a3be8c;">start reload stop
</span><span style="color:#8fa1b3;">start</span><span>:
</span><span> </span><span style="color:#bf616a;">systemctl</span><span> start caddy
</span><span style="color:#8fa1b3;">stop</span><span>:
</span><span> </span><span style="color:#bf616a;">systemctl</span><span> stop caddy
</span><span style="color:#8fa1b3;">reload</span><span>:
</span><span> </span><span style="color:#bf616a;">systemctl</span><span> reload caddy
</span></code></pre>
<h2 id="replace-some-line">Replace some line<a class="zola-anchor" href="#replace-some-line" aria-label="Anchor link for: replace-some-line">🔗</a></h2>
<p>`make set count=5</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">set:
</span><span> </span><span style="color:#bf616a;">@test </span><span>$(</span><span style="color:#bf616a;">count</span><span>)
</span><span> </span><span style="color:#bf616a;">@sed -i -e </span><span>'</span><span style="color:#a3be8c;">$(LINE)s/.*/ POST_WEIBO_COUNT: $(count)/</span><span>' docker-compose.yaml
</span><span> </span><span style="color:#bf616a;">@echo</span><span> Change Count to $(</span><span style="color:#bf616a;">count</span><span>) Success! Now ready to restart!
</span><span> </span><span style="color:#bf616a;">docker-compose</span><span> up</span><span style="color:#bf616a;"> -d
</span></code></pre>
Mastdon Development Setup
2022-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/mastdon-setup/
<p>Also see <a rel="noopener nofollow noreferrer" target="_blank" href="https://docs.joinmastodon.org/dev/setup/">dev/setup</a> <a rel="noopener nofollow noreferrer" target="_blank" href="https://docs.joinmastodon.org/admin/install/">admin/install</a></p>
<span id="continue-reading"></span>
<ol>
<li>Install Ruby</li>
</ol>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://www.ruby-lang.org/en/documentation/installation/">Installing Ruby</a> 2. Install Node</p>
<p>See <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/nvm-sh/nvm">nvm</a></p>
<ol start="3">
<li>Install postgres</li>
</ol>
<p><a href="https://www.owenyoung.com/en/blog/postgres-setup-for-debian/">Postgres Setup for Debian</a></p>
<ol start="4">
<li>Install redit</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">brew</span><span> install redis
</span><span style="color:#bf616a;">brew</span><span> services start redis
</span></code></pre>
<ol start="5">
<li>Clone</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">git</span><span> clone git@github.com:mastodon/mastodon.git live
</span></code></pre>
<ol start="6">
<li>Install ruby dependences</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#96b5b4;">cd</span><span> live
</span><span style="color:#bf616a;">bundle</span><span> config set</span><span style="color:#bf616a;"> --local</span><span> with '</span><span style="color:#a3be8c;">development</span><span>'
</span><span style="color:#bf616a;">bundle</span><span> install
</span></code></pre>
<ol start="6">
<li>Install yarn</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">npm</span><span> install</span><span style="color:#bf616a;"> --global</span><span> yarn
</span></code></pre>
<ol start="7">
<li>Install nodejs dependences</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">yarn
</span></code></pre>
<ol>
<li>Init database</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">RAILS_ENV</span><span>=</span><span style="color:#a3be8c;">development </span><span style="color:#bf616a;">bundle</span><span> exec rails db:setup
</span></code></pre>
<ol start="8">
<li>Install foreman for multiple run</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">gem</span><span> install foreman</span><span style="color:#bf616a;"> --no-document
</span></code></pre>
<ol start="9">
<li>Open</li>
</ol>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="http://localhost:3000">http://localhost:3000</a></p>
<p>email: <code>admin@localhost:3000</code>
password: <code>mastodonadmin</code></p>
Mini Reset CSS
2022-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/mini-reset-css/
<blockquote>
<p>Forked by <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/jgthms/minireset.css/blob/master/minireset.css">https://github.com/jgthms/minireset.css/blob/master/minireset.css</a></p>
</blockquote>
<span id="continue-reading"></span><pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#bf616a;">html </span><span>{
</span><span> font-family: "</span><span style="color:#a3be8c;">SF Pro SC</span><span>", "</span><span style="color:#a3be8c;">SF Pro Text</span><span>", "</span><span style="color:#a3be8c;">SF Pro Icons</span><span>", "</span><span style="color:#a3be8c;">PingFang SC</span><span>",
</span><span> "</span><span style="color:#a3be8c;">Helvetica Neue</span><span>", "</span><span style="color:#a3be8c;">Helvetica</span><span>", "</span><span style="color:#a3be8c;">Arial</span><span>", sans-serif;
</span><span>}
</span><span style="color:#bf616a;">body </span><span>{
</span><span> overflow-wrap: break-word;
</span><span> word-wrap: break-word;
</span><span> -ms-word-break: break-all;
</span><span> word-break: break-word;
</span><span> -ms-hyphens: auto;
</span><span> -moz-hyphens: auto;
</span><span> -webkit-hyphens: auto;
</span><span> hyphens: auto;
</span><span>}
</span><span style="color:#bf616a;">html</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">body</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">p</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">dt</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">dd</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">blockquote</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">figure</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">fieldset</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">legend</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">textarea</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">pre</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">iframe</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">hr</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">h1</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">h2</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">h3</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">h4</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">h5</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">h6 </span><span>{
</span><span> margin: </span><span style="color:#d08770;">0</span><span>;
</span><span> padding: </span><span style="color:#d08770;">0</span><span>;
</span><span>}
</span><span>
</span><span style="color:#bf616a;">h1</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">h2</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">h3</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">h4</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">h5</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">h6 </span><span>{
</span><span> font-size: </span><span style="color:#d08770;">100%</span><span>;
</span><span> font-weight: normal;
</span><span>}
</span><span>
</span><span style="color:#bf616a;">button</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">input</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">select </span><span>{
</span><span> margin: </span><span style="color:#d08770;">0</span><span>;
</span><span>}
</span><span>
</span><span style="color:#bf616a;">html </span><span>{
</span><span> box-sizing: border-box;
</span><span>}
</span><span>
</span><span style="color:#bf616a;">*</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">*</span><span style="color:#8fa1b3;">::</span><span style="color:#b48ead;">before,
</span><span style="color:#bf616a;">*</span><span style="color:#8fa1b3;">::</span><span style="color:#b48ead;">after </span><span>{
</span><span> box-sizing: inherit;
</span><span>}
</span><span>
</span><span style="color:#bf616a;">img</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">video </span><span>{
</span><span> height: auto;
</span><span> max-width: </span><span style="color:#d08770;">100%</span><span>;
</span><span>}
</span><span>
</span><span style="color:#bf616a;">iframe </span><span>{
</span><span> border: </span><span style="color:#d08770;">0</span><span>;
</span><span>}
</span><span>
</span><span style="color:#bf616a;">table </span><span>{
</span><span> border-collapse: collapse;
</span><span> border-spacing: </span><span style="color:#d08770;">0</span><span>;
</span><span>}
</span><span>
</span><span style="color:#bf616a;">td</span><span style="color:#b48ead;">,
</span><span style="color:#bf616a;">th </span><span>{
</span><span> padding: </span><span style="color:#d08770;">0</span><span>;
</span><span>}
</span></code></pre>
Nodejs Monorepo
2022-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/nodejs-monorepo/
<p>Use changesets as monorepo mananger.</p>
<span id="continue-reading"></span><h2 id="tools">Tools<a class="zola-anchor" href="#tools" aria-label="Anchor link for: tools">🔗</a></h2>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/atlassian/changesets">GitHub - atlassian/changesets: 🦋 A way to manage your versioning and changelogs with a focus on monorepos</a></p>
<h2 id="use-changesets-as-monorepo-manager">Use Changesets as monorepo manager<a class="zola-anchor" href="#use-changesets-as-monorepo-manager" aria-label="Anchor link for: use-changesets-as-monorepo-manager">🔗</a></h2>
<h3 id="init-a-monorepo">Init a monorepo<a class="zola-anchor" href="#init-a-monorepo" aria-label="Anchor link for: init-a-monorepo">🔗</a></h3>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">mkdir</span><span> monorepo && </span><span style="color:#96b5b4;">cd</span><span> monorepo
</span><span style="color:#bf616a;">git</span><span> init
</span><span style="color:#65737e;"># Add .gitignore file for nodejs <https://github.com/github/gitignore/blob/master/Node.gitignore>
</span><span style="color:#bf616a;">npm</span><span> init</span><span style="color:#bf616a;"> --yes
</span></code></pre>
<p>Add <code>"private":"true"</code> to the root <code>package.json</code></p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">npm</span><span> init</span><span style="color:#bf616a;"> -w</span><span> packages/a
</span><span style="color:#bf616a;">npm</span><span> init</span><span style="color:#bf616a;"> -w</span><span> packages/b
</span><span style="color:#bf616a;">npm</span><span> init</span><span style="color:#bf616a;"> -w</span><span> packages/c
</span></code></pre>
<p>Let <code>c</code> depends <code>a</code> and <code>b</code>,</p>
<p>Add</p>
<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>"</span><span style="color:#a3be8c;">dependencies</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">a</span><span>":"</span><span style="color:#a3be8c;">^1.0.0</span><span>",
</span><span> "</span><span style="color:#a3be8c;">b</span><span>":"</span><span style="color:#a3be8c;">^1.0.0</span><span>"
</span><span>}
</span></code></pre>
<p>to <code>packages/c/package.json</code></p>
<h3 id="install-changesets">Install changesets<a class="zola-anchor" href="#install-changesets" aria-label="Anchor link for: install-changesets">🔗</a></h3>
<p>Also see <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/atlassian/changesets/blob/main/docs/intro-to-using-changesets.md">here</a></p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">npm</span><span> install</span><span style="color:#bf616a;"> -D</span><span> @changesets/cli && </span><span style="color:#bf616a;">npx</span><span> changeset init
</span></code></pre>
<p>This action will add a <code>.changeset</code> folder, and a couple of files to help you out:</p>
<p>You should change the <code>.changeset/config.json</code> -> <code>baseBranch</code> to yourself main branch, also change <code>access</code> to <code>public</code></p>
<p>Commit current files.</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">git</span><span> add .
</span><span style="color:#bf616a;">git</span><span> commit</span><span style="color:#bf616a;"> -m </span><span>"</span><span style="color:#a3be8c;">feat: init</span><span>"
</span></code></pre>
<p>That’s done.</p>
<h3 id="usage">Usage<a class="zola-anchor" href="#usage" aria-label="Anchor link for: usage">🔗</a></h3>
<h4 id="first-publish">First publish<a class="zola-anchor" href="#first-publish" aria-label="Anchor link for: first-publish">🔗</a></h4>
<p>First publish you should just use the follow command to publish your module</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">npx</span><span> changeset publish
</span></code></pre>
<h4 id="future-changes">Future changes<a class="zola-anchor" href="#future-changes" aria-label="Anchor link for: future-changes">🔗</a></h4>
<p>You should see <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/atlassian/changesets/blob/main/docs/detailed-explanation.md">changesets philosophy</a></p>
<p>You should first create an <code>intent to change</code>, that said, before what ever changes you want to make, you should create a <code>intent change</code></p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">npx</span><span> changeset
</span></code></pre>
<p>…Make some changes</p>
<p>Now, generate new version, changeset will take care your dependences,</p>
<blockquote>
<p>Note, by default, if <code>a</code> upgrade from <code>1.0.1</code> to <code>1.0.2</code>, <code>c</code> depends on <code>a@^1.0.1</code>, then <code>c</code> <code>package.json</code> will not change, cause npm will auto update <code>a@^1.0.1</code> to the last version <code>1.0.2</code>
if you want change to the exact version every time, you can set the config to</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span> "</span><span style="color:#a3be8c;">___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH</span><span>"</span><span style="color:#bf616a;">: </span><span>{
</span><span> "</span><span style="color:#a3be8c;">updateInternalDependents</span><span>": "</span><span style="color:#a3be8c;">always</span><span>"
</span><span> }
</span></code></pre>
</blockquote>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">npx</span><span> changeset version
</span></code></pre>
<p>Then, you can publish it.</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">npx</span><span> changeset publish
</span></code></pre>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">git</span><span> add .
</span><span style="color:#bf616a;">git</span><span> commit</span><span style="color:#bf616a;"> -m </span><span>"</span><span style="color:#a3be8c;">chore: version</span><span>"
</span><span style="color:#bf616a;">git</span><span> push</span><span style="color:#bf616a;"> --follow-tags
</span></code></pre>
<h2 id="with-ci">With CI<a class="zola-anchor" href="#with-ci" aria-label="Anchor link for: with-ci">🔗</a></h2>
<p>I’ll use Github Actions as example.</p>
<p>The CI workflow assume that you use <a href="https://www.owenyoung.com/dev-tips/#git-tips">开发技巧收藏</a> as your git workflow.</p>
<blockquote>
<p>Note, you can use <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/atlassian/changesets/tree/main/packages/changelog-github"><code>@changesets/changelog-github</code></a> as your changelog format log. with this, you can generate a log like <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/monorepo-example/releases/tag/%40theowenyoung%2Fpackage-example-b%401.1.0">this</a> , without this, then the log will only include <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/monorepo-example/releases/tag/%40theowenyoung%2Fpackage-example-c%401.1.2">commit link</a> > <code>npm i -D @changesets/changelog-github</code></p>
<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
</span><span> "</span><span style="color:#a3be8c;">changelog</span><span>": [
</span><span> "</span><span style="color:#a3be8c;">@changesets/changelog-github</span><span>",
</span><span> { "</span><span style="color:#a3be8c;">repo</span><span>": "</span><span style="color:#a3be8c;">theowenyoung/monorepo-example</span><span>" }
</span><span> ]
</span><span>}
</span></code></pre>
</blockquote>
<ol>
<li>Create a script in root <code>package.json</code></li>
</ol>
<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
</span><span> "</span><span style="color:#a3be8c;">scripts</span><span>": {
</span><span> "</span><span style="color:#a3be8c;">version-packages</span><span>": "</span><span style="color:#a3be8c;">changeset version</span><span>",
</span><span> "</span><span style="color:#a3be8c;">release</span><span>": "</span><span style="color:#a3be8c;">changeset publish</span><span>"
</span><span> }
</span><span>}
</span></code></pre>
<ol start="2">
<li>Create github actions workflows</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">mkdir -p</span><span> .github/workflows
</span></code></pre>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">touch</span><span> .github/workflows/release.yml
</span></code></pre>
<p>With the following content:</p>
<pre data-lang="yaml" style="background-color:#2b303b;color:#c0c5ce;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Release
</span><span>
</span><span style="color:#d08770;">on</span><span>:
</span><span> </span><span style="color:#bf616a;">workflow_dispatch</span><span>:
</span><span> </span><span style="color:#bf616a;">push</span><span>:
</span><span> </span><span style="color:#bf616a;">branches</span><span>:
</span><span> - </span><span style="color:#a3be8c;">main
</span><span>
</span><span style="color:#bf616a;">jobs</span><span>:
</span><span> </span><span style="color:#bf616a;">release</span><span>:
</span><span> </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Release
</span><span> </span><span style="color:#bf616a;">runs-on</span><span>: </span><span style="color:#a3be8c;">ubuntu-latest
</span><span> </span><span style="color:#bf616a;">steps</span><span>:
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Checkout Repo
</span><span> </span><span style="color:#bf616a;">uses</span><span>: </span><span style="color:#a3be8c;">actions/checkout@master
</span><span> </span><span style="color:#bf616a;">with</span><span>:
</span><span> </span><span style="color:#65737e;"># This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
</span><span> </span><span style="color:#bf616a;">fetch-depth</span><span>: </span><span style="color:#d08770;">0
</span><span>
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Setup Node.js 12.x
</span><span> </span><span style="color:#bf616a;">uses</span><span>: </span><span style="color:#a3be8c;">actions/setup-node@master
</span><span> </span><span style="color:#bf616a;">with</span><span>:
</span><span> </span><span style="color:#bf616a;">node-version</span><span>: </span><span style="color:#a3be8c;">12.x
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Setup NPM Latest
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">npm i -g npm
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Install Dependencies
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">npm i
</span><span>
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Create Release Pull Request or Publish to npm
</span><span> </span><span style="color:#bf616a;">uses</span><span>: </span><span style="color:#a3be8c;">changesets/action@master
</span><span> </span><span style="color:#bf616a;">with</span><span>:
</span><span> </span><span style="color:#65737e;"># this expects you to have a script called release which does a build for your packages and calls changeset publish
</span><span> </span><span style="color:#bf616a;">publish</span><span>: </span><span style="color:#a3be8c;">npm run release
</span><span> </span><span style="color:#bf616a;">version</span><span>: </span><span style="color:#a3be8c;">npm run version-packages
</span><span> </span><span style="color:#bf616a;">env</span><span>:
</span><span> </span><span style="color:#bf616a;">GITHUB_TOKEN</span><span>: </span><span style="color:#a3be8c;">${{ secrets.GITHUB_TOKEN }}
</span><span> </span><span style="color:#bf616a;">NPM_TOKEN</span><span>: </span><span style="color:#a3be8c;">${{ secrets.NPM_TOKEN }}
</span></code></pre>
<ol start="3">
<li>Add <code>NPM_TOKEN</code> to your github repo secret settings</li>
</ol>
<p>By <a href="https://www.owenyoung.com/dev-tips/">开发技巧收藏</a></p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">npm</span><span> token create
</span></code></pre>
<p>Done.</p>
<p>Every time you use <code>npx changeset</code> to generate a new changeset intent, and the change is pulled to the <code>main</code> branch, then CI will create a pull request to generate a new version, and after the pull request was merged, CI will publish npm packages, and create a new release.</p>
Rclone Setup
2022-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/rclone-setup/
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://rclone.org/">Rclone</a> setup.</p>
<span id="continue-reading"></span><h2 id="install">Install<a class="zola-anchor" href="#install" aria-label="Anchor link for: install">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">curl</span><span> https://rclone.org/install.sh | </span><span style="color:#bf616a;">sudo</span><span> bash
</span></code></pre>
<p>Also see <a rel="noopener nofollow noreferrer" target="_blank" href="https://rclone.org/drive/">here</a></p>
<h2 id="transfer">Transfer<a class="zola-anchor" href="#transfer" aria-label="Anchor link for: transfer">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">cat ~</span><span>/.config/rclone
</span></code></pre>
<p>Copy that to another machine.</p>
<h2 id="web-ui">Web UI<a class="zola-anchor" href="#web-ui" aria-label="Anchor link for: web-ui">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">rclone</span><span> rcd</span><span style="color:#bf616a;"> --rc-web-gui --rc-user</span><span>=test</span><span style="color:#bf616a;"> --rc-pass</span><span>=test</span><span style="color:#bf616a;"> --rc-web-gui-update --stats</span><span>=24h</span><span style="color:#bf616a;"> --rc-enable-metrics --rc-web-gui-no-open-browser
</span></code></pre>
FileBrowser Setup
2022-02-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/file-browser-setup/
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/filebrowser/filebrowser">File Browser</a> Setup.</p>
<span id="continue-reading"></span><h2 id="install">Install<a class="zola-anchor" href="#install" aria-label="Anchor link for: install">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">curl -fsSL</span><span> https://raw.githubusercontent.com/filebrowser/get/master/get.sh | </span><span style="color:#bf616a;">bash
</span></code></pre>
<h2 id="systemd">Systemd<a class="zola-anchor" href="#systemd" aria-label="Anchor link for: systemd">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> vim /etc/systemd/system/filebrowser.service
</span></code></pre>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">[Unit]
</span><span style="color:#bf616a;">Description</span><span>=</span><span style="color:#a3be8c;">File </span><span style="color:#bf616a;">browser: </span><span>%</span><span style="color:#bf616a;">I
</span><span style="color:#bf616a;">After</span><span>=</span><span style="color:#a3be8c;">network.target
</span><span>
</span><span style="color:#bf616a;">[Service]
</span><span style="color:#bf616a;">User</span><span>=</span><span style="color:#a3be8c;">www-data
</span><span style="color:#bf616a;">Group</span><span>=</span><span style="color:#a3be8c;">www-data
</span><span style="color:#bf616a;">ExecStart</span><span>=</span><span style="color:#a3be8c;">/usr/local/sbin/filebrowser </span><span style="color:#bf616a;">-c</span><span> /etc/filebrowser/%</span><span style="color:#bf616a;">I -d</span><span> /etc/filebrowser/filebrowser.db
</span><span>
</span><span style="color:#bf616a;">[Install]
</span><span style="color:#bf616a;">WantedBy</span><span>=</span><span style="color:#a3be8c;">multi-user.target
</span></code></pre>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> systemctl daemon-reload
</span><span style="color:#bf616a;">sudo</span><span> systemctl enable</span><span style="color:#bf616a;"> --now</span><span> filebrowser
</span></code></pre>
Bazarr Setup
2021-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/bazarr-setup/
<p>Bazarr setup.</p>
<span id="continue-reading"></span><h2 id="install">Install<a class="zola-anchor" href="#install" aria-label="Anchor link for: install">🔗</a></h2>
<p>See also <a rel="noopener nofollow noreferrer" target="_blank" href="https://wiki.bazarr.media/Getting-Started/Installation/Linux/linux/">here</a></p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> apt-get install python3-pip python3-distutils</span><span style="color:#bf616a;"> -y
</span><span style="color:#bf616a;">wget</span><span> https://github.com/morpheus65535/bazarr/releases/latest/download/bazarr.zip
</span><span style="color:#bf616a;">unzip</span><span> bazarr.zip</span><span style="color:#bf616a;"> -d ~</span><span>/bazarr
</span><span style="color:#96b5b4;">cd</span><span> bazarr
</span><span style="color:#bf616a;">python3 -m</span><span> pip install</span><span style="color:#bf616a;"> -r</span><span> requirements.txt
</span></code></pre>
<h3 id="setup-as-system-service">Setup as system service<a class="zola-anchor" href="#setup-as-system-service" aria-label="Anchor link for: setup-as-system-service">🔗</a></h3>
<p>Reference at <a rel="noopener nofollow noreferrer" target="_blank" href="https://wiki.bazarr.media/Getting-Started/Autostart/Linux/linux/">here</a></p>
<p>You have to create a <code>bazarr.service</code> file in <code>/etc/systemd/system</code> that would contain the following text:</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> vim /etc/systemd/system/bazarr.service
</span></code></pre>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">[Unit]
</span><span style="color:#bf616a;">Description</span><span>=</span><span style="color:#a3be8c;">Bazarr </span><span style="color:#bf616a;">Daemon
</span><span style="color:#bf616a;">After</span><span>=</span><span style="color:#a3be8c;">syslog.target </span><span style="color:#bf616a;">network.target
</span><span>
</span><span style="color:#65737e;"># After=syslog.target network.target sonarr.service radarr.service
</span><span>
</span><span style="color:#bf616a;">[Service]
</span><span style="color:#bf616a;">WorkingDirectory</span><span>=</span><span style="color:#a3be8c;">/home/green/bazarr/
</span><span style="color:#bf616a;">User</span><span>=</span><span style="color:#a3be8c;">green
</span><span style="color:#bf616a;">Group</span><span>=</span><span style="color:#a3be8c;">admin
</span><span style="color:#bf616a;">UMask</span><span>=</span><span style="color:#a3be8c;">0002
</span><span style="color:#bf616a;">Restart</span><span>=</span><span style="color:#a3be8c;">on-failure
</span><span style="color:#bf616a;">RestartSec</span><span>=</span><span style="color:#a3be8c;">5
</span><span style="color:#bf616a;">Type</span><span>=</span><span style="color:#a3be8c;">simple
</span><span style="color:#bf616a;">ExecStart</span><span>=</span><span style="color:#a3be8c;">/usr/bin/python3 </span><span style="color:#bf616a;">/home/green/bazarr/bazarr.py
</span><span style="color:#bf616a;">KillSignal</span><span>=</span><span style="color:#a3be8c;">SIGINT
</span><span style="color:#bf616a;">TimeoutStopSec</span><span>=</span><span style="color:#a3be8c;">20
</span><span style="color:#bf616a;">SyslogIdentifier</span><span>=</span><span style="color:#a3be8c;">bazarr
</span><span style="color:#bf616a;">ExecStartPre</span><span>=</span><span style="color:#a3be8c;">/bin/sleep </span><span style="color:#bf616a;">30
</span><span>
</span><span style="color:#bf616a;">[Install]
</span><span style="color:#bf616a;">WantedBy</span><span>=</span><span style="color:#a3be8c;">multi-user.target
</span></code></pre>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> systemctl daemon-reload
</span><span style="color:#bf616a;">sudo</span><span> systemctl enable</span><span style="color:#bf616a;"> --now</span><span> bazarr
</span><span style="color:#bf616a;">sudo</span><span> systemctl status bazarr
</span><span style="color:#bf616a;">sudo</span><span> systemctl restart bazarr
</span><span>
</span></code></pre>
<h2 id="configure">Configure<a class="zola-anchor" href="#configure" aria-label="Anchor link for: configure">🔗</a></h2>
<p>You can set your password at the UI.</p>
<h3 id="sonarr">Sonarr<a class="zola-anchor" href="#sonarr" aria-label="Anchor link for: sonarr">🔗</a></h3>
<p>Enabled, See <a rel="noopener nofollow noreferrer" target="_blank" href="https://wiki.bazarr.media/Getting-Started/Setup-Guide/#sonarr">here</a></p>
<h3 id="provider">Provider<a class="zola-anchor" href="#provider" aria-label="Anchor link for: provider">🔗</a></h3>
<p>Add Zimuku, assrt, Opensubtitle</p>
<h3 id="language">Language<a class="zola-anchor" href="#language" aria-label="Anchor link for: language">🔗</a></h3>
<p>See <a rel="noopener nofollow noreferrer" target="_blank" href="https://wiki.bazarr.media/Getting-Started/Setup-Guide/#languages">here</a></p>
<p>Add default settings, see <a rel="noopener nofollow noreferrer" target="_blank" href="https://wiki.bazarr.media/Getting-Started/Setup-Guide/#default-settings">here</a></p>
Caddy2 Setup for Debian
2021-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/caddy2-setup-for-debian/
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://caddyserver.com/v2">Official Site</a></p>
<span id="continue-reading"></span><h2 id="install">Install<a class="zola-anchor" href="#install" aria-label="Anchor link for: install">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> apt install</span><span style="color:#bf616a;"> -y</span><span> debian-keyring debian-archive-keyring apt-transport-https
</span><span style="color:#bf616a;">curl -1sLf </span><span>'</span><span style="color:#a3be8c;">https://dl.cloudsmith.io/public/caddy/stable/gpg.key</span><span>' | </span><span style="color:#bf616a;">sudo</span><span> tee /etc/apt/trusted.gpg.d/caddy-stable.asc
</span><span style="color:#bf616a;">curl -1sLf </span><span>'</span><span style="color:#a3be8c;">https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt</span><span>' | </span><span style="color:#bf616a;">sudo</span><span> tee /etc/apt/sources.list.d/caddy-stable.list
</span><span style="color:#bf616a;">sudo</span><span> apt update
</span><span style="color:#bf616a;">sudo</span><span> apt install caddy
</span></code></pre>
<p>Once installed, caddy is running.</p>
<p>The default config file at <code>/etc/caddy/Caddyfile</code></p>
<h2 id="resources">Resources<a class="zola-anchor" href="#resources" aria-label="Anchor link for: resources">🔗</a></h2>
<ul>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/greenpau/caddy-auth-portal">GitHub - greenpau/caddy-auth-portal: Authentication Plugin for Caddy v2 implementing Form-Based, Basic, Local, LDAP, OpenID Connect, OAuth 2.0 (Github, Google, Facebook, Okta, etc.), SAML Authentication</a></li>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/greenpau/caddy-auth-jwt">GitHub - greenpau/caddy-auth-jwt: JWT Authorization Plugin for Caddy v2</a></li>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://caddyserver.com/docs/caddyfile/directives/basicauth#basicauth">basicauth (Caddyfile directive) — Caddy Documentation</a></li>
</ul>
Debian Server Setup
2021-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/debian-server-setup/
<blockquote>
<p>For linux common commands, see <a href="https://www.owenyoung.com/dev-tips/#linux-common-commands">开发技巧收藏</a></p>
</blockquote>
<span id="continue-reading"></span>
<ol>
<li>
<p>Update Source</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">apt</span><span> update</span><span style="color:#bf616a;"> --yes </span><span>&& </span><span style="color:#bf616a;">apt</span><span> upgrade</span><span style="color:#bf616a;"> --yes
</span></code></pre>
</li>
<li>
<p>Install sudo package</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">apt</span><span> install</span><span style="color:#bf616a;"> -y</span><span> sudo
</span></code></pre>
</li>
<li>
<p>Open BBR: <a href="https://www.owenyoung.com/en/blog/open-bbr-for-debian/">Open BBR for Debian</a></p>
</li>
<li>
<p>Use ssh key login instead of password, for more details see <a rel="noopener nofollow noreferrer" target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-configure-ssh-key-based-authentication-on-a-linux-server">How To Configure SSH Key-Based Authentication on a Linux Server</a></p>
</li>
<li>
<p>In your local machine:</p>
</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">ssh-copy-id -i ~</span><span>/.ssh/id_rsa.pub username@remote_host
</span></code></pre>
<blockquote>
<p>This will copy your id_rsa.pub to remote machine <code>~/.ssh/authorized_keys</code></p>
</blockquote>
<ol start="3">
<li>
<p>Change the default password</p>
<ol>
<li><code>passwd</code></li>
</ol>
</li>
<li>
<p>Create an admin group</p>
<ol>
<li><code>groupadd admin</code></li>
<li>Give admin group root access, and no password sudo
<ol>
<li>Backup sudo files <code>cp /etc/sudoers /root/sudoers.bak</code></li>
<li>Edit the <code>/etc/sudoers</code> file by typing the command: <code>visudo</code></li>
<li>Add <code>%admin ALL=(ALL:ALL) NOPASSWD:ALL</code> after <code>sudo</code> group</li>
</ol>
</li>
</ol>
</li>
<li>
<p>Create a admin group user</p>
<ol>
<li><code>useradd username -g admin</code></li>
</ol>
</li>
<li>
<p>Use ssh key login instead of password for normal user</p>
<ol>
<li><code>ssh-copy-id -i ~/.ssh/id_rsa.pub username@remote_host</code></li>
</ol>
</li>
<li>
<p>Login with new normal user <code>ssh username@ip</code></p>
</li>
<li>
<p>Install common utils:</p>
<ol>
<li><code>sudo apt install build-essential software-properties-common curl vim unzip git pkg-config libssl-dev --yes</code></li>
</ol>
</li>
<li>
<p>Create <code>/data</code> for store data files with <code>777</code> permissions, avoid to use <code>/home</code> directory</p>
</li>
</ol>
<p>You might want to read also:</p>
<ul>
<li><a href="https://www.owenyoung.com/en/blog/shadowsocks-rust-setup-for-debian/">Shadowsocks Rust Setup for Debian</a></li>
<li><a href="https://www.owenyoung.com/en/blog/caddy2-setup-for-debian/">Caddy2 Setup for Debian</a></li>
</ul>
How to check if there are uncommited changes in Github Actions?
2021-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/how-to-check-if-there-are-uncommited-changes-in-github/
<p>Check if there are uncommited changes in Github Actions workflow file.</p>
<span id="continue-reading"></span><pre data-lang="yaml" style="background-color:#2b303b;color:#c0c5ce;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Github Actions check if there are uncommited changes
</span><span style="color:#d08770;">on</span><span>:
</span><span> </span><span style="color:#bf616a;">repository_dispatch</span><span>:
</span><span> </span><span style="color:#bf616a;">workflow_dispatch</span><span>:
</span><span> </span><span style="color:#bf616a;">push</span><span>:
</span><span> </span><span style="color:#bf616a;">branches</span><span>:
</span><span> - </span><span style="color:#a3be8c;">main
</span><span style="color:#bf616a;">jobs</span><span>:
</span><span> </span><span style="color:#bf616a;">test</span><span>:
</span><span> </span><span style="color:#bf616a;">runs-on</span><span>: </span><span style="color:#a3be8c;">ubuntu-latest
</span><span> </span><span style="color:#bf616a;">steps</span><span>:
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">Check out repository code
</span><span> </span><span style="color:#bf616a;">uses</span><span>: </span><span style="color:#a3be8c;">actions/checkout@v2
</span><span> - </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">touch index.html
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">chown
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">sudo chown -R $USER:$USER ./
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">git config
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">git config --global user.name "github-actions[bot]" && git config --global user.email github-actions-bot@users.noreply.github.com
</span><span> - </span><span style="color:#bf616a;">name</span><span>: </span><span style="color:#a3be8c;">git add
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">git add .
</span><span> - </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">git status
</span><span> - </span><span style="color:#bf616a;">id</span><span>: </span><span style="color:#a3be8c;">isChanged
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">git diff-index --cached --quiet HEAD || echo '::set-output name=changed::true'
</span><span> - </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">echo ${{ steps.isChanged.outputs.changed }}
</span><span> - </span><span style="color:#bf616a;">if</span><span>: </span><span style="color:#a3be8c;">${{ steps.isChanged.outputs.changed == 'true' }}
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">echo 'yes, changed'
</span><span> - </span><span style="color:#bf616a;">if</span><span>: </span><span style="color:#a3be8c;">${{ steps.isChanged.outputs.changed != 'true' }}
</span><span> </span><span style="color:#bf616a;">run</span><span>: </span><span style="color:#a3be8c;">echo 'no any change'
</span></code></pre>
Postgres Setup for Debian
2021-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/postgres-setup-for-debian/
<p>Postgres Setup for Debian.</p>
<span id="continue-reading"></span><h2 id="install">Install<a class="zola-anchor" href="#install" aria-label="Anchor link for: install">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> sh</span><span style="color:#bf616a;"> -c </span><span>'</span><span style="color:#a3be8c;">echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list</span><span>'
</span><span style="color:#bf616a;">wget --quiet -O</span><span> - https://www.postgresql.org/media/keys/ACCC4CF8.asc | </span><span style="color:#bf616a;">sudo</span><span> apt-key add -
</span><span style="color:#bf616a;">sudo</span><span> apt-get update
</span><span style="color:#bf616a;">sudo</span><span> apt-get</span><span style="color:#bf616a;"> -y</span><span> install postgresql
</span></code></pre>
<h3 id="optional">Optional<a class="zola-anchor" href="#optional" aria-label="Anchor link for: optional">🔗</a></h3>
<p>Install postgis</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> apt</span><span style="color:#bf616a;"> -y</span><span> install postgis
</span></code></pre>
qBittorrent setup for Debian
2021-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/qbittorrent-setup-for-debian/
<p>Because there is no official ppa of qBittorrent for debian, so we use a <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/userdocs/qbittorrent-nox-static">third party service - qbittorrent-nox-static</a> to compile qb.</p>
<span id="continue-reading"></span><h2 id="install">Install<a class="zola-anchor" href="#install" aria-label="Anchor link for: install">🔗</a></h2>
<ol>
<li>Download script</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">wget -qO ~</span><span>/qbittorrent-nox-static.sh https://git.io/qbstatic
</span><span style="color:#bf616a;">chmod</span><span> +x </span><span style="color:#bf616a;">~</span><span>/qbittorrent-nox-static.sh
</span><span style="color:#65737e;"># pre
</span><span style="color:#bf616a;">sudo ~</span><span>/qbittorrent-nox-static.sh
</span><span style="color:#65737e;"># build
</span><span style="color:#bf616a;">~/qbittorrent-nox-static.sh</span><span> all
</span><span style="color:#65737e;"># install
</span><span style="color:#bf616a;">sudo ~</span><span>/qbittorrent-nox-static.sh install
</span></code></pre>
<h2 id="configure">Configure<a class="zola-anchor" href="#configure" aria-label="Anchor link for: configure">🔗</a></h2>
<p>Read more at <a rel="noopener nofollow noreferrer" target="_blank" href="https://userdocs.github.io/qbittorrent-nox-static/#/install-qbittorrent?id=configuring-qbittorrent">qbittorrent-nox configure</a></p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">vim ~</span><span>/.config/qBittorrent/qBittorrent.conf
</span></code></pre>
<p>With follow config:</p>
<pre data-lang="ini" style="background-color:#2b303b;color:#c0c5ce;" class="language-ini "><code class="language-ini" data-lang="ini"><span style="color:#b48ead;">[LegalNotice]
</span><span style="color:#bf616a;">Accepted</span><span>=</span><span style="color:#d08770;">true
</span><span>
</span><span style="color:#b48ead;">[Preferences]
</span><span style="color:#bf616a;">WebUI</span><span>\Port=</span><span style="color:#d08770;">8080
</span><span style="color:#bf616a;">WebUI</span><span>\HostHeaderValidation=</span><span style="color:#d08770;">false
</span></code></pre>
<h2 id="systemd-service">Systemd service<a class="zola-anchor" href="#systemd-service" aria-label="Anchor link for: systemd-service">🔗</a></h2>
<p>Reference at <a rel="noopener nofollow noreferrer" target="_blank" href="https://userdocs.github.io/qbittorrent-nox-static/#/systemd">here</a></p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#65737e;"># create a user
</span><span style="color:#bf616a;">sudo</span><span> vim /etc/systemd/system/qbittorrent.service
</span></code></pre>
<p>Config:</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">[Unit]
</span><span style="color:#bf616a;">Description</span><span>=</span><span style="color:#a3be8c;">qBittorrent-nox </span><span style="color:#bf616a;">service
</span><span style="color:#bf616a;">Wants</span><span>=</span><span style="color:#a3be8c;">network-online.target
</span><span style="color:#bf616a;">After</span><span>=</span><span style="color:#a3be8c;">network-online.target </span><span style="color:#bf616a;">nss-lookup.target
</span><span>
</span><span style="color:#bf616a;">[Service]
</span><span style="color:#bf616a;">Type</span><span>=</span><span style="color:#a3be8c;">exec
</span><span style="color:#bf616a;">User</span><span>=</span><span style="color:#a3be8c;">qbtuser
</span><span style="color:#bf616a;">ExecStart</span><span>=</span><span style="color:#a3be8c;">/usr/local/bin/qbittorrent-nox
</span><span style="color:#bf616a;">Restart</span><span>=</span><span style="color:#a3be8c;">on-failure
</span><span style="color:#bf616a;">SyslogIdentifier</span><span>=</span><span style="color:#a3be8c;">qbittorrent-nox
</span><span>
</span><span style="color:#bf616a;">[Install]
</span><span style="color:#bf616a;">WantedBy</span><span>=</span><span style="color:#a3be8c;">multi-user.target
</span></code></pre>
<p>After any changes to the services reload using this command.</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> systemctl daemon-reload
</span></code></pre>
<p>Now you can enable the service</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> systemctl enable</span><span style="color:#bf616a;"> --now</span><span> qbittorrent.service
</span></code></pre>
<p>Now you can use these commands</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">systemctl</span><span> stop qbittorrent
</span><span style="color:#bf616a;">systemctl</span><span> start qbittorrent
</span><span style="color:#bf616a;">systemctl</span><span> restart qbittorrent
</span><span style="color:#bf616a;">systemctl</span><span> status qbittorrent
</span><span>
</span></code></pre>
<h2 id="reverse-proxy-optional">Reverse Proxy (Optional)<a class="zola-anchor" href="#reverse-proxy-optional" aria-label="Anchor link for: reverse-proxy-optional">🔗</a></h2>
<p>Use Caddy to access qBittorrent with domain. This should install <a href="https://www.owenyoung.com/en/blog/caddy2-setup-for-debian/">Caddy2 Setup for Debian</a></p>
<p>Config:</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">example.com </span><span>{
</span><span> push
</span><span> reverse_proxy 127.0.0.1:8080
</span><span>}
</span><span>
</span></code></pre>
<p>Now, you can access your qBittorrent web UI at: example.com, the default username and password is : admin/adminadmin</p>
<blockquote>
<p>Note, you should change the default username/password</p>
</blockquote>
<h2 id="config">Config<a class="zola-anchor" href="#config" aria-label="Anchor link for: config">🔗</a></h2>
<ul>
<li>Download directory, <code>/data/Downloads</code></li>
<li>Settings, auto add <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/XIU2/TrackersListCollection">China</a> <a rel="noopener nofollow noreferrer" target="_blank" href="https://trackerslist.com/best.txt">https://trackerslist.com/best.txt</a> , <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/ngosang/trackerslist">US</a> <a rel="noopener nofollow noreferrer" target="_blank" href="https://ngosang.github.io/trackerslist/trackers_best.txt">https://ngosang.github.io/trackerslist/trackers_best.txt</a></li>
<li>Open <code>announce_to_all_trackers</code> at settings.</li>
</ul>
<h2 id="install-search-plugin-jackett">Install Search Plugin Jackett<a class="zola-anchor" href="#install-search-plugin-jackett" aria-label="Anchor link for: install-search-plugin-jackett">🔗</a></h2>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/Jackett/Jackett">Jackett</a> is a server program that provides support for more than 400 torrent sites (public and private).</p>
<h3 id="install-jackett">Install Jackett<a class="zola-anchor" href="#install-jackett" aria-label="Anchor link for: install-jackett">🔗</a></h3>
<p>See <a href="https://www.owenyoung.com/blog/jackett-setup/">Jackett Setup</a></p>
<h3 id="install-jackett-plugin">Install Jackett Plugin<a class="zola-anchor" href="#install-jackett-plugin" aria-label="Anchor link for: install-jackett-plugin">🔗</a></h3>
<p>See also at <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/qbittorrent/search-plugins/wiki/How-to-configure-Jackett-plugin">here</a></p>
<p>Open qBittorrent Web UI, In the Search tab, click the Search plugins… button (bottom-right) -> add new plugin -> <code>https://raw.githubusercontent.com/qbittorrent/search-plugins/master/nova3/engines/jackett.py</code></p>
<p>Change API key settings, You can get it from Jackett UI</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">vim ~</span><span>/.local/share/qBittorrent/nova3/engines/jackett.json
</span></code></pre>
<h2 id="resource">Resource<a class="zola-anchor" href="#resource" aria-label="Anchor link for: resource">🔗</a></h2>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://sleele.com/2020/03/16/%E9%AB%98%E9%98%B6%E6%95%99%E7%A8%8B-%E8%BF%BD%E5%89%A7%E5%85%A8%E6%B5%81%E7%A8%8B%E8%87%AA%E5%8A%A8%E5%8C%96/">高阶教程-追剧全流程自动化</a></p>
Shadowsocks Rust Setup for Debian
2021-03-10T00:00:00+00:00
2022-06-01T00:00:00+00:00
https://www.owenyoung.com/en/blog/shadowsocks-rust-setup-for-debian/
<p>Shadowsocks Repo: <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/shadowsocks/shadowsocks-rust">https://github.com/shadowsocks/shadowsocks-rust</a></p>
<p>Setup rust first: <a href="https://www.owenyoung.com/blog/rust-environment-setup-for-debian/">Debian 初始化 Rust 环境</a></p>
<span id="continue-reading"></span><h2 id="install">Install<a class="zola-anchor" href="#install" aria-label="Anchor link for: install">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">rustup</span><span> default nightly
</span><span style="color:#bf616a;">cargo</span><span> install shadowsocks-rust
</span></code></pre>
<h2 id="configure">Configure<a class="zola-anchor" href="#configure" aria-label="Anchor link for: configure">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">mkdir</span><span> ss
</span></code></pre>
<p>Server config sample:</p>
<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{
</span><span> "</span><span style="color:#a3be8c;">servers</span><span>": [
</span><span> {
</span><span> "</span><span style="color:#a3be8c;">address</span><span>": "</span><span style="color:#a3be8c;">::</span><span>",
</span><span> "</span><span style="color:#a3be8c;">port</span><span>": </span><span style="color:#d08770;">9982</span><span>,
</span><span> "</span><span style="color:#a3be8c;">method</span><span>": "</span><span style="color:#a3be8c;">chacha20-ietf-poly1305</span><span>",
</span><span> "</span><span style="color:#a3be8c;">password</span><span>": "</span><span style="color:#a3be8c;">strong-password</span><span>",
</span><span> "</span><span style="color:#a3be8c;">mode</span><span>": "</span><span style="color:#a3be8c;">tcp_and_udp</span><span>",
</span><span> "</span><span style="color:#a3be8c;">fast_open</span><span>": </span><span style="color:#d08770;">false</span><span>,
</span><span> "</span><span style="color:#a3be8c;">timeout</span><span>": </span><span style="color:#d08770;">7200
</span><span> }
</span><span> ]
</span><span>}
</span></code></pre>
<h2 id="add-as-a-system-service">Add as a system service<a class="zola-anchor" href="#add-as-a-system-service" aria-label="Anchor link for: add-as-a-system-service">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> vim /etc/systemd/system/ss.service
</span></code></pre>
<blockquote>
<p>Note: Change <code>username</code> to your own username</p>
</blockquote>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">[Unit]
</span><span style="color:#bf616a;">Description</span><span>=</span><span style="color:#a3be8c;">ssserver </span><span style="color:#bf616a;">service
</span><span style="color:#bf616a;">After</span><span>=</span><span style="color:#a3be8c;">network.target
</span><span>
</span><span style="color:#bf616a;">[Service]
</span><span style="color:#bf616a;">ExecStart</span><span>=</span><span style="color:#a3be8c;">/home/username/.cargo/bin/ssserver </span><span style="color:#bf616a;">-c</span><span> /home/username/ss/config.json
</span><span style="color:#bf616a;">ExecStop</span><span>=</span><span style="color:#a3be8c;">/usr/bin/killall </span><span style="color:#bf616a;">ssserver
</span><span style="color:#bf616a;">Restart</span><span>=</span><span style="color:#a3be8c;">on-failure
</span><span style="color:#bf616a;">StandardOutput</span><span>=</span><span style="color:#a3be8c;">syslog </span><span style="color:#65737e;"># Output to syslog
</span><span style="color:#bf616a;">StandardError</span><span>=</span><span style="color:#a3be8c;">syslog </span><span style="color:#65737e;"># Output to syslog
</span><span style="color:#bf616a;">SyslogIdentifier</span><span>=</span><span style="color:#a3be8c;">ss
</span><span style="color:#bf616a;">User</span><span>=</span><span style="color:#a3be8c;">username
</span><span style="color:#bf616a;">Group</span><span>=</span><span style="color:#a3be8c;">admin
</span><span>
</span><span style="color:#bf616a;">[Install]
</span><span style="color:#bf616a;">WantedBy</span><span>=</span><span style="color:#a3be8c;">multi-user.target
</span></code></pre>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> systemctl daemon-reload
</span><span style="color:#bf616a;">sudo</span><span> systemctl enable</span><span style="color:#bf616a;"> --now</span><span> ss
</span></code></pre>
<h2 id="run">Run<a class="zola-anchor" href="#run" aria-label="Anchor link for: run">🔗</a></h2>
<h2 id="status">Status<a class="zola-anchor" href="#status" aria-label="Anchor link for: status">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> systemctl status ss
</span></code></pre>
<h2 id="stop">Stop<a class="zola-anchor" href="#stop" aria-label="Anchor link for: stop">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> systemctl stop ss
</span></code></pre>
<h2 id="update">Update<a class="zola-anchor" href="#update" aria-label="Anchor link for: update">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">cargo</span><span> install shadowsocks-rust
</span></code></pre>
<h2 id="multiple-ports">Multiple Ports<a class="zola-anchor" href="#multiple-ports" aria-label="Anchor link for: multiple-ports">🔗</a></h2>
<p>Also see <a rel="noopener nofollow noreferrer" target="_blank" href="https://gfw.report/blog/ss_tutorial/zh/">here</a></p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> iptables</span><span style="color:#bf616a;"> -t</span><span> nat</span><span style="color:#bf616a;"> -A</span><span> PREROUTING</span><span style="color:#bf616a;"> -p</span><span> tcp</span><span style="color:#bf616a;"> --dport</span><span> 12000:12010</span><span style="color:#bf616a;"> -j</span><span> REDIRECT</span><span style="color:#bf616a;"> --to-port</span><span> 9982
</span><span style="color:#bf616a;">sudo</span><span> iptables</span><span style="color:#bf616a;"> -t</span><span> nat</span><span style="color:#bf616a;"> -A</span><span> PREROUTING</span><span style="color:#bf616a;"> -p</span><span> udp</span><span style="color:#bf616a;"> --dport</span><span> 12000:12010</span><span style="color:#bf616a;"> -j</span><span> REDIRECT</span><span style="color:#bf616a;"> --to-port</span><span> 9982
</span></code></pre>
<h3 id="show-if-success">Show if success:<a class="zola-anchor" href="#show-if-success" aria-label="Anchor link for: show-if-success">🔗</a></h3>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> iptables</span><span style="color:#bf616a;"> -t</span><span> nat</span><span style="color:#bf616a;"> -L</span><span> PREROUTING</span><span style="color:#bf616a;"> -nv --line-number
</span></code></pre>
<h3 id="delete-the-rule">Delete the rule:<a class="zola-anchor" href="#delete-the-rule" aria-label="Anchor link for: delete-the-rule">🔗</a></h3>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> iptables</span><span style="color:#bf616a;"> -t</span><span> nat</span><span style="color:#bf616a;"> -D</span><span> PREROUTING <number>
</span></code></pre>
<h2 id="other-resources">Other Resources<a class="zola-anchor" href="#other-resources" aria-label="Anchor link for: other-resources">🔗</a></h2>
<ul>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/haoel/haoel.github.io">Through out Firewall</a></li>
</ul>
A new Gatsby theme [Timeline] - Show your all posts, tweets, instagram posts into one
2021-01-29T00:00:00+00:00
2022-03-29T00:00:00+00:00
https://www.owenyoung.com/en/blog/gatsby-timeline-theme/
<p>Don’t you think there are too many different personal profile for us? Like
twitter, instagram, facebook, blog, etc, So do I, I suppose there is an elegant
site to show all my creative work, that’s the new gatsby theme
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/gatsby-theme-timeline">Timeline</a>, a
<a rel="noopener nofollow noreferrer" target="_blank" href="https://www.gatsbyjs.com/">Gatsby</a> theme, you can use it to show your all
posts, tweets, instagram posts etc into one blog. In my opinion, this is a real
personal home.</p>
<p>Checkout my blog: https://blog.owenyoung.com/</p>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/gatsby-theme-timeline">Source Repo</a></p>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://gatsby-theme-timeline.owenyoung.com/">Live Demo</a></p>
<span id="continue-reading"></span>
<p><img src="https://www.owenyoung.com/en/blog/gatsby-timeline-theme/./screenshot.png" alt="Screen" /></p>
<p>Here are the features for now:</p>
<ul>
<li>Support markdown, tweet, instagram posts, youtube videos, hacker news, reddit
post</li>
<li>Support i18n by
<a rel="noopener nofollow noreferrer" target="_blank" href="https://www.gatsbyjs.com/plugins/gatsby-theme-i18n/">gatsby-theme-i18n</a>, you
can choose your own
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/gatsbyjs/themes/tree/master/packages">i18n library</a></li>
<li>Support comments platform <a rel="noopener nofollow noreferrer" target="_blank" href="https://disqus.com/">disqus</a> or
<a rel="noopener nofollow noreferrer" target="_blank" href="https://utteranc.es/">utterances</a></li>
<li>Support Tags</li>
<li>Pagination, even tag pages support pagination</li>
<li>SEO Optimization</li>
</ul>
<p>Just start a new blog for yourself:</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">gatsby</span><span> new my-themed-blog https://github.com/theowenyoung/gatsby-starter-timeline
</span></code></pre>
<blockquote>
<p>For more about installation please see
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/gatsby-theme-timeline/tree/main/packages/gatsby-theme-timeline#installation">here</a></p>
</blockquote>
<h2 id="note">Note<a class="zola-anchor" href="#note" aria-label="Anchor link for: note">🔗</a></h2>
<p>In the starter demo, I use two gatsby plugins
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/G100g/gatsby-source-twitter">gatsby-source-twitter</a> and
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/gatsby-source-instagram">gatsby-source-instagram</a>
as the blog’s sources. But in <a rel="noopener nofollow noreferrer" target="_blank" href="https://blog.owenyoung.com">my blog</a>, I use
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/actionsflow/actionsflow">Actionsflow</a> to get my tweets,
instagram data, sync the JSON file to
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/story">my content source repository</a> ((Why
Actionsflow? For more stably, and I can store all my creative work into one)),
and <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/theowenyoung.github.io">site repository</a>
use plugin
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/theowenyoung/gatsby-source-git">gatsby-source-git</a> to sync
those data, and my markdown posts, for more tech stack, you can see
<a rel="noopener nofollow noreferrer" target="_blank" href="https://blog.owenyoung.com/en/posts/how-i-built-my-blog/">How I Built my Blog?</a></p>
<blockquote>
<p>Disclaimer: I made the
<a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/actionsflow/actionsflow">Actionsflow</a> :)</p>
</blockquote>
Open BBR for Debian
2021-01-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/open-bbr-for-debian/
<p>Enable TCP BBR on Debian.</p>
<span id="continue-reading"></span><h2 id="steps">Steps<a class="zola-anchor" href="#steps" aria-label="Anchor link for: steps">🔗</a></h2>
<ol>
<li>Open the following configuration file to enable TCP BBR.</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">vi</span><span> /etc/sysctl.conf
</span></code></pre>
<ol start="2">
<li>At the end of the config file, add the following lines.</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">net.core.default_qdisc</span><span>=</span><span style="color:#a3be8c;">fq
</span><span style="color:#bf616a;">net.ipv4.tcp_congestion_control</span><span>=</span><span style="color:#a3be8c;">bbr
</span></code></pre>
<ol start="3">
<li>reload</li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sysctl -p
</span></code></pre>
<p>Now, Verify if BBR is enabled in your system,</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sysctl</span><span> net.ipv4.tcp_congestion_control
</span></code></pre>
<p>Output:</p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">root@vps:~#</span><span> sysctl net.ipv4.tcp_congestion_control
</span><span style="color:#bf616a;">net.ipv4.tcp_congestion_control</span><span> = bbr
</span></code></pre>
Install PHP 8.0 On CentOS 7 / RHEL 7
2020-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/install-php8-on-centos7/
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span>
</span><span style="color:#bf616a;">yum</span><span> install</span><span style="color:#bf616a;"> -y</span><span> https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
</span><span>
</span><span style="color:#bf616a;">yum</span><span> install</span><span style="color:#bf616a;"> -y</span><span> https://rpms.remirepo.net/enterprise/remi-release-7.rpm
</span><span style="color:#bf616a;">yum</span><span> install</span><span style="color:#bf616a;"> -y --enablerepo</span><span>=remi-php80 php php-cli
</span><span style="color:#bf616a;">php -v
</span></code></pre>
Metube Setup
2020-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/metube-setup/
<p>Metube Setup with docker.</p>
<span id="continue-reading"></span><h2 id="list">List<a class="zola-anchor" href="#list" aria-label="Anchor link for: list">🔗</a></h2>
<ul>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/alexta69/metube">GitHub - alexta69/metube: youtube-dl web UI</a></li>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://chrome.google.com/webstore/detail/metube-downloader/fbmkmdnlhacefjljljlbhkodfmfkijdh">Metube chrome extension</a></li>
</ul>
<h2 id="setup">Setup<a class="zola-anchor" href="#setup" aria-label="Anchor link for: setup">🔗</a></h2>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://docs.docker.com/engine/install/">Docker Install</a></p>
<h2 id="config">Config<a class="zola-anchor" href="#config" aria-label="Anchor link for: config">🔗</a></h2>
<pre data-lang="yaml" style="background-color:#2b303b;color:#c0c5ce;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#bf616a;">version</span><span>: "</span><span style="color:#a3be8c;">3</span><span>"
</span><span style="color:#bf616a;">services</span><span>:
</span><span> </span><span style="color:#bf616a;">metube</span><span>:
</span><span> </span><span style="color:#bf616a;">image</span><span>: </span><span style="color:#a3be8c;">alexta69/
</span><span>
</span><span> </span><span style="color:#bf616a;">container_name</span><span>: </span><span style="color:#a3be8c;">metube
</span><span> </span><span style="color:#bf616a;">restart</span><span>: </span><span style="color:#a3be8c;">unless-stopped
</span><span> </span><span style="color:#bf616a;">ports</span><span>:
</span><span> - "</span><span style="color:#a3be8c;">10005:8081</span><span>"
</span><span> </span><span style="color:#bf616a;">volumes</span><span>:
</span><span> - </span><span style="color:#a3be8c;">/data/youtube:/downloads
</span></code></pre>
Nginx Setup for Debian
2020-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/nginx-setup-for-debian/
<p>Install latest stable nginx for debian</p>
<blockquote>
<p>Also see <a rel="noopener nofollow noreferrer" target="_blank" href="https://www.nginx.com/resources/wiki/start/topics/tutorials/install/">here</a></p>
</blockquote>
<span id="continue-reading"></span><pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> su
</span><span style="color:#bf616a;">cat </span><span><<</span><span style="color:#b48ead;">EOF </span><span>>> /etc/apt/sources.list.d/nginx.list
</span><span style="color:#a3be8c;">deb http://nginx.org/packages/debian/ buster nginx
</span><span style="color:#a3be8c;">deb-src http://nginx.org/packages/debian/ buster nginx
</span><span style="color:#b48ead;">EOF
</span><span style="color:#96b5b4;">exit
</span><span style="color:#bf616a;">curl -L</span><span> https://nginx.org/keys/nginx_signing.key | </span><span style="color:#bf616a;">sudo</span><span> apt-key add -
</span><span style="color:#bf616a;">sudo</span><span> apt update
</span><span style="color:#bf616a;">sudo</span><span> apt install nginx
</span></code></pre>
<h2 id="optional-generate-nginx-conf-online">Optional generate nginx conf online<a class="zola-anchor" href="#optional-generate-nginx-conf-online" aria-label="Anchor link for: optional-generate-nginx-conf-online">🔗</a></h2>
<p>Generate a nginx conf online at <a rel="noopener nofollow noreferrer" target="_blank" href="https://www.digitalocean.com/community/tools/nginx">here</a></p>
<p>If nginx conf need dhparam.pem, run :<code>cd /etc/nginx sudo openssl dhparam -dsaparam -out dhparam.pem 4096</code></p>
<h2 id="optional-install-acme-sh">Optional install acme.sh<a class="zola-anchor" href="#optional-install-acme-sh" aria-label="Anchor link for: optional-install-acme-sh">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> su
</span><span style="color:#bf616a;">wget -O</span><span> - https://get.acme.sh | </span><span style="color:#bf616a;">sh -s</span><span> email=my@example.com
</span></code></pre>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#96b5b4;">source </span><span style="color:#bf616a;">~</span><span>/.bashrc
</span><span style="color:#bf616a;">acme.sh --issue -d</span><span> example.com</span><span style="color:#bf616a;"> --nginx
</span></code></pre>
<h2 id="optional-install-certbot">Optional Install certbot<a class="zola-anchor" href="#optional-install-certbot" aria-label="Anchor link for: optional-install-certbot">🔗</a></h2>
<h3 id="install-snap">Install snap<a class="zola-anchor" href="#install-snap" aria-label="Anchor link for: install-snap">🔗</a></h3>
<blockquote>
<p>https://snapcraft.io/docs/installing-snapd</p>
</blockquote>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> apt</span><span style="color:#bf616a;"> -y</span><span> install snapd
</span><span style="color:#bf616a;">sudo</span><span> snap install core; </span><span style="color:#bf616a;">sudo</span><span> snap refresh core
</span><span>
</span></code></pre>
<blockquote>
<p>https://certbot.eff.org/lets-encrypt/debianbuster-nginx</p>
</blockquote>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> snap install</span><span style="color:#bf616a;"> --classic</span><span> certbot
</span><span style="color:#bf616a;">sudo</span><span> snap install certbot-dns-cloudflare
</span><span style="color:#bf616a;">sudo</span><span> ln</span><span style="color:#bf616a;"> -s</span><span> /snap/bin/certbot /usr/bin/certbot
</span></code></pre>
<h2 id="generate-ssl">Generate SSL<a class="zola-anchor" href="#generate-ssl" aria-label="Anchor link for: generate-ssl">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> certbot</span><span style="color:#bf616a;"> --nginx
</span></code></pre>
Nodejs Setup for Debian
2020-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/nodejs-setup-for-debian/
<h2 id="install">Install<a class="zola-anchor" href="#install" aria-label="Anchor link for: install">🔗</a></h2>
<p>See also <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/nodesource/distributions/blob/master/README.md">here</a></p>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#65737e;"># Using Debian, as root
</span><span style="color:#bf616a;">curl -fsSL</span><span> https://deb.nodesource.com/setup_14.x | </span><span style="color:#bf616a;">bash</span><span> -
</span><span style="color:#bf616a;">apt-get</span><span> install</span><span style="color:#bf616a;"> -y</span><span> nodejs
</span></code></pre>
Peertube Setup with Caddy Server
2020-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/peertube-setup-with-caddy-server/
<p>Peertube is an activityPub-federated video streaming platform using P2P directly in your web browser.</p>
<span id="continue-reading"></span>
<p>Also see <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/production.md">here</a></p>
<h2 id="pre-requirement">Pre Requirement<a class="zola-anchor" href="#pre-requirement" aria-label="Anchor link for: pre-requirement">🔗</a></h2>
<ol>
<li><a href="https://www.owenyoung.com/en/blog/debian-server-setup/">Debian Server Setup</a></li>
<li><a href="https://www.owenyoung.com/en/blog/caddy2-setup-for-debian/">Caddy2 Setup for Debian</a></li>
<li><a href="https://www.owenyoung.com/en/blog/nodejs-setup-for-debian/">Nodejs Setup for Debian</a></li>
<li>Install yarn, and be sure to have <a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/yarnpkg/yarn/releases/latest">a recent version</a>:
<a rel="noopener nofollow noreferrer" target="_blank" href="https://yarnpkg.com/en/docs/install#linux-tab">https://yarnpkg.com/en/docs/install#linux-tab</a></li>
</ol>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> npm i</span><span style="color:#bf616a;"> -g</span><span> yarn
</span></code></pre>
<ol start="4">
<li>Install Python:</li>
</ol>
<p>On Ubuntu <= bionic (18.04 LTS) or Debian <= Buster:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>sudo apt update
</span><span>sudo apt install python-dev
</span><span>python --version # Should be >= 2.x or >= 3.x
</span></code></pre>
<p>On Ubuntu >= focal (20.04 LTS) or Debian >= Bullseye:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>sudo apt update
</span><span>sudo apt install python3-dev python-is-python3 # python-is-python2 should also work
</span><span>python --version # Should be >= 2.x or >= 3.x
</span></code></pre>
<h2 id="install">Install<a class="zola-anchor" href="#install" aria-label="Anchor link for: install">🔗</a></h2>
<ol>
<li>Install common dependencies:</li>
</ol>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>sudo apt update
</span><span>sudo apt install certbot nginx ffmpeg postgresql postgresql-contrib openssl g++ make redis-server git cron wget
</span><span>ffmpeg -version # Should be >= 4.1
</span><span>g++ -v # Should be >= 5.x
</span></code></pre>
<p>Now that dependencies are installed, before running PeerTube you should start PostgreSQL and Redis:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>sudo systemctl start redis postgresql
</span></code></pre>
<h3 id="peertube-user">PeerTube user<a class="zola-anchor" href="#peertube-user" aria-label="Anchor link for: peertube-user">🔗</a></h3>
<p>Create a <code>peertube</code> user with <code>/var/www/peertube</code> home:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo useradd -m -d /var/www/peertube -s /bin/bash -p peertube peertube
</span></code></pre>
<p>Set its password:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo passwd peertube
</span></code></pre>
<h3 id="database">Database<a class="zola-anchor" href="#database" aria-label="Anchor link for: database">🔗</a></h3>
<p>Create the production database and a peertube user inside PostgreSQL:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube
</span><span>$ sudo -u postgres createuser -P peertube
</span></code></pre>
<p>For password you can use <code>peertube</code> from the default config yaml.</p>
<p>Here you should enter a password for PostgreSQL <code>peertube</code> user, that should be copied in <code>production.yaml</code> file.
Don’t just hit enter else it will be empty.</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo -u postgres createdb -O peertube -E UTF8 -T template0 peertube_prod
</span></code></pre>
<p>Then enable extensions PeerTube needs:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo -u postgres psql -c "CREATE EXTENSION pg_trgm;" peertube_prod
</span><span>$ sudo -u postgres psql -c "CREATE EXTENSION unaccent;" peertube_prod
</span></code></pre>
<h3 id="prepare-peertube-directory">Prepare PeerTube directory<a class="zola-anchor" href="#prepare-peertube-directory" aria-label="Anchor link for: prepare-peertube-directory">🔗</a></h3>
<p>Fetch the latest tagged version of Peertube</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ VERSION=$(curl -s https://api.github.com/repos/chocobozzz/peertube/releases/latest | grep tag_name | cut -d '"' -f 4) && echo "Latest Peertube version is $VERSION"
</span></code></pre>
<p>Open the peertube directory, create a few required directories</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube
</span><span>$ sudo -u peertube mkdir config storage versions
</span><span>$ sudo -u peertube chmod 750 config/
</span></code></pre>
<p>Download the latest version of the Peertube client, unzip it and remove the zip</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube/versions
</span><span>$ sudo -u peertube wget -q "https://github.com/Chocobozzz/PeerTube/releases/download/${VERSION}/peertube-${VERSION}.zip"
</span><span>$ sudo -u peertube unzip -q peertube-${VERSION}.zip && sudo -u peertube rm peertube-${VERSION}.zip
</span></code></pre>
<p>Install Peertube:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>cd /var/www/peertube
</span><span>sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest
</span><span>cd ./peertube-latest
</span><span>sudo -H -u peertube yarn install --production --pure-lockfile
</span></code></pre>
<h3 id="peertube-configuration">PeerTube configuration<a class="zola-anchor" href="#peertube-configuration" aria-label="Anchor link for: peertube-configuration">🔗</a></h3>
<p>Copy the default configuration file that contains the default configuration provided by PeerTube.
You <strong>must not</strong> update this file.</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube
</span><span>$ sudo -u peertube cp peertube-latest/config/default.yaml config/default.yaml
</span></code></pre>
<p>Now copy the production example configuration:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube
</span><span>$ sudo -u peertube cp peertube-latest/config/production.yaml.example config/production.yaml
</span></code></pre>
<p>Then edit the <code>config/production.yaml</code> file according to your webserver
and database configuration (<code>webserver</code>, <code>database</code>, <code>redis</code>, <code>smtp</code> and <code>admin.email</code> sections in particular).
Keys defined in <code>config/production.yaml</code> will override keys defined in <code>config/default.yaml</code>.</p>
<p><strong>PeerTube does not support webserver host change</strong>. Even though <a rel="noopener nofollow noreferrer" target="_blank" href="https://docs.joinpeertube.org/maintain-tools?id=update-hostjs">PeerTube CLI can help you to switch hostname</a> there’s no official support for that since it is a risky operation that might result in unforeseen errors.</p>
<h3 id="webserver">Webserver<a class="zola-anchor" href="#webserver" aria-label="Anchor link for: webserver">🔗</a></h3>
<p>We only provide official configuration files for Nginx.</p>
<p>Copy the nginx configuration template:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo /etc/nginx/sites-available/peertube
</span></code></pre>
<p>with the following config:</p>
<pre data-lang="conf" style="background-color:#2b303b;color:#c0c5ce;" class="language-conf "><code class="language-conf" data-lang="conf"><span style="color:#65737e;"># Minimum Nginx version required: 1.13.0 (released Apr 25, 2017)
</span><span style="color:#65737e;"># Please check your Nginx installation features the following modules via 'nginx -V':
</span><span style="color:#65737e;"># STANDARD HTTP MODULES: Core, Proxy, Rewrite, Access, Gzip, Headers, HTTP/2, Log, Real IP, SSL, Thread Pool, Upstream, AIO Multithreading.
</span><span style="color:#65737e;"># THIRD PARTY MODULES: None.
</span><span>
</span><span style="color:#bf616a;">upstream </span><span>backend {
</span><span> </span><span style="color:#bf616a;">server </span><span style="color:#d08770;">127.0.0.1:9000</span><span>;
</span><span>}
</span><span>
</span><span style="color:#b48ead;">server </span><span>{
</span><span> </span><span style="color:#bf616a;">listen </span><span style="color:#d08770;">8880</span><span>;
</span><span> </span><span style="color:#bf616a;">listen </span><span>[::]:</span><span style="color:#d08770;">8880</span><span>;
</span><span> </span><span style="color:#bf616a;">access_log </span><span>/var/log/nginx/peertube.access.log; </span><span style="color:#65737e;"># reduce I/0 with buffer=10m flush=5m
</span><span> </span><span style="color:#bf616a;">error_log </span><span>/var/log/nginx/peertube.error.log;
</span><span>
</span><span> </span><span style="color:#65737e;">##
</span><span> </span><span style="color:#65737e;"># Certificates
</span><span> </span><span style="color:#65737e;"># you need a certificate to run in production. see https://letsencrypt.org/
</span><span> </span><span style="color:#65737e;">##
</span><span>
</span><span style="color:#b48ead;"> location @api</span><span> {
</span><span> </span><span style="color:#bf616a;">proxy_set_header </span><span>X-Forwarded-For </span><span style="color:#b48ead;">$proxy_add_x_forwarded_for</span><span>;
</span><span> </span><span style="color:#bf616a;">proxy_set_header </span><span>Host </span><span style="color:#b48ead;">$host</span><span>;
</span><span> </span><span style="color:#bf616a;">proxy_set_header </span><span>X-Real-IP </span><span style="color:#b48ead;">$remote_addr</span><span>;
</span><span>
</span><span> </span><span style="color:#bf616a;">client_max_body_size </span><span style="color:#d08770;">100k</span><span>; </span><span style="color:#65737e;"># default is 1M
</span><span>
</span><span> </span><span style="color:#bf616a;">proxy_connect_timeout </span><span style="color:#d08770;">10m</span><span>;
</span><span> </span><span style="color:#bf616a;">proxy_send_timeout </span><span style="color:#d08770;">10m</span><span>;
</span><span> </span><span style="color:#bf616a;">proxy_read_timeout </span><span style="color:#d08770;">10m</span><span>;
</span><span> </span><span style="color:#bf616a;">send_timeout </span><span style="color:#d08770;">10m</span><span>;
</span><span>
</span><span> </span><span style="color:#bf616a;">proxy_pass </span><span style="color:#d08770;">http://backend</span><span>;
</span><span> }
</span><span>
</span><span style="color:#b48ead;"> location </span><span>/ {
</span><span> </span><span style="color:#bf616a;">try_files </span><span>/dev/</span><span style="color:#d08770;">null </span><span style="color:#b48ead;">@api</span><span>;
</span><span> }
</span><span>
</span><span style="color:#b48ead;"> location </span><span>= /api/v1/videos/upload-resumable {
</span><span> </span><span style="color:#bf616a;">client_max_body_size </span><span style="color:#d08770;">0</span><span>;
</span><span> </span><span style="color:#bf616a;">proxy_request_buffering </span><span style="color:#d08770;">off</span><span>;
</span><span>
</span><span> </span><span style="color:#bf616a;">try_files </span><span>/dev/</span><span style="color:#d08770;">null </span><span style="color:#b48ead;">@api</span><span>;
</span><span> }
</span><span>
</span><span style="color:#b48ead;"> location </span><span>= /api/v1/videos/upload {
</span><span> </span><span style="color:#bf616a;">limit_except </span><span>POST HEAD { deny all; }
</span><span>
</span><span> </span><span style="color:#65737e;"># This is the maximum upload size, which roughly matches the maximum size of a video file.
</span><span> </span><span style="color:#65737e;"># Note that temporary space is needed equal to the total size of all concurrent uploads.
</span><span> </span><span style="color:#65737e;"># This data gets stored in /var/lib/nginx by default, so you may want to put this directory
</span><span> </span><span style="color:#65737e;"># on a dedicated filesystem.
</span><span> </span><span style="color:#bf616a;">client_max_body_size </span><span style="color:#d08770;">12G</span><span>; </span><span style="color:#65737e;"># default is 1M
</span><span> </span><span style="color:#bf616a;">add_header </span><span>X-File-Maximum-Size </span><span style="color:#d08770;">8G</span><span> always; </span><span style="color:#65737e;"># inform backend of the set value in bytes before mime-encoding (x * 1.4 >= client_max_body_size)
</span><span>
</span><span> </span><span style="color:#bf616a;">try_files </span><span>/dev/</span><span style="color:#d08770;">null </span><span style="color:#b48ead;">@api</span><span>;
</span><span> }
</span><span>
</span><span style="color:#b48ead;"> location </span><span>~ ^/api/v1/(videos|video-playlists|video-channels|users/me) {
</span><span> </span><span style="color:#bf616a;">client_max_body_size </span><span style="color:#d08770;">6M</span><span>; </span><span style="color:#65737e;"># default is 1M
</span><span> </span><span style="color:#bf616a;">add_header </span><span>X-File-Maximum-Size </span><span style="color:#d08770;">4M</span><span> always; </span><span style="color:#65737e;"># inform backend of the set value in bytes before mime-encoding (x * 1.4 >= client_max_body_size)
</span><span>
</span><span> </span><span style="color:#bf616a;">try_files </span><span>/dev/</span><span style="color:#d08770;">null </span><span style="color:#b48ead;">@api</span><span>;
</span><span> }
</span><span>
</span><span> </span><span style="color:#65737e;">##
</span><span> </span><span style="color:#65737e;"># Websocket
</span><span> </span><span style="color:#65737e;">##
</span><span>
</span><span style="color:#b48ead;"> location @api_websocket</span><span> {
</span><span> </span><span style="color:#bf616a;">proxy_http_version </span><span style="color:#d08770;">1</span><span>.</span><span style="color:#d08770;">1</span><span>;
</span><span> </span><span style="color:#bf616a;">proxy_set_header </span><span>X-Forwarded-For </span><span style="color:#b48ead;">$proxy_add_x_forwarded_for</span><span>;
</span><span> </span><span style="color:#bf616a;">proxy_set_header </span><span>Host </span><span style="color:#b48ead;">$host</span><span>;
</span><span> </span><span style="color:#bf616a;">proxy_set_header </span><span>X-Real-IP </span><span style="color:#b48ead;">$remote_addr</span><span>;
</span><span> </span><span style="color:#bf616a;">proxy_set_header </span><span>Upgrade </span><span style="color:#b48ead;">$http_upgrade</span><span>;
</span><span> </span><span style="color:#bf616a;">proxy_set_header </span><span>Connection </span><span style="color:#a3be8c;">"upgrade"</span><span>;
</span><span>
</span><span> </span><span style="color:#bf616a;">proxy_pass </span><span style="color:#d08770;">http://backend</span><span>;
</span><span> }
</span><span>
</span><span style="color:#b48ead;"> location </span><span>/socket.io {
</span><span> </span><span style="color:#bf616a;">try_files </span><span>/dev/</span><span style="color:#d08770;">null </span><span style="color:#b48ead;">@api_websocket</span><span>;
</span><span> }
</span><span>
</span><span style="color:#b48ead;"> location </span><span>/tracker/socket {
</span><span> </span><span style="color:#65737e;"># Peers send a message to the tracker every 15 minutes
</span><span> </span><span style="color:#65737e;"># Don't close the websocket before then
</span><span> </span><span style="color:#bf616a;">proxy_read_timeout </span><span style="color:#d08770;">15m</span><span>; </span><span style="color:#65737e;"># default is 60s
</span><span>
</span><span> </span><span style="color:#bf616a;">try_files </span><span>/dev/</span><span style="color:#d08770;">null </span><span style="color:#b48ead;">@api_websocket</span><span>;
</span><span> }
</span><span>
</span><span> </span><span style="color:#65737e;">##
</span><span> </span><span style="color:#65737e;"># Performance optimizations
</span><span> </span><span style="color:#65737e;"># For extra performance please refer to https://github.com/denji/nginx-tuning
</span><span> </span><span style="color:#65737e;">##
</span><span>
</span><span> </span><span style="color:#bf616a;">root </span><span>/var/www/peertube/storage;
</span><span>
</span><span> </span><span style="color:#65737e;"># Enable compression for JS/CSS/HTML, for improved client load times.
</span><span> </span><span style="color:#65737e;"># It might be nice to compress JSON/XML as returned by the API, but
</span><span> </span><span style="color:#65737e;"># leaving that out to protect against potential BREACH attack.
</span><span> </span><span style="color:#bf616a;">gzip </span><span style="color:#d08770;">on</span><span>;
</span><span> </span><span style="color:#bf616a;">gzip_vary </span><span style="color:#d08770;">on</span><span>;
</span><span> </span><span style="color:#bf616a;">gzip_types </span><span># text/html is always compressed by HttpGzipModule
</span><span> </span><span style="color:#bf616a;">text</span><span>/css
</span><span> </span><span style="color:#bf616a;">application</span><span>/javascript
</span><span> </span><span style="color:#bf616a;">font</span><span>/truetype
</span><span> </span><span style="color:#bf616a;">font</span><span>/opentype
</span><span> </span><span style="color:#bf616a;">application</span><span>/vnd.ms-fontobject
</span><span> </span><span style="color:#bf616a;">image</span><span>/svg+xml;
</span><span> </span><span style="color:#bf616a;">gzip_min_length </span><span style="color:#d08770;">1000</span><span>; </span><span style="color:#65737e;"># default is 20 bytes
</span><span> </span><span style="color:#bf616a;">gzip_buffers </span><span style="color:#d08770;">16 8k</span><span>;
</span><span> </span><span style="color:#bf616a;">gzip_comp_level </span><span style="color:#d08770;">2</span><span>; </span><span style="color:#65737e;"># default is 1
</span><span>
</span><span> </span><span style="color:#bf616a;">client_body_timeout </span><span style="color:#d08770;">30s</span><span>; </span><span style="color:#65737e;"># default is 60
</span><span> </span><span style="color:#bf616a;">client_header_timeout </span><span style="color:#d08770;">10s</span><span>; </span><span style="color:#65737e;"># default is 60
</span><span> </span><span style="color:#bf616a;">send_timeout </span><span style="color:#d08770;">10s</span><span>; </span><span style="color:#65737e;"># default is 60
</span><span> </span><span style="color:#bf616a;">keepalive_timeout </span><span style="color:#d08770;">10s</span><span>; </span><span style="color:#65737e;"># default is 75
</span><span> </span><span style="color:#bf616a;">resolver_timeout </span><span style="color:#d08770;">10s</span><span>; </span><span style="color:#65737e;"># default is 30
</span><span> </span><span style="color:#bf616a;">reset_timedout_connection </span><span style="color:#d08770;">on</span><span>;
</span><span> </span><span style="color:#bf616a;">proxy_ignore_client_abort </span><span style="color:#d08770;">on</span><span>;
</span><span>
</span><span> </span><span style="color:#bf616a;">tcp_nopush </span><span style="color:#d08770;">on</span><span>; </span><span style="color:#65737e;"># send headers in one piece
</span><span> </span><span style="color:#bf616a;">tcp_nodelay </span><span style="color:#d08770;">on</span><span>; </span><span style="color:#65737e;"># don't buffer data sent, good for small data bursts in real time
</span><span>
</span><span> </span><span style="color:#65737e;"># If you have a small /var/lib partition, it could be interesting to store temp nginx uploads in a different place
</span><span> </span><span style="color:#65737e;"># See https://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_temp_path
</span><span> </span><span style="color:#65737e;">#client_body_temp_path /var/www/peertube/storage/nginx/;
</span><span>
</span><span> </span><span style="color:#65737e;"># Bypass PeerTube for performance reasons. Optional.
</span><span> </span><span style="color:#65737e;"># Should be consistent with client-overrides assets list in /server/controllers/client.ts
</span><span style="color:#b48ead;"> location </span><span>~ </span><span style="color:#96b5b4;">^/client/(assets/images/(icons/icon-36x36\.png|icons/icon-48x48\.png|icons/icon-72x72\.png|icons/icon-96x96\.png|icons/icon-144x144\.png|icons/icon-192x192\.png|icons/icon-512x512\.png|logo\.svg|favicon\.png))$</span><span> {
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Cache-Control </span><span style="color:#a3be8c;">"public, max-age=31536000, immutable"</span><span>; </span><span style="color:#65737e;"># Cache 1 year
</span><span>
</span><span> </span><span style="color:#bf616a;">root </span><span>/var/www/peertube;
</span><span>
</span><span> </span><span style="color:#bf616a;">try_files </span><span>/storage/client-overrides/</span><span style="color:#b48ead;">$1 </span><span>/peertube-latest/client/dist/</span><span style="color:#b48ead;">$1 @api</span><span>;
</span><span> }
</span><span>
</span><span> </span><span style="color:#65737e;"># Bypass PeerTube for performance reasons. Optional.
</span><span style="color:#b48ead;"> location </span><span>~ </span><span style="color:#96b5b4;">^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$</span><span> {
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Cache-Control </span><span style="color:#a3be8c;">"public, max-age=31536000, immutable"</span><span>; </span><span style="color:#65737e;"># Cache 1 year
</span><span>
</span><span style="color:#b48ead;"> alias </span><span>/var/www/peertube/peertube-latest/client/dist/</span><span style="color:#b48ead;">$1</span><span>;
</span><span> }
</span><span>
</span><span> </span><span style="color:#65737e;"># Bypass PeerTube for performance reasons. Optional.
</span><span style="color:#b48ead;"> location </span><span>~ ^/static/(thumbnails|avatars)/ {
</span><span style="color:#b48ead;"> if</span><span> (</span><span style="color:#b48ead;">$request_method </span><span>= </span><span style="color:#a3be8c;">'OPTIONS'</span><span>) {
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Origin </span><span style="color:#a3be8c;">'*'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Methods </span><span style="color:#a3be8c;">'GET, OPTIONS'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Headers </span><span style="color:#a3be8c;">'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Max-Age </span><span style="color:#d08770;">1728000</span><span>; </span><span style="color:#65737e;"># Preflight request can be cached 20 days
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Content-Type </span><span style="color:#a3be8c;">'text/plain charset=UTF-8'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Content-Length </span><span style="color:#d08770;">0</span><span>;
</span><span> </span><span style="color:#bf616a;">return </span><span style="color:#d08770;">204</span><span>;
</span><span> }
</span><span>
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Origin </span><span style="color:#a3be8c;">'*'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Methods </span><span style="color:#a3be8c;">'GET, OPTIONS'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Headers </span><span style="color:#a3be8c;">'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Cache-Control </span><span style="color:#a3be8c;">"public, max-age=7200"</span><span>; </span><span style="color:#65737e;"># Cache response 2 hours
</span><span>
</span><span style="color:#b48ead;"> rewrite </span><span style="color:#96b5b4;">^/static/(.*)$ /$</span><span style="color:#d08770;">1</span><span> break;
</span><span>
</span><span> </span><span style="color:#bf616a;">try_files </span><span style="color:#b48ead;">$uri @api</span><span>;
</span><span> }
</span><span>
</span><span> </span><span style="color:#65737e;"># Bypass PeerTube for performance reasons. Optional.
</span><span style="color:#b48ead;"> location </span><span>~ ^/static/(webseed|redundancy|streaming-playlists)/ {
</span><span> </span><span style="color:#bf616a;">limit_rate_after </span><span style="color:#d08770;">5M</span><span>;
</span><span>
</span><span> </span><span style="color:#65737e;"># Clients usually have 4 simultaneous webseed connections, so the real limit is 3MB/s per client
</span><span> </span><span style="color:#bf616a;">set </span><span style="color:#b48ead;">$peertube_limit_rate </span><span style="color:#d08770;">800k</span><span>;
</span><span>
</span><span> </span><span style="color:#65737e;"># Increase rate limit in HLS mode, because we don't have multiple simultaneous connections
</span><span style="color:#b48ead;"> if</span><span> (</span><span style="color:#b48ead;">$request_uri </span><span>~ -fragmented.mp4$) {
</span><span> </span><span style="color:#bf616a;">set </span><span style="color:#b48ead;">$peertube_limit_rate </span><span style="color:#d08770;">5M</span><span>;
</span><span> }
</span><span>
</span><span> </span><span style="color:#65737e;"># Use this line with nginx >= 1.17.0
</span><span> </span><span style="color:#65737e;">#limit_rate $peertube_limit_rate;
</span><span> </span><span style="color:#65737e;"># Or this line if your nginx < 1.17.0
</span><span> </span><span style="color:#bf616a;">set </span><span style="color:#b48ead;">$limit_rate $peertube_limit_rate</span><span>;
</span><span>
</span><span style="color:#b48ead;"> if</span><span> (</span><span style="color:#b48ead;">$request_method </span><span>= </span><span style="color:#a3be8c;">'OPTIONS'</span><span>) {
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Origin </span><span style="color:#a3be8c;">'*'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Methods </span><span style="color:#a3be8c;">'GET, OPTIONS'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Headers </span><span style="color:#a3be8c;">'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Max-Age </span><span style="color:#d08770;">1728000</span><span>; </span><span style="color:#65737e;"># Preflight request can be cached 20 days
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Content-Type </span><span style="color:#a3be8c;">'text/plain charset=UTF-8'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Content-Length </span><span style="color:#d08770;">0</span><span>;
</span><span> </span><span style="color:#bf616a;">return </span><span style="color:#d08770;">204</span><span>;
</span><span> }
</span><span>
</span><span style="color:#b48ead;"> if</span><span> (</span><span style="color:#b48ead;">$request_method </span><span>= </span><span style="color:#a3be8c;">'GET'</span><span>) {
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Origin </span><span style="color:#a3be8c;">'*'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Methods </span><span style="color:#a3be8c;">'GET, OPTIONS'</span><span>;
</span><span> </span><span style="color:#bf616a;">add_header </span><span>Access-Control-Allow-Headers </span><span style="color:#a3be8c;">'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'</span><span>;
</span><span>
</span><span> </span><span style="color:#65737e;"># Don't spam access log file with byte range requests
</span><span> </span><span style="color:#bf616a;">access_log </span><span style="color:#d08770;">off</span><span>;
</span><span> }
</span><span>
</span><span> </span><span style="color:#65737e;"># Enabling the sendfile directive eliminates the step of copying the data into the buffer
</span><span> </span><span style="color:#65737e;"># and enables direct copying data from one file descriptor to another.
</span><span> </span><span style="color:#bf616a;">sendfile </span><span style="color:#d08770;">on</span><span>;
</span><span> </span><span style="color:#bf616a;">sendfile_max_chunk </span><span style="color:#d08770;">1M</span><span>; </span><span style="color:#65737e;"># prevent one fast connection from entirely occupying the worker process. should be > 800k.
</span><span> </span><span style="color:#bf616a;">aio </span><span>threads;
</span><span>
</span><span style="color:#b48ead;"> rewrite </span><span style="color:#96b5b4;">^/static/webseed/(.*)$ /videos/$</span><span style="color:#d08770;">1</span><span> break;
</span><span style="color:#b48ead;"> rewrite </span><span style="color:#96b5b4;">^/static/(.*)$ /$</span><span style="color:#d08770;">1</span><span> break;
</span><span>
</span><span> </span><span style="color:#bf616a;">try_files </span><span style="color:#b48ead;">$uri @api</span><span>;
</span><span> }
</span><span>}
</span></code></pre>
<p>Add caddy config:</p>
<pre data-lang="conf" style="background-color:#2b303b;color:#c0c5ce;" class="language-conf "><code class="language-conf" data-lang="conf"><span style="color:#bf616a;">example</span><span>.com {
</span><span> </span><span style="color:#bf616a;">reverse_proxy </span><span style="color:#d08770;">127.0.0.1:8880
</span><span>}
</span></code></pre>
<p>Then modify the webserver configuration file. Please pay attention to the <code>alias</code> keys of the static locations.
It should correspond to the paths of your storage directories (set in the configuration file inside the <code>storage</code> key).</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo vim /etc/nginx/sites-available/peertube
</span></code></pre>
<p>Activate the configuration file:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo ln -s /etc/nginx/sites-available/peertube /etc/nginx/sites-enabled/peertube
</span></code></pre>
<p>To generate the certificate for your domain as required to make https work you can use <a rel="noopener nofollow noreferrer" target="_blank" href="https://letsencrypt.org/">Let’s Encrypt</a>:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo systemctl stop nginx
</span><span>$ sudo certbot certonly --standalone --post-hook "systemctl restart nginx"
</span><span>$ sudo systemctl reload nginx
</span></code></pre>
<p>Now you have the certificates you can reload nginx:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo systemctl reload nginx
</span></code></pre>
<p>Certbot should have installed a cron to automatically renew your certificate.
Since our nginx template supports webroot renewal, we suggest you to update the renewal config file to use the <code>webroot</code> authenticator:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ # Replace authenticator = standalone by authenticator = webroot
</span><span>$ # Add webroot_path = /var/www/certbot
</span><span>$ sudo vim /etc/letsencrypt/renewal/your-domain.com.conf
</span></code></pre>
<h3 id="tcp-ip-tuning">TCP/IP Tuning<a class="zola-anchor" href="#tcp-ip-tuning" aria-label="Anchor link for: tcp-ip-tuning">🔗</a></h3>
<p><strong>On Linux</strong></p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo cp /var/www/peertube/peertube-latest/support/sysctl.d/30-peertube-tcp.conf /etc/sysctl.d/
</span><span>$ sudo sysctl -p /etc/sysctl.d/30-peertube-tcp.conf
</span></code></pre>
<p>Your distro may enable this by default, but at least Debian 9 does not, and the default FIFO
scheduler is quite prone to “Buffer Bloat” and extreme latency when dealing with slower client
links as we often encounter in a video server.</p>
<h3 id="systemd">systemd<a class="zola-anchor" href="#systemd" aria-label="Anchor link for: systemd">🔗</a></h3>
<p>If your OS uses systemd, copy the configuration template:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo cp /var/www/peertube/peertube-latest/support/systemd/peertube.service /etc/systemd/system/
</span></code></pre>
<p>Check the service file (PeerTube paths and security directives):</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo vim /etc/systemd/system/peertube.service
</span></code></pre>
<p>Tell systemd to reload its config:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo systemctl daemon-reload
</span></code></pre>
<p>If you want to start PeerTube on boot:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo systemctl enable peertube
</span></code></pre>
<p>Run:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo systemctl start peertube
</span><span>$ sudo journalctl -feu peertube
</span></code></pre>
<p>Run:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo service peertube start
</span></code></pre>
<h3 id="administrator">Administrator<a class="zola-anchor" href="#administrator" aria-label="Anchor link for: administrator">🔗</a></h3>
<p>The administrator password is automatically generated and can be found in the PeerTube
logs (path defined in <code>production.yaml</code>). You can also set another password with:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube/peertube-latest && NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run reset-password -- -u root
</span></code></pre>
<p>Alternatively you can set the environment variable <code>PT_INITIAL_ROOT_PASSWORD</code>,
to your own administrator password, although it must be 6 characters or more.</p>
<h3 id="what-now">What now?<a class="zola-anchor" href="#what-now" aria-label="Anchor link for: what-now">🔗</a></h3>
<p>Now your instance is up you can:</p>
<ul>
<li>Add your instance to the public PeerTube instances index if you want to: https://instances.joinpeertube.org/</li>
<li>Check <a href="/support/doc/tools.md">available CLI tools</a></li>
</ul>
<h2 id="upgrade">Upgrade<a class="zola-anchor" href="#upgrade" aria-label="Anchor link for: upgrade">🔗</a></h2>
<h3 id="peertube-instance">PeerTube instance<a class="zola-anchor" href="#peertube-instance" aria-label="Anchor link for: peertube-instance">🔗</a></h3>
<p><strong>Check the changelog (in particular BREAKING CHANGES!):</strong> https://github.com/Chocobozzz/PeerTube/blob/develop/CHANGELOG.md</p>
<h4 id="auto">Auto<a class="zola-anchor" href="#auto" aria-label="Anchor link for: auto">🔗</a></h4>
<p>The password it asks is PeerTube’s database user password.</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube/peertube-latest/scripts && sudo -H -u peertube ./upgrade.sh
</span><span>$ sudo systemctl restart peertube # Or use your OS command to restart PeerTube if you don't use systemd
</span></code></pre>
<h4 id="manually">Manually<a class="zola-anchor" href="#manually" aria-label="Anchor link for: manually">🔗</a></h4>
<p>Make a SQL backup</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ SQL_BACKUP_PATH="backup/sql-peertube_prod-$(date -Im).bak" && \
</span><span> cd /var/www/peertube && sudo -u peertube mkdir -p backup && \
</span><span> sudo -u postgres pg_dump -F c peertube_prod | sudo -u peertube tee "$SQL_BACKUP_PATH" >/dev/null
</span></code></pre>
<p>Fetch the latest tagged version of Peertube:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ VERSION=$(curl -s https://api.github.com/repos/chocobozzz/peertube/releases/latest | grep tag_name | cut -d '"' -f 4) && echo "Latest Peertube version is $VERSION"
</span></code></pre>
<p>Download the new version and unzip it:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube/versions && \
</span><span> sudo -u peertube wget -q "https://github.com/Chocobozzz/PeerTube/releases/download/${VERSION}/peertube-${VERSION}.zip" && \
</span><span> sudo -u peertube unzip -o peertube-${VERSION}.zip && \
</span><span> sudo -u peertube rm peertube-${VERSION}.zip
</span></code></pre>
<p>Install node dependencies:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube/versions/peertube-${VERSION} && \
</span><span> sudo -H -u peertube yarn install --production --pure-lockfile
</span></code></pre>
<p>Copy new configuration defaults values and update your configuration file:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo -u peertube cp /var/www/peertube/versions/peertube-${VERSION}/config/default.yaml /var/www/peertube/config/default.yaml
</span><span>$ diff /var/www/peertube/versions/peertube-${VERSION}/config/production.yaml.example /var/www/peertube/config/production.yaml
</span></code></pre>
<p>Change the link to point to the latest version:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube && \
</span><span> sudo unlink ./peertube-latest && \
</span><span> sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest
</span></code></pre>
<h3 id="nginx">nginx<a class="zola-anchor" href="#nginx" aria-label="Anchor link for: nginx">🔗</a></h3>
<p>Check changes in nginx configuration:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube/versions
</span><span>$ diff "$(ls --sort=t | head -2 | tail -1)/support/nginx/peertube" "$(ls --sort=t | head -1)/support/nginx/peertube"
</span></code></pre>
<h3 id="systemd-1">systemd<a class="zola-anchor" href="#systemd-1" aria-label="Anchor link for: systemd-1">🔗</a></h3>
<p>Check changes in systemd configuration:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ cd /var/www/peertube/versions
</span><span>$ diff "$(ls --sort=t | head -2 | tail -1)/support/systemd/peertube.service" "$(ls --sort=t | head -1)/support/systemd/peertube.service"
</span></code></pre>
<h3 id="restart-peertube">Restart PeerTube<a class="zola-anchor" href="#restart-peertube" aria-label="Anchor link for: restart-peertube">🔗</a></h3>
<p>If you changed your nginx configuration:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo systemctl reload nginx
</span></code></pre>
<p>If you changed your systemd configuration:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo systemctl daemon-reload
</span></code></pre>
<p>Restart PeerTube and check the logs:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ sudo systemctl restart peertube && sudo journalctl -fu peertube
</span></code></pre>
<h3 id="things-went-wrong">Things went wrong?<a class="zola-anchor" href="#things-went-wrong" aria-label="Anchor link for: things-went-wrong">🔗</a></h3>
<p>Change <code>peertube-latest</code> destination to the previous version and restore your SQL backup:</p>
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ OLD_VERSION="v0.42.42" && SQL_BACKUP_PATH="backup/sql-peertube_prod-2018-01-19T10:18+01:00.bak" && \
</span><span> cd /var/www/peertube && sudo -u peertube unlink ./peertube-latest && \
</span><span> sudo -u peertube ln -s "versions/peertube-$OLD_VERSION" peertube-latest && \
</span><span> sudo -u postgres pg_restore -c -C -d postgres "$SQL_BACKUP_PATH" && \
</span><span> sudo systemctl restart peertube
</span></code></pre>
Radarr Setup
2020-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/radarr-setup/
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/Radarr/Radarr">repo</a></p>
<span id="continue-reading"></span><h2 id="install">Install<a class="zola-anchor" href="#install" aria-label="Anchor link for: install">🔗</a></h2>
<p>See also <a rel="noopener nofollow noreferrer" target="_blank" href="https://wiki.servarr.com/radarr/installation#linux">here</a></p>
<p><a rel="noopener nofollow noreferrer" target="_blank" href="https://github.com/Radarr/Radarr/releases">Latest Release</a></p>
<h3 id="systemd">Systemd<a class="zola-anchor" href="#systemd" aria-label="Anchor link for: systemd">🔗</a></h3>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">cat </span><span><< </span><span style="color:#b48ead;">EOF </span><span>| </span><span style="color:#bf616a;">sudo</span><span> tee /etc/systemd/system/radarr.service > /dev/null
</span><span style="color:#a3be8c;">[Unit]
</span><span style="color:#a3be8c;">Description=Radarr Daemon
</span><span style="color:#a3be8c;">After=syslog.target network.target
</span><span style="color:#a3be8c;">[Service]
</span><span style="color:#a3be8c;">User=green
</span><span style="color:#a3be8c;">Group=admin
</span><span style="color:#a3be8c;">Type=simple
</span><span style="color:#a3be8c;">
</span><span style="color:#a3be8c;">ExecStart=/home/green/radarr/Radarr -nobrowser -data=/home/green/.config/radarr/
</span><span style="color:#a3be8c;">TimeoutStopSec=20
</span><span style="color:#a3be8c;">KillMode=process
</span><span style="color:#a3be8c;">Restart=always
</span><span style="color:#a3be8c;">[Install]
</span><span style="color:#a3be8c;">WantedBy=multi-user.target
</span><span style="color:#b48ead;">EOF
</span></code></pre>
Sonarr Setup
2020-03-26T00:00:00+00:00
2022-03-26T00:00:00+00:00
https://www.owenyoung.com/en/blog/sonarr-setup/
<p>Before setup sonarr, you should setup <a href="https://www.owenyoung.com/en/blog/qbittorrent-setup-for-debian/">qBittorrent setup for Debian</a></p>
<span id="continue-reading"></span><h2 id="install">Install<a class="zola-anchor" href="#install" aria-label="Anchor link for: install">🔗</a></h2>
<p>See <a rel="noopener nofollow noreferrer" target="_blank" href="https://sonarr.tv/#downloads-v3-linux">here</a></p>
<p>Once installed, visit <a rel="noopener nofollow noreferrer" target="_blank" href="http://ip:8989">http://ip:8989</a></p>
<h2 id="restart">Restart<a class="zola-anchor" href="#restart" aria-label="Anchor link for: restart">🔗</a></h2>
<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">sudo</span><span> systemctl restart sonarr
</span></code></pre>
<h2 id="setup">Setup<a class="zola-anchor" href="#setup" aria-label="Anchor link for: setup">🔗</a></h2>
<p>See also <a rel="noopener nofollow noreferrer" target="_blank" href="https://wiki.servarr.com/sonarr/quick-start-guide">Sonarr Quick Start Guide | WikiArr</a></p>
<ol>
<li>
<p>Click on <code>Settings => Media Management</code> on the left menu.</p>
<p>Check <code>Rename Episodes</code></p>
</li>
<li>
<p>Check left top <code>Show Advanced</code></p>
<p><code>Importing</code> -> <code>Import Extra Files</code> <code>.srt,.ass,.sub</code></p>
</li>
<li>
<p>Add <code>/data/TV</code> to <code>Root Folders</code></p>
</li>
<li>
<p>Indexers, Add <code>eztv</code>, <code>RARBG</code> as Indexers, see jackett home page.</p>
</li>
</ol>
<h3 id="add-qbittorrent-client">Add qBittorrent Client:<a class="zola-anchor" href="#add-qbittorrent-client" aria-label="Anchor link for: add-qbittorrent-client">🔗</a></h3>
<p><code>Setting</code> -> <code>Download Clients </code> -> <code>qBittorrent</code></p>
<h2 id="resource">Resource<a class="zola-anchor" href="#resource" aria-label="Anchor link for: resource">🔗</a></h2>
<ul>
<li><a rel="noopener nofollow noreferrer" target="_blank" href="https://sleele.com/2020/03/16/%E9%AB%98%E9%98%B6%E6%95%99%E7%A8%8B-%E8%BF%BD%E5%89%A7%E5%85%A8%E6%B5%81%E7%A8%8B%E8%87%AA%E5%8A%A8%E5%8C%96/">高阶教程-追剧全流程自动化</a></li>
</ul>