Initial import of Hugo site to Forgejo
This commit is contained in:
commit
cb1ba0317f
1259 changed files with 236349 additions and 0 deletions
693
public-onion/posts/hedge_doc/index.html
Normal file
693
public-onion/posts/hedge_doc/index.html
Normal file
|
|
@ -0,0 +1,693 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" dir="auto">
|
||||
|
||||
<head><meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title>Self-Hosting HedgeDoc with Docker + Nginx + Let's Encrypt | AlipourIm journeys</title>
|
||||
<meta name="keywords" content="hedgedoc, docker, nginx, letsencrypt, self-hosting">
|
||||
<meta name="description" content="HedgeDoc is an open-source collaborative Markdown editor. Think Google Docs for Markdown: multiple people can edit the same note in real-time, with support for diagrams, math, polls, and slide decks. In this post we’ll walk through setting up your own instance on a server, secured with HTTPS.
|
||||
|
||||
Prerequisites
|
||||
|
||||
A Linux server with Docker and Docker Compose
|
||||
A domain name pointing to your server (e.g. notes.alipourimjourneys.ir)
|
||||
Nginx installed for reverse proxying
|
||||
Certbot for Let’s Encrypt certificates
|
||||
|
||||
|
||||
1. Create the project directory
|
||||
mkdir ~/hedgedoc && cd ~/hedgedoc
|
||||
|
||||
2. Create .env
|
||||
POSTGRES_PASSWORD=ChangeThisStrongPassword
|
||||
HD_DOMAIN=notes.alipourimjourneys.ir
|
||||
Generate a strong password with:">
|
||||
<meta name="author" content="Iman Alipour">
|
||||
<link rel="canonical" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/hedge_doc/">
|
||||
<link crossorigin="anonymous" href="/assets/css/stylesheet.31b150d909186c48d6dbbf653acc2489945fa9ac2c8f8cd3fe8448e89e40fccf.css" integrity="sha256-MbFQ2QkYbEjW279lOswkiZRfqawsj4zT/oRI6J5A/M8=" rel="preload stylesheet" as="style">
|
||||
<link rel="icon" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/favicon.ico">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/favicon-16x16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/favicon-32x32.png">
|
||||
<link rel="apple-touch-icon" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/apple-touch-icon.png">
|
||||
<link rel="mask-icon" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/safari-pinned-tab.svg">
|
||||
<meta name="theme-color" content="#2e2e33">
|
||||
<meta name="msapplication-TileColor" content="#2e2e33">
|
||||
<link rel="alternate" hreflang="en" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/hedge_doc/">
|
||||
<noscript>
|
||||
<style>
|
||||
#theme-toggle,
|
||||
.top-link {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--theme: rgb(29, 30, 32);
|
||||
--entry: rgb(46, 46, 51);
|
||||
--primary: rgb(218, 218, 219);
|
||||
--secondary: rgb(155, 156, 157);
|
||||
--tertiary: rgb(65, 66, 68);
|
||||
--content: rgb(196, 196, 197);
|
||||
--code-block-bg: rgb(46, 46, 51);
|
||||
--code-bg: rgb(55, 56, 62);
|
||||
--border: rgb(51, 51, 51);
|
||||
}
|
||||
|
||||
.list {
|
||||
background: var(--theme);
|
||||
}
|
||||
|
||||
.list:not(.dark)::-webkit-scrollbar-track {
|
||||
background: 0 0;
|
||||
}
|
||||
|
||||
.list:not(.dark)::-webkit-scrollbar-thumb {
|
||||
border-color: var(--theme);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</noscript><script>
|
||||
(function () {
|
||||
|
||||
|
||||
window.goatcounter = { endpoint: "/count" };
|
||||
|
||||
const s = document.createElement("script");
|
||||
s.async = true;
|
||||
s.src = "/js/count.js";
|
||||
document.head.appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
|
||||
const GC = "";
|
||||
const nf = new Intl.NumberFormat();
|
||||
const SING = "visit";
|
||||
const PLUR = "visits";
|
||||
|
||||
function ready(fn) {
|
||||
if (document.readyState === "complete" || document.readyState === "interactive") setTimeout(fn, 0);
|
||||
else document.addEventListener("DOMContentLoaded", fn);
|
||||
}
|
||||
|
||||
function gcPath() {
|
||||
try {
|
||||
return window.goatcounter && window.goatcounter.get_data
|
||||
? window.goatcounter.get_data().p
|
||||
: location.pathname;
|
||||
} catch (_) {
|
||||
return location.pathname;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCount(path, period) {
|
||||
try {
|
||||
const q = new URLSearchParams({ t: String(Date.now()) });
|
||||
if (period) q.set("start", period);
|
||||
const url = `${GC}/counter/${encodeURIComponent(path)}.json?${q}`;
|
||||
const r = await fetch(url, { credentials: "omit" });
|
||||
if (r.status === 404) return 0;
|
||||
if (!r.ok) throw 0;
|
||||
const j = await r.json();
|
||||
|
||||
const n = Number(String(j.count).replace(/,/g, ""));
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const labelVisits = (n) =>
|
||||
n === null ? "—" : `${nf.format(n)} ${n === 1 ? SING : PLUR}`;
|
||||
|
||||
ready(async function () {
|
||||
const post = document.querySelector(".post-single");
|
||||
if (post) {
|
||||
const meta = post.querySelector(
|
||||
".post-header .post-meta, .post-header .entry-meta, .post-meta"
|
||||
);
|
||||
const visits = await fetchCount(gcPath());
|
||||
if (meta) {
|
||||
let span = meta.querySelector(".post-views");
|
||||
if (!span) {
|
||||
span = document.createElement("span");
|
||||
span.className = "post-views";
|
||||
meta.appendChild(span);
|
||||
}
|
||||
span.textContent = labelVisits(visits ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
const cards = document.querySelectorAll("article.post-entry");
|
||||
await Promise.all(
|
||||
Array.from(cards).map(async (card) => {
|
||||
const meta = card.querySelector(".entry-footer, .post-meta");
|
||||
const link = card.querySelector("a.entry-link, h2 a, .entry-title a");
|
||||
if (!meta || !link) return;
|
||||
try {
|
||||
const u = new URL(link.getAttribute("href"), location.origin);
|
||||
if (u.origin !== location.origin) return;
|
||||
let span = meta.querySelector(".post-views");
|
||||
if (!span) {
|
||||
span = document.createElement("span");
|
||||
span.className = "post-views";
|
||||
meta.appendChild(span);
|
||||
}
|
||||
span.textContent = labelVisits((await fetchCount(u.pathname)) ?? 0);
|
||||
} catch {}
|
||||
})
|
||||
);
|
||||
|
||||
const uvEl = document.getElementById("gc_site_visitors_week");
|
||||
if (uvEl) {
|
||||
const n = await fetchCount("TOTAL", "week");
|
||||
uvEl.textContent = n === null ? "—" : nf.format(n);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="" id="top">
|
||||
<script>
|
||||
if (localStorage.getItem("pref-theme") === "dark") {
|
||||
document.body.classList.add('dark');
|
||||
} else if (localStorage.getItem("pref-theme") === "light") {
|
||||
document.body.classList.remove('dark')
|
||||
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.body.classList.add('dark');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<header class="header">
|
||||
<nav class="nav">
|
||||
<div class="logo">
|
||||
<a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/" accesskey="h" title="AlipourIm journeys (Alt + H)">AlipourIm journeys</a>
|
||||
<div class="logo-switches">
|
||||
<button id="theme-toggle" accesskey="t" title="(Alt + T)" aria-label="Toggle theme">
|
||||
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
</svg>
|
||||
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="5"></circle>
|
||||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="menu">
|
||||
<li>
|
||||
<a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/" title="Posts">
|
||||
<span>Posts</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/phd_journey/" title="PhD_journey">
|
||||
<span>PhD_journey</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/about/" title="About">
|
||||
<span>About</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main class="main">
|
||||
|
||||
<article class="post-single">
|
||||
<header class="post-header">
|
||||
<div class="breadcrumbs"><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/">Home</a> » <a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/">Posts</a></div>
|
||||
<h1 class="post-title entry-hint-parent">
|
||||
Self-Hosting HedgeDoc with Docker + Nginx + Let's Encrypt
|
||||
</h1>
|
||||
<div class="post-meta"><span title='2025-08-29 00:00:00 +0000 UTC'>August 29, 2025</span> · 2 min · Iman Alipour
|
||||
|
||||
<span class="post-meta-item">
|
||||
<a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/hedge_doc/#isso-thread" class="isso-comments-link">Comments</a>
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</header>
|
||||
<figure class="entry-cover">
|
||||
<img loading="eager" src="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/images/hedgedoc-cover.png" alt="HedgeDoc collaborative editing">
|
||||
<figcaption>Collaborative Markdown editing with HedgeDoc</figcaption>
|
||||
</figure>
|
||||
<div class="post-content"><p>HedgeDoc is an open-source collaborative Markdown editor. Think <em>Google Docs for Markdown</em>: multiple people can edit the same note in real-time, with support for diagrams, math, polls, and slide decks. In this post we’ll walk through setting up your own instance on a server, secured with HTTPS.</p>
|
||||
<hr>
|
||||
<h2 id="prerequisites">Prerequisites<a hidden class="anchor" aria-hidden="true" href="#prerequisites">#</a></h2>
|
||||
<ul>
|
||||
<li>A Linux server with <strong>Docker</strong> and <strong>Docker Compose</strong></li>
|
||||
<li>A domain name pointing to your server (e.g. <code>notes.alipourimjourneys.ir</code>)</li>
|
||||
<li><strong>Nginx</strong> installed for reverse proxying</li>
|
||||
<li><strong>Certbot</strong> for Let’s Encrypt certificates</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h2 id="1-create-the-project-directory">1. Create the project directory<a hidden class="anchor" aria-hidden="true" href="#1-create-the-project-directory">#</a></h2>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mkdir ~/hedgedoc <span style="color:#f92672">&&</span> cd ~/hedgedoc</span></span></code></pre></div>
|
||||
<hr>
|
||||
<h2 id="2-create-env">2. Create <code>.env</code><a hidden class="anchor" aria-hidden="true" href="#2-create-env">#</a></h2>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-env" data-lang="env"><span style="display:flex;"><span>POSTGRES_PASSWORD<span style="color:#f92672">=</span>ChangeThisStrongPassword
|
||||
</span></span><span style="display:flex;"><span>HD_DOMAIN<span style="color:#f92672">=</span>notes.alipourimjourneys.ir</span></span></code></pre></div>
|
||||
<p>Generate a strong password with:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>openssl rand -base64 <span style="color:#ae81ff">32</span></span></span></code></pre></div>
|
||||
<hr>
|
||||
<h2 id="3-create-docker-composeyml">3. Create <code>docker-compose.yml</code><a hidden class="anchor" aria-hidden="true" href="#3-create-docker-composeyml">#</a></h2>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">version</span>: <span style="color:#e6db74">"3.9"</span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">services</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">db</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">image</span>: <span style="color:#ae81ff">postgres:16</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">environment</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">POSTGRES_USER</span>: <span style="color:#ae81ff">hedgedoc</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">POSTGRES_PASSWORD</span>: <span style="color:#ae81ff">${POSTGRES_PASSWORD}</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">POSTGRES_DB</span>: <span style="color:#ae81ff">hedgedoc</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">volumes</span>:
|
||||
</span></span><span style="display:flex;"><span> - <span style="color:#ae81ff">db:/var/lib/postgresql/data</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">restart</span>: <span style="color:#ae81ff">unless-stopped</span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">hedgedoc</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">image</span>: <span style="color:#ae81ff">quay.io/hedgedoc/hedgedoc:1.10.2</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">depends_on</span>:
|
||||
</span></span><span style="display:flex;"><span> - <span style="color:#ae81ff">db</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">environment</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">CMD_DB_URL</span>: <span style="color:#ae81ff">postgres://hedgedoc:${POSTGRES_PASSWORD}@db:5432/hedgedoc</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">CMD_DOMAIN</span>: <span style="color:#ae81ff">${HD_DOMAIN}</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">CMD_PROTOCOL_USESSL</span>: <span style="color:#e6db74">"true"</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">CMD_URL_ADDPORT</span>: <span style="color:#e6db74">"false"</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">CMD_PORT</span>: <span style="color:#e6db74">"3000"</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">CMD_EMAIL</span>: <span style="color:#e6db74">"true"</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">CMD_ALLOW_EMAIL_REGISTER</span>: <span style="color:#e6db74">"false"</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">volumes</span>:
|
||||
</span></span><span style="display:flex;"><span> - <span style="color:#ae81ff">uploads:/hedgedoc/public/uploads</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">ports</span>:
|
||||
</span></span><span style="display:flex;"><span> - <span style="color:#e6db74">"127.0.0.1:3000:3000"</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">restart</span>: <span style="color:#ae81ff">unless-stopped</span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">volumes</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">db</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#ae81ff">uploads:</span></span></span></code></pre></div>
|
||||
<p>Bring it up:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker compose up -d</span></span></code></pre></div>
|
||||
<hr>
|
||||
<h2 id="4-get-a-lets-encrypt-certificate">4. Get a Let’s Encrypt certificate<a hidden class="anchor" aria-hidden="true" href="#4-get-a-lets-encrypt-certificate">#</a></h2>
|
||||
<p>Request a cert with a <strong>DNS challenge</strong>:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo certbot certonly --manual --preferred-challenges dns -d notes.alipourimjourneys.ir -m you@example.com --agree-tos --no-eff-email</span></span></code></pre></div>
|
||||
<p>Add the TXT record certbot asks for, wait for DNS to propagate, then continue.<br>
|
||||
Certificates will be in:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>/etc/letsencrypt/live/notes.alipourimjourneys.ir/</span></span></code></pre></div>
|
||||
<hr>
|
||||
<h2 id="5-configure-nginx">5. Configure Nginx<a hidden class="anchor" aria-hidden="true" href="#5-configure-nginx">#</a></h2>
|
||||
<p>Create <code>/etc/nginx/sites-available/notes.alipourimjourneys.ir</code>:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">server_name</span> <span style="color:#e6db74">notes.alipourimjourneys.ir</span>;
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">listen</span> <span style="color:#ae81ff">80</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:80</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">return</span> <span style="color:#ae81ff">301</span> <span style="color:#e6db74">https://</span>$host$request_uri;
|
||||
</span></span><span style="display:flex;"><span>}
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">server_name</span> <span style="color:#e6db74">notes.alipourimjourneys.ir</span>;
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">listen</span> <span style="color:#ae81ff">443</span> <span style="color:#e6db74">ssl</span> <span style="color:#e6db74">http2</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:443</span> <span style="color:#e6db74">ssl</span> <span style="color:#e6db74">http2</span>;
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">ssl_certificate</span> <span style="color:#e6db74">/etc/letsencrypt/live/notes.alipourimjourneys.ir/fullchain.pem</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">ssl_certificate_key</span> <span style="color:#e6db74">/etc/letsencrypt/live/notes.alipourimjourneys.ir/privkey.pem</span>;
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">location</span> <span style="color:#e6db74">/</span> {
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">http://127.0.0.1:3000</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Host</span> $host;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Real-IP</span> $remote_addr;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-For</span> $proxy_add_x_forwarded_for;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Proto</span> <span style="color:#e6db74">https</span>;
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_http_version</span> <span style="color:#ae81ff">1</span><span style="color:#e6db74">.1</span>;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Upgrade</span> $http_upgrade;
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Connection</span> <span style="color:#e6db74">"upgrade"</span>;
|
||||
</span></span><span style="display:flex;"><span> }
|
||||
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div>
|
||||
<p>Enable the config and reload Nginx:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ln -s /etc/nginx/sites-available/notes.alipourimjourneys.ir /etc/nginx/sites-enabled/
|
||||
</span></span><span style="display:flex;"><span>sudo nginx -t <span style="color:#f92672">&&</span> sudo systemctl reload nginx</span></span></code></pre></div>
|
||||
<hr>
|
||||
<h2 id="6-create-users">6. Create users<a hidden class="anchor" aria-hidden="true" href="#6-create-users">#</a></h2>
|
||||
<p>Because we disabled self-registration, create accounts manually:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker compose exec hedgedoc ./bin/manage_users --add alice@example.com</span></span></code></pre></div>
|
||||
<p>You’ll be prompted for a password.<br>
|
||||
To reset a password later:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker compose exec hedgedoc ./bin/manage_users --reset alice@example.com</span></span></code></pre></div>
|
||||
<hr>
|
||||
<h2 id="7-done">7. Done!<a hidden class="anchor" aria-hidden="true" href="#7-done">#</a></h2>
|
||||
<p>Visit <a href="https://notes.alipourimjourneys.ir">https://notes.alipourimjourneys.ir</a> and log in with the user you created. You now have your own collaborative Markdown editor 🎉</p>
|
||||
<hr>
|
||||
<p><strong>Extras:</strong></p>
|
||||
<ul>
|
||||
<li>Backups: dump the PostgreSQL database and save the <code>uploads</code> volume.</li>
|
||||
<li>Upgrades: <code>docker pull quay.io/hedgedoc/hedgedoc:latest && docker compose up -d</code>.</li>
|
||||
<li>Integrations: HedgeDoc supports S3/MinIO image storage, GitHub/GitLab/Google login, and more.</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<div class="signature-block" style="margin-top:1rem">
|
||||
<p><strong>Downloads:</strong>
|
||||
<a href="/sources/posts/hedge_doc.md">Markdown</a> ·
|
||||
<a href="/sources/posts/hedge_doc.md.asc">Signature (.asc)</a>
|
||||
</p><details class="signature">
|
||||
<summary>View OpenPGP signature</summary>
|
||||
<pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQIzBAABCgAdFiEEVaKl3oR5K6zGyu4/tYgoUOBMjSoFAmpkbucACgkQtYgoUOBM
|
||||
jSrPHA//WlMEZx1k/aGx3zau+wRrAOq4XlrvC7keBvqMs0PJGhJmT8jnOMh0+26w
|
||||
AGav2jBuChLYsDx+O8l18uxcsu9fdrz8r4N4sDOnsndSsg15rTT28P3MNvvUL8ns
|
||||
iOc9uEUkxF59rT3OXRI56hH3VX6vzxakdAnW3Afhki2Bl54jur2+6RVtuthGUEV+
|
||||
QZ/36QVXLrOAas1bqq3f38m4M2no3ZqVvpj+Alur2Ji/gcGZlSArBnIoJQoK5L+r
|
||||
UZLJsQ+LrqCJp4i+GkkX05si2srSTjawBu+ssHf+uwPg0Q6AMTbubrGiHrMMtMbM
|
||||
4PCFtS8vD/ZBVkVpSBybyXdMxQU+y9AI1LZ//X4cQWdqgRpinOVENuZ2FeVI6qa/
|
||||
sBGHLThq9nZEXmMT2oQa1wi+CaY17z9fYj20F5moOp2Q6pxBGPyHZRV3WAr5xURU
|
||||
5B9SwVtNqIzm7eoAbwckU3h+TfIJ4vGulSqPqHvZ/XAdbzCVXaSXBN951oA0No1y
|
||||
MyvsIskzKVPvSqndYjxLGiopvOpITgxN5q6a7ubBmxYCrS+SF74jPkDjtc4EFuKB
|
||||
QrMTBm/+Xl/VEESYv5Mx7hwYv1dnmJUFrx62FUYmAMaFP2UcMIlJJ5zQCwsDdREk
|
||||
aG3wFuWH4d9UFohK+MJIHqYk1+TbZJm3bnds8pOqniIZ7M5PcEg=
|
||||
=uf5y
|
||||
-----END PGP SIGNATURE-----
|
||||
</pre>
|
||||
</details><p><em>Verify locally:</em></p>
|
||||
<pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/hedge_doc.md
|
||||
torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/hedge_doc.md.asc
|
||||
gpg --verify hedge_doc.md.asc hedge_doc.md
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="post-footer">
|
||||
<ul class="post-tags">
|
||||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/hedgedoc/">Hedgedoc</a></li>
|
||||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/docker/">Docker</a></li>
|
||||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/nginx/">Nginx</a></li>
|
||||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/letsencrypt/">Letsencrypt</a></li>
|
||||
<li><a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/self-hosting/">Self-Hosting</a></li>
|
||||
</ul>
|
||||
<nav class="paginav">
|
||||
<a class="prev" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/phd_journey/augest_2025/">
|
||||
<span class="title">« Prev</span>
|
||||
<br>
|
||||
<span>Augest '25</span>
|
||||
</a>
|
||||
<a class="next" href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/matrix_setup/">
|
||||
<span class="title">Next »</span>
|
||||
<br>
|
||||
<span>Self-hosting Matrix + Element Call with LiveKit: from zero to working (and the [not so!!] fun debugging along the way)</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</footer><div class="comments-switch" style="display:flex;gap:.5rem;margin:.5rem 0 1rem;">
|
||||
<button id="btn-isso" type="button" aria-pressed="true">Anonymous (Isso)</button>
|
||||
<button id="btn-giscus" type="button" aria-pressed="false">GitHub (Giscus)</button>
|
||||
<a href="https://github.com/AlipourIm/blog-comments/discussions/categories/comments" target="_blank" rel="noopener">Open on GitHub ↗</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div id="panel-giscus" style="display:none">
|
||||
<section id="giscus-thread"></section>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="panel-isso">
|
||||
<section id="isso-thread"
|
||||
data-isso-id="/posts/hedge_doc/"
|
||||
data-title="Self-Hosting HedgeDoc with Docker + Nginx + Let&#39;s Encrypt">
|
||||
<noscript>Javascript needs to be activated to view comments.</noscript>
|
||||
</section>
|
||||
<script
|
||||
src="/isso/js/embed.min.js"
|
||||
data-isso="/isso/"
|
||||
data-isso-css="false"
|
||||
data-isso-lang="en"
|
||||
data-isso-max-comments-nested="5"
|
||||
data-isso-sorting="newest"
|
||||
async>
|
||||
</script>
|
||||
<small class="isso-powered" style="display:block;margin:.5rem 0;color:var(--secondary,#888)">
|
||||
Comments powered by <a href="https://isso-comments.de" target="_blank" rel="noopener">Isso</a>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var panelIsso = document.getElementById('panel-isso');
|
||||
var panelGisc = document.getElementById('panel-giscus');
|
||||
var issoBtn = document.getElementById('btn-isso');
|
||||
var giscBtn = document.getElementById('btn-giscus');
|
||||
var gLoaded = false;
|
||||
|
||||
function isDark() {
|
||||
var pref = localStorage.getItem('pref-theme');
|
||||
if (pref === 'dark') return true;
|
||||
if (pref === 'light') return false;
|
||||
|
||||
var html = document.documentElement, body = document.body;
|
||||
if (
|
||||
(html.classList && html.classList.contains('dark')) ||
|
||||
(body.classList && body.classList.contains('dark')) ||
|
||||
html.getAttribute('data-theme') === 'dark' ||
|
||||
body.getAttribute('data-theme') === 'dark' ||
|
||||
(html.classList && html.classList.contains('theme-dark')) ||
|
||||
(body.classList && body.classList.contains('theme-dark'))
|
||||
) return true;
|
||||
|
||||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
function gTheme(){ return isDark() ? 'dark_dimmed' : 'noborder_light'; }
|
||||
|
||||
function loadGiscus() {
|
||||
if (gLoaded) return;
|
||||
var s = document.createElement('script');
|
||||
s.src = 'https://giscus.app/client.js';
|
||||
s.async = true;
|
||||
s.crossOrigin = 'anonymous';
|
||||
s.setAttribute('data-repo','AlipourIm/blog-comments');
|
||||
s.setAttribute('data-repo-id','R_kgDOQGARyA');
|
||||
s.setAttribute('data-category','Comments');
|
||||
s.setAttribute('data-category-id','DIC_kwDOQGARyM4Cw3x-');
|
||||
s.setAttribute('data-mapping','pathname');
|
||||
s.setAttribute('data-strict','0');
|
||||
s.setAttribute('data-reactions-enabled','1');
|
||||
s.setAttribute('data-emit-metadata','0');
|
||||
s.setAttribute('data-input-position','bottom');
|
||||
s.setAttribute('data-lang','en');
|
||||
s.setAttribute('data-theme', gTheme());
|
||||
document.getElementById('giscus-thread').appendChild(s);
|
||||
|
||||
function reTheme(){
|
||||
var iframe = document.querySelector('iframe.giscus-frame');
|
||||
if (iframe && iframe.contentWindow) {
|
||||
iframe.contentWindow.postMessage(
|
||||
{ giscus: { setConfig: { theme: gTheme() } } },
|
||||
'https://giscus.app'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var tbtn = document.getElementById('theme-toggle');
|
||||
if (tbtn) tbtn.addEventListener('click', function(){ setTimeout(reTheme, 0); });
|
||||
|
||||
var mo = new MutationObserver(function(){ reTheme(); });
|
||||
mo.observe(document.documentElement, { attributes:true, attributeFilter:['class','data-theme'] });
|
||||
mo.observe(document.body, { attributes:true, attributeFilter:['class','data-theme'] });
|
||||
|
||||
gLoaded = true;
|
||||
}
|
||||
|
||||
function show(which) {
|
||||
var showIsso = which === 'isso';
|
||||
panelIsso.style.display = showIsso ? 'block' : 'none';
|
||||
panelGisc.style.display = showIsso ? 'none' : 'block';
|
||||
if (issoBtn) issoBtn.setAttribute('aria-pressed', showIsso ? 'true' : 'false');
|
||||
if (giscBtn) giscBtn.setAttribute('aria-pressed', showIsso ? 'false' : 'true');
|
||||
if (!showIsso) loadGiscus();
|
||||
}
|
||||
|
||||
if (issoBtn) issoBtn.addEventListener('click', function(e){ e.preventDefault(); show('isso'); });
|
||||
if (giscBtn) giscBtn.addEventListener('click', function(e){ e.preventDefault(); show('giscus'); });
|
||||
|
||||
|
||||
show('isso');
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<span>© 2026 <a href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/">AlipourIm journeys</a></span> ·
|
||||
|
||||
<span>
|
||||
Powered by
|
||||
<a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank">Hugo</a> &
|
||||
<a href="https://github.com/adityatelange/hugo-PaperMod/" rel="noopener" target="_blank">PaperMod</a>
|
||||
</span>
|
||||
</footer>
|
||||
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
|
||||
<path d="M12 6H0l6-6z" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
<div class="site-meta-row" style="display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;">
|
||||
<a href="/index.xml" rel="alternate" type="application/rss+xml" title="RSS"
|
||||
class="rss-link" style="display:inline-flex;align-items:center;gap:.35rem;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 11a9 9 0 0 1 9 9" />
|
||||
<path d="M4 4a16 16 0 0 1 16 16" />
|
||||
<circle cx="5" cy="19" r="1" />
|
||||
</svg>
|
||||
<span>RSS</span>
|
||||
</a>
|
||||
|
||||
<span aria-hidden="true">·</span>
|
||||
|
||||
<span>Visitors this week: <span id="gc_site_visitors_week">—</span></span>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
let menu = document.getElementById('menu')
|
||||
if (menu) {
|
||||
menu.scrollLeft = localStorage.getItem("menu-scroll-position");
|
||||
menu.onscroll = function () {
|
||||
localStorage.setItem("menu-scroll-position", menu.scrollLeft);
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
var id = this.getAttribute("href").substr(1);
|
||||
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
|
||||
behavior: "smooth"
|
||||
});
|
||||
} else {
|
||||
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
|
||||
}
|
||||
if (id === "top") {
|
||||
history.replaceState(null, null, " ");
|
||||
} else {
|
||||
history.pushState(null, null, `#${id}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
<script>
|
||||
var mybutton = document.getElementById("top-link");
|
||||
window.onscroll = function () {
|
||||
if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
|
||||
mybutton.style.visibility = "visible";
|
||||
mybutton.style.opacity = "1";
|
||||
} else {
|
||||
mybutton.style.visibility = "hidden";
|
||||
mybutton.style.opacity = "0";
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById("theme-toggle").addEventListener("click", () => {
|
||||
if (document.body.className.includes("dark")) {
|
||||
document.body.classList.remove('dark');
|
||||
localStorage.setItem("pref-theme", 'light');
|
||||
} else {
|
||||
document.body.classList.add('dark');
|
||||
localStorage.setItem("pref-theme", 'dark');
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
<script>
|
||||
document.querySelectorAll('pre > code').forEach((codeblock) => {
|
||||
const container = codeblock.parentNode.parentNode;
|
||||
|
||||
const copybutton = document.createElement('button');
|
||||
copybutton.classList.add('copy-code');
|
||||
copybutton.innerHTML = 'copy';
|
||||
|
||||
function copyingDone() {
|
||||
copybutton.innerHTML = 'copied!';
|
||||
setTimeout(() => {
|
||||
copybutton.innerHTML = 'copy';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
copybutton.addEventListener('click', (cb) => {
|
||||
if ('clipboard' in navigator) {
|
||||
navigator.clipboard.writeText(codeblock.textContent);
|
||||
copyingDone();
|
||||
return;
|
||||
}
|
||||
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(codeblock);
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
copyingDone();
|
||||
} catch (e) { };
|
||||
selection.removeRange(range);
|
||||
});
|
||||
|
||||
if (container.classList.contains("highlight")) {
|
||||
container.appendChild(copybutton);
|
||||
} else if (container.parentNode.firstChild == container) {
|
||||
|
||||
} else if (codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
|
||||
|
||||
codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copybutton);
|
||||
} else {
|
||||
|
||||
codeblock.parentNode.appendChild(copybutton);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue