mirror of
https://github.com/pikvm/pikvm.git
synced 2026-10-20 00:16:50 +00:00
3908 lines
98 KiB
HTML
3908 lines
98 KiB
HTML
<!DOCTYPE html><html lang="en" class="no-js"><head>
|
||
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
|
||
<meta name="description" content="How to configure the access to your PiKVM using Tailscale VPN">
|
||
|
||
|
||
<meta name="author" content="Maxim Devaev">
|
||
|
||
|
||
<link rel="canonical" href="https://pikvm.github.io/pikvm/tailscale/">
|
||
|
||
|
||
<link rel="prev" href="../reverse_proxy/">
|
||
|
||
|
||
<link rel="next" href="../cloudflared/">
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="alternate" type="application/rss+xml" title="RSS feed" href="../feed_rss_created.xml">
|
||
<link rel="alternate" type="application/rss+xml" title="RSS feed of updated content" href="../feed_rss_updated.xml">
|
||
|
||
<link rel="icon" href="../_assets/favicon.ico">
|
||
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.7">
|
||
|
||
|
||
|
||
<title>Tailscale VPN - PiKVM Handbook</title>
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="../assets/stylesheets/main.ec1eaa64.min.css">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=arial,+sans-serif:300,300i,400,400i,700,700i%7Cmonospace:400,400i,700,700i&display=fallback">
|
||
<style>:root{--md-text-font:"arial, sans-serif";--md-code-font:"monospace"}</style>
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="../_assets/user.css">
|
||
|
||
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
|
||
|
||
|
||
|
||
|
||
|
||
<link href="../assets/stylesheets/glightbox.min.css" rel="stylesheet"><script src="../assets/javascripts/glightbox.min.js"></script><style id="glightbox-style">
|
||
html.glightbox-open { overflow: initial; height: 100%; }
|
||
.gslide-title { margin-top: 0px; user-select: text; }
|
||
.gslide-desc { color: #666; user-select: text; }
|
||
.gslide-image img { background: white; }
|
||
.gscrollbar-fixer { padding-right: 15px; }
|
||
.gdesc-inner { font-size: 0.75rem; }
|
||
body[data-md-color-scheme="slate"] .gdesc-inner { background: var(--md-default-bg-color); }
|
||
body[data-md-color-scheme="slate"] .gslide-title { color: var(--md-default-fg-color); }
|
||
body[data-md-color-scheme="slate"] .gslide-desc { color: var(--md-default-fg-color); }
|
||
</style></head>
|
||
|
||
|
||
<body dir="ltr">
|
||
|
||
|
||
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
|
||
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
|
||
<label class="md-overlay" for="__drawer"></label>
|
||
<div data-md-component="skip">
|
||
|
||
|
||
<a href="#configuring-the-pikvm" class="md-skip">
|
||
Skip to content
|
||
</a>
|
||
|
||
</div>
|
||
<div data-md-component="announce">
|
||
|
||
</div>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<header class="md-header md-header--shadow" data-md-component="header">
|
||
<nav class="md-header__inner md-grid" aria-label="Header">
|
||
<a href=".." title="PiKVM Handbook" class="md-header__button md-logo" aria-label="PiKVM Handbook" data-md-component="logo">
|
||
|
||
<img src="../_assets/logo.png" alt="logo">
|
||
|
||
</a>
|
||
<label class="md-header__button md-icon" for="__drawer">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"></path></svg>
|
||
</label>
|
||
<div class="md-header__title" data-md-component="header-title">
|
||
<div class="md-header__ellipsis">
|
||
<div class="md-header__topic">
|
||
<span class="md-ellipsis">
|
||
PiKVM Handbook
|
||
</span>
|
||
</div>
|
||
<div class="md-header__topic" data-md-component="header-topic">
|
||
<span class="md-ellipsis">
|
||
|
||
Tailscale VPN
|
||
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
|
||
|
||
|
||
|
||
|
||
|
||
<label class="md-header__button md-icon" for="__search">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"></path></svg>
|
||
</label>
|
||
<div class="md-search" data-md-component="search" role="dialog">
|
||
<label class="md-search__overlay" for="__search"></label>
|
||
<div class="md-search__inner" role="search">
|
||
<form class="md-search__form" name="search">
|
||
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required="">
|
||
<label class="md-search__icon md-icon" for="__search">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"></path></svg>
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"></path></svg>
|
||
</label>
|
||
<nav class="md-search__options" aria-label="Search">
|
||
|
||
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard="" data-clipboard-text="" data-md-component="search-share" tabindex="-1">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"></path></svg>
|
||
</a>
|
||
|
||
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></svg>
|
||
</button>
|
||
</nav>
|
||
|
||
<div class="md-search__suggest" data-md-component="search-suggest"></div>
|
||
|
||
</form>
|
||
<div class="md-search__output">
|
||
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix="">
|
||
<div class="md-search-result" data-md-component="search-result">
|
||
<div class="md-search-result__meta">
|
||
Initializing search
|
||
</div>
|
||
<ol class="md-search-result__list" role="presentation"></ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<div class="md-header__source">
|
||
<a href="https://github.com/pikvm/pikvm" title="Go to repository" class="md-source" data-md-component="source">
|
||
<div class="md-source__icon md-icon">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"></path></svg>
|
||
</div>
|
||
<div class="md-source__repository">
|
||
pikvm/pikvm
|
||
</div>
|
||
</a>
|
||
</div>
|
||
|
||
</nav>
|
||
|
||
</header>
|
||
|
||
<div class="md-container" data-md-component="container">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<main class="md-main" data-md-component="main">
|
||
<div class="md-main__inner md-grid">
|
||
|
||
|
||
|
||
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation">
|
||
<div class="md-sidebar__scrollwrap">
|
||
<div class="md-sidebar__inner">
|
||
|
||
|
||
|
||
|
||
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
|
||
<label class="md-nav__title" for="__drawer">
|
||
<a href=".." title="PiKVM Handbook" class="md-nav__button md-logo" aria-label="PiKVM Handbook" data-md-component="logo">
|
||
|
||
<img src="../_assets/logo.png" alt="logo">
|
||
|
||
</a>
|
||
PiKVM Handbook
|
||
</label>
|
||
|
||
<div class="md-nav__source">
|
||
<a href="https://github.com/pikvm/pikvm" title="Go to repository" class="md-source" data-md-component="source">
|
||
<div class="md-source__icon md-icon">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"></path></svg>
|
||
</div>
|
||
<div class="md-source__repository">
|
||
pikvm/pikvm
|
||
</div>
|
||
</a>
|
||
</div>
|
||
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_1">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_1" id="__nav_1_label" tabindex="">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Device guides
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_1_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_1">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Device guides
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../v4/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
PiKVM V4 Mini & Plus
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../v3/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
PiKVM V3
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../switch/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
PiKVM Switch
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../v2/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
DIY PiKVM V2
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../v1/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
DIY PiKVM V1
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Getting started
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_2">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Getting started
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../cheatsheet/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Cheat sheet
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../webui/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Web UI overview
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../shortcuts/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Sending shortcuts
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../config/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Configuring PiKVM
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../auth/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Authentication & 2FA
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../faq/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
FAQ & troubleshooting
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../flashing_os/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Flashing OS
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" checked="">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Networking
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="true">
|
||
<label class="md-nav__title" for="__nav_3">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Networking
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3_1" checked="">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_3_1" id="__nav_3_1_label" tabindex="0">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Internet access
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_3_1_label" aria-expanded="true">
|
||
<label class="md-nav__title" for="__nav_3_1">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Internet access
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../port_forwarding/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Port forwarding
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../reverse_proxy/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Reverse proxy
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--active">
|
||
|
||
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
|
||
|
||
|
||
|
||
<label class="md-nav__link md-nav__link--active" for="__toc">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Tailscale VPN
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<a href="./" class="md-nav__link md-nav__link--active">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Tailscale VPN
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
|
||
|
||
|
||
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
|
||
|
||
|
||
|
||
|
||
<label class="md-nav__title" for="__toc">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
Table of contents
|
||
</label>
|
||
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix="">
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#configuring-the-pikvm" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Configuring the PiKVM
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#configuring-a-client-device" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Configuring a client device
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#using-tailscale-certificates" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Using Tailscale Certificates
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#automatic-certificate-renewal-for-nginx" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Automatic Certificate Renewal for Nginx
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#automated-ephemeral-tailscale-certificates-renewal" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Automated Ephemeral Tailscale Certificates Renewal
|
||
|
||
</span>
|
||
</a>
|
||
|
||
<nav class="md-nav" aria-label="Automated Ephemeral Tailscale Certificates Renewal">
|
||
<ul class="md-nav__list">
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#root-cause" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Root cause
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#solution" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Solution
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#boot-sequence-recap" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Boot sequence recap:
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#troubleshooting" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Troubleshooting
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../cloudflared/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Cloudflare tunnel
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../netbird/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
NetBird VPN
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../wifi/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Setting up Wi-Fi
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../modem/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Setting up 3G/4G/LTE modem
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../letsencrypt/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Let's Encrypt certificates
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_4">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_4" id="__nav_4_label" tabindex="">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Video
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_4_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_4">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Video
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../video/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Video modes (WebRTC, Direct)
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../webrtc_config/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
WebRTC configuration
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../edid/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Tuning HDMI EDID
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../pass/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
HDMI passthrough
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../latency/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Latency
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_5">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Peripheral devices
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_5_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_5">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Peripheral devices
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../atx_board/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
ATX board
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../usb/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
USB configuration
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../usb_pass/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
USB passthrough
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../audio/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Audio / Microphone
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../camera/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Web camera
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_5_6">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_5_6" id="__nav_5_6_label" tabindex="0">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Keyboard & mouse
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_6_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_5_6">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Keyboard & mouse
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../mouse/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Mouse modes
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../mouse_jiggler/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Mouse jiggler
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../pico_hid/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Pico HID (USB, PS/2)
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../bluetooth_hid/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Bluetooth HID
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../msd/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Mass storage drive
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../usb_ethernet/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Ethernet-over-USB
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../usb_serial/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Serial-over-USB
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../gpio/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
GPIO (pins, relays, lamps, etc)
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_6">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_6" id="__nav_6_label" tabindex="">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Advanced usage
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_6_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_6">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Advanced usage
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../auth_advanced/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Advanced authentication
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../id/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
PiKVM identification
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../on_boot_config/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
On-boot configuration
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../auto_snapshots/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Automatic snapshots
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../vnc/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Using VNC
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../multiport/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Multiport KVM-over-IP
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../wol/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Wake-on-LAN the server
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../ipmi/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
IPMI & Redfish integration
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../pst/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Persistent storage
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../prometheus/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Prometheus monitoring
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_7">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_7" id="__nav_7_label" tabindex="">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Development
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_7_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_7">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Development
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../api/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
HTTP API reference
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../building_os/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Building PiKVM OS
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../3d_printing/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Cases for 3D printing
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_8">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_8" id="__nav_8_label" tabindex="">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Legacy
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_8_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_8">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Legacy
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../arduino_hid/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Arduino HID
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_9">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_9" id="__nav_9_label" tabindex="">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
PiKVM info
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_9_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_9">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
PiKVM info
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../compliance/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Compliance
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_9_2">
|
||
|
||
|
||
<div class="md-nav__link md-nav__container">
|
||
<a href="../blog/" class="md-nav__link ">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Blog & news
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
|
||
|
||
<label class="md-nav__link " for="__nav_9_2" id="__nav_9_2_label" tabindex="0">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
</div>
|
||
|
||
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_9_2_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_9_2">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Blog & news
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_9_2_2">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_9_2_2" id="__nav_9_2_2_label" tabindex="0">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Archive
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="3" aria-labelledby="__nav_9_2_2_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_9_2_2">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Archive
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../blog/archive/2026/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
2026
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../blog/archive/2025/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
2025
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../blog/archive/2024/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
2024
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../blog/archive/2023/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
2023
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../blog/archive/2022/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
2022
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../blog/archive/2021/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
2021
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../blog/archive/2020/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
2020
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--nested">
|
||
|
||
|
||
|
||
|
||
|
||
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_9_2_3">
|
||
|
||
|
||
<label class="md-nav__link" for="__nav_9_2_3" id="__nav_9_2_3_label" tabindex="0">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Categories
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<nav class="md-nav" data-md-level="3" aria-labelledby="__nav_9_2_3_label" aria-expanded="false">
|
||
<label class="md-nav__title" for="__nav_9_2_3">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
|
||
|
||
Categories
|
||
|
||
|
||
</label>
|
||
<ul class="md-nav__list" data-md-scrollfix="">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../blog/category/development/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Development
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../blog/category/products/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Products
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../blog/category/releases/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Releases
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc">
|
||
<div class="md-sidebar__scrollwrap">
|
||
<div class="md-sidebar__inner">
|
||
|
||
|
||
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
|
||
|
||
|
||
|
||
|
||
<label class="md-nav__title" for="__toc">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
Table of contents
|
||
</label>
|
||
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix="">
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#configuring-the-pikvm" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Configuring the PiKVM
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#configuring-a-client-device" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Configuring a client device
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#using-tailscale-certificates" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Using Tailscale Certificates
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#automatic-certificate-renewal-for-nginx" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Automatic Certificate Renewal for Nginx
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#automated-ephemeral-tailscale-certificates-renewal" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Automated Ephemeral Tailscale Certificates Renewal
|
||
|
||
</span>
|
||
</a>
|
||
|
||
<nav class="md-nav" aria-label="Automated Ephemeral Tailscale Certificates Renewal">
|
||
<ul class="md-nav__list">
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#root-cause" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Root cause
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#solution" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Solution
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#boot-sequence-recap" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Boot sequence recap:
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#troubleshooting" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Troubleshooting
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<div class="md-content" data-md-component="content">
|
||
|
||
<article class="md-content__inner md-typeset">
|
||
|
||
|
||
|
||
|
||
|
||
<h1>Tailscale VPN</h1>
|
||
|
||
<div><p>The <a href="https://tailscale.com/">Tailscale VPN</a> can be used to access PiKVM
|
||
from the Internet if configuring <a href="../port_forwarding/">port forwarding</a>
|
||
is not possible or more security is desired. Tailscale is a convenient
|
||
and free (for private use) tool for organizing a small VPN network.</p>
|
||
<p>The basic Tailscale configuration commands are shown below. For detailed
|
||
instructions, refer to <a href="https://tailscale.com/contact/support/">Tailscale
|
||
support</a>.</p>
|
||
<hr>
|
||
<h2 id="configuring-the-pikvm">Configuring the PiKVM<a class="headerlink" href="#configuring-the-pikvm" title="Permanent link">¶</a></h2>
|
||
<ol>
|
||
<li>
|
||
<p>Update OS:</p>
|
||
<details class="note">
|
||
<summary>Updating PiKVM OS</summary>
|
||
<div class="admonition tip">
|
||
<p class="admonition-title">Tip</p>
|
||
<p>We recommend updating PiKVM OS only if you have physical access to the device, or in the most extreme cases.
|
||
The update process is very reliable, but there is always a small chance that something may go wrong
|
||
so reflashing will be required. PiKVM cannot be bricked, but you need physical access to the memory card
|
||
for this operation.</p>
|
||
</div>
|
||
<p>To update, run following commands under the <code>root</code> user:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>pikvm-update
|
||
</code></pre></div>
|
||
<p>If you encounter an error like:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>pikvm-update
|
||
<span class="go">bash: pikvm-update: command not found</span>
|
||
</code></pre></div>
|
||
<p>It's most likely you have an old OS release. You can update the OS as follows:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>rw
|
||
<span class="gp">[root@pikvm ~]# </span>pacman<span class="w"> </span>-Syy
|
||
<span class="gp">[root@pikvm ~]# </span>pacman<span class="w"> </span>-S<span class="w"> </span>pikvm-os-updater
|
||
<span class="gp">[root@pikvm ~]# </span>pikvm-update
|
||
</code></pre></div>
|
||
<p>Next time you will be able to use the usual method with <code>pikvm-update</code>.</p>
|
||
</details>
|
||
</li>
|
||
<li>
|
||
<p>Install the Tailscale client, run <code>tailscaled</code> service and register it in the network:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>rw
|
||
<span class="gp"># </span>If<span class="w"> </span>you<span class="w"> </span>were<span class="w"> </span>afraid<span class="w"> </span>to<span class="w"> </span>pikvm-update<span class="w"> </span>above<span class="w"> </span>first<span class="w"> </span>run<span class="w"> </span>pacman<span class="w"> </span>-Syy
|
||
<span class="gp">[root@pikvm ~]# </span>pacman<span class="w"> </span>-S<span class="w"> </span>tailscale-pikvm
|
||
<span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>--now<span class="w"> </span>tailscaled
|
||
<span class="gp">[root@pikvm ~]# </span>tailscale<span class="w"> </span>up
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Follow the link to authorize this installation.
|
||
You likely want to <a href="https://tailscale.com/kb/1028/key-expiry/">disable key expiry</a>!</p>
|
||
</li>
|
||
<li>
|
||
<p>After authorization success, reboot to make sure that everything works correctly:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>reboot
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Now, you can view the IP address of the Tailscale network interface:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>ip<span class="w"> </span>addr<span class="w"> </span>show<span class="w"> </span>tailscale0
|
||
</code></pre></div>
|
||
</li>
|
||
</ol>
|
||
<p>If everything is successful, PiKVM will become a member of your VPN network.</p>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Do not update Tailscale if you don't have access to PiKVM without VPN</p>
|
||
<p>Unfortunately, sometimes, updating the Tailscale client can cause problems due to
|
||
breaking changes. These are compatibility issues on the Tailscale side.
|
||
Remember this when updating.</p>
|
||
</div>
|
||
<hr>
|
||
<h2 id="configuring-a-client-device">Configuring a client device<a class="headerlink" href="#configuring-a-client-device" title="Permanent link">¶</a></h2>
|
||
<ul>
|
||
<li><a href="https://tailscale.com/download">Download</a> and install the Tailscale client
|
||
to the system you are using (not to the system you want to control).</li>
|
||
<li>Check the <a href="https://login.tailscale.com/admin/machines">Tailscale admin page</a> to view your VPN network.</li>
|
||
<li>Follow the URL in the web browser: <code>https://<tailscale_kvm_ip></code> and you will see the PiKVM web interface.</li>
|
||
</ul>
|
||
<hr>
|
||
<h2 id="using-tailscale-certificates">Using Tailscale Certificates<a class="headerlink" href="#using-tailscale-certificates" title="Permanent link">¶</a></h2>
|
||
<p>PiKVM uses self-signed SSL certificates out of the box. You can also use
|
||
<a href="https://tailscale.com/kb/1153/enabling-https">Tailscale certificates</a> in place of the default one.</p>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<p>Tailscale certificates are provided by Let's Encrypt and have a default
|
||
<a href="https://letsencrypt.org/2015/11/09/why-90-days/">expiry of 90 days</a>.
|
||
Certificates created with <code>tailscale cert</code> are not installed or renewed
|
||
automatically. Follow the steps below for a manual installation, or use
|
||
the <a href="#automatic-certificate-renewal-for-nginx">automatic renewal</a> setup.</p>
|
||
</div>
|
||
<ol>
|
||
<li>
|
||
<p>Switch filesystem to RW if in ReadOnly mode and delete existing PiKVM certificates for nginx and vnc.</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>rw
|
||
<span class="gp">[root@pikvm ~]# </span>rm<span class="w"> </span>-v<span class="w"> </span>/etc/kvmd/<span class="o">{</span>nginx,vnc<span class="o">}</span>/ssl/*.<span class="o">{</span>crt,key<span class="o">}</span>
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Provision new certificates using <a href="https://tailscale.com/kb/1080/cli#cert"><code>tailscale cert</code></a>
|
||
command. Optionally you may create a directory to store the certificates.</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>mkdir<span class="w"> </span>.cert
|
||
<span class="gp">[root@pikvm ~]# </span><span class="nb">cd</span><span class="w"> </span>.cert
|
||
<span class="gp">[root@pikvm .cert]# </span>tailscale<span class="w"> </span>cert<span class="w"> </span><tailscale_hostname>
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Copy the certificates to nginx's and vnc's ssl directories.</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>cp<span class="w"> </span>/root/.cert/<tailscale_hostname>.key<span class="w"> </span>/etc/kvmd/nginx/ssl/server.key
|
||
<span class="gp">[root@pikvm ~]# </span>cp<span class="w"> </span>/root/.cert/<tailscale_hostname>.crt<span class="w"> </span>/etc/kvmd/nginx/ssl/server.crt
|
||
</code></pre></div>
|
||
<p>Repeat the same steps for vnc if you have configured it.</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>cp<span class="w"> </span>/root/.cert/<tailscale_hostname>.key<span class="w"> </span>/etc/kvmd/vnc/ssl/server.key
|
||
<span class="gp">[root@pikvm ~]# </span>cp<span class="w"> </span>/root/.cert/<tailscale_hostname>.crt<span class="w"> </span>/etc/kvmd/vnc/ssl/server.crt
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Grant file ownership to nginx and vnc services. Switch filesystem to ReadOnly again</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>chown<span class="w"> </span>:kvmd-nginx<span class="w"> </span>/etc/kvmd/nginx/ssl/*
|
||
<span class="gp">[root@pikvm ~]# </span>chown<span class="w"> </span>:kvmd-vnc<span class="w"> </span>/etc/kvmd/vnc/ssl/*
|
||
<span class="gp">[root@pikvm ~]# </span>ro
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Restart nginx and vnc services</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>restart<span class="w"> </span>kvmd-nginx
|
||
<span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>restart<span class="w"> </span>kvmd-vnc
|
||
</code></pre></div>
|
||
</li>
|
||
</ol>
|
||
<hr>
|
||
<h2 id="automatic-certificate-renewal-for-nginx">Automatic Certificate Renewal for Nginx<a class="headerlink" href="#automatic-certificate-renewal-for-nginx" title="Permanent link">¶</a></h2>
|
||
<p>The following systemd service checks the certificate daily and renews it only
|
||
when it does not match the configured Tailscale hostname or has less than 30
|
||
days remaining. It stages and validates the new certificate and key before
|
||
installing them and reloads nginx without interrupting existing connections.
|
||
The certificate and key are kept in <a href="../pst/">persistent storage</a>, which
|
||
<code>kvmd-pstrun</code> makes writable only while the renewal script is running. The root
|
||
filesystem remains read-only during scheduled checks and renewals.</p>
|
||
<p>This setup manages the nginx certificate only. If you use <a href="../vnc/">KVMD-VNC</a>,
|
||
extend the script to install copies of the certificate and key with the
|
||
<code>kvmd-vnc</code> group and restart <code>kvmd-vnc</code> after renewal.</p>
|
||
<ol>
|
||
<li>
|
||
<p>Switch the filesystem to RW mode and create
|
||
<code>/etc/conf.d/kvmd-tailscale-cert-renew</code>:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>rw
|
||
<span class="gp">[root@pikvm ~]# </span>nano<span class="w"> </span>/etc/conf.d/kvmd-tailscale-cert-renew
|
||
</code></pre></div>
|
||
<p>Set the full MagicDNS hostname shown by the Tailscale admin console:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="nv">TAILSCALE_DOMAIN</span><span class="o">=</span><span class="s2">"pikvm.example.ts.net"</span>
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Create <code>/usr/local/bin/kvmd-tailscale-cert-renew</code>:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="ch">#!/usr/bin/bash</span>
|
||
<span class="nb">set</span><span class="w"> </span>-Eeuo<span class="w"> </span>pipefail
|
||
|
||
:<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">TAILSCALE_DOMAIN</span><span class="p">:?Set TAILSCALE_DOMAIN in /etc/conf.d/kvmd-tailscale-cert-renew</span><span class="si">}</span><span class="s2">"</span>
|
||
:<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">KVMD_PST_DATA</span><span class="p">:?Run this script with kvmd-pstrun</span><span class="si">}</span><span class="s2">"</span>
|
||
|
||
<span class="nb">readonly</span><span class="w"> </span><span class="nv">domain</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">TAILSCALE_DOMAIN</span><span class="p">%.</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="nb">readonly</span><span class="w"> </span><span class="nv">cert_dir</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">KVMD_PST_DATA</span><span class="si">}</span><span class="s2">/tailscale-cert"</span>
|
||
<span class="nb">readonly</span><span class="w"> </span><span class="nv">cert_file</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_dir</span><span class="si">}</span><span class="s2">/server.crt"</span>
|
||
<span class="nb">readonly</span><span class="w"> </span><span class="nv">key_file</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_dir</span><span class="si">}</span><span class="s2">/server.key"</span>
|
||
<span class="nb">readonly</span><span class="w"> </span><span class="nv">renew_before_seconds</span><span class="o">=</span><span class="m">2592000</span>
|
||
|
||
<span class="nb">exec</span><span class="w"> </span><span class="m">9</span>>/run/lock/kvmd-tailscale-cert-renew.lock
|
||
<span class="k">if</span><span class="w"> </span>!<span class="w"> </span>flock<span class="w"> </span>-n<span class="w"> </span><span class="m">9</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
|
||
<span class="w"> </span><span class="nb">printf</span><span class="w"> </span><span class="s1">'%s\n'</span><span class="w"> </span><span class="s2">"Certificate renewal is already running"</span>
|
||
<span class="w"> </span><span class="nb">exit</span><span class="w"> </span><span class="m">0</span>
|
||
<span class="k">fi</span>
|
||
|
||
install<span class="w"> </span>-d<span class="w"> </span>-o<span class="w"> </span>root<span class="w"> </span>-g<span class="w"> </span>kvmd-nginx<span class="w"> </span>-m<span class="w"> </span><span class="m">0750</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_dir</span><span class="si">}</span><span class="s2">"</span>
|
||
|
||
certificate_is_fresh<span class="o">()</span><span class="w"> </span><span class="o">{</span>
|
||
<span class="w"> </span><span class="o">[[</span><span class="w"> </span>-r<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_file</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="o">]]</span><span class="w"> </span><span class="o">&&</span>
|
||
<span class="w"> </span>openssl<span class="w"> </span>x509<span class="w"> </span>-in<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_file</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span>-noout<span class="w"> </span>-checkhost<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">domain</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span>>/dev/null<span class="w"> </span><span class="m">2</span>><span class="p">&</span><span class="m">1</span><span class="w"> </span><span class="o">&&</span>
|
||
<span class="w"> </span>openssl<span class="w"> </span>x509<span class="w"> </span>-in<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_file</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span>-noout<span class="w"> </span>-checkend<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">renew_before_seconds</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span>>/dev/null<span class="w"> </span><span class="m">2</span>><span class="p">&</span><span class="m">1</span>
|
||
<span class="o">}</span>
|
||
|
||
<span class="k">if</span><span class="w"> </span>certificate_is_fresh<span class="p">;</span><span class="w"> </span><span class="k">then</span>
|
||
<span class="w"> </span><span class="nb">printf</span><span class="w"> </span><span class="s1">'%s\n'</span><span class="w"> </span><span class="s2">"Certificate for </span><span class="si">${</span><span class="nv">domain</span><span class="si">}</span><span class="s2"> is valid for more than 30 days; nothing to do"</span>
|
||
<span class="w"> </span><span class="nb">exit</span><span class="w"> </span><span class="m">0</span>
|
||
<span class="k">fi</span>
|
||
|
||
<span class="nb">printf</span><span class="w"> </span><span class="s1">'%s\n'</span><span class="w"> </span><span class="s2">"Certificate for </span><span class="si">${</span><span class="nv">domain</span><span class="si">}</span><span class="s2"> is missing, mismatched, or due within 30 days"</span>
|
||
tailscale<span class="w"> </span><span class="nb">wait</span><span class="w"> </span>--timeout<span class="o">=</span>60s
|
||
|
||
<span class="nb">umask</span><span class="w"> </span><span class="m">077</span>
|
||
<span class="nv">tmp_dir</span><span class="o">=</span><span class="s2">"</span><span class="k">$(</span>mktemp<span class="w"> </span>-d<span class="w"> </span>/run/kvmd-tailscale-cert.XXXXXX<span class="k">)</span><span class="s2">"</span>
|
||
<span class="nv">staged_cert</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">tmp_dir</span><span class="si">}</span><span class="s2">/server.crt"</span>
|
||
<span class="nv">staged_key</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">tmp_dir</span><span class="si">}</span><span class="s2">/server.key"</span>
|
||
<span class="nv">old_cert</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">tmp_dir</span><span class="si">}</span><span class="s2">/old-server.crt"</span>
|
||
<span class="nv">old_key</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">tmp_dir</span><span class="si">}</span><span class="s2">/old-server.key"</span>
|
||
<span class="nv">new_cert</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_file</span><span class="si">}</span><span class="s2">.new.</span><span class="nv">$$</span><span class="s2">"</span>
|
||
<span class="nv">new_key</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">key_file</span><span class="si">}</span><span class="s2">.new.</span><span class="nv">$$</span><span class="s2">"</span>
|
||
<span class="nv">previous_certificate_available</span><span class="o">=</span><span class="nb">false</span>
|
||
<span class="nv">deployment_started</span><span class="o">=</span><span class="nb">false</span>
|
||
<span class="nv">deployment_succeeded</span><span class="o">=</span><span class="nb">false</span>
|
||
|
||
cleanup<span class="o">()</span><span class="w"> </span><span class="o">{</span>
|
||
<span class="w"> </span><span class="nb">local</span><span class="w"> </span><span class="nv">status</span><span class="o">=</span><span class="nv">$?</span>
|
||
<span class="w"> </span><span class="nb">trap</span><span class="w"> </span>-<span class="w"> </span>EXIT<span class="w"> </span>HUP<span class="w"> </span>INT<span class="w"> </span>TERM
|
||
<span class="w"> </span><span class="nb">set</span><span class="w"> </span>+e
|
||
|
||
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">[[</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">deployment_started</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">true</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">deployment_succeeded</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span>!<span class="o">=</span><span class="w"> </span><span class="nb">true</span><span class="w"> </span><span class="o">]]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
|
||
<span class="w"> </span><span class="nb">printf</span><span class="w"> </span><span class="s1">'%s\n'</span><span class="w"> </span><span class="s2">"Deployment failed; rolling back the nginx certificate"</span><span class="w"> </span>><span class="p">&</span><span class="m">2</span>
|
||
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">[[</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">previous_certificate_available</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nb">true</span><span class="w"> </span><span class="o">]]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
|
||
<span class="w"> </span>install<span class="w"> </span>-o<span class="w"> </span>root<span class="w"> </span>-g<span class="w"> </span>kvmd-nginx<span class="w"> </span>-m<span class="w"> </span><span class="m">0444</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">old_cert</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_file</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="w"> </span>install<span class="w"> </span>-o<span class="w"> </span>root<span class="w"> </span>-g<span class="w"> </span>kvmd-nginx<span class="w"> </span>-m<span class="w"> </span><span class="m">0440</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">old_key</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">key_file</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="w"> </span>systemctl<span class="w"> </span>reload<span class="w"> </span>kvmd-nginx.service
|
||
<span class="w"> </span><span class="k">else</span>
|
||
<span class="w"> </span>rm<span class="w"> </span>-f<span class="w"> </span>--<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_file</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">key_file</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="w"> </span><span class="k">fi</span>
|
||
<span class="w"> </span><span class="k">fi</span>
|
||
|
||
<span class="w"> </span>rm<span class="w"> </span>-f<span class="w"> </span>--<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">new_cert</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">new_key</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="w"> </span>rm<span class="w"> </span>-rf<span class="w"> </span>--<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">tmp_dir</span><span class="si">}</span><span class="s2">"</span>
|
||
|
||
<span class="w"> </span><span class="nb">exit</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">status</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="o">}</span>
|
||
|
||
<span class="nb">trap</span><span class="w"> </span>cleanup<span class="w"> </span>EXIT
|
||
<span class="nb">trap</span><span class="w"> </span><span class="s1">'exit 129'</span><span class="w"> </span>HUP
|
||
<span class="nb">trap</span><span class="w"> </span><span class="s1">'exit 130'</span><span class="w"> </span>INT
|
||
<span class="nb">trap</span><span class="w"> </span><span class="s1">'exit 143'</span><span class="w"> </span>TERM
|
||
|
||
tailscale<span class="w"> </span>cert<span class="w"> </span><span class="se">\</span>
|
||
<span class="w"> </span>--min-validity<span class="o">=</span>720h<span class="w"> </span><span class="se">\</span>
|
||
<span class="w"> </span>--cert-file<span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">staged_cert</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||
<span class="w"> </span>--key-file<span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">staged_key</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||
<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">domain</span><span class="si">}</span><span class="s2">"</span>
|
||
|
||
openssl<span class="w"> </span>x509<span class="w"> </span>-in<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">staged_cert</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span>-noout<span class="w"> </span>-checkhost<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">domain</span><span class="si">}</span><span class="s2">"</span>
|
||
openssl<span class="w"> </span>x509<span class="w"> </span>-in<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">staged_cert</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span>-noout<span class="w"> </span>-checkend<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">renew_before_seconds</span><span class="si">}</span><span class="s2">"</span>
|
||
|
||
<span class="nv">cert_pubkey_hash</span><span class="o">=</span><span class="s2">"</span><span class="k">$(</span>openssl<span class="w"> </span>x509<span class="w"> </span>-in<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">staged_cert</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span>-pubkey<span class="w"> </span>-noout<span class="w"> </span><span class="p">|</span>
|
||
<span class="w"> </span>openssl<span class="w"> </span>pkey<span class="w"> </span>-pubin<span class="w"> </span>-outform<span class="w"> </span>DER<span class="w"> </span><span class="m">2</span>>/dev/null<span class="w"> </span><span class="p">|</span>
|
||
<span class="w"> </span>sha256sum<span class="k">)</span><span class="s2">"</span>
|
||
<span class="nv">cert_pubkey_hash</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_pubkey_hash</span><span class="p">%% *</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="nv">key_pubkey_hash</span><span class="o">=</span><span class="s2">"</span><span class="k">$(</span>openssl<span class="w"> </span>pkey<span class="w"> </span>-in<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">staged_key</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span>-pubout<span class="w"> </span>-outform<span class="w"> </span>DER<span class="w"> </span><span class="m">2</span>>/dev/null<span class="w"> </span><span class="p">|</span>
|
||
<span class="w"> </span>sha256sum<span class="k">)</span><span class="s2">"</span>
|
||
<span class="nv">key_pubkey_hash</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">key_pubkey_hash</span><span class="p">%% *</span><span class="si">}</span><span class="s2">"</span>
|
||
|
||
<span class="k">if</span><span class="w"> </span><span class="o">[[</span><span class="w"> </span>-z<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_pubkey_hash</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_pubkey_hash</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span>!<span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">key_pubkey_hash</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="o">]]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
|
||
<span class="w"> </span><span class="nb">printf</span><span class="w"> </span><span class="s1">'%s\n'</span><span class="w"> </span><span class="s2">"ERROR: staged certificate and private key do not match"</span><span class="w"> </span>><span class="p">&</span><span class="m">2</span>
|
||
<span class="w"> </span><span class="nb">exit</span><span class="w"> </span><span class="m">1</span>
|
||
<span class="k">fi</span>
|
||
|
||
<span class="k">if</span><span class="w"> </span><span class="o">[[</span><span class="w"> </span>-f<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_file</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="o">&&</span><span class="w"> </span>-f<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">key_file</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="o">]]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span>
|
||
<span class="w"> </span>cp<span class="w"> </span>-a<span class="w"> </span>--<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_file</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">old_cert</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="w"> </span>cp<span class="w"> </span>-a<span class="w"> </span>--<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">key_file</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">old_key</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="w"> </span><span class="nv">previous_certificate_available</span><span class="o">=</span><span class="nb">true</span>
|
||
<span class="k">fi</span>
|
||
|
||
install<span class="w"> </span>-o<span class="w"> </span>root<span class="w"> </span>-g<span class="w"> </span>kvmd-nginx<span class="w"> </span>-m<span class="w"> </span><span class="m">0444</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">staged_cert</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">new_cert</span><span class="si">}</span><span class="s2">"</span>
|
||
install<span class="w"> </span>-o<span class="w"> </span>root<span class="w"> </span>-g<span class="w"> </span>kvmd-nginx<span class="w"> </span>-m<span class="w"> </span><span class="m">0440</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">staged_key</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">new_key</span><span class="si">}</span><span class="s2">"</span>
|
||
|
||
<span class="nv">deployment_started</span><span class="o">=</span><span class="nb">true</span>
|
||
mv<span class="w"> </span>-f<span class="w"> </span>--<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">new_cert</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_file</span><span class="si">}</span><span class="s2">"</span>
|
||
mv<span class="w"> </span>-f<span class="w"> </span>--<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">new_key</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">key_file</span><span class="si">}</span><span class="s2">"</span>
|
||
|
||
/usr/bin/nginx<span class="w"> </span>-t<span class="w"> </span>-p<span class="w"> </span>/etc/kvmd/nginx<span class="w"> </span>-c<span class="w"> </span>/run/kvmd/nginx.conf
|
||
systemctl<span class="w"> </span>reload<span class="w"> </span>kvmd-nginx.service
|
||
<span class="nv">deployment_succeeded</span><span class="o">=</span><span class="nb">true</span>
|
||
|
||
<span class="nb">printf</span><span class="w"> </span><span class="s1">'%s\n'</span><span class="w"> </span><span class="s2">"Installed certificate for </span><span class="si">${</span><span class="nv">domain</span><span class="si">}</span><span class="s2">"</span>
|
||
openssl<span class="w"> </span>x509<span class="w"> </span>-in<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">cert_file</span><span class="si">}</span><span class="s2">"</span><span class="w"> </span>-noout<span class="w"> </span>-subject<span class="w"> </span>-issuer<span class="w"> </span>-dates
|
||
</code></pre></div>
|
||
<p>Make the script executable:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>chmod<span class="w"> </span><span class="m">755</span><span class="w"> </span>/usr/local/bin/kvmd-tailscale-cert-renew
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Create the certificate directory in persistent storage and point nginx to
|
||
it. The script uses <code>KVMD_PST_DATA</code> instead of a fixed path because that
|
||
path can differ between PiKVM versions:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>kvmd-pstrun<span class="w"> </span>--<span class="w"> </span>/usr/bin/bash<span class="w"> </span>-c<span class="w"> </span><span class="err">'</span>
|
||
<span class="go"> set -eu</span>
|
||
<span class="go"> cert_dir="${KVMD_PST_DATA}/tailscale-cert"</span>
|
||
<span class="go"> install -d -o root -g kvmd-nginx -m 0750 "${cert_dir}"</span>
|
||
<span class="go"> ln -sfn "${cert_dir}/server.crt" /etc/kvmd/nginx/ssl/server.crt</span>
|
||
<span class="go"> ln -sfn "${cert_dir}/server.key" /etc/kvmd/nginx/ssl/server.key</span>
|
||
<span class="go">'</span>
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Create <code>/etc/systemd/system/kvmd-tailscale-cert-renew.service</code>:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="k">[Unit]</span>
|
||
<span class="na">Description</span><span class="o">=</span><span class="s">Renew the PiKVM nginx Tailscale certificate</span>
|
||
<span class="na">Documentation</span><span class="o">=</span><span class="s">https://tailscale.com/docs/reference/tailscale-cli</span><span class="c1">#cert</span>
|
||
<span class="na">Wants</span><span class="o">=</span><span class="s">network-online.target</span>
|
||
<span class="na">After</span><span class="o">=</span><span class="s">network-online.target kvmd-pst.service tailscaled.service</span>
|
||
<span class="na">Requires</span><span class="o">=</span><span class="s">kvmd-pst.service tailscaled.service</span>
|
||
|
||
<span class="k">[Service]</span>
|
||
<span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
|
||
<span class="na">EnvironmentFile</span><span class="o">=</span><span class="s">/etc/conf.d/kvmd-tailscale-cert-renew</span>
|
||
<span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/kvmd-pstrun -- /usr/local/bin/kvmd-tailscale-cert-renew</span>
|
||
<span class="na">TimeoutStartSec</span><span class="o">=</span><span class="s">5min</span>
|
||
<span class="na">UMask</span><span class="o">=</span><span class="s">0077</span>
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Create <code>/etc/systemd/system/kvmd-tailscale-cert-renew.timer</code>:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="k">[Unit]</span>
|
||
<span class="na">Description</span><span class="o">=</span><span class="s">Check the PiKVM nginx Tailscale certificate daily</span>
|
||
|
||
<span class="k">[Timer]</span>
|
||
<span class="na">OnCalendar</span><span class="o">=</span><span class="s">daily</span>
|
||
<span class="na">RandomizedDelaySec</span><span class="o">=</span><span class="s">1h</span>
|
||
<span class="na">AccuracySec</span><span class="o">=</span><span class="s">5m</span>
|
||
<span class="na">Persistent</span><span class="o">=</span><span class="s">true</span>
|
||
<span class="na">Unit</span><span class="o">=</span><span class="s">kvmd-tailscale-cert-renew.service</span>
|
||
|
||
<span class="k">[Install]</span>
|
||
<span class="na">WantedBy</span><span class="o">=</span><span class="s">timers.target</span>
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Load the units, enable the timer, install the initial certificate,
|
||
and return the filesystem to RO mode:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>daemon-reload
|
||
<span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>--now<span class="w"> </span>kvmd-tailscale-cert-renew.timer
|
||
<span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>start<span class="w"> </span>kvmd-tailscale-cert-renew.service
|
||
<span class="gp">[root@pikvm ~]# </span>ro
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>Check the service log and the next scheduled run:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>journalctl<span class="w"> </span>-u<span class="w"> </span>kvmd-tailscale-cert-renew.service
|
||
<span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>list-timers<span class="w"> </span>kvmd-tailscale-cert-renew.timer
|
||
</code></pre></div>
|
||
</li>
|
||
</ol>
|
||
<hr>
|
||
<h2 id="automated-ephemeral-tailscale-certificates-renewal">Automated Ephemeral Tailscale Certificates Renewal<a class="headerlink" href="#automated-ephemeral-tailscale-certificates-renewal" title="Permanent link">¶</a></h2>
|
||
<p><a href="https://tailscale.com/kb/1312/serve"><code>tailscale serve</code></a> can terminate HTTPS
|
||
and proxy requests to PiKVM within your tailnet. Tailscale renews its Let's
|
||
Encrypt certificates automatically, but it must write certificate and service
|
||
state under <code>/var/lib/tailscale</code>. Because the PiKVM root filesystem is
|
||
read-only, those writes fail and repeated certificate requests can eventually
|
||
hit Let's Encrypt rate limits.</p>
|
||
<p>The overlay below keeps Tailscale's runtime state writable in RAM. Use the
|
||
<a href="#automatic-certificate-renewal-for-nginx">nginx renewal setup</a> instead if the
|
||
certificate must survive reboots.</p>
|
||
<p>Here's the command that allows you to seamlessly run HTTPS proxy for your PiKVM:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>tailscale<span class="w"> </span>serve<span class="w"> </span>--bg<span class="w"> </span>https+insecure://localhost:443
|
||
</code></pre></div>
|
||
<p>And if you want to stop tailscale from serving HTTPS, you can do this by running:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>tailscale<span class="w"> </span>serve<span class="w"> </span>--https<span class="o">=</span><span class="m">443</span><span class="w"> </span>off
|
||
</code></pre></div>
|
||
<h3 id="root-cause">Root cause<a class="headerlink" href="#root-cause" title="Permanent link">¶</a></h3>
|
||
<p>Tailscale needs to refresh TLS certificates and write state under <code>/var/lib/tailscale</code>.
|
||
On PiKVM, the root filesystem is read-only, so direct writes fail.</p>
|
||
<p>We can fix this by mounting an <strong>ephemeral overlay filesystem (tmpfs) in RAM</strong> for <code>/var/lib/tailscale</code>, backed by a persistent lowerdir (<code>/root/tailscale-state</code>).</p>
|
||
<p>This ensures that certificate rotation and state writes work without breaking PiKVM’s read-only state.</p>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<p>The <strong>caveat</strong> is that renewed certificates exist only in RAM. After a reboot, Tailscale falls back to the older certificates on disk, requests fresh ones, and stores them in RAM again.
|
||
If you reboot PiKVM too frequently, this can trigger Let's Encrypt's rate limits.</p>
|
||
</div>
|
||
<h3 id="solution">Solution<a class="headerlink" href="#solution" title="Permanent link">¶</a></h3>
|
||
<p>Core idea:
|
||
- Mount a <strong>tmpfs</strong> over Tailscale's state folder stored in root's home: /root/tailscale-state.
|
||
- Mount the resulting <em>merged</em> layer onto the actual Tailscale state folder at /var/lib/tailscale.
|
||
- An <strong>overlayfs</strong> will transparently present this folder to Tailscale, while changes are kept in the RAM-based overlay layer.</p>
|
||
<p><strong>Note</strong>: Overlayfs requires that the upperdir and workdir exist before creating the overlay.
|
||
Since these directories live in RAM, they disappear after every reboot.
|
||
This means we cannot use fstab to declare the mount points.
|
||
Instead, we implement this with a systemd service that runs a setup script during boot, before tailscaled starts.</p>
|
||
<ol>
|
||
<li>Switch filesystem to RW and copy Tailscale state:</li>
|
||
</ol>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>rw
|
||
<span class="gp">[root@pikvm ~]# </span>cp<span class="w"> </span>-a<span class="w"> </span>/var/lib/tailscale<span class="w"> </span>/root/tailscale-state
|
||
</code></pre></div>
|
||
<ol>
|
||
<li>Create a helper script, save as <code>/usr/local/bin/setup-tailscale-overlay.sh</code>:</li>
|
||
</ol>
|
||
<div class="highlight"><pre><span></span><code><span class="ch">#!/bin/bash</span>
|
||
<span class="nb">set</span><span class="w"> </span>-e
|
||
|
||
<span class="c1"># Make tmpfs for tailscale overlay</span>
|
||
mkdir<span class="w"> </span>-p<span class="w"> </span>/tmp/tailscale-tmpfs
|
||
mountpoint<span class="w"> </span>-q<span class="w"> </span>/tmp/tailscale-tmpfs<span class="w"> </span><span class="o">||</span><span class="w"> </span>mount<span class="w"> </span>-t<span class="w"> </span>tmpfs<span class="w"> </span>tmpfs<span class="w"> </span>/tmp/tailscale-tmpfs
|
||
|
||
<span class="c1"># Prepare overlay dirs</span>
|
||
mkdir<span class="w"> </span>-p<span class="w"> </span>/tmp/tailscale-tmpfs/upper
|
||
mkdir<span class="w"> </span>-p<span class="w"> </span>/tmp/tailscale-tmpfs/work
|
||
mkdir<span class="w"> </span>-p<span class="w"> </span>/tmp/tailscale-merged
|
||
|
||
<span class="c1"># Mount overlay (lowerdir = persistent readonly state in /root)</span>
|
||
mountpoint<span class="w"> </span>-q<span class="w"> </span>/tmp/tailscale-merged<span class="w"> </span><span class="o">||</span><span class="w"> </span>mount<span class="w"> </span>-t<span class="w"> </span>overlay<span class="w"> </span>overlay<span class="w"> </span><span class="se">\</span>
|
||
<span class="w"> </span>-o<span class="w"> </span><span class="nv">lowerdir</span><span class="o">=</span>/root/tailscale-state,upperdir<span class="o">=</span>/tmp/tailscale-tmpfs/upper,workdir<span class="o">=</span>/tmp/tailscale-tmpfs/work<span class="w"> </span><span class="se">\</span>
|
||
<span class="w"> </span>/tmp/tailscale-merged
|
||
|
||
<span class="c1"># Bind merged to /var/lib/tailscale</span>
|
||
mountpoint<span class="w"> </span>-q<span class="w"> </span>/var/lib/tailscale<span class="w"> </span><span class="o">&&</span><span class="w"> </span>umount<span class="w"> </span>/var/lib/tailscale<span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nb">true</span>
|
||
mount<span class="w"> </span>--bind<span class="w"> </span>/tmp/tailscale-merged<span class="w"> </span>/var/lib/tailscale
|
||
</code></pre></div>
|
||
<p>Make it executable:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>chmod<span class="w"> </span>+x<span class="w"> </span>/usr/local/bin/setup-tailscale-overlay.sh
|
||
</code></pre></div>
|
||
<ol>
|
||
<li>Create a systemd unit</li>
|
||
</ol>
|
||
<p>We need to run the overlay setup <strong>after <code>/tmp</code> is mounted</strong> but <strong>before <code>tailscaled.service</code></strong>.</p>
|
||
<p>Save as <code>/etc/systemd/system/tailscale-overlay.service</code>:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="k">[Unit]</span>
|
||
<span class="na">Description</span><span class="o">=</span><span class="s">Setup overlayfs for Tailscale</span>
|
||
<span class="na">After</span><span class="o">=</span><span class="s">local-fs.target tmp.mount</span>
|
||
<span class="na">Before</span><span class="o">=</span><span class="s">tailscaled.service</span>
|
||
|
||
<span class="k">[Service]</span>
|
||
<span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
|
||
<span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/local/bin/setup-tailscale-overlay.sh</span>
|
||
<span class="na">RemainAfterExit</span><span class="o">=</span><span class="s">yes</span>
|
||
|
||
<span class="k">[Install]</span>
|
||
<span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
|
||
</code></pre></div>
|
||
<p><strong>Notes:</strong></p>
|
||
<ul>
|
||
<li><code>local-fs.target</code> ensures all local filesystems (including <code>/tmp</code> tmpfs from fstab) are mounted.</li>
|
||
<li><code>tmp.mount</code> is added explicitly in case your system defines it.</li>
|
||
<li>
|
||
<p>Overlay is mounted and bound before <code>tailscaled</code> starts.</p>
|
||
</li>
|
||
<li>
|
||
<p>Enable and reload</p>
|
||
</li>
|
||
</ul>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>daemon-reload
|
||
<span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>tailscale-overlay.service
|
||
<span class="gp">[root@pikvm ~]# </span>ro
|
||
</code></pre></div>
|
||
<hr>
|
||
<h3 id="boot-sequence-recap">Boot sequence recap:<a class="headerlink" href="#boot-sequence-recap" title="Permanent link">¶</a></h3>
|
||
<ol>
|
||
<li>tmpfs is mounted at <code>/tmp/tailscale-tmpfs</code></li>
|
||
<li><code>upper</code> + <code>work</code> dirs are recreated inside tmpfs</li>
|
||
<li>overlay is mounted with <code>/root/tailscale-state</code> as lowerdir</li>
|
||
<li>overlay bind-mounted to <code>/var/lib/tailscale</code></li>
|
||
<li><code>tailscaled.service</code> starts with writable state</li>
|
||
</ol>
|
||
<hr>
|
||
<h2 id="troubleshooting">Troubleshooting<a class="headerlink" href="#troubleshooting" title="Permanent link">¶</a></h2>
|
||
<ul>
|
||
<li>
|
||
<p>If something does not work, the usual advice is to completely remove Tailscale from PiKVM and perform a clean installation:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>rw
|
||
<span class="gp">[root@pikvm ~]# </span>pacman<span class="w"> </span>-Rscnd<span class="w"> </span>tailscale
|
||
<span class="gp">[root@pikvm ~]# </span>rm<span class="w"> </span>-rf<span class="w"> </span>/var/lib/tailscale<span class="w"> </span>/var/cache/tailscale
|
||
<span class="gp">[root@pikvm ~]# </span>reboot
|
||
</code></pre></div>
|
||
</li>
|
||
</ul>
|
||
<p>Now, follow the instructions from the beginning to re-install Tailscale.</p>
|
||
<ul>
|
||
<li>
|
||
<p>In case of certificate issues you can try the following steps to debug and fix.</p>
|
||
</li>
|
||
<li>
|
||
<p>Check if the services are running. If not please start them. For example,
|
||
web UI service can be checked using:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>status<span class="w"> </span>kvmd-nginx
|
||
</code></pre></div>
|
||
<p>For VNC:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>systemctl<span class="w"> </span>status<span class="w"> </span>kvmd-vnc
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>If the services are running but not accessible or showing a warning, check
|
||
the respective logs. For web UI:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>journalctl<span class="w"> </span>-xeu<span class="w"> </span>kvmd-nginx
|
||
</code></pre></div>
|
||
</li>
|
||
<li>
|
||
<p>If the logs shows TLS/certificate/permissions errors, the issue may be with
|
||
file ownership. The services must have at least the group ownership of the
|
||
certificates. The ownership should look similar to this:</p>
|
||
<div class="highlight"><pre><span></span><code><span class="gp">[root@pikvm ~]# </span>ls<span class="w"> </span>-l<span class="w"> </span>/etc/kvmd/<span class="o">{</span>nginx,vnc<span class="o">}</span>/ssl
|
||
<span class="go">/etc/kvmd/nginx/ssl:</span>
|
||
<span class="go">total 8</span>
|
||
<span class="go">-r--r--r-- 1 root kvmd-nginx 2872 Jan 3 16:07 server.crt</span>
|
||
<span class="go">-r--r----- 1 root kvmd-nginx 227 Jan 3 16:07 server.key</span>
|
||
|
||
<span class="go">/etc/kvmd/vnc/ssl:</span>
|
||
<span class="go">total 8</span>
|
||
<span class="go">-r--r--r-- 1 root kvmd-vnc 2872 Jan 3 16:07 server.crt</span>
|
||
<span class="go">-r--r----- 1 root kvmd-vnc 227 Jan 3 16:07 server.key</span>
|
||
</code></pre></div>
|
||
</li>
|
||
</ul></div>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</article>
|
||
</div>
|
||
|
||
|
||
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
|
||
</div>
|
||
|
||
<button type="button" class="md-top md-icon" data-md-component="top" hidden="">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"></path></svg>
|
||
Back to top
|
||
</button>
|
||
|
||
</main>
|
||
|
||
<footer class="md-footer">
|
||
|
||
<div class="md-footer-meta md-typeset">
|
||
<div class="md-footer-meta__inner md-grid">
|
||
<div class="md-copyright">
|
||
|
||
<div class="md-copyright__highlight">
|
||
Copyright © 2018-2026 Maxim Devaev
|
||
</div>
|
||
|
||
|
||
Made with
|
||
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
|
||
Material for MkDocs
|
||
</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
</div>
|
||
<div class="md-dialog" data-md-component="dialog">
|
||
<div class="md-dialog__inner md-typeset"></div>
|
||
</div>
|
||
|
||
|
||
|
||
|
||
|
||
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": ["navigation.indexes", "navigation.sections", "navigation.top", "navigation.expand", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
|
||
|
||
|
||
<script src="../assets/javascripts/bundle.d7400e89.min.js"></script>
|
||
|
||
<script src="../_assets/add_paragraphs.js"></script>
|
||
|
||
<script src="../_assets/scroll_to_summary.js"></script>
|
||
|
||
|
||
|
||
<script id="init-glightbox">const lightbox = GLightbox({"touchNavigation": false, "loop": false, "zoomable": true, "draggable": true, "openEffect": "zoom", "closeEffect": "zoom", "slideEffect": "slide"});
|
||
document$.subscribe(()=>{ lightbox.reload(); });
|
||
</script></body></html> |