Migrate docudjeex to Docus v4 with EN/FR content
This commit is contained in:
+44
@@ -0,0 +1,44 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
.eslintcache
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# npm pack
|
||||
*.tgz
|
||||
|
||||
# Temp files
|
||||
.tmp
|
||||
.profile
|
||||
*.0x
|
||||
|
||||
#VSC
|
||||
.history
|
||||
.wrangler
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
*.pyc
|
||||
|
||||
# Scratch/demo files (not part of the site)
|
||||
scratch
|
||||
@@ -0,0 +1,123 @@
|
||||
# Docus i18n Starter
|
||||
|
||||
> A beautiful, internationalized starter for creating multi-language documentation with Docus
|
||||
|
||||
This is the i18n Docus starter template that provides everything you need to build beautiful, multi-language documentation sites with Markdown and Vue components.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🌍 **Internationalization** - Native i18n support for multi-language docs
|
||||
- 🎨 **Beautiful Design** - Clean, modern documentation theme
|
||||
- 📱 **Responsive** - Mobile-first responsive design
|
||||
- 🌙 **Dark Mode** - Built-in dark/light mode support
|
||||
- 🔍 **Search** - Full-text search functionality per language
|
||||
- 📝 **Markdown Enhanced** - Extended markdown with custom components
|
||||
- 🎨 **Customizable** - Easy theming and brand customization
|
||||
- ⚡ **Fast** - Optimized for performance with Nuxt 4
|
||||
- 🔧 **TypeScript** - Full TypeScript support
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start development server
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Your multilingual documentation site will be running at `http://localhost:3000`
|
||||
|
||||
## 🌍 Languages
|
||||
|
||||
This starter comes pre-configured with:
|
||||
- 🇺🇸 **English** (`en`) - Default language
|
||||
- 🇫🇷 **Français** (`fr`) - French translation
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
my-docs/
|
||||
├── content/ # Your markdown content
|
||||
│ ├── en/ # English content
|
||||
│ │ ├── index.md # English homepage
|
||||
│ │ └── docs/ # English documentation
|
||||
│ └── fr/ # French content
|
||||
│ ├── index.md # French homepage
|
||||
│ └── docs/ # French documentation
|
||||
├── public/ # Static assets
|
||||
├── nuxt.config.ts # Nuxt configuration with i18n setup
|
||||
└── package.json # Dependencies and scripts
|
||||
```
|
||||
|
||||
### Content Structure
|
||||
|
||||
The content is organized by language, making it easy to manage translations:
|
||||
|
||||
```
|
||||
content/
|
||||
├── en/ # English content
|
||||
│ ├── index.md
|
||||
│ ├── 1.getting-started/
|
||||
│ │ ├── installation.md
|
||||
│ │ └── configuration.md
|
||||
│ └── 2.essentials/
|
||||
│ ├── markdown.md
|
||||
│ └── components.md
|
||||
└── fr/ # French content
|
||||
├── index.md
|
||||
├── 1.getting-started/
|
||||
│ ├── installation.md
|
||||
│ └── configuration.md
|
||||
└── 2.essentials/
|
||||
├── markdown.md
|
||||
└── components.md
|
||||
```
|
||||
|
||||
## 🔗 URL Structure
|
||||
|
||||
The i18n starter generates URLs with language prefixes:
|
||||
|
||||
- English: `/en/getting-started/installation`
|
||||
- French: `/fr/getting-started/installation`
|
||||
- Default locale fallback: `/getting-started/installation` (redirects to English)
|
||||
|
||||
## ⚡ Built with
|
||||
|
||||
This starter comes pre-configured with:
|
||||
|
||||
- [Nuxt 4](https://nuxt.com) - The web framework
|
||||
- [Nuxt Content](https://content.nuxt.com/) - File-based CMS
|
||||
- [Nuxt i18n](https://i18n.nuxt.com/) - Internationalization
|
||||
- [Nuxt UI](https://ui.nuxt.com) - UI components
|
||||
- [Nuxt Image](https://image.nuxt.com/) - Optimized images
|
||||
- [Tailwind CSS 4](https://tailwindcss.com/) - Utility-first CSS
|
||||
- [Docus Layer](https://www.npmjs.com/package/docus) - Documentation theme
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
For detailed documentation on customizing your Docus project, visit the [Docus Documentation](https://docus.dev)
|
||||
|
||||
### 🤖 AI Assistant Skill
|
||||
|
||||
Get started quickly with Docus by adding specialized knowledge to your AI assistant (Cursor, Claude, etc.):
|
||||
|
||||
```bash
|
||||
npx skills add nuxt-content/docus
|
||||
```
|
||||
|
||||
This skill helps you create documentation faster by providing your AI assistant with best practices, MDC component usage, ready-to-use templates, writing guidelines, and configuration tips for Docus. Perfect for quickly scaffolding new documentation projects.
|
||||
|
||||
## 🚀 Deployment
|
||||
|
||||
Build for production:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
The built files will be in the `.output` directory, ready for deployment to any hosting provider that supports Node.js.
|
||||
|
||||
## 📄 License
|
||||
|
||||
[MIT License](https://opensource.org/licenses/MIT)
|
||||
@@ -0,0 +1,67 @@
|
||||
export default defineAppConfig({
|
||||
docus: {
|
||||
locale: 'en',
|
||||
colorMode: 'dark',
|
||||
},
|
||||
navigation: {
|
||||
sub: 'header',
|
||||
},
|
||||
header: {
|
||||
title: 'Docudjeex',
|
||||
logo: {
|
||||
light: '/img/logo.svg',
|
||||
dark: '/img/logo.svg',
|
||||
alt: 'Docudjeex',
|
||||
},
|
||||
},
|
||||
socials: {
|
||||
gitea: 'https://git.djeex.fr/Djeex/docudjeex',
|
||||
},
|
||||
github: {
|
||||
url: 'https://github.com/Djeex/docudjeex',
|
||||
},
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'cyan',
|
||||
neutral: 'zinc',
|
||||
},
|
||||
prose: {
|
||||
card: {
|
||||
slots: {
|
||||
base: 'bg-[rgba(12,13,12,0.8)] border-[#121110]',
|
||||
},
|
||||
},
|
||||
pre: {
|
||||
slots: {
|
||||
base: 'bg-[#121110] border-[#201e1b] rounded-lg',
|
||||
header: 'bg-[#121110] border-[#201e1b]',
|
||||
},
|
||||
},
|
||||
},
|
||||
header: {
|
||||
slots: {
|
||||
root: 'bg-[rgba(12,13,12,0.8)] backdrop-blur-[20px] backdrop-saturate-200 border-b border-default h-(--ui-header-height) sticky top-0 z-50',
|
||||
},
|
||||
},
|
||||
contentSearchButton: {
|
||||
slots: {
|
||||
base: 'bg-[rgba(12,13,12,0.8)] hover:bg-[rgba(18,17,16,0.9)] border border-[#121110]',
|
||||
},
|
||||
},
|
||||
contentSurround: {
|
||||
slots: {
|
||||
link: 'bg-[rgba(12,13,12,0.8)] border-[#121110] hover:bg-primary/10 hover:border-primary',
|
||||
linkLeading: 'bg-[rgba(12,13,12,0.8)] ring-1 ring-[var(--ui-text-highlighted)]/50 group-hover:bg-primary/10 group-hover:ring-primary/50',
|
||||
},
|
||||
},
|
||||
kbd: {
|
||||
compoundVariants: [
|
||||
{
|
||||
color: 'neutral',
|
||||
variant: 'subtle',
|
||||
class: 'ring-[#121110] bg-[rgba(12,13,12,0.8)] text-default',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/* Restore the old site's near-black dark background (#0B0A0A) instead of
|
||||
Nuxt UI's default zinc-900 */
|
||||
.dark {
|
||||
--ui-bg: #0B0A0A;
|
||||
/* Same border color used on cards, applied sitewide (header, separators,
|
||||
the horizontal nav menu row, etc.) for a consistent look */
|
||||
--ui-border: #121110;
|
||||
}
|
||||
|
||||
/* Old site's container was 1280px with 24px padding each side (1232px of
|
||||
actual content). Nuxt UI's container uses a bigger lg:px-8 (32px) padding,
|
||||
so the max-width is bumped to 81rem (1296px) to land on the same 1232px
|
||||
content width rather than reproducing the outer 1280px figure verbatim. */
|
||||
:root {
|
||||
--ui-container: 81rem;
|
||||
}
|
||||
|
||||
/* Docus hardcodes --ui-header-height to 112px (64px header + 48px sub-nav
|
||||
bar) whenever navigation.sub is 'header', regardless of what's actually
|
||||
in that bar. Our horizontal menu now lives in the main header row itself
|
||||
(AppHeaderCenter.vue) and the sub-nav bar (AppHeaderBottom.vue) is empty,
|
||||
so the header is back to a single 64px row. */
|
||||
@media (min-width: 1024px) {
|
||||
.docus-sub-header {
|
||||
--ui-header-height: 4rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 1000 1000">
|
||||
<!-- Generator: Adobe Illustrator 30.7.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 114) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: url(#Dégradé_sans_nom_5);
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: url(#Dégradé_sans_nom_264);
|
||||
stroke: url(#Dégradé_sans_nom_334);
|
||||
}
|
||||
|
||||
.st1, .st2, .st3, .st4, .st5, .st6, .st7, .st8, .st9, .st10, .st11, .st12 {
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.st2 {
|
||||
fill: url(#Dégradé_sans_nom_262);
|
||||
stroke: url(#Dégradé_sans_nom_332);
|
||||
}
|
||||
|
||||
.st3 {
|
||||
fill: url(#Dégradé_sans_nom_269);
|
||||
stroke: url(#Dégradé_sans_nom_339);
|
||||
}
|
||||
|
||||
.st4 {
|
||||
fill: url(#Dégradé_sans_nom_263);
|
||||
stroke: url(#Dégradé_sans_nom_333);
|
||||
}
|
||||
|
||||
.st5 {
|
||||
fill: url(#Dégradé_sans_nom_265);
|
||||
stroke: url(#Dégradé_sans_nom_335);
|
||||
}
|
||||
|
||||
.st6 {
|
||||
fill: url(#Dégradé_sans_nom_268);
|
||||
stroke: url(#Dégradé_sans_nom_338);
|
||||
}
|
||||
|
||||
.st7 {
|
||||
fill: url(#Dégradé_sans_nom_261);
|
||||
stroke: url(#Dégradé_sans_nom_331);
|
||||
}
|
||||
|
||||
.st8 {
|
||||
fill: url(#Dégradé_sans_nom_266);
|
||||
stroke: url(#Dégradé_sans_nom_336);
|
||||
}
|
||||
|
||||
.st9 {
|
||||
fill: url(#Dégradé_sans_nom_267);
|
||||
stroke: url(#Dégradé_sans_nom_337);
|
||||
}
|
||||
|
||||
.st10 {
|
||||
fill: url(#Dégradé_sans_nom_26);
|
||||
stroke: url(#Dégradé_sans_nom_33);
|
||||
}
|
||||
|
||||
.st11 {
|
||||
fill: url(#Dégradé_sans_nom_2610);
|
||||
stroke: url(#Dégradé_sans_nom_3310);
|
||||
}
|
||||
|
||||
.st12 {
|
||||
fill: url(#Dégradé_sans_nom_2611);
|
||||
stroke: url(#Dégradé_sans_nom_3311);
|
||||
}
|
||||
|
||||
.st13 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.st14 {
|
||||
fill: url(#Dégradé_sans_nom_51);
|
||||
}
|
||||
</style>
|
||||
<linearGradient id="Dégradé_sans_nom_5" data-name="Dégradé sans nom 5" x1="245" y1="503" x2="747" y2="503" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#55c3ec"/>
|
||||
<stop offset="1" stop-color="#1d71b8" stop-opacity=".8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="Dégradé_sans_nom_51" data-name="Dégradé sans nom 5" x1="399.4" y1="504.3" x2="595.1" y2="504.3" xlink:href="#Dégradé_sans_nom_5"/>
|
||||
<linearGradient id="Dégradé_sans_nom_26" data-name="Dégradé sans nom 26" x1="74.5" y1="560.2" x2="106.2" y2="591.8" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#55c3ec"/>
|
||||
<stop offset="1" stop-color="#1d71b8" stop-opacity=".8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="Dégradé_sans_nom_33" data-name="Dégradé sans nom 33" x1="74.2" y1="559.9" x2="106.5" y2="592.2" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#55c3ec"/>
|
||||
<stop offset="1" stop-color="#1d71b8" stop-opacity=".5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="Dégradé_sans_nom_261" data-name="Dégradé sans nom 26" x1="158.2" y1="648.8" x2="176.7" y2="667.3" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_331" data-name="Dégradé sans nom 33" x1="157.8" y1="648.4" x2="177.1" y2="667.7" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_262" data-name="Dégradé sans nom 26" x1="210.2" y1="714.7" x2="249.8" y2="754.3" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_332" data-name="Dégradé sans nom 33" x1="209.9" y1="714.3" x2="250.2" y2="754.6" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_263" data-name="Dégradé sans nom 26" x1="-54" y1="72.2" x2="-14.4" y2="111.8" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_333" data-name="Dégradé sans nom 33" x1="-54.4" y1="71.9" x2="-14.1" y2="112.2" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_264" data-name="Dégradé sans nom 26" x1="485.1" y1="-9.2" x2="503.7" y2="9.4" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_334" data-name="Dégradé sans nom 33" x1="484.8" y1="-9.6" x2="504" y2="9.7" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_265" data-name="Dégradé sans nom 26" x1="825.1" y1="682.3" x2="856.8" y2="713.9" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_335" data-name="Dégradé sans nom 33" x1="824.8" y1="681.9" x2="857.1" y2="714.3" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_266" data-name="Dégradé sans nom 26" x1="308.5" y1="356.5" x2="340.3" y2="388.3" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_336" data-name="Dégradé sans nom 33" x1="308.1" y1="356.2" x2="340.7" y2="388.7" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_267" data-name="Dégradé sans nom 26" x1="540.4" y1="450.4" x2="559" y2="469" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_337" data-name="Dégradé sans nom 33" x1="540.1" y1="450" x2="559.4" y2="469.3" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_268" data-name="Dégradé sans nom 26" x1="-336.4" y1="326.9" x2="-296.8" y2="366.5" gradientTransform="translate(342.9 490.5) rotate(-175.4)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_338" data-name="Dégradé sans nom 33" x1="-336.7" y1="326.5" x2="-296.4" y2="366.8" gradientTransform="translate(342.9 490.5) rotate(-175.4)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_269" data-name="Dégradé sans nom 26" x1="115" y1="-29.9" x2="133.6" y2="-11.3" gradientTransform="translate(814.9 151.4) rotate(139.6)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_339" data-name="Dégradé sans nom 33" x1="114.7" y1="-30.2" x2="134" y2="-11" gradientTransform="translate(814.9 151.4) rotate(139.6)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2610" data-name="Dégradé sans nom 26" x1="94.5" y1="304.2" x2="124.4" y2="334" gradientTransform="translate(568 142) rotate(97.9)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_3310" data-name="Dégradé sans nom 33" x1="94.2" y1="303.8" x2="124.7" y2="334.4" gradientTransform="translate(568 142) rotate(97.9)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2611" data-name="Dégradé sans nom 26" x1="435.8" y1="254.1" x2="454.4" y2="272.7" gradientTransform="translate(257.1 -349) rotate(52.9)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_3311" data-name="Dégradé sans nom 33" x1="435.5" y1="253.8" x2="454.8" y2="273.1" gradientTransform="translate(257.1 -349) rotate(52.9)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
</defs>
|
||||
<g id="Calque_1">
|
||||
<circle class="st13" cx="499.5" cy="499.5" r="499.5"/>
|
||||
<g>
|
||||
<path class="st0" d="M618.3,252h-244.6c-72.1,0-128.7,59.2-128.7,128.7v244.6c0,72.1,59.2,128.7,128.7,128.7h244.6c72.1,0,128.7-59.2,128.7-128.7v-244.6c0-72.1-59.2-128.7-128.7-128.7ZM497.3,656.2c-82.4,0-151.9-69.5-151.9-151.9s66.9-151.9,151.9-151.9,151.9,69.5,151.9,151.9-69.5,151.9-151.9,151.9ZM659.5,378.2c-20.6,0-36-15.4-36-36s15.4-36,36-36,36,15.4,36,36c0,20.6-15.4,36-36,36Z"/>
|
||||
<path class="st14" d="M497.3,406.5c-54.1,0-97.8,43.8-97.8,97.8s43.8,97.8,97.8,97.8,97.8-43.8,97.8-97.8-43.8-97.8-97.8-97.8Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Calque_2">
|
||||
<g id="Calque_3">
|
||||
<circle class="st10" cx="90.4" cy="576" r="22.4"/>
|
||||
<circle class="st7" cx="175.6" cy="607.9" r="13.1"/>
|
||||
<circle class="st2" cx="140.8" cy="691.6" r="28"/>
|
||||
<circle class="st4" cx="829.7" cy="602.6" r="28"/>
|
||||
<circle class="st1" cx="908.9" cy="562.1" r="13.1"/>
|
||||
<circle class="st5" cx="840.9" cy="698.1" r="22.4"/>
|
||||
<circle class="st8" cx="466.1" cy="876.5" r="22.5"/>
|
||||
<circle class="st9" cx="538.6" cy="839.8" r="13.1"/>
|
||||
<circle class="st6" cx="686.1" cy="170.1" r="28"/>
|
||||
<circle class="st3" cx="733.7" cy="247.7" r="13.1"/>
|
||||
<circle class="st11" cx="236.9" cy="206.5" r="21.1"/>
|
||||
<circle class="st12" cx="315.4" cy="164.9" r="13.1"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
@@ -0,0 +1,154 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 1000 1000">
|
||||
<!-- Generator: Adobe Illustrator 29.7.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 8) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: url(#Dégradé_sans_nom_265);
|
||||
stroke: url(#Dégradé_sans_nom_33);
|
||||
}
|
||||
|
||||
.st0, .st1, .st2, .st3, .st4, .st5, .st6, .st7, .st8, .st9, .st10, .st11 {
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: url(#Dégradé_sans_nom_269);
|
||||
stroke: url(#Dégradé_sans_nom_334);
|
||||
}
|
||||
|
||||
.st2 {
|
||||
fill: url(#Dégradé_sans_nom_268);
|
||||
stroke: url(#Dégradé_sans_nom_333);
|
||||
}
|
||||
|
||||
.st3 {
|
||||
fill: url(#Dégradé_sans_nom_266);
|
||||
stroke: url(#Dégradé_sans_nom_331);
|
||||
}
|
||||
|
||||
.st4 {
|
||||
fill: url(#Dégradé_sans_nom_267);
|
||||
stroke: url(#Dégradé_sans_nom_332);
|
||||
}
|
||||
|
||||
.st12 {
|
||||
fill: url(#Dégradé_sans_nom_261);
|
||||
}
|
||||
|
||||
.st13 {
|
||||
fill: url(#Dégradé_sans_nom_262);
|
||||
}
|
||||
|
||||
.st14 {
|
||||
fill: url(#Dégradé_sans_nom_264);
|
||||
}
|
||||
|
||||
.st15 {
|
||||
fill: url(#Dégradé_sans_nom_263);
|
||||
}
|
||||
|
||||
.st5 {
|
||||
fill: url(#Dégradé_sans_nom_2616);
|
||||
stroke: url(#Dégradé_sans_nom_3311);
|
||||
}
|
||||
|
||||
.st6 {
|
||||
fill: url(#Dégradé_sans_nom_2615);
|
||||
stroke: url(#Dégradé_sans_nom_3310);
|
||||
}
|
||||
|
||||
.st16 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.st17 {
|
||||
fill: url(#Dégradé_sans_nom_26);
|
||||
}
|
||||
|
||||
.st7 {
|
||||
fill: url(#Dégradé_sans_nom_2610);
|
||||
stroke: url(#Dégradé_sans_nom_335);
|
||||
}
|
||||
|
||||
.st8 {
|
||||
fill: url(#Dégradé_sans_nom_2613);
|
||||
stroke: url(#Dégradé_sans_nom_338);
|
||||
}
|
||||
|
||||
.st9 {
|
||||
fill: url(#Dégradé_sans_nom_2614);
|
||||
stroke: url(#Dégradé_sans_nom_339);
|
||||
}
|
||||
|
||||
.st10 {
|
||||
fill: url(#Dégradé_sans_nom_2611);
|
||||
stroke: url(#Dégradé_sans_nom_336);
|
||||
}
|
||||
|
||||
.st11 {
|
||||
fill: url(#Dégradé_sans_nom_2612);
|
||||
stroke: url(#Dégradé_sans_nom_337);
|
||||
}
|
||||
</style>
|
||||
<linearGradient id="Dégradé_sans_nom_26" data-name="Dégradé sans nom 26" x1="373.2" y1="159.5" x2="625.1" y2="411.5" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#55c3ec"/>
|
||||
<stop offset="1" stop-color="#1d71b8" stop-opacity=".8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="Dégradé_sans_nom_261" data-name="Dégradé sans nom 26" x1="143.1" y1="200.5" x2="395" y2="452.4" gradientTransform="translate(30.8 109.3)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_262" data-name="Dégradé sans nom 26" x1="81.3" y1="60.6" x2="333.2" y2="312.5" gradientTransform="translate(187.1 873.6) rotate(-90)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_263" data-name="Dégradé sans nom 26" x1="-44.4" y1="16.5" x2="207.5" y2="268.4" gradientTransform="translate(705.4 808.2) rotate(-180)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_264" data-name="Dégradé sans nom 26" x1="-67.9" y1="-58.9" x2="184" y2="193" gradientTransform="translate(770.9 385.1) rotate(90)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_265" data-name="Dégradé sans nom 26" x1="74.5" y1="560.2" x2="106.2" y2="591.8" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_33" data-name="Dégradé sans nom 33" x1="74.2" y1="559.9" x2="106.5" y2="592.2" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#55c3ec"/>
|
||||
<stop offset="1" stop-color="#1d71b8" stop-opacity=".5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="Dégradé_sans_nom_266" data-name="Dégradé sans nom 26" x1="158.2" y1="648.8" x2="176.7" y2="667.3" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_331" data-name="Dégradé sans nom 33" x1="157.8" y1="648.4" x2="177.1" y2="667.7" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_267" data-name="Dégradé sans nom 26" x1="210.2" y1="714.7" x2="249.8" y2="754.3" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_332" data-name="Dégradé sans nom 33" x1="209.9" y1="714.3" x2="250.2" y2="754.6" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_268" data-name="Dégradé sans nom 26" x1="-54" y1="72.2" x2="-14.4" y2="111.8" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_333" data-name="Dégradé sans nom 33" x1="-54.4" y1="71.9" x2="-14.1" y2="112.2" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_269" data-name="Dégradé sans nom 26" x1="485.1" y1="-9.2" x2="503.7" y2="9.4" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_334" data-name="Dégradé sans nom 33" x1="484.8" y1="-9.6" x2="504" y2="9.7" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2610" data-name="Dégradé sans nom 26" x1="825.1" y1="682.3" x2="856.8" y2="713.9" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_335" data-name="Dégradé sans nom 33" x1="824.8" y1="681.9" x2="857.1" y2="714.3" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2611" data-name="Dégradé sans nom 26" x1="308.5" y1="356.5" x2="340.3" y2="388.3" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_336" data-name="Dégradé sans nom 33" x1="308.1" y1="356.2" x2="340.7" y2="388.7" gradientTransform="translate(909.8 659.5) rotate(105)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2612" data-name="Dégradé sans nom 26" x1="540.4" y1="450.4" x2="559" y2="469" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_337" data-name="Dégradé sans nom 33" x1="540.1" y1="450" x2="559.4" y2="469.3" gradientTransform="translate(661.8 133.9) rotate(60)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2613" data-name="Dégradé sans nom 26" x1="-336.4" y1="326.9" x2="-296.8" y2="366.5" gradientTransform="translate(342.9 490.5) rotate(-175.4)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_338" data-name="Dégradé sans nom 33" x1="-336.7" y1="326.5" x2="-296.4" y2="366.8" gradientTransform="translate(342.9 490.5) rotate(-175.4)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2614" data-name="Dégradé sans nom 26" x1="115" y1="-29.9" x2="133.6" y2="-11.3" gradientTransform="translate(814.9 151.4) rotate(139.6)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_339" data-name="Dégradé sans nom 33" x1="114.7" y1="-30.2" x2="134" y2="-11" gradientTransform="translate(814.9 151.4) rotate(139.6)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2615" data-name="Dégradé sans nom 26" x1="94.5" y1="304.2" x2="124.4" y2="334" gradientTransform="translate(568 142) rotate(97.9)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_3310" data-name="Dégradé sans nom 33" x1="94.2" y1="303.8" x2="124.7" y2="334.4" gradientTransform="translate(568 142) rotate(97.9)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
<linearGradient id="Dégradé_sans_nom_2616" data-name="Dégradé sans nom 26" x1="435.8" y1="254.1" x2="454.4" y2="272.7" gradientTransform="translate(257.1 -349) rotate(52.9)" xlink:href="#Dégradé_sans_nom_26"/>
|
||||
<linearGradient id="Dégradé_sans_nom_3311" data-name="Dégradé sans nom 33" x1="435.5" y1="253.8" x2="454.8" y2="273.1" gradientTransform="translate(257.1 -349) rotate(52.9)" xlink:href="#Dégradé_sans_nom_33"/>
|
||||
</defs>
|
||||
<g id="Calque_1">
|
||||
<circle class="st16" cx="499.5" cy="499.5" r="499.5"/>
|
||||
</g>
|
||||
<g id="Calque_2">
|
||||
<g id="Calque_3">
|
||||
<ellipse class="st17" cx="499.2" cy="285.5" rx="139.8" ry="209.5"/>
|
||||
<ellipse class="st12" cx="299.9" cy="435.8" rx="139.8" ry="209.5" transform="translate(-207.3 586.3) rotate(-72)"/>
|
||||
<ellipse class="st13" cx="373.6" cy="666.3" rx="209.5" ry="139.8" transform="translate(-385.1 576.9) rotate(-54)"/>
|
||||
<ellipse class="st15" cx="623.9" cy="665.8" rx="139.8" ry="209.5" transform="translate(-272.2 493.9) rotate(-36)"/>
|
||||
<ellipse class="st14" cx="703.9" cy="443.1" rx="209.5" ry="139.8" transform="translate(-94.9 211.2) rotate(-16)"/>
|
||||
<circle class="st0" cx="90.4" cy="576" r="22.4"/>
|
||||
<circle class="st3" cx="175.6" cy="607.9" r="13.1"/>
|
||||
<circle class="st4" cx="140.8" cy="691.6" r="28"/>
|
||||
<circle class="st2" cx="829.7" cy="602.6" r="28"/>
|
||||
<circle class="st1" cx="908.9" cy="562.1" r="13.1"/>
|
||||
<circle class="st7" cx="840.9" cy="698.1" r="22.4"/>
|
||||
<circle class="st10" cx="466.1" cy="876.5" r="22.5"/>
|
||||
<circle class="st11" cx="538.6" cy="839.8" r="13.1"/>
|
||||
<circle class="st8" cx="686.1" cy="170.1" r="28"/>
|
||||
<circle class="st9" cx="733.7" cy="247.7" r="13.1"/>
|
||||
<circle class="st6" cx="236.9" cy="206.5" r="21.1"/>
|
||||
<circle class="st5" cx="315.4" cy="164.9" r="13.1"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.6 KiB |
@@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
const appConfig = useAppConfig()
|
||||
const { forced: forcedColorMode } = useDocusColorMode()
|
||||
|
||||
const { isEnabled: isAssistantEnabled } = useAssistant()
|
||||
const { isEnabled, locales } = useDocusI18n()
|
||||
const { subNavigationMode } = useSubNavigation()
|
||||
|
||||
const links = computed(() => {
|
||||
const list = []
|
||||
if (appConfig.socials?.gitea) {
|
||||
list.push({
|
||||
'icon': 'i-simple-icons-gitea',
|
||||
'to': appConfig.socials.gitea,
|
||||
'target': '_blank',
|
||||
'aria-label': 'Gitea',
|
||||
})
|
||||
}
|
||||
if (appConfig.github?.url) {
|
||||
list.push({
|
||||
'icon': 'i-simple-icons-github',
|
||||
'to': appConfig.github.url,
|
||||
'target': '_blank',
|
||||
'aria-label': 'GitHub',
|
||||
})
|
||||
}
|
||||
return list
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UHeader
|
||||
:ui="{ center: 'flex-1' }"
|
||||
:class="{ 'flex flex-col': subNavigationMode === 'header' }"
|
||||
>
|
||||
<AppHeaderCenter />
|
||||
|
||||
<template #left>
|
||||
<AppHeaderLeft />
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<AppHeaderCTA />
|
||||
|
||||
<template v-if="isAssistantEnabled">
|
||||
<AssistantChat />
|
||||
</template>
|
||||
|
||||
<template v-if="isEnabled && locales.length > 1">
|
||||
<ClientOnly>
|
||||
<LanguageSelect />
|
||||
|
||||
<template #fallback>
|
||||
<div class="h-8 w-8 animate-pulse bg-neutral-200 dark:bg-neutral-800 rounded-md" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
|
||||
<USeparator
|
||||
orientation="vertical"
|
||||
class="h-8"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<UContentSearchButton class="lg:hidden" />
|
||||
|
||||
<ClientOnly v-if="!forcedColorMode">
|
||||
<UColorModeButton />
|
||||
|
||||
<template #fallback>
|
||||
<div class="h-8 w-8 animate-pulse bg-neutral-200 dark:bg-neutral-800 rounded-md" />
|
||||
</template>
|
||||
</ClientOnly>
|
||||
|
||||
<template v-if="links?.length">
|
||||
<UButton
|
||||
v-for="(link, index) of links"
|
||||
:key="index"
|
||||
v-bind="{ color: 'neutral', variant: 'ghost', ...link }"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #toggle="{ open, toggle }">
|
||||
<IconMenuToggle
|
||||
:open="open"
|
||||
class="lg:hidden"
|
||||
@click="toggle"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<AppHeaderBody />
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-if="subNavigationMode === 'header'"
|
||||
#bottom
|
||||
>
|
||||
<AppHeaderBottom />
|
||||
</template>
|
||||
</UHeader>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
const { sections } = useSubNavigation()
|
||||
|
||||
const navMenuVariants = useUIConfig('navigationMenu')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="sections.length">
|
||||
<!-- Empty spacer: keeps the header's flex-1 center slot from collapsing
|
||||
while the real menu below is absolutely positioned so it can match
|
||||
the article's content-column width instead of this slot's width. -->
|
||||
<div class="hidden lg:block w-full" />
|
||||
|
||||
<UContainer class="absolute inset-x-0 inset-y-0 hidden lg:flex items-center pointer-events-none">
|
||||
<!-- Mirrors the docs page's actual layout: an outer 10-col grid (left
|
||||
doc-tree sidebar = col-span-2) containing a second, nested 10-col
|
||||
grid for the article body (right TOC sidebar = col-span-2 of that
|
||||
inner grid). Matching both levels is what lines this menu up with
|
||||
the real content column instead of a naive single-level fraction. -->
|
||||
<div class="grid grid-cols-10 gap-10 w-full pointer-events-auto">
|
||||
<div class="col-span-8 col-start-3 grid grid-cols-10 gap-10">
|
||||
<div class="col-span-8 col-start-1">
|
||||
<UNavigationMenu
|
||||
:items="sections"
|
||||
:highlight="navMenuVariants.highlight ?? true"
|
||||
:highlight-color="navMenuVariants.highlightColor"
|
||||
:variant="navMenuVariants.variant ?? 'pill'"
|
||||
:color="navMenuVariants.color"
|
||||
class="-mx-[10px] w-[calc(100%+20px)] [&>div]:w-full"
|
||||
:ui="{ list: 'w-full justify-between', item: 'py-0' }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</template>
|
||||
|
||||
<UContentSearchButton
|
||||
v-else
|
||||
:collapsed="false"
|
||||
class="w-full"
|
||||
variant="soft"
|
||||
:ui="{
|
||||
leadingIcon: 'size-4 mx-0.5',
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{
|
||||
width?: string
|
||||
height?: string
|
||||
zIndex?: string
|
||||
top?: string
|
||||
left?: string
|
||||
right?: string
|
||||
blur?: string
|
||||
colors?: string[]
|
||||
}>(), {
|
||||
width: '10rem',
|
||||
height: '10rem',
|
||||
zIndex: '10',
|
||||
top: '0',
|
||||
left: 'auto',
|
||||
right: 'auto',
|
||||
blur: '50px',
|
||||
colors: () => ['rgba(0, 71, 225, 0.34)', 'rgba(26, 214, 255, 0.22)', 'rgba(0, 71, 225, 0.22)'],
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="pointer-events-none absolute w-full"
|
||||
:style="{
|
||||
top,
|
||||
insetInlineStart: left,
|
||||
insetInlineEnd: right,
|
||||
zIndex,
|
||||
maxWidth: width,
|
||||
height,
|
||||
filter: `blur(${blur})`,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="w-full h-full"
|
||||
:style="{
|
||||
background: `linear-gradient(97.62deg, ${colors[0]} 2.27%, ${colors[1]} 65%, ${colors[2]} 98.48%)`,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
const { sidebarNavigation } = useSubNavigation()
|
||||
|
||||
const contentNavVariants = useUIConfig('contentNavigation')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UContentNavigation
|
||||
:collapsible="true"
|
||||
:default-open="false"
|
||||
:highlight="contentNavVariants.highlight ?? true"
|
||||
:highlight-color="contentNavVariants.highlightColor"
|
||||
:variant="contentNavVariants.variant ?? 'link'"
|
||||
:color="contentNavVariants.color"
|
||||
:navigation="sidebarNavigation"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
const { subNavigationMode, sections } = useSubNavigation()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="subNavigationMode === 'aside'"
|
||||
class="mb-2"
|
||||
>
|
||||
<UPageAnchors :links="sections" />
|
||||
<USeparator
|
||||
type="dashed"
|
||||
class="my-4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="mb-4"
|
||||
>
|
||||
<UContentSearchButton
|
||||
:collapsed="false"
|
||||
class="w-full"
|
||||
variant="soft"
|
||||
:ui="{
|
||||
leadingIcon: 'size-4 mx-0.5',
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { joinURL, withTrailingSlash } from 'ufo'
|
||||
import { useRuntimeConfig } from '#imports'
|
||||
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const appBaseURL = runtimeConfig.app?.baseURL || '/'
|
||||
const mcpRoute = (runtimeConfig.public.mcp as { route?: string } | undefined)?.route || '/mcp'
|
||||
|
||||
const { copy, copied } = useClipboard()
|
||||
const { t } = useDocusI18n()
|
||||
|
||||
const markdownLink = computed(() => `${window?.location?.origin}${withTrailingSlash(appBaseURL)}raw${route.path}.md`)
|
||||
const mcpServerUrl = computed(() => `${window?.location?.origin}${joinURL(appBaseURL, mcpRoute)}`)
|
||||
const mcpDeeplink = computed(() => `${window?.location?.origin}${joinURL(appBaseURL, mcpRoute, 'deeplink')}`)
|
||||
const items = computed(() => [
|
||||
[{
|
||||
label: t('docs.copy.link'),
|
||||
icon: 'i-lucide-link',
|
||||
onSelect() {
|
||||
copy(markdownLink.value)
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t('docs.copy.view'),
|
||||
icon: 'i-simple-icons:markdown',
|
||||
target: '_blank',
|
||||
to: markdownLink.value,
|
||||
},
|
||||
{
|
||||
label: t('docs.copy.gpt'),
|
||||
icon: 'i-simple-icons:openai',
|
||||
target: '_blank',
|
||||
to: `https://chatgpt.com/?hints=search&q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
|
||||
},
|
||||
{
|
||||
label: t('docs.copy.claude'),
|
||||
icon: 'i-simple-icons:anthropic',
|
||||
target: '_blank',
|
||||
to: `https://claude.ai/new?q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`,
|
||||
}],
|
||||
[
|
||||
{
|
||||
label: 'Copy MCP Server URL',
|
||||
icon: 'i-lucide-link',
|
||||
onSelect() {
|
||||
copy(mcpServerUrl.value)
|
||||
toast.add({
|
||||
title: 'Copied to clipboard',
|
||||
icon: 'i-lucide-check-circle',
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Add MCP Server',
|
||||
icon: 'i-simple-icons:cursor',
|
||||
target: '_blank',
|
||||
to: mcpDeeplink.value,
|
||||
},
|
||||
],
|
||||
])
|
||||
|
||||
async function copyPage() {
|
||||
const page = await $fetch<string>(`/raw${route.path}.md`)
|
||||
copy(page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UFieldGroup size="sm">
|
||||
<UButton
|
||||
:label="t('docs.copy.page')"
|
||||
:icon="copied ? 'i-lucide-check' : 'i-lucide-copy'"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
class="bg-[rgba(12,13,12,0.8)] hover:bg-[rgba(18,17,16,0.9)] border border-[#121110]"
|
||||
:ui="{
|
||||
leadingIcon: 'text-neutral size-3.5',
|
||||
}"
|
||||
@click="copyPage"
|
||||
/>
|
||||
|
||||
<UDropdownMenu
|
||||
size="sm"
|
||||
:items="items"
|
||||
:content="{
|
||||
align: 'end',
|
||||
side: 'bottom',
|
||||
sideOffset: 8,
|
||||
}"
|
||||
>
|
||||
<UButton
|
||||
icon="i-lucide-chevron-down"
|
||||
color="neutral"
|
||||
variant="soft"
|
||||
class="bg-[rgba(12,13,12,0.8)] hover:bg-[rgba(18,17,16,0.9)] border border-[#121110] border-l-[#121110]"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</UFieldGroup>
|
||||
</template>
|
||||
@@ -0,0 +1,164 @@
|
||||
<script setup lang="ts">
|
||||
import { kebabCase } from 'scule'
|
||||
import type { ContentNavigationItem, Collections, DocsCollectionItem } from '@nuxt/content'
|
||||
import { findPageHeadline } from '@nuxt/content/utils'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'docs',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const { locale, isEnabled, t } = useDocusI18n()
|
||||
const { isOpen } = useAssistant()
|
||||
const appConfig = useAppConfig()
|
||||
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
|
||||
const collectionName = computed(() => isEnabled.value ? `docs_${locale.value}` : 'docs')
|
||||
|
||||
const [{ data: page }, { data: surround }] = await Promise.all([
|
||||
useAsyncData(kebabCase(route.path), () => queryCollection(collectionName.value as keyof Collections).path(route.path).first() as Promise<DocsCollectionItem>),
|
||||
useAsyncData(`${kebabCase(route.path)}-surround`, () => {
|
||||
return queryCollectionItemSurroundings(collectionName.value as keyof Collections, route.path, {
|
||||
fields: ['description'],
|
||||
})
|
||||
}),
|
||||
])
|
||||
|
||||
if (!page.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
||||
}
|
||||
|
||||
const title = page.value.seo?.title || page.value.title
|
||||
const description = page.value.seo?.description || page.value.description
|
||||
|
||||
const headline = ref(findPageHeadline(navigation?.value, page.value?.path))
|
||||
const breadcrumbs = computed(() => findPageBreadcrumbs(navigation?.value, page.value?.path || ''))
|
||||
|
||||
// Set `hideHeader: true` in a page's frontmatter to skip the title/description
|
||||
// block entirely (e.g. for a page that builds its own custom layout).
|
||||
const hideHeader = computed(() => !!(page.value as unknown as Record<string, unknown>)?.hideHeader)
|
||||
|
||||
// Set `hideCopyPage: true` in a page's frontmatter to hide the "Copy page"
|
||||
// dropdown (copy link / view as markdown / open in ChatGPT / Claude).
|
||||
const hideCopyPage = computed(() => !!(page.value as unknown as Record<string, unknown>)?.hideCopyPage)
|
||||
|
||||
// Set `hideToc: true` in a page's frontmatter to hide the right-hand
|
||||
// "On this page" table-of-contents sidebar.
|
||||
const hideToc = computed(() => !!(page.value as unknown as Record<string, unknown>)?.hideToc)
|
||||
|
||||
useSeo({
|
||||
title,
|
||||
description,
|
||||
type: 'article',
|
||||
modifiedAt: (page.value as unknown as Record<string, unknown>).modifiedAt as string | undefined,
|
||||
breadcrumbs,
|
||||
})
|
||||
watch(() => navigation?.value, () => {
|
||||
headline.value = findPageHeadline(navigation?.value, page.value?.path) || headline.value
|
||||
})
|
||||
|
||||
defineOgImage('Docs', {
|
||||
headline: headline.value,
|
||||
title: title?.slice(0, 60),
|
||||
description: formatOgDescription(title, description),
|
||||
})
|
||||
|
||||
const github = computed(() => appConfig.github ? appConfig.github : null)
|
||||
const giteaUrl = computed(() => appConfig.socials?.gitea as string | undefined)
|
||||
|
||||
// "Edit this page" points at Gitea (git.djeex.fr), not the GitHub mirror.
|
||||
// Gitea's edit route is `/{owner}/{repo}/_edit/{branch}/{path}` (note the
|
||||
// leading underscore — different from GitHub's `/edit/{branch}/{path}`).
|
||||
const editLink = computed(() => {
|
||||
if (!giteaUrl.value) {
|
||||
return
|
||||
}
|
||||
|
||||
return [
|
||||
giteaUrl.value,
|
||||
'_edit',
|
||||
'main',
|
||||
'content',
|
||||
`${page.value?.stem}.${page.value?.extension}`,
|
||||
].filter(Boolean).join('/')
|
||||
})
|
||||
|
||||
// Add the page path to the prerender list
|
||||
addPrerenderPath(`/raw${route.path}.md`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPage
|
||||
v-if="page"
|
||||
class="relative"
|
||||
:ui="isOpen ? { center: 'lg:col-span-10' } : undefined"
|
||||
>
|
||||
<UPageHeader
|
||||
v-if="!hideHeader"
|
||||
:title="page.title"
|
||||
:description="page.description"
|
||||
:headline="headline"
|
||||
:ui="{
|
||||
wrapper: 'flex-row items-center flex-wrap justify-between',
|
||||
}"
|
||||
>
|
||||
<template #links>
|
||||
<UButton
|
||||
v-for="(link, index) in (page as DocsCollectionItem).links"
|
||||
:key="index"
|
||||
size="sm"
|
||||
v-bind="link"
|
||||
/>
|
||||
|
||||
<DocsPageHeaderLinks v-if="!hideCopyPage" />
|
||||
</template>
|
||||
</UPageHeader>
|
||||
|
||||
<UPageBody>
|
||||
<ContentRenderer
|
||||
v-if="page"
|
||||
:value="page"
|
||||
/>
|
||||
|
||||
<USeparator v-if="giteaUrl || github">
|
||||
<div
|
||||
class="flex items-center gap-2 text-sm text-muted"
|
||||
>
|
||||
<UButton
|
||||
v-if="editLink"
|
||||
variant="link"
|
||||
color="neutral"
|
||||
:to="editLink"
|
||||
target="_blank"
|
||||
icon="i-lucide-pen"
|
||||
:ui="{ leadingIcon: 'size-4' }"
|
||||
>
|
||||
{{ t('docs.edit') }}
|
||||
</UButton>
|
||||
<template v-if="giteaUrl">
|
||||
<span>{{ t('common.or') }}</span>
|
||||
<UButton
|
||||
variant="link"
|
||||
color="neutral"
|
||||
:to="`${giteaUrl}/issues/new`"
|
||||
target="_blank"
|
||||
icon="i-lucide-alert-circle"
|
||||
:ui="{ leadingIcon: 'size-4' }"
|
||||
>
|
||||
{{ t('docs.report') }}
|
||||
</UButton>
|
||||
</template>
|
||||
</div>
|
||||
</USeparator>
|
||||
<UContentSurround :surround="surround" />
|
||||
</UPageBody>
|
||||
|
||||
<template
|
||||
v-if="!isOpen && !hideToc"
|
||||
#right
|
||||
>
|
||||
<DocsAsideRight
|
||||
:page="page"
|
||||
/>
|
||||
</template>
|
||||
</UPage>
|
||||
</template>
|
||||
@@ -0,0 +1,95 @@
|
||||
import type { DefinedCollection } from '@nuxt/content'
|
||||
import { defineContentConfig, defineCollection, z } from '@nuxt/content'
|
||||
import { useNuxt } from '@nuxt/kit'
|
||||
import { joinURL } from 'ufo'
|
||||
import { existsSync } from 'node:fs'
|
||||
|
||||
const { options } = useNuxt()
|
||||
const cwd = joinURL(options.rootDir, 'content')
|
||||
const locales = options.i18n?.locales
|
||||
|
||||
// Same checks as docus's own content.config.ts (node_modules/docus/utils/pages.ts,
|
||||
// not a published package export, so reimplemented here rather than imported).
|
||||
function landingPageExists(rootDir: string): boolean {
|
||||
return existsSync(joinURL(rootDir, 'app', 'pages', 'index.vue'))
|
||||
}
|
||||
function docsFolderExists(rootDir: string, locale?: string): boolean {
|
||||
return existsSync(locale ? joinURL(rootDir, 'content', locale, 'docs') : joinURL(rootDir, 'content', 'docs'))
|
||||
}
|
||||
|
||||
const hasLandingPage = landingPageExists(options.rootDir)
|
||||
const hasDocsFolder = docsFolderExists(options.rootDir)
|
||||
|
||||
// Same as docus's own createDocsSchema(), plus the two custom per-page
|
||||
// frontmatter toggles used by app/pages/[[lang]]/[...slug].vue. Nuxt
|
||||
// Content's Zod schema silently strips any frontmatter key that isn't
|
||||
// declared here, which is why hideHeader/hideCopyPage did nothing until
|
||||
// this schema was extended.
|
||||
const createDocsSchema = () => z.object({
|
||||
links: z.array(z.object({
|
||||
label: z.string(),
|
||||
icon: z.string(),
|
||||
to: z.string(),
|
||||
target: z.string().optional(),
|
||||
})).optional(),
|
||||
hideHeader: z.boolean().optional(),
|
||||
hideCopyPage: z.boolean().optional(),
|
||||
hideToc: z.boolean().optional(),
|
||||
})
|
||||
|
||||
let collections: Record<string, DefinedCollection>
|
||||
|
||||
if (locales && Array.isArray(locales)) {
|
||||
collections = {}
|
||||
for (const locale of locales) {
|
||||
const code = (typeof locale === 'string' ? locale : locale.code).replace('-', '_')
|
||||
const hasLocaleDocs = docsFolderExists(options.rootDir, code)
|
||||
|
||||
if (!hasLandingPage) {
|
||||
collections[`landing_${code}`] = defineCollection({
|
||||
type: 'page',
|
||||
source: {
|
||||
cwd,
|
||||
include: `${code}/index.md`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
collections[`docs_${code}`] = defineCollection({
|
||||
type: 'page',
|
||||
source: {
|
||||
cwd,
|
||||
include: hasLocaleDocs ? `${code}/docs/**` : `${code}/**/*`,
|
||||
prefix: hasLocaleDocs ? `/${code}/docs` : `/${code}`,
|
||||
exclude: [`${code}/index.md`],
|
||||
},
|
||||
schema: createDocsSchema(),
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
collections = {
|
||||
docs: defineCollection({
|
||||
type: 'page',
|
||||
source: {
|
||||
cwd,
|
||||
include: hasDocsFolder ? 'docs/**' : '**',
|
||||
prefix: hasDocsFolder ? '/docs' : '/',
|
||||
exclude: ['index.md'],
|
||||
},
|
||||
schema: createDocsSchema(),
|
||||
}),
|
||||
}
|
||||
|
||||
if (!hasLandingPage) {
|
||||
collections.landing = defineCollection({
|
||||
type: 'page',
|
||||
source: {
|
||||
cwd,
|
||||
include: 'index.md',
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default defineContentConfig({ collections })
|
||||
@@ -0,0 +1,2 @@
|
||||
title: About
|
||||
icon: i-noto-star
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: Welcome
|
||||
description: Introduction to Docudjeex — a personal homelab documentation site covering self-hosted services, Debian, and Docker infrastructure.
|
||||
navigation:
|
||||
icon: i-lucide-home
|
||||
hideHeader: true
|
||||
hideCopyPage: true
|
||||
hideToc: true
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{right=0px width=75% blur=150px zIndex=60}
|
||||
# docu[·]{style="color: #1ad6ff"}what?
|
||||
|
||||
__Docu[·]{style="color: #1ad6ff"}djeex__ is a site containing the documentation of my personal servers, originally created to easily keep track of my configurations and commands.
|
||||
My infrastructure is built around the Debian 13 + Docker combo, making exporting and deployment simpler.
|
||||
Special thanks to __Nipah__, __Xenio__, and others for their patience and support. Most of this content comes directly from them.
|
||||
|
||||
## About the documentation
|
||||
|
||||
The documentation provided here is experimental and shared in a spirit of open knowledge and experience.
|
||||
It is not intended to build production-grade or industrialized infrastructure.
|
||||
It may contain mistakes and/or approximations.
|
||||
|
||||
Naturally, this documentation should only be used within a strictly legal framework.
|
||||
|
||||
### Available or Upcoming Documentation
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-noto-open-book" title="General" to="/general/networking/nat"}
|
||||
Networking, storage, and hardware basics
|
||||
::
|
||||
|
||||
::card{icon="i-noto-microscope" title="Serveex" to="/serveex/introduction"}
|
||||
Step-by-step Homelab Deployment Guide
|
||||
::
|
||||
|
||||
::card{icon="i-noto-computer-disk" title="Stockeex"}
|
||||
*(coming soon)* Build your own home NAS to store your data and media
|
||||
::
|
||||
|
||||
::card{icon="i-noto-test-tube" title="My nonsense" to="/nonsense/python/nvidia-stock-bot"}
|
||||
Personal scripts and side projects
|
||||
::
|
||||
|
||||
::card{icon="i-noto-recycling-symbol" title="Recycled" to="/recycled/deprecated/wireguard-14"}
|
||||
Deprecated pages, kept for archive
|
||||
::
|
||||
::
|
||||
@@ -0,0 +1,2 @@
|
||||
title: General
|
||||
icon: i-noto-open-book
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Networking
|
||||
icon: i-lucide-network
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: NAT & DHCP
|
||||
description: Learn how NAT, port forwarding, and DHCP work on a home router. Configure fixed IP leases and understand how to expose local services.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Router and NAT
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Understand how port forwarding works
|
||||
- Learn how to configure router NAT
|
||||
- Learn how to issue DHCP leases (fixed IPs)
|
||||
::
|
||||
|
||||

|
||||
|
||||
## What is a "port"?
|
||||
---
|
||||
Ports are different channels through which your router sends and receives data. This allows multiple services to run simultaneously.
|
||||
When it receives data through a port, your router forwards that data to the machine that:
|
||||
|
||||
- either initiated the request,
|
||||
- or is configured to receive data on a specific port.
|
||||
|
||||
Your router has over 65,000 ports available.
|
||||
|
||||
Some programs and applications are designed to use specific ports. For example, when your network sends data from an HTML page, the router receives it through port 80 (non-secure) or port `443` (secure via SSL).
|
||||
|
||||
So, your router acts as a data dispatcher between the internet and your local machines.
|
||||
|
||||
## Port Forwarding
|
||||
---
|
||||
Forwarding a `port` means setting a rule that specifies which `source` can send data to which `port` on your router, which will then forward it to a specific `port` on a specific `machine`. The `sources` and `destination machine` are identified by their IP addresses.
|
||||
|
||||
| Variable | Description | Example |
|
||||
|------------------------|---------------------------------------------------------|-------------------------|
|
||||
| `source machine` | IP of the source machine (from the internet) | `All`<br>`123.45.67.89` |
|
||||
| `source port` | Incoming port on the router | `443` |
|
||||
| `destination port` | Port on the destination machine | `3000` |
|
||||
| `destination machine` | IP of the target machine (on your local network) | `192.168.1.50` |
|
||||
|
||||
According to the table:
|
||||
If we remove `All` and keep the IP `123.45.67.89`, all traffic from this IP sent to port `443` on your router will be forwarded to port `3000` on the local IP `192.168.1.50`.
|
||||
|
||||
If we remove the IP and keep `All`, then all traffic from the internet on port `443` will be redirected to port `3000` on `192.168.1.50`.
|
||||
|
||||
This is useful when you have a server that must be accessible from the internet. For instance, a website uses port `80` (non-secure) or `443` (SSL-secured).
|
||||
To make the website accessible, you'll configure your router to redirect the domain request to your local server.
|
||||
Assume your service runs on port `3000` locally (`http://192.168.1.50:3000`), you would redirect all traffic from port `443` on the router to port `3000` on the local server.
|
||||
|
||||
::warning
|
||||
|
||||
__Warning:__ If you have multiple services to expose like `subdomain1.mydomain.com` and `subdomain2.mydomain.com`, your router cannot differentiate requests and forward to different ports.
|
||||
You must use a [Reverse Proxy](../../serveex/core/swag) to route traffic based on the request.
|
||||
::
|
||||
|
||||
## DHCP
|
||||
---
|
||||
Every time a device connects to your local network, your router assigns it an IP address using DHCP rules.
|
||||
This IP is randomly selected from a predefined pool.
|
||||
At every device reboot, the IP may change — which is problematic if you're forwarding ports, as the target IP may no longer be valid.
|
||||
|
||||
To avoid this, use your router's DHCP server to assign a static IP address.
|
||||
|
||||
Each device has a physical "MAC address".
|
||||
To assign a fixed IP, you must know your device's MAC address (visible in your router when it's connected), and assign it a static IP.
|
||||
This is called a "static DHCP lease."
|
||||
|
||||
That way, your machine's IP never changes and your port forwarding rules remain effective.
|
||||
|
||||
| Variable | Description | Example |
|
||||
|---------------|----------------------------------|---------------------|
|
||||
| `IP` | Fixed local IP to assign | `192.168.1.50` |
|
||||
| `MAC Address` | Physical address of the device | `5E:FF:56:A2:AF:15` |
|
||||
|
||||
For more information, refer to your router's documentation.
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: DNS Zone
|
||||
description: Understand how DNS works, how to read and edit a DNS zone, and how to configure domain names for your self-hosted services.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Domain Names and DNS Zones
|
||||
|
||||
::note
|
||||
🎯 __Objectives:__
|
||||
|
||||
- Understand how a DNS server works
|
||||
- Learn how to edit a DNS zone
|
||||
::
|
||||
|
||||
## Introduction
|
||||
---
|
||||
When you browse a website or use an app, requests are made to one or more domains to fetch content for the page. Your device doesn't know the IP addresses of these servers, so it contacts a _name server_ (Domain Name Server), which responds with the most up-to-date IP address for the domain being requested.
|
||||
|
||||
The DNS zone is like a registry with signposts that direct your requests to the correct destination.
|
||||
|
||||

|
||||
|
||||
## The DNS Zone
|
||||
---
|
||||
When you purchase a domain from a registrar (Cloudflare, OVH, etc.), the registrar assigns you a DNS zone that you can customize.
|
||||
|
||||
You can enter _records_ into this DNS zone to direct requests properly. You can find [more information here](https://help.ovhcloud.com/csm/fr-dns-servers-general-information?id=kb_article_view&sysparm_article=KB0051661).
|
||||
|
||||
Example of a DNS zone for the domain `mydomain.com`:
|
||||
|
||||
```
|
||||
@ IN SOA ns1.dns.me. dns.net. (2024051800 86400 3600 3600000 60)
|
||||
IN NS ns1.dns.me.
|
||||
IN NS ns2.dns.me.
|
||||
IN A 203.0.113.0
|
||||
www IN CNAME mydomain.com
|
||||
sousdomaine IN CNAME mydomain.com
|
||||
```
|
||||
|
||||
In this example:
|
||||
|
||||
- `$TTL 3600` tells global name servers that the records are valid for 1 hour (after which they need to re-check).
|
||||
- `IN SOA ns1.dns.me. dns.net. (...)` indicates `ns1.dns.me` as the primary DNS server, with refresh intervals.
|
||||
- `IN NS` records define the authoritative name servers for the domain.
|
||||
- `IN A 203.0.113.0` means `mydomain.com` points to IP `203.0.113.0`.
|
||||
- `subdomain IN CNAME mydomain.com` means `subdomain.mydomain.com` points to the same destination as `mydomain.com`.
|
||||
|
||||
So, if you want to point `mydomain.com` to your server, you can do it by adding an `A` record pointing to your server's public IP address.
|
||||
|
||||
::warning
|
||||
|
||||
- __Warning:__ If your server is hosted at home:
|
||||
- Your public IP is the one assigned to your home router. Make sure it's static, or configure [DDNS](https://aws.amazon.com/fr/what-is/dynamic-dns/).
|
||||
- Make sure you've [set up port 443 forwarding to your server's listening port](/general/networking/nat).
|
||||
::
|
||||
|
||||
If you're adding a subdomain that should also point to your server, use a `CNAME` record pointing to `mydomain.com`.
|
||||
|
||||
::note
|
||||
|
||||
__Why not use an `A` record for the subdomain?__ If your subdomain points to the same server as `mydomain.com`, it's better to use a `CNAME` record because if the server's IP changes, you won’t need to update the subdomain record.
|
||||
::
|
||||
|
||||
Most registrars offer user-friendly interfaces to manage DNS records. Refer to your registrar’s documentation for specific instructions.
|
||||
@@ -0,0 +1,227 @@
|
||||
---
|
||||
title: Samba
|
||||
description: Set up Samba on Debian to share folders over your local network and access them from Windows, macOS, or Linux.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Samba
|
||||
|
||||
Samba is a protocol that allows access to a folder located on a network drive. It can be configured on macOS, Windows, or Linux.
|
||||
|
||||
There are many tutorials for setting up Samba on Windows or on NAS systems like Synology, but here we focus on Debian.
|
||||
|
||||
::note
|
||||
🎯 __Objectives:__
|
||||
|
||||
- Create a network folder on a remote machine
|
||||
- Access the network folder from our server
|
||||
::
|
||||
|
||||

|
||||
|
||||
## Sharing a Network Folder
|
||||
---
|
||||
::note
|
||||
|
||||
In this example, we will share the `/video` folder from a remote machine called `remote-machine`. We will access this folder from a machine called `local-machine`. The user connecting to the network drive will be `sambauser`.
|
||||
::
|
||||
|
||||
### Install Samba Server
|
||||
|
||||
```sh
|
||||
sudo apt update && sudo apt upgrade
|
||||
sudo apt install samba smbclient cifs-utils
|
||||
```
|
||||
|
||||
### Create the `/video` Folder
|
||||
|
||||
```sh
|
||||
sudo mkdir /video
|
||||
```
|
||||
|
||||
### Configure the Share
|
||||
|
||||
Now, edit the file `/etc/samba/smb.conf`.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can use [File Browser](/serveex/files/file-browser) to navigate and edit your files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vim /etc/samba/smb.conf
|
||||
```
|
||||
|
||||
Find the `workgroup` variable, press `i` to enter insert mode, and name your workgroup (e.g., `workgroup = WORKGROUP`).
|
||||
|
||||
Then scroll to the end of the file and add the following configuration:
|
||||
|
||||
```properties
|
||||
[video]
|
||||
comment = Video folder
|
||||
path = /video
|
||||
writable = yes
|
||||
guest ok = no
|
||||
valid users = @smbshare
|
||||
force create mode = 770
|
||||
force directory mode = 770
|
||||
inherit permissions = yes
|
||||
```
|
||||
|
||||
Press `Esc` to exit insert mode, then type `:x` and press `Enter` to save and exit.
|
||||
|
||||
### Create a Samba User and Group
|
||||
|
||||
Since we're using a secured share, we need to create a user and group to access it remotely.
|
||||
|
||||
Create the group:
|
||||
|
||||
```sh
|
||||
sudo groupadd smbshare
|
||||
```
|
||||
|
||||
Give the group control over the `/video` folder:
|
||||
|
||||
```sh
|
||||
sudo chgrp -R smbshare /video
|
||||
```
|
||||
|
||||
Set inherited permissions:
|
||||
|
||||
```sh
|
||||
sudo chmod 2775 /video
|
||||
```
|
||||
|
||||
Now add a no-login user — this user cannot log into the server but can access Samba.
|
||||
|
||||
```sh
|
||||
sudo useradd -M -s /sbin/nologin sambauser
|
||||
```
|
||||
|
||||
Add the user to the `smbshare` group:
|
||||
|
||||
```sh
|
||||
sudo usermod -aG smbshare sambauser
|
||||
```
|
||||
|
||||
Set a Samba password:
|
||||
|
||||
```sh
|
||||
sudo smbpasswd -a sambauser
|
||||
```
|
||||
|
||||
Enable the Samba account:
|
||||
|
||||
```sh
|
||||
sudo smbpasswd -e sambauser
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo ufw allow from remote-ip to any app Samba
|
||||
```
|
||||
|
||||
## Accessing a Shared Folder
|
||||
|
||||
---
|
||||
|
||||
\::
|
||||
|
||||
### Install Required Packages
|
||||
|
||||
```sh
|
||||
sudo apt update && sudo apt upgrade
|
||||
sudo apt install cifs-utils
|
||||
```
|
||||
|
||||
### Create the Mount Destination
|
||||
|
||||
We will create a folder on our local machine where the remote `/video` folder will be mounted — e.g., `/mnt/video`.
|
||||
|
||||
```sh
|
||||
sudo mkdir /mnt/video
|
||||
```
|
||||
|
||||
### Prepare the .credentials File
|
||||
|
||||
To avoid typing our username and password every time, create a `.credentials` file storing the login info.
|
||||
|
||||
Create it in the `/smb` folder:
|
||||
|
||||
```sh
|
||||
sudo mkdir /smb
|
||||
sudo vi /smb/.credentials
|
||||
```
|
||||
|
||||
Enter insert mode (`i`) and write:
|
||||
|
||||
```properties
|
||||
username=smbuser
|
||||
password=password
|
||||
```
|
||||
|
||||
* `smbuser`: the user we created on the `remote-machine`
|
||||
* `password`: the password set earlier
|
||||
|
||||
Press `Esc`, then `:x` and `Enter` to save and exit.
|
||||
|
||||
Set proper file permissions:
|
||||
|
||||
```sh
|
||||
sudo chmod 600 /smb/.credentials
|
||||
```
|
||||
|
||||
### Mount the Shared Folder
|
||||
|
||||
Now mount the folder:
|
||||
|
||||
```sh
|
||||
sudo mount -t cifs -o credentials=/smb/.credentials //remote-ip/video /mnt/video
|
||||
```
|
||||
|
||||
Replace `remote-ip` with your `remote-machine`'s IP address.
|
||||
|
||||
Verify the mount:
|
||||
|
||||
```sh
|
||||
sudo mount -t cifs
|
||||
```
|
||||
|
||||
You’ll see details confirming the mount is successful.
|
||||
|
||||
Now you can access the `/video` folder of the `remote-machine` from your `local-machine`!
|
||||
|
||||
### Auto-mount on Boot
|
||||
|
||||
By default, shares aren't auto-mounted after reboot. To automate this, edit the `/etc/fstab` file.
|
||||
|
||||
First, back it up:
|
||||
|
||||
```sh
|
||||
sudo cp /etc/fstab /etc/fstab.bak
|
||||
```
|
||||
|
||||
Then add the mount configuration line:
|
||||
|
||||
```sh
|
||||
sudo echo //remote-ip/video /mnt/video cifs _netdev,nofail,credentials=/smb/.credentials,x-systemd.automount,x-systemd.device-timeout=15 0 0 >> /etc/fstab
|
||||
```
|
||||
|
||||
Reboot the machine:
|
||||
|
||||
```sh
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
After rebooting, verify the mount:
|
||||
|
||||
```sh
|
||||
sudo mount -t cifs
|
||||
```
|
||||
|
||||
And done!
|
||||
|
||||
### Unmount the Shared Folder
|
||||
|
||||
```sh
|
||||
sudo umount -t cifs /mnt/video
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Storage
|
||||
icon: i-lucide-hard-drive
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: RAID
|
||||
description: Understand RAID concepts — hardware vs software, RAID levels, and how to set up redundant disk arrays for your homelab.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# RAID
|
||||
|
||||
_Redundant Array of Independent Disks_
|
||||
|
||||
In computing, RAID (Redundant Array of Independent Disks) is a system that allows multiple hard drives to be combined to improve performance and/or reliability. It works by restructuring and distributing data blocks across the drives.
|
||||
|
||||
Originally, RAID systems were hardware-based, meaning a dedicated controller (a specific chip) managed data distribution and RAID operations. Today, most RAID systems (or their equivalents) are software-based. In fact, many software technologies can create RAID-like systems with features not available in hardware RAID, such as automatic repair (data scrubbing), snapshots, and more.
|
||||
|
||||
## Different Types of RAID
|
||||
|
||||
There are several types of RAID, each offering its own pros and cons. In general, RAID impacts the following five factors:
|
||||
|
||||
- Number of drives
|
||||
- Total storage capacity
|
||||
- Read speed
|
||||
- Write speed
|
||||
- Fault tolerance (resistance to hardware failure)
|
||||
|
||||
::warning
|
||||
|
||||
- RAID is not a backup system but a service continuity system! It only allows hot-swapping of drives without interrupting your server or restoring from backup. You still need an external backup system.
|
||||
|
||||
### No RAID
|
||||
---
|
||||
|
||||
<div style="display: flex; align-items: center;">
|
||||
<img src="/img/global/no-raid.svg" alt="Image" style="max-width: 30%; max-height:230px; margin-right: 20px;">
|
||||
<ul>
|
||||
<li>Just your disks, without RAID. Data is stored disk by disk.</li>
|
||||
<li>If you lose a disk, only its data is lost.</li>
|
||||
<li>Total capacity is the sum of all disks.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Use your disks without RAID when you're not afraid of data loss and can tolerate service interruptions between failure and backup restoration.
|
||||
|
||||
### RAID 0
|
||||
---
|
||||
|
||||
<div style="display: flex; align-items: center;">
|
||||
<img src="/img/global/raid0.svg" alt="Image" style="max-width: 30%; max-height:230px; margin-right: 20px;">
|
||||
<ul>
|
||||
<li>OS sees 1 drive.</li>
|
||||
<li>Data is striped across all disks.</li>
|
||||
<li>If you lose one disk, you lose all data.</li>
|
||||
<li>High read and write performance (multiplied by number of disks).</li>
|
||||
<li>Total capacity is the sum of all disks.</li>
|
||||
<li>Minimum of 2 disks required.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Use RAID 0 when you prioritize performance and are not concerned about data loss. Ideal for temporary, high-speed storage (video editing, AI workloads, etc). Not suitable for long-term storage, as one failure means total data loss.
|
||||
|
||||
### RAID 1
|
||||
---
|
||||
|
||||
<div style="display: flex; align-items: center;">
|
||||
<img src="/img/global/raid1.svg" alt="Image" style="max-width: 30%; max-height:230px; margin-right: 20px;">
|
||||
<ul>
|
||||
<li>OS sees 1 drive.</li>
|
||||
<li>All disks contain identical data.</li>
|
||||
<li>You can lose all but one disk.</li>
|
||||
<li>Improved read speed (scales with number of disks).</li>
|
||||
<li>Total capacity is equal to one disk (e.g., 2×10TB = 10TB).</li>
|
||||
<li>Minimum of 2 disks required.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Use RAID 1 for strong redundancy. Each disk contains all data, so performance remains unaffected during a failure. Once failed disks are replaced, data is quickly restored. However, usable storage is limited to one disk’s capacity, making it an expensive solution.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can combine RAID 1 with other RAID types to create mirrored arrays.
|
||||
::
|
||||
|
||||
### RAID 5
|
||||
---
|
||||
<p align="center">
|
||||
<img src="/img/global/raid5.svg" alt="Image" style="max-width: 40%; margin-right: 20px;">
|
||||
</p>
|
||||
|
||||
- OS sees 1 drive.
|
||||
- Data is striped with parity blocks for redundancy.
|
||||
- You can lose 1 disk and recover data.
|
||||
- Improved read speed (scales with number of disks).
|
||||
- Total capacity is the sum of all disks minus one (e.g., 3×10TB = 20TB).
|
||||
- Minimum of 3 disks (4 recommended to reduce capacity loss).
|
||||
|
||||
Use RAID 5 when you want reliable storage with 3 to 5 disks and minimal space loss. It tolerates one disk failure but may have degraded performance during recovery, which can take days.
|
||||
|
||||
### RAID 6
|
||||
---
|
||||
<p align="center">
|
||||
<img src="/img/global/raid6.svg" alt="Image" style="max-width: 50%; margin-right: 20px;">
|
||||
</p>
|
||||
|
||||
- OS sees 1 drive.
|
||||
- Data is striped with dual parity blocks.
|
||||
- You can lose 2 disks and still recover data.
|
||||
- Improved read speed (scales with number of disks).
|
||||
- Total capacity is the sum of all disks minus two (e.g., 4×10TB = 20TB).
|
||||
- Minimum of 4 disks (6 recommended to minimize space loss).
|
||||
|
||||
Use RAID 6 in similar situations as RAID 5, especially with 6 or more disks. More disks mean higher failure risk. RAID 6 offers peace of mind by tolerating two simultaneous failures.
|
||||
|
||||
## Software RAID
|
||||
(coming soon)
|
||||
::
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: ZFS
|
||||
description: Introduction to ZFS — a combined file system and volume manager with snapshots, checksums, and built-in redundancy for reliable homelab storage.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# ZFS
|
||||
|
||||
::note
|
||||
🎯 __Objectives:__
|
||||
|
||||
Understand what ZFS is and why it's useful
|
||||
::
|
||||
|
||||
ZFS is widely used in the world of servers, NAS systems (like FreeNAS / TrueNAS), virtualization, and even by tech-savvy individuals who want reliable storage. It is both a _file system_ (like NTFS for Windows, EXT4, FAT32, etc.) and a _volume manager_ (similar to LVM).
|
||||
|
||||
To put it simply:
|
||||
|
||||
- A **volume manager** organizes physical storage (like one or more hard drives).
|
||||
- A **file system** organizes how data blocks are written, read, and deleted within those volumes.
|
||||
|
||||
ZFS goes far beyond traditional file systems in terms of performance and features.
|
||||
Here’s what we’re most interested in:
|
||||
|
||||
- Its __snapshot management__ features, allowing you to quickly roll back in case of issues.
|
||||
- Its support for disk groupings and [__RAID-like structures__](/general/storage/raid) (Z-Mirror, RAIDZ1, RAIDZ2, RAIDZ3).
|
||||
- Its __automatic recovery of corrupted data__ (through scrubbing).
|
||||
- Its performance, enhanced by RAM caching (ZFS ARC).
|
||||
- Its robust error notifications and monitoring.
|
||||
|
||||
## Structure
|
||||
---
|
||||

|
||||
|
||||
ZFS has a unique structure:
|
||||
|
||||
- **vdev** (virtual device): a group of physical or virtual disks.
|
||||
- **zpool**: a collection of vdevs configured as a single storage pool. A zpool can contain multiple vdevs, but a vdev belongs to only one zpool.
|
||||
- **dataset**: a logical data container within a zpool. Each dataset can have its own settings (compression, quotas, permissions, etc.).
|
||||
|
||||
There are several dataset types:
|
||||
|
||||
- **file system**: a standard ZFS filesystem, mounted without storage quotas.
|
||||
- **zvol**: a "virtual disk" with a defined size, which you can format and partition as if it were a physical disk.
|
||||
- **snapshot**: a frozen-in-time version of another dataset. Snapshots can be created manually or through backup tools. They can be mounted to browse data as it was at the snapshot time.
|
||||
|
||||
## Why ZFS over others?
|
||||
---
|
||||
### Data Integrity
|
||||
|
||||
ZFS continuously checks that your stored data hasn't become corrupted. Every block of data is associated with a checksum, allowing ZFS to detect even the smallest alteration. If corruption is found and a healthy copy exists elsewhere, ZFS can repair the data automatically.
|
||||
|
||||
### Built-in RAID
|
||||
|
||||
ZFS includes its own volume management system (vdevs). You can build a zpool using multiple disks—similar to traditional [RAID](/general/storage/raid) setups—but with more flexibility. For example:
|
||||
|
||||
- **Z-mirror** → equivalent to RAID 1
|
||||
- **RAIDZ1** → equivalent to RAID 5 (tolerates 1 disk failure)
|
||||
- **RAIDZ2** → equivalent to RAID 6 (tolerates 2 disk failures)
|
||||
- **RAIDZ3** → tolerates up to 3 disk failures
|
||||
|
||||
ZFS handles all this natively—no external RAID software needed.
|
||||
|
||||
::note
|
||||
|
||||
Check out the [article on RAID](/general/storage/raid) to find the right solution for your needs.
|
||||
::
|
||||
|
||||
### Snapshots and Clones
|
||||
|
||||
ZFS allows you to create snapshots—instantaneous images of a dataset's state. Snapshots take up minimal space and can be scheduled frequently. You can also create clones: writable copies of snapshots.
|
||||
|
||||
### Compression and Deduplication
|
||||
|
||||
ZFS can compress data on the fly (transparently to the user), saving disk space. It also supports deduplication (removing duplicate data), though this feature requires a lot of memory and is not recommended for all use cases.
|
||||
|
||||
---
|
||||
|
||||
Now you know why ZFS is *the* file system to deploy on your NAS.
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Hardware
|
||||
icon: i-lucide-server
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
title: The Basics
|
||||
description: Overview of server hardware fundamentals — CPUs, RAM, storage, and form factors to understand before building your homelab.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Server Basics
|
||||
|
||||
::note
|
||||
🎯 __Objectives:__
|
||||
|
||||
Understand the fundamentals of server hardware
|
||||
::
|
||||
|
||||

|
||||
|
||||
A __server__ is essentially a computer dedicated to specific tasks, designed to remain accessible at all times. Structurally, it's not much different from a regular computer. Depending on its intended use, some components may vary. This article serves as a reference to help you understand the essential components of a server and how their roles adapt based on your needs.
|
||||
|
||||
## Motherboard
|
||||
---
|
||||
The __motherboard__ is the foundation of your machine. It's the component that connects all others together. It enables communication between components and interaction with peripherals (keyboard, mouse, etc.). Choose it based on your I/O (Input/Output) needs like USB ports, network ports, speed, etc., and ensure compatibility with the components you plan to install.
|
||||
|
||||
Key components connected to the motherboard:
|
||||
|
||||
- CPU
|
||||
- RAM
|
||||
- Storage (HDD and/or SSD)
|
||||
- Optional dedicated GPU
|
||||
|
||||
Common consumer motherboard formats:
|
||||
|
||||
- E-ATX: largest
|
||||
- ATX: standard
|
||||
- Micro-ATX: smaller
|
||||
- Mini-ITX: smallest
|
||||
|
||||
Larger boards generally offer more ports and features. Pre-built systems might use proprietary formats.
|
||||
|
||||
## CPU
|
||||
---
|
||||
<div style="display: flex; align-items: center;">
|
||||
<img src="/img/global/cpu.svg" alt="Image" style="max-width: 25%; max-height:230px; margin-right: 20px;">
|
||||
<p>The <strong>CPU</strong> (Central Processing Unit) is the computer's calculator. It processes most software tasks. Modern CPUs have multiple cores, often with virtual threads, to better handle workloads. They need to be cooled using either an active cooler (with a fan) or a passive one (fanless), depending on power consumption (watts). Choose your CPU based on how you plan to use the server.</p>
|
||||
</div>
|
||||
|
||||
::warning
|
||||
|
||||
__Caution:__ Ensure third-party coolers are compatible with the CPU socket and always apply thermal paste before installing the cooler.
|
||||
::
|
||||
|
||||
Consider:
|
||||
|
||||
- Number of cores (more cores = better multitasking)
|
||||
- Clock speed in GHz
|
||||
- Power consumption in Watts
|
||||
|
||||
For low-power home servers or NAS (non-intensive computing), consider Intel N100/150 (4 cores) or N305/N355 (8 cores)—efficient and low power (ideal for 24/7 uptime).
|
||||
|
||||
## RAM
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/global/ram.svg" alt="Image" style="max-width: 65%;">
|
||||
</p>
|
||||
|
||||
__RAM__ (Random Access Memory) is fast, temporary memory used by the CPU (and iGPU if applicable) for quick access during execution. It clears periodically and when the machine powers down. Better RAM = better CPU performance.
|
||||
|
||||
Comes as sticks installed on the motherboard. Varies by format and generation (currently DDR5).
|
||||
|
||||
## GPU
|
||||
---
|
||||
|
||||
The __GPU__ (Graphics Processing Unit) handles graphical, video, and sometimes AI-related processing. Its main theoretical use is to display the image on your screen. In servers, it's useful for media centers (e.g. [Plex](/serveex/media/plex)) and for accelerating AI tasks like facial recognition or photo indexing (e.g. [Immich](/serveex/cloud/immich)).
|
||||
|
||||
Depending on the required performance, one can choose between a dedicated GPU with its own VRAM (a graphics card connected to a PCIe slot on the motherboard), or an iGPU—an integrated GPU built into the CPU (such as the N100/N150 or N305/N355), which uses the system’s shared RAM.
|
||||
|
||||
### HDD(s)
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/global/hdd.svg" alt="Image" style="max-width: 50%; margin-right: 20px;">
|
||||
</p>
|
||||
|
||||
An __HDD__ (Hard Disk Drive), or hard drive, is a component used to store data. It was once the standard storage device in computers. HDDs consist of one or more stacked platters and read/write heads—somewhat like a vinyl record player.
|
||||
|
||||
Today, HDDs can store enormous amounts of data (up to 30TB, or 30,000 gigabytes, for consumer models), but their read and write speeds are limited due to their mechanical nature. They are also bulky and heavy.
|
||||
|
||||
Generally, HDDs are best suited for storing data that doesn’t require frequent access or fast write speeds, such as media files (videos, photos), cloud drives, or archived data. They perform well in these scenarios and, most importantly, are significantly cheaper than SSDs for the same amount of storage.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Use multiple HDDs in [RAID](/general/storage/raid) to enhance performance and redundancy.
|
||||
::
|
||||
|
||||
Comes in 3.5" and 2.5" formats; servers usually favor the more reliable 3.5".
|
||||
|
||||
### SSD(s)
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/global/nvme.svg" alt="Image" style="max-width: 50%; margin-right: 20px;">
|
||||
</p>
|
||||
|
||||
An __SSD__ (Solid State Drive) is a small circuit board with memory chips soldered onto it, used to store information. Unlike RAM, these chips retain data even when not powered, meaning the information is preserved after a reboot. SSDs are generally used as the main storage medium for your server.
|
||||
|
||||
Unlike HDDs, SSDs have no moving parts, are highly compact, and most importantly, are extremely fast—offering speeds of several gigabytes per second for high-performance models.
|
||||
|
||||
SSDs come in various formats, but today the preferred choice is the M.2 NVMe version, as it is the smallest, fastest, and has become the standard on modern motherboards.
|
||||
|
||||
However, SSDs are significantly more expensive than hard drives for the same storage capacity. Typically, the operating system (OS) is installed on the SSD to ensure fast performance. In a server environment, it's also ideal to store [Docker containers](/serveex/core/docker) and databases on the SSD. More broadly, any data that needs to be accessed frequently and quickly—such as websites, applications, or processing workloads—should be stored on an SSD.
|
||||
|
||||
### Network Card
|
||||
---
|
||||
|
||||
A __network card__ allows your machine to communicate with your network (including the internet). It consists of a controller chip and one or more network ports. These ports—often Ethernet ports—can come in different physical formats and support various data transfer standards:
|
||||
|
||||
- __RJ45 Gigabit Ethernet (10/100/1000):__ The standard RJ45 connector, supporting speeds from 10 Mbps (0.125 MB/s) up to 1000 Mbps (125 MB/s).
|
||||
- __RJ45 2.5G:__ Same connector type, supporting up to 2.5 Gbps (2,500 Mbps or 312.5 MB/s).
|
||||
- __RJ45 5G:__ Same connector, supporting up to 5 Gbps (625 MB/s).
|
||||
- __RJ45 10G Base-T:__ Same RJ45 format, supporting up to 10 Gbps (1.25 GB/s).
|
||||
- __SFP 1G:__ SFP port, commonly used for fiber optic connections, supporting speeds up to 1 Gbps.
|
||||
- __SFP+ 10G:__ An enhanced version of the SFP port, also used for fiber optics, supporting up to 10 Gbps.
|
||||
|
||||
::warning
|
||||
|
||||
__Caution:__ Match network gear (router, switch, cables) to your desired speed. For most uses, CAT5E cables are enough; use CAT6A beyond 10 Gbps. Fiber requires additional care (simplex, duplex, transceivers...).
|
||||
::
|
||||
|
||||
The network card is usually built directly into the motherboard, but you can also use dedicated network cards, for example via USB or a PCIe expansion slot.
|
||||
|
||||
In general, for a server setup, it's recommended to have at least two Ethernet ports to ensure redundancy in case one connection fails.
|
||||
|
||||
### Input/Output Ports
|
||||
---
|
||||
|
||||
__I/O__ ports allow communication with external devices (displays, keyboard, mouse, network...). Motherboards typically offer:
|
||||
|
||||
- Ethernet ports
|
||||
- USB ports (varied types/speeds)
|
||||
- Video ports
|
||||
- Audio jacks
|
||||
|
||||
Choose a motherboard and expansions based on your I/O needs.
|
||||
|
||||
### Power Supply
|
||||
---
|
||||
|
||||
The __power supply unit__ (PSU) is the component that provides electrical power to your machine’s components. It connects to the wall via a power cord and has several output cables that plug into the motherboard and various peripherals, such as hard drives or dedicated graphics cards.
|
||||
|
||||
A power supply is defined by several key characteristics:
|
||||
|
||||
- Wattage (its total power output),
|
||||
- Modularity (whether the cables are fixed or detachable),
|
||||
- Efficiency (measured as a percentage). For example, a 500W PSU with 80% efficiency will actually draw 625W from the wall to deliver 500W to the system.
|
||||
|
||||
Another important factor is the form factor. There are several standard sizes, from ATX L (for larger cases) to SFX (for compact builds). There are also specialized models for rack-mounted servers, which are typically flat and space-efficient.
|
||||
|
||||
To choose the right PSU, a common rule of thumb is to estimate your system’s power needs based on usage, and then double that value. This is because most power supplies operate at optimal efficiency around 50% of their maximum load.
|
||||
|
||||
### Case
|
||||
---
|
||||
|
||||
<div style="display: flex; align-items: center;">
|
||||
<img src="/img/global/case.svg" alt="Image" style="max-width: 25%; max-height:230px; margin-right: 20px;">
|
||||
<p>The <strong>case</strong> is also an essential component of your machine. It plays a key role in cooling, through its fans and airflow design, and it determines the form factor compatibility for your motherboard, power supply, and any dedicated GPU you may install.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Additionally, the case dictates how many HDDs you can install and what formats they support. Some cases are rack-mountable, meaning they can be installed in server racks (server cabinets).
|
||||
|
||||
Choose your case carefully based on your specific needs and the hardware you plan to use.
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
title: Network
|
||||
description: Overview of networking hardware for homelabs — switches, NICs, cables, and how to connect your servers efficiently.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Network
|
||||
|
||||
::note
|
||||
🎯 __Objectives:__
|
||||
|
||||
Understand the basics of networking hardware
|
||||
::
|
||||
|
||||

|
||||
|
||||
A computer network cannot exist without the hardware required to build it. Hardware determines the size of the network, communication speeds, and its overall performance. In this article, we will focus on the simplest types of networks, typically found in home environments.
|
||||
|
||||
## The Router
|
||||
---
|
||||
The __router__ is the central hub of your network. It directs __packets__—the blocks of data that travel across your network—from the sender to the appropriate recipient. It manages the routing of data both within your local network and to/from external networks. In short, it enables devices to communicate with each other and with the internet.
|
||||
|
||||
Everyone has a router at home—it's the __internet box__ provided by your ISP (Internet Service Provider).
|
||||
|
||||
In general, a router consists of:
|
||||
|
||||
- a WAN (Wide Area Network) port that receives data from the internet (or from a higher-level network). For example, it could be a port for a fiber optic connection from your ISP, or an SFP+/RJ45 port for a third-party router.
|
||||
- a switch, i.e., a hub with several __LAN__ (Local Area Network) ports allowing multiple devices to connect to your network. These ports can be RJ45 or SFP/SFP+.
|
||||
- sometimes a built-in WiFi transmitter/receiver.
|
||||
|
||||
A router may also include _firewall_ capabilities, allowing you to restrict traffic from specific devices, as well as _[NAT (Network Address Translation)](/general/networking/nat)_ for port forwarding. It generally includes a _[DHCP (Dynamic Host Configuration Protocol)](/general/networking/nat#dhcp)_ server to automatically assign _IP addresses_ to devices connected to the network.
|
||||
|
||||
The router directly affects communication speeds between devices. The WAN port limits the maximum internet speed you can receive from your ISP. For example, if your subscription offers 5 Gb/s, you’ll need a WAN port that supports at least 5 Gb/s. Likewise, internal device-to-device communication is limited by the speed of the switch. If your devices communicate at 5 Gb/s, the router’s switch must have 5 Gb/s ports. If you're using WiFi 7 equipment and want to enjoy its full speed, your router must support it as well. If you’re using a separate WiFi access point, make sure its network port matches or exceeds the speed of the WiFi it broadcasts—and that the router supports it too.
|
||||
|
||||
Internet speed, number of devices, WiFi speed, and internal network speed—these are the four key factors to consider when choosing an internet box or buying your own router.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__
|
||||
You can easily use a third-party router to manage your network if your ISP’s internet box supports _bridge mode_. In France, only the provider Free offers this option. It is technically possible with other providers that do not support bridge mode, but it can be quite difficult and may prevent you from using all the features a third-party router provides.
|
||||
::
|
||||
|
||||
## The Switch
|
||||
---
|
||||
|
||||
The __switch__, or network switch, is a device that allows multiple devices to connect to the network. It acts as a literal hub, connecting directly to the router or to another switch upstream. It helps avoid overloading the switch ports on your router or relocating devices to another room without running a cable from each one back to the router. Another common use case is to segment multiple networks that are managed by the same router.
|
||||
|
||||
There are generally two types of switches:
|
||||
|
||||
- **Unmanaged switches**, the most common. These are plug-and-play: you just plug them in and everything works automatically.
|
||||
- **Managed switches**. These offer a configuration interface (via command line or web UI), allowing you to fine-tune routing rules under the control of the router. They are powerful for creating virtual networks between your devices, but usually require more setup time and are less convenient than simple unmanaged switches.
|
||||
|
||||
::warning
|
||||
|
||||
__Warning:__ Make sure to use a switch with ports that match the speeds supported by your network devices.
|
||||
::
|
||||
|
||||
## Cables
|
||||
---
|
||||
|
||||
Cables are essential components of your network. Depending on their type and category, they can limit the bandwidth between devices, so they must be chosen to match your network's specifications. They also need to be compatible with your devices' ports.
|
||||
|
||||
Here’s a quick reference of the most common cable and port standards:
|
||||
|
||||
- **RJ45 Gigabit Ethernet 10/100/1000**: The standard RJ45 connector, supporting speeds from 10 Mbps (0.125 MB/s) to 1000 Mbps (125 MB/s)
|
||||
- **RJ45 2.5G**: Same connector, supporting speeds up to 2.5 Gbps (312.5 MB/s)
|
||||
- **RJ45 5G**: Same connector, supporting speeds up to 5 Gbps (625 MB/s)
|
||||
- **RJ45 10GBase-T**: Same connector, supporting speeds up to 10 Gbps (1.25 GB/s)
|
||||
- **SFP 1G**: SFP port, typically used for fiber optics, supporting up to 1 Gbps
|
||||
- **SFP+ 10G**: Enhanced SFP port, also for fiber, supporting up to 10 Gbps
|
||||
|
||||
### Ethernet Cables
|
||||
|
||||
These copper cables usually use the standard `RJ45` connector. It's the most common network connector found on routers and switches.
|
||||
|
||||
Ethernet cables are divided into categories that define their maximum speed based on distance:
|
||||
|
||||
| Speed | Cable Type | Max Distance |
|
||||
|-----------|------------|--------------|
|
||||
| 10 Gb/s | CAT 6A | 100 m |
|
||||
| | CAT 6 | 55 m |
|
||||
| | CAT 5e | 30 m |
|
||||
| 5 Gb/s | CAT 6 | 100 m |
|
||||
| | CAT 5e | 30 m |
|
||||
| 2.5 Gb/s | CAT 5e | 100 m |
|
||||
| 1 Gb/s | CAT 5e | 100 m |
|
||||
| 100 Mb/s | CAT 5 | 100 m |
|
||||
|
||||
Some of these cables are flat, round, shielded (requiring grounding), etc. Choose based on your setup. What matters is that, for example, if you want to connect a device with a 2.5 Gb/s RJ45 port to a 2.5 Gb/s router, you’ll need at least a `CAT 5e` cable.
|
||||
|
||||
On the other hand, if your device is limited to 100 Mb/s, a simple `CAT 5` cable will suffice.
|
||||
|
||||
Nowadays, in new buildings, it is standard practice to install `CAT 6A` cables inside walls. This way, wall ports are ready to support 10 Gb/s over 100 meters.
|
||||
|
||||
---
|
||||
|
||||
### Optical Cables
|
||||
|
||||
Very thin but fragile, optical cables are increasingly appearing in home networks. It often starts with the fiber cable connecting your ISP’s outlet to your box/router. They have several advantages:
|
||||
|
||||
- Extremely compact
|
||||
- Zero electrical consumption (unlike copper, which loses energy as heat)
|
||||
- No electromagnetic radiation (no shielding needed, no signal interference)
|
||||
- Very high speeds over long distances
|
||||
|
||||
For local networking, it's important to understand that several types of fiber cables exist. Their performance depends on both distance and compatibility with the appropriate `transceiver`. Fiber cables connect to your devices' SFP+ ports via a small device called a transceiver, which converts electrical signals to light (and vice versa).
|
||||
|
||||
For local networks, the recommended standard is a **multimode OM3 fiber with LC connectors**, paired with a **10G LC SFP+ transceiver**. This setup allows 10 Gb/s connections and is compatible with most devices featuring SFP+ ports.
|
||||
|
||||
::warning
|
||||
|
||||
__Warning:__ Make sure to use transceivers that are compatible with your devices (routers, switches, or other hardware). There is no universal standard yet, and manufacturers usually specify which brands are supported.
|
||||
::
|
||||
|
||||
---
|
||||
|
||||
### DAC Cables
|
||||
|
||||
These are copper cables with integrated `transceivers`. They allow two SFP/SFP+ ports to communicate over short distances without using fragile fiber or RJ45 adapters. However, they consume more energy due to natural copper loss, which is non-negligible.
|
||||
|
||||
---
|
||||
|
||||
### SFP+ Transceivers
|
||||
|
||||
These let you connect different types of cables to your SFP/SFP+ ports. Variants are available for:
|
||||
|
||||
- Fiber optic
|
||||
- DAC
|
||||
- RJ45
|
||||
|
||||
::warning
|
||||
|
||||
RJ45 transceivers consume a lot of energy due to copper signal loss and can generate significant heat. Low-power models (under 2W) exist and are generally rated for longer cables (e.g., 80m instead of 30m). Surprisingly, these are preferred over short-distance models because they generate less heat and consume less energy—making them more compatible with sensitive devices. Using the wrong type can cause network degradation or even outages.
|
||||
::
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: The ProloNAS
|
||||
description: Build a capable home server on a budget using an Intel N100 mini PC — a practical guide to getting started with self-hosting for under $130.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# The ProloNAS
|
||||
|
||||
When you decide to dive into the adventure of running your own home server, the same questions usually come up: _“Where should I start?”_, _“Isn’t it expensive?”_. And often, people either give up or end up buying a proprietary NAS that they’ll throw away a year later once they realize it only brings headaches and wasted money.
|
||||
|
||||
A server isn’t a piece of furniture. It’s simply any computer capable of running Linux.That’s why mini PCs powered by **Intel N100** processors are so popular: for around $100–130 on Chinese platforms, you can get a machine that runs **24/7** for years, capable of handling everything you’d expect from a home server or personal cloud without sacrificing performance.
|
||||
|
||||
It’s **objectively inexpensive**, and anyone with a bit of curiosity can get started.
|
||||
|
||||
A mini PC for $100 + a USB dock for $50 that holds multiple hard drives = a complete platform for $150, versus **$350–1200** for branded NAS systems.
|
||||
|
||||
That’s all a **ProloNAS** is. It’s then up to you to scale your storage capacity according to your needs.
|
||||
|
||||

|
||||
|
||||
## Example Hardware
|
||||
|
||||
- Mini PC — **Note: choose 16 GB / 512 GB**: [Aliexpress](https://fr.aliexpress.com/item/1005008477986765.html)
|
||||
- DAS (Direct Attached Storage) — **Note: select “EU plug”**: [Aliexpress](https://fr.aliexpress.com/item/1005007933987260.html)
|
||||
- More refined alternative with a fan: [Amazon](https://www.amazon.fr/Boîtier-Disque-Ventilateur-Supportant-Capacité/dp/B0DD3GSSCX)
|
||||
|
||||
> *These are not affiliate links — buy wherever you prefer.*
|
||||
|
||||
## Why a NAS?
|
||||
|
||||
A **NAS** (Network Attached Storage) is a machine centered around storage, designed to be shared over a network.The idea is to have a **reliable and secure** storage space that serves as the backbone for your personal services and apps such as a self-hosted cloud like [Nextcloud](/serveex/cloud/nextcloud), a photo sync tool like [Immich](/serveex/cloud/immich), or a media server like [Plex](/serveex/media/plex). You can also store camera footage, backups, or even development projects on it.
|
||||
|
||||
### But why not just use a mini PC with an external hard drive?
|
||||
|
||||
Sure, a simple mini PC with 1–2 TB of storage will do for most people.And your movie collection might fit on an external drive of a few extra terabytes. But that’s **neither reliable nor scalable** a single shock or hardware failure could permanently destroy your data.
|
||||
|
||||
A real NAS is built around **storage reliability**. It uses redundancy strategies like [RAID](/general/storage/raid) to protect against drive failure, and snapshot systems like [ZFS](/general/storage/zfs) to guard against corruption.
|
||||
|
||||
In short, a NAS lets you **host everything yourself** that you currently entrust to third parties while maintaining control, reliability, and data safety.
|
||||
|
||||
## The Problem with Consumer NAS Systems
|
||||
|
||||
Many brands offer “ready-to-use” NAS platforms: Synology, QNAP, Ugreen, and others. They promise simplicity and sleek web interfaces, but the reality is quite different.
|
||||
|
||||
### First, the price.
|
||||
|
||||
$350 is the starting price for a 2-bay NAS (without drives) from Synology. For that, you get a 2019 processor, no SSD slot for the OS, and a measly 2 GB of RAM.
|
||||
|
||||
Now, compare that to the **ProloNAS**: an N100 (4 cores), 16 GB RAM, a 512 GB SSD for $100–130, plus a 4-bay DAS for $55. That’s **half the price** of a 2-bay Synology, and **a quarter of the price** of a 4-bay one.
|
||||
|
||||
### Locked-Down Operating Systems
|
||||
|
||||
“Yeah, but at least with a Synology, you plug it in and everything just works.”
|
||||
|
||||
One year.
|
||||
That’s how long it took before I threw away my Synology and realized I should have started with a **ProloNAS** (which later became a full-fledged server).
|
||||
|
||||
Manufacturers ship heavily customized Linux-based OSes: ancient kernels, limited app repositories, and complete dependence on their proprietary tools. As a result, you can’t fully tailor your NAS to your needs, and many Docker containers simply won’t run because the kernel is too old.
|
||||
|
||||
### Total Vendor Lock-In
|
||||
|
||||
“I’m fine with the built-in apps.”
|
||||
Yeah, I thought so too… until my needs exploded: media center, password manager, Git hosting, strong authentication, web hosting, and more.
|
||||
|
||||
Why stay stuck with half-baked proprietary tools when you can rely on **open-source projects** that are regularly updated and interoperable?
|
||||
|
||||
And what happens when the manufacturer decides to drop support or limit hardware compatibility? It’s already happened, Synology made certain drives **incompatible** unless they were “certified” by them. They even **disabled hardware transcoding** on their NAS units: [see here](https://www.cachem.fr/synology-desactive-transcodage-materiel-nas/).
|
||||
|
||||
In short, you have **no control** over a product that isn’t open, nor truly yours.
|
||||
|
||||
## OK, but how do I turn my Mini PC Serveex into a ProloNAS?
|
||||
|
||||
As mentioned earlier: by adding a **DAS (drive hub)** and setting up a redundant storage system with [RAID](/general/storage/raid) and [ZFS](/general/storage/zfs), you can transform your mini PC into a robust and scalable NAS.
|
||||
|
||||
Enjoy !
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Serveex
|
||||
icon: i-noto-microscope
|
||||
@@ -0,0 +1,194 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: Introduction to Serveex — a personal homelab project to self-host everyday services using Debian and Docker, replacing Google, Apple, and Netflix.
|
||||
navigation:
|
||||
icon: i-lucide-bookmark
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
## A Home Lab by a Beginner, for Beginners
|
||||
|
||||

|
||||
|
||||
**Serveex** is primarily a personal project aimed at hosting as many everyday services as possible at home, without relying on proprietary platforms (Google, Apple, Netflix, etc.). The goal was to experiment, learn, and document every step along the way. This is purely a scientific project and is not intended for production use.
|
||||
|
||||
A big thanks to **Nipah** for sharing his infinite knowledge and, above all, for his patience.
|
||||
|
||||
::note
|
||||
**Prerequisites:**
|
||||
|
||||
- Have [an online VPS](https://www.it-connect.fr/les-serveurs-prives-virtuels-vps-pour-les-debutants/) or a local machine: ideally a mini PC (you can find N100 models for around €100), but it also works on a laptop or [a virtual machine](https://openclassrooms.com/fr/courses/2035806-virtualisez-votre-architecture-et-vos-environnements-de-travail/6313946-installez-virtualbox). The [Freebox Delta/Ultra offer virtual machines](https://next.ink/3493/machines-virtuelles-et-freebox-delta-comment-heberger-votre-premiere-page-web/).
|
||||
- Know how to configure [NAT rules on a router and assign DHCP leases](/general/networking/nat)
|
||||
- Know how to configure the [DNS zone of a domain name](/general/networking/dns)
|
||||
::
|
||||
|
||||
<div align="center">
|
||||
<img src="/img/serveex/serveex.svg" align="center" width="700">
|
||||
</div>
|
||||
|
||||
The goal is to be easily deployable and easy to migrate, so here is its structure:
|
||||
|
||||
### The Core of the Server
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-logos-debian" title="Operating System" to="/serveex/core/installation"}
|
||||
Install and configure Debian 13
|
||||
::
|
||||
|
||||
::card{icon="i-logos-docker-icon" title="Container Engine" to="/serveex/core/docker"}
|
||||
Install Docker
|
||||
::
|
||||
|
||||
::card{icon="i-carbon-container-registry" title="Docker GUI" to="/serveex/core/docker#installer-dockge-pour-gérer-et-déployer-les-conteneurs"}
|
||||
Install and deploy Dockge
|
||||
::
|
||||
|
||||
::card{icon="i-noto-globe-showing-americas" title="Reverse Proxy" to="/serveex/core/swag"}
|
||||
Expose your services with SWAG
|
||||
::
|
||||
::
|
||||
|
||||
### Security
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-simple-icons-wireguard" title="VPN" to="/serveex/security/wireguard"}
|
||||
Install and deploy Wireguard
|
||||
::
|
||||
|
||||
::card{icon="i-noto-key" title="SSO & MFA" to="/serveex/security/authentik"}
|
||||
Install and deploy Authentik
|
||||
::
|
||||
|
||||
::card{icon="i-logos-cloudflare-icon" title="Zero Trust" to="/serveex/security/cloudflare"}
|
||||
Install and deploy Cloudflared
|
||||
::
|
||||
::
|
||||
|
||||
### Monitoring
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-solar-pulse-linear" title="Service Status" to="/serveex/monitoring/uptime-kuma"}
|
||||
Install and deploy Uptime-Kuma
|
||||
::
|
||||
|
||||
::card{icon="i-lucide-logs" title="Log Management" to="/serveex/monitoring/dozzle"}
|
||||
Install and deploy Dozzle
|
||||
::
|
||||
|
||||
::card{icon="i-noto-rabbit" title="Connection Management" to="/serveex/monitoring/speedtest-tracker"}
|
||||
Install and deploy Speedtest Tracker
|
||||
::
|
||||
|
||||
::card{icon="i-lucide-chart-column-decreasing" title="Resource Status" to="/serveex/monitoring/beszel"}
|
||||
Install and deploy Beszel
|
||||
::
|
||||
|
||||
::card{icon="i-lucide-circle-power" title="Wake on Lan" to="/serveex/monitoring/upsnap"}
|
||||
Install and deploy UpSnap
|
||||
::
|
||||
::
|
||||
|
||||
### Media
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-cbi-plex-alt" title="Media" to="/serveex/media/plex"}
|
||||
Install and deploy Plex
|
||||
::
|
||||
|
||||
::card{icon="i-cbi-qbittorrent" title="Seedbox" to="/serveex/media/qbittorrent"}
|
||||
Install and deploy Qbittorrent
|
||||
::
|
||||
::
|
||||
|
||||
### Cloud Drive & Photos
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-cib-nextcloud" title="Drive" to="/serveex/cloud/nextcloud"}
|
||||
Install and deploy Nextcloud
|
||||
::
|
||||
|
||||
::card{icon="i-simple-icons-immich" title="Photos" to="/serveex/cloud/immich"}
|
||||
Install and deploy Immich
|
||||
::
|
||||
::
|
||||
|
||||
### Files & Sharing
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-noto-open-file-folder" title="File Explorer" to="/serveex/files/file-browser"}
|
||||
Install and deploy file-browser
|
||||
::
|
||||
|
||||
::card{icon="i-carbon-share" title="Sharing" to="/serveex/files/pingvin"}
|
||||
Install and deploy Pingvin
|
||||
::
|
||||
::
|
||||
|
||||
### Development Tools
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-logos-visual-studio-code" title="Visual Studio Code" to="/serveex/development/code-server"}
|
||||
Install and deploy code-server
|
||||
::
|
||||
|
||||
::card{icon="i-simple-icons-gitea" title="Git Repository" to="/serveex/development/gitea"}
|
||||
Install and deploy Gitea
|
||||
::
|
||||
|
||||
::card{icon="i-noto-hammer-and-wrench" title="Tools" to="/serveex/development/it-tools"}
|
||||
Install and deploy IT Tools
|
||||
::
|
||||
::
|
||||
|
||||
### Useful Applications
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-cbi-adguard" title="Ad-blocking DNS and Filters" to="/serveex/apps/adguard"}
|
||||
Install and deploy Adguard Home
|
||||
::
|
||||
|
||||
::card{icon="i-cbi-bitwarden" title="Password Manager" to="/serveex/apps/vaultwarden"}
|
||||
Install and deploy Vaultwarden
|
||||
::
|
||||
::
|
||||
|
||||
## Coming Soon
|
||||
---
|
||||
|
||||
- Homepage, to have all your services at a glance and access them easily
|
||||
- Mkdocs for your documentation
|
||||
- Docus, an alternative to Mkdocs
|
||||
- UpSnap to remotely wake your machines
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Server core
|
||||
icon: i-lucide-server-cog
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: Debian 13
|
||||
description: Step-by-step guide to install Debian 13 on a home server and set up SSH access, essential packages, and a ready-to-use base system.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Debian 13
|
||||
::note
|
||||
🎯 __Goal:__ Install Debian 13 and the main dependencies to have a ready-to-use OS, accessible via SSH.
|
||||
::
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
|
||||
1. [BIOS Setup](https://www.debian.org/releases/stable/i386/ch03s06.en.html#bios-setup)
|
||||
2. [Download Debian Image](https://www.debian.org/download.en.html)
|
||||
3. [Create Bootable USB (Rufus)](https://dev.to/devops2808/how-to-create-bootable-usb-installer-for-debian-12-4f66)
|
||||
4. [Install Debian and Set Up SSH](https://www.howtoforge.com/tutorial/debian-minimal-server/)
|
||||
5. Install sudo and add a user to the sudo group for administrative privileges.
|
||||
Log in as root:
|
||||
```sh
|
||||
su -
|
||||
```
|
||||
Enter your password, then type:
|
||||
```sh
|
||||
apt install sudo
|
||||
```
|
||||
Add the user to the sudo group:
|
||||
```sh
|
||||
adduser <username> sudo
|
||||
```
|
||||
|
||||
Next time the user logs in, they will be able to use the `sudo` command to execute commands with administrative privileges.
|
||||
|
||||
6. [Everything About Remote Console Access (SSH)](https://www.digitalocean.com/community/tutorials/ssh-essentials-working-with-ssh-servers-clients-and-keys)
|
||||
7. Optional - [UPS Client in Case of Power Outage](https://www.sindastra.de/p/2078/how-to-connect-linux-server-to-synology-ups-server) / [also here](https://www.reddit.com/r/synology/comments/gtkjam/use_synology_nas_as_ups_server_to_safely_power/)
|
||||
8. Optional - Wake up after power outage → configure BIOS S0 state
|
||||
9. Optional - [Wake Server Remotely (WoW - WoL)](https://dev.to/zakery1369/enable-wake-on-lan-on-debian-4ljd)
|
||||
|
||||
## Must-Have CLI Apps
|
||||
---
|
||||
Some essential apps you’ll likely need at some point, so might as well install them early:
|
||||
```sh
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
sudo apt install vim btop ranger git duf neofetch samba cifs-utils tree unzip
|
||||
```
|
||||
|
||||
Additionally:
|
||||
|
||||
- [gping](https://www.linode.com/docs/guides/how-to-use-gping-on-linux/) - Graphical ping tool
|
||||
- [lazydocker](https://github.com/jesseduffield/lazydocker) - CLI Docker container manager
|
||||
|
||||
## Useful Features
|
||||
---
|
||||
### Firewall
|
||||
|
||||
- [ufw](https://www.zenarmor.com/docs/network-security-tutorials/how-to-set-up-a-firewall-with-ufw-on-debian)
|
||||
- [Firewalld](https://linuxcapable.com/how-to-install-firewalld-on-debian-linux/)
|
||||
|
||||
### Samba Sharing (Access a Remote Network Disk)
|
||||
|
||||
- [Create and Access a Samba Share](/general/networking/samba)
|
||||
|
||||
### File Transfer via rsync
|
||||
|
||||
```sh
|
||||
sudo rsync -avhHSP /source /destination
|
||||
```
|
||||
::note
|
||||
|
||||
Add ` --exclude @eaDir`{lang=shell} if the source is a Synology NAS
|
||||
::
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
title: Docker
|
||||
description: Install Docker and Dockge on Debian to deploy and manage self-hosted services with simple container stacks.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Docker
|
||||
|
||||
Docker, to install deployable services in seconds and manage them with just a few commands or clicks.
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Install [Docker](https://www.docker.com/)
|
||||
- Install [Dockge](https://github.com/louislam/dockge) to manage stacks
|
||||
- Install [Watchtower](https://github.com/containrrr/watchtower) to update containers
|
||||
::
|
||||
|
||||

|
||||
|
||||
## Install Docker
|
||||
---
|
||||
Add the Docker repositories and GPG key:
|
||||
|
||||
```sh
|
||||
# Add Docker's official GPG key:
|
||||
sudo apt-get update
|
||||
sudo apt-get install ca-certificates curl
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||||
|
||||
# Add the repository to Apt sources:
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
```
|
||||
|
||||
Install the packages:
|
||||
|
||||
```sh
|
||||
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
```
|
||||
|
||||
That's it!
|
||||
|
||||
**More options:** [Install Docker for Debian 13](https://docs.docker.com/engine/install/debian/)
|
||||
|
||||
::note
|
||||
|
||||
From here on, we assume the stacks are installed in the `/docker` folder, created using the command:
|
||||
```sh
|
||||
sudo mkdir /docker
|
||||
```
|
||||
::
|
||||
|
||||
## Install [Dockge](https://github.com/louislam/dockge) to manage and deploy containers
|
||||
---
|
||||
[Dockge](https://github.com/louislam/dockge) is a web tool to create, configure, launch, and manage Docker containers. It's a simple, intuitive interface that’s lighter and easier for beginners than using the CLI or Portainer.
|
||||
|
||||

|
||||
|
||||
### Configuration
|
||||
|
||||
File structure we will create:
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── dockge
|
||||
└── compose.yml
|
||||
```
|
||||
|
||||
Create the stack folder:
|
||||
|
||||
```sh
|
||||
cd /docker
|
||||
sudo mkdir dockge
|
||||
```
|
||||
|
||||
Then create the `compose.yml` file in this folder using `vim`:
|
||||
|
||||
```sh
|
||||
cd /docker/dockge
|
||||
sudo vi compose.yml
|
||||
```
|
||||
Press `i` to enter insert mode and paste the following:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
dockge:
|
||||
image: louislam/dockge:1
|
||||
restart: unless-stopped
|
||||
container_name: dockge
|
||||
ports:
|
||||
|
||||
- 3555:5001 # LAN-accessible port will be 3555
|
||||
|
||||
volumes:
|
||||
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /docker/dockge/data:/app/data
|
||||
- /docker:/docker
|
||||
environment:
|
||||
|
||||
- DOCKGE_STACKS_DIR=/docker
|
||||
```
|
||||
|
||||
Press `Esc` and type `:x` to save and exit.
|
||||
|
||||
To launch the container:
|
||||
|
||||
```sh
|
||||
cd /docker/dockge
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
Then go to `http://yourserverip:3555` in your browser to access the login page.
|
||||
|
||||
More info on [Dockge and how to use it](https://github.com/louislam/dockge)
|
||||
|
||||
And there you go — Docker and a tool to easily manage your containers are ready!
|
||||
|
||||
## [Watchtower](https://watchtower.nickfedor.com/), to auto-update containers
|
||||
---
|
||||
Watchtower is a container that checks for updates and pulls new images automatically, just by adding a label in your containers’ `compose.yml` files.
|
||||
|
||||
### Configuration
|
||||
|
||||
- Open Dockge in your browser
|
||||
- Click `compose`
|
||||
- Name the stack `watchtower`
|
||||
- Paste the config below into the default config area in Dockge
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
watchtower:
|
||||
container_name: watchtower
|
||||
image: ghcr.io/nicholas-fedor/watchtower:latest
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
environment:
|
||||
|
||||
- TZ=Europe/Paris
|
||||
- WATCHTOWER_SCHEDULE=${SCHEDULE}
|
||||
- WATCHTOWER_LABEL_ENABLE=true
|
||||
- WATCHTOWER_CLEANUP=true
|
||||
- WATCHTOWER_REMOVE_VOLUMES=true
|
||||
# Discord notifications - uncomment if used
|
||||
#- WATCHTOWER_NOTIFICATIONS=slack
|
||||
#- WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=Watchtower
|
||||
#- WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL=${WH_URL}
|
||||
volumes:
|
||||
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
Then fill in the `.env` section in Dockge with the following:
|
||||
|
||||
```properties
|
||||
SCHEDULE=
|
||||
WH_URL=
|
||||
```
|
||||
|
||||
| Property | Value | Examples |
|
||||
|----------------|--------------------------------------------------------------------|----------------------------------------------|
|
||||
| `SCHEDULE` | Cron format | `0 0 6 * * *` (every day at 6 AM) |
|
||||
| `WH_URL` | Your Discord webhook URL - append `/slack` at the end | `https://yourdiscordserver/webhook/slack` |
|
||||
|
||||
To have Watchtower monitor your other containers, add this to their `compose.yml`:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
|
||||
Then restart the modified stacks. And that's it — you now have a solid base to start deploying the services you want!
|
||||
@@ -0,0 +1,381 @@
|
||||
---
|
||||
title: SWAG
|
||||
description: Set up SWAG as a reverse proxy with automatic SSL, expose your services securely, and configure geo-blocking on your homelab.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# SWAG
|
||||
|
||||
::note
|
||||
🎯 __Objectives:__
|
||||
|
||||
- Install Swag
|
||||
- Enable SSL
|
||||
- Access the dashboard
|
||||
- Configure regional blocking
|
||||
- Expose Dockge
|
||||
::
|
||||
|
||||
[Swag](https://docs.linuxserver.io/general/swag/) is the core of this homelab. It’s a powerful reverse proxy that allows you to expose services on the internet using domain names, handling SSL certificate issuance (for encrypted connections), request routing, and access security (via HTTP auth or SSO like Authelia or Authentik). All the necessary documentation is [available here](https://docs.linuxserver.io/general/swag).
|
||||
|
||||
::warning
|
||||
|
||||
SWAG is only useful for exposing your services to the internet—i.e., accessing them via a public URL like `https://service.mydomain.com`. If you don’t want to expose your services and prefer to always use a VPN to connect remotely, you can go [here instead](/serveex/security/wireguard).
|
||||
::
|
||||
|
||||
Below is an example exposing Dockge. We will install SWAG along with the dbip mod for geolocation-based blocking, and the dashboard mod for managing swag, fail2ban, and geolocation.
|
||||
|
||||
**Reverse proxy principle and its application in our case:**
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
|
||||
::note
|
||||
|
||||
This tutorial assumes you have a domain name pointing to your server, and that your router has a NAT rule forwarding port `443` to your server's IP and port `443`. The example domain will be `mydomain.com`.
|
||||
::
|
||||
|
||||
File structure to be modified:
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── swag
|
||||
├── config
|
||||
│ ├── dns-conf
|
||||
│ │ └── ovh.ini
|
||||
│ └── nginx
|
||||
│ ├── dbip.conf
|
||||
│ ├── nginx.conf
|
||||
│ └── proxy-confs
|
||||
│ └── dockge.subdomain.conf
|
||||
├── compose.yml
|
||||
└── .env
|
||||
```
|
||||
|
||||
Open Dockge in your browser, click on `compose`, name the stack `swag`, and copy the following config:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
swag:
|
||||
image: lscr.io/linuxserver/swag:latest
|
||||
container_name: swag
|
||||
cap_add:
|
||||
|
||||
- NET_ADMIN
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
environment:
|
||||
|
||||
- TZ=Europe/Paris
|
||||
- URL=${DOMAIN}
|
||||
- EXTRA_DOMAINS=${DOMAINS}
|
||||
- SUBDOMAINS=wildcard
|
||||
- VALIDATION=dns
|
||||
- DNSPLUGIN=${PLUGIN}
|
||||
- EMAIL=${EMAIL}
|
||||
- DOCKER_MODS=linuxserver/mods:swag-dbip|linuxserver/mods:swag-dashboard|linuxserver/mods:swag-auto-reload
|
||||
volumes:
|
||||
|
||||
- /docker/swag/config:/config
|
||||
ports:
|
||||
|
||||
- 80:80
|
||||
- 443:443
|
||||
- 81:81
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
|
||||
- swag
|
||||
|
||||
networks:
|
||||
swag:
|
||||
name: swag_default
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__
|
||||
Add the watchtower label to each container to enable automatic updates
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Then in the `.env` file:
|
||||
|
||||
```properties
|
||||
DOMAIN=
|
||||
DOMAINS=
|
||||
EMAIL=
|
||||
PLUGIN=
|
||||
```
|
||||
|
||||
Fill out the variables as follows:
|
||||
|
||||
| Property | Value | Examples |
|
||||
|-------------------------|---------------------------------------------------------------------------|-----------------------|
|
||||
| `DOMAIN` | Your domain (covers all subdomains too) | `mydomain.com` |
|
||||
| `DOMAINS` | Any additional domains | `myseconddomain.com` |
|
||||
| `EMAIL` | Your email for generating the certificate | `[email protected]` |
|
||||
| `PLUGIN` | Plugin for certificate generation—depends on your [DNS provider](https://docs.linuxserver.io/general/swag/) | `ovh`<br>`cloudflare` |
|
||||
|
||||
Assuming your DNS zone is managed by OVH, deploy the stack once. The logs will show a failure in creating the SSL certificate due to a missing `ovh.ini` configuration. Stop the stack.
|
||||
|
||||
In CLI, go to the dns-conf folder and edit the `ovh.ini` file:
|
||||
|
||||
::tip
|
||||
✨ __Tip for terminal-shy users:__
|
||||
You can use [File Browser](/serveex/files/file-browser) to browse and edit files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/dns-conf/ovh.ini
|
||||
```
|
||||
|
||||
You should see:
|
||||
|
||||
```properties
|
||||
# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-ovh/certbot_dns_ovh/__init__.py#L20
|
||||
# Replace with your values
|
||||
dns_ovh_endpoint = ovh-eu
|
||||
dns_ovh_application_key =
|
||||
dns_ovh_application_secret =
|
||||
dns_ovh_consumer_key =
|
||||
```
|
||||
|
||||
Authenticate and create [your token here](https://www.ovh.com/auth/?onsuccess=https%3A%2F%2Fwww.ovh.com%2Fauth%2Fapi%2FcreateToken).
|
||||
|
||||
Set the following permissions:
|
||||
|
||||
* `GET /domain/zone/*`
|
||||
* `PUT /domain/zone/*`
|
||||
* `POST /domain/zone/*`
|
||||
* `DELETE /domain/zone/*`
|
||||
|
||||
Note the 3 keys temporarily and enter them in `ovh.ini`. (In vim, press `i` to edit, `Esc` when done, `:x` to save and exit)
|
||||
|
||||
Save and exit the file.
|
||||
|
||||
Now configure swag to access DBIP, the geolocation-based access control module. Open the `nginx.conf` file:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/nginx.conf
|
||||
```
|
||||
|
||||
Add the following line below the `http` section:
|
||||
|
||||
```nginx
|
||||
include /config/nginx/dbip.conf;
|
||||
```
|
||||
|
||||
Restart the stack in Dockge. This time, the SSL certificate should be successfully generated! Check the logs to confirm the server is ready.
|
||||
|
||||
## Dashboard
|
||||
---
|
||||
Access the dashboard locally by going to `http://yourserverip:81`
|
||||
On the left, you'll see a list of currently "proxied" services (none yet). On the right, the list of banned IPs. Below, various indicators. For more details, [click here](https://www.linuxserver.io/blog/introducing-swag-dashboard).
|
||||
|
||||

|
||||
|
||||
## DBIP
|
||||
---
|
||||
DBIP allows you to block connections based on countries. It relies on the configuration file named `dbip.conf` located in `/docker/swag/config/nginx`. [More info here](https://virtualize.link/secure/).
|
||||
|
||||
In this example, we’ll configure it to block a list of countries known to be the source of most malicious traffic. We’ll also configure a variable to allow internal server traffic, your box’s local network, and a potential VPN in the 10.x.x.x range to access your services — but not the open internet.
|
||||
|
||||
This configuration can be enabled or disabled per service (see the Dockge example below).
|
||||
|
||||
Open `dbip.conf`:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/dbip.conf
|
||||
```
|
||||
|
||||
Make your changes ([see documentation](https://github.com/linuxserver/docker-mods/tree/swag-dbip)), or use the following example:
|
||||
|
||||
```nginx
|
||||
geoip2 /config/geoip2db/dbip-country-lite.mmdb {
|
||||
auto_reload 1w;
|
||||
$geoip2_data_continent_code continent code;
|
||||
$geoip2_data_country_iso_code country iso_code;
|
||||
}
|
||||
|
||||
# Country Codes: https://en.wikipedia.org/wiki/ISO_3166-2
|
||||
|
||||
map $geoip2_data_country_iso_code $geo-whitelist {
|
||||
default no;
|
||||
FR yes;
|
||||
}
|
||||
|
||||
map $geoip2_data_country_iso_code $geo-blacklist {
|
||||
default yes;
|
||||
CN no; #China
|
||||
RU no; #Russia
|
||||
HK no; #Hong Kong
|
||||
IN no; #India
|
||||
IR no; #Iran
|
||||
VN no; #Vietnam
|
||||
TR no; #Turkey
|
||||
EG no; #Egypt
|
||||
MX no; #Mexico
|
||||
JP no; #Japan
|
||||
KR no; #South Korea
|
||||
KP no; #North Korea
|
||||
PE no; #Peru
|
||||
BR no; #Brazil
|
||||
UA no; #Ukraine
|
||||
ID no; #Indonesia
|
||||
TH no; #Thailand
|
||||
}
|
||||
|
||||
geo $lan-ip {
|
||||
default no;
|
||||
10.0.0.0/8 yes;
|
||||
172.16.0.0/12 yes;
|
||||
192.168.0.0/16 yes;
|
||||
127.0.0.1 yes;
|
||||
}
|
||||
```
|
||||
|
||||
Save and close the file. Restart the stack.
|
||||
|
||||
In the domain config files (see next section), you can enable or disable the whitelist or blacklist ([see documentation here](https://www.forum-nas.fr/threads/tuto-installer-swag-en-docker-reverse-proxy.15057/)). In our case, the whitelist allows only French requests. The blacklist blocks only the listed countries. We'll use the blacklist, like so:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name some-app.*;
|
||||
include /config/nginx/ssl.conf;
|
||||
client_max_body_size 0;
|
||||
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
location / {
|
||||
```
|
||||
|
||||
## Exposing Dockge
|
||||
---
|
||||
::note
|
||||
📋 __Prerequisite:__ <br/><br/>
|
||||
We assume that you have created a subdomain like `dockge.mydomain.com` in your [DNS zone](/general/networking/dns), with a `CNAME` pointing to `mydomain.com` and — unless you're using [Cloudflare Zero Trust](/serveex/security/cloudflare) — that you've forwarded port `443` from your router to the server's `443` in [your NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
Now it's time to expose Dockge on the internet so you can access and manage your containers remotely. We assume you've set up the subdomain `dockge.mydomain.com` with a `CNAME` pointing to `mydomain.com`.
|
||||
|
||||
::warning
|
||||
|
||||
Dockge does not support multi-factor authentication. Exposing it online could compromise all connected machines. Only do this if you're using an MFA solution like [Authentik](/serveex/security/authentik/). Otherwise, don’t expose it with SWAG — use a VPN like [Wireguard](/serveex/security/wireguard) instead.
|
||||
::
|
||||
|
||||
Open the `dockge.subdomain.conf` file:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/dockge.subdomain.conf
|
||||
```
|
||||
|
||||
Configure it like this:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name dockge.*; # define the subdomain to redirect
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; } # all countries un blacklist are forbidden
|
||||
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
|
||||
set $upstream_app dockge; # container name
|
||||
set $upstream_port 5001; # internal container port (not exposed port)
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Save and exit. The configuration will update within a few seconds.
|
||||
|
||||
::note
|
||||
|
||||
By default, SWAG doesn’t recognize the name "dockge". You’ll need to add Dockge’s network to SWAG’s `compose.yml`.
|
||||
::
|
||||
|
||||
Go to the SWAG stack, click `edit`, and modify the config file like this (note the `networks` section):
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: #...
|
||||
# ...
|
||||
networks: # Link the container to the custom network
|
||||
|
||||
- dockge # Network name as defined in the stack
|
||||
|
||||
networks: # Define the custom network
|
||||
# ...
|
||||
dockge: # Network name as defined in the stack
|
||||
name: dockge_default # True external network name
|
||||
external: true
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
We assume the Dockge network is named `dockge_default`. You can verify the setup works by checking the SWAG dashboard at `http://yourserverip:81`.
|
||||
::
|
||||
|
||||
Redeploy the SWAG stack.
|
||||
|
||||
Wait a moment, then visit `https://dockge.mydomain.com` in your browser — you should be redirected to Dockge. You can also check the service status from the dashboard (`http://yourserverip:81` on your local network).
|
||||
|
||||
## Exposing Another Service with SWAG
|
||||
---
|
||||
SWAG includes templates for most known services, named `servicename.subdomain.conf.sample`. Just create the subdomain in your registrar's DNS zone (like OVH), point it to your main domain via a CNAME, then copy and rename the sample file:
|
||||
|
||||
```sh
|
||||
cd /docker/swag/config/proxy-confs
|
||||
sudo cp servicename.subdomain.conf.sample servicename.subdomain.conf
|
||||
```
|
||||
|
||||
::caution
|
||||
|
||||
- __If the subdomain is not redirected properly__
|
||||
- Open the file and verify the container name in `set $upstream_app containername;`{lang=nginx}
|
||||
- Make sure you added the container's network in SWAG’s `compose.yml`
|
||||
::
|
||||
|
||||
You can also customize the subdomain by editing `server_name yoursubdomain.*;`{lang=nginx} and renaming the file to `yoursubdomain.subdomain.conf`.
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Security
|
||||
icon: i-lucide-shield
|
||||
@@ -0,0 +1,260 @@
|
||||
---
|
||||
title: Wireguard
|
||||
description: Install and configure WireGuard VPN to securely access your homelab from anywhere and connect all your devices to your private network.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Wireguard
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Install Wireguard
|
||||
- Configure clients
|
||||
- Access the secure network
|
||||
::
|
||||
|
||||
## Introduction
|
||||
---
|
||||
Using a VPN allows remote access to a server’s local resources without exposing them to the internet. It’s a clean and secure way to access services like SSH without exposing the port publicly. With a VPN, you can securely connect to your network from anywhere and make devices on different networks communicate.
|
||||
|
||||
Here we will use [Wireguard](https://www.wireguard.com/), a secure and high-performance VPN server, using containers:
|
||||
|
||||
- [wg-easy](https://github.com/wg-easy/wg-easy) as the server, providing a very simple web UI to manage connections and download config files (including QR codes for phones)
|
||||
- [Wireguard](https://docs.linuxserver.io/images/docker-wireguard/?h=wireguard) as the client for Linux systems
|
||||
|
||||
Clients are also available for Windows, macOS, iOS, and Android.
|
||||
|
||||
The concept:
|
||||
|
||||
- On the internet, anyone can reach any internet box and thus any exposed server.
|
||||
- Your server is on your local network. It is accessible only locally unless services are explicitly exposed (as we did with Dockge). To access non-exposed resources, you must be on the same local network.
|
||||
- We want to securely access these unexposed services (like SSH) from anywhere.
|
||||
- We also want to connect services between servers, like linking two Dockge instances securely.
|
||||
|
||||
To achieve this, we’ll create a **Virtual Private Network** (VPN), i.e., a secure tunnel that only connected machines can use. They’ll appear to be on the same private network.
|
||||
|
||||
Additionally, you can add your phone, laptop, or other devices to the VPN and securely access your server resources wherever you are.
|
||||
|
||||

|
||||
|
||||
In this diagram, machine 1 is part of two networks:
|
||||
|
||||
- Its local network (devices behind the same router, e.g. `192.168.x.x` – machines 1 and 2)
|
||||
- The VPN network (VPN devices with a second IP, e.g. `10.8.x.x` – machines 1 and 4)
|
||||
|
||||
You *can* allow VPN clients to share access to their local networks, but we won’t do that here for security and subnet conflict reasons (e.g., if two remote machines use the same local IP like `192.168.1.1`).
|
||||
|
||||
So only VPN-connected devices can communicate with each other on the VPN, not with other local devices outside the VPN.
|
||||
|
||||
## Server Setup
|
||||
---
|
||||
::note
|
||||
📋 **Pre-flight Checklist:**
|
||||
|
||||
- Ensure port `51820 UDP` is free on your server and correctly forwarded from your router (`51820 UDP -> Server`).
|
||||
- Ensure port `51821 TCP` is free for the web UI.
|
||||
::
|
||||
|
||||
::warning
|
||||
|
||||
__Warning__: If your IP is not static, use a Dynamic DNS service ([DynDNS](https://en.wikipedia.org/wiki/Dynamic_DNS)). If your ISP uses [CGNAT](https://en.wikipedia.org/wiki/Carrier-grade_NAT), you’ll need to use an external VPS and connect your local server as a client.
|
||||
::
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── wg-easy
|
||||
├── config
|
||||
│ └── etc_wireguard
|
||||
├── compose.yaml
|
||||
└── .env
|
||||
```
|
||||
|
||||
Open Dockge, click **Compose**, and name the stack `wg_easy`.
|
||||
|
||||
Copy the following configuration:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
wg-easy:
|
||||
environment:
|
||||
|
||||
- INSECURE=true
|
||||
image: ghcr.io/wg-easy/wg-easy:15
|
||||
container_name: wg-easy
|
||||
networks:
|
||||
wg:
|
||||
ipv4_address: 10.42.42.42
|
||||
ipv6_address: fdcc:ad94:bacf:61a3::2a
|
||||
volumes:
|
||||
|
||||
- ./etc_wireguard:/etc/wireguard
|
||||
- /lib/modules:/lib/modules:ro
|
||||
ports:
|
||||
|
||||
- "51820:51820/udp"
|
||||
- "51821:51821/tcp"
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
sysctls:
|
||||
|
||||
- net.ipv4.ip_forward=1
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
- net.ipv6.conf.all.disable_ipv6=0
|
||||
- net.ipv6.conf.all.forwarding=1
|
||||
- net.ipv6.conf.default.forwarding=1
|
||||
|
||||
networks:
|
||||
wg:
|
||||
driver: bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
|
||||
- subnet: 10.42.42.0/24
|
||||
- subnet: fdcc:ad94:bacf:61a3::/64
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ **Tip:**
|
||||
|
||||
- You can customize WireGuard and web UI ports.
|
||||
- Add a Watchtower label for automatic updates:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wg-easy:
|
||||
# ...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Deploy the stack and access the local web UI at `http://server-ip:51821`.
|
||||
|
||||
::caution
|
||||
|
||||
If the deployment fails, check your firewall rules.
|
||||
::
|
||||
|
||||
Once connected, follow the web UI instructions to:
|
||||
|
||||
- Create your admin account and password.
|
||||
- Set the host field (use your public IP or domain name).
|
||||
|
||||
Then go to *Administrator → Admin Panel → Config*:
|
||||
|
||||
- Change `Allowed IPs` from `0.0.0.0/24` to `10.8.0.0/24` for **split tunneling**.
|
||||
- Remove IPv6 (it often causes unnecessary issues).
|
||||
|
||||
### Retrieve Configuration Files
|
||||
|
||||
To configure clients:
|
||||
|
||||
1. Access the web UI: `http://server-ip:51821`
|
||||
2. Create a new client
|
||||
3. Edit the client and add `10.8.0.0/24` to `Server Allowed IPs`
|
||||
4. (Optional) Set `Persistent Keep Alive` to `25` if it’s a permanently connected client
|
||||
5. Save, download, and rename the file to `wg0.conf` (or `wg1.conf`, etc.)
|
||||
|
||||
## Client Server Setup
|
||||
---
|
||||
::note
|
||||
|
||||
We assume the client server runs Linux with Docker installed.
|
||||
::
|
||||
|
||||
### Folder Structure
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── wireguard
|
||||
└── config
|
||||
│ └── wg_confs
|
||||
└── compose.yaml
|
||||
```
|
||||
|
||||
Create the folder:
|
||||
|
||||
```sh
|
||||
sudo mkdir -p /docker/wireguard/config/wg_confs
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ **Tip:** You can use [File Browser](/serveex/files/file-browser) instead of the terminal to edit and upload files.
|
||||
::
|
||||
|
||||
Create the `wg0.conf` file:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/wireguard/config/wg_confs/wg0.conf
|
||||
```
|
||||
|
||||
Enter insert mode (`i`), paste the downloaded configuration, then save (`Esc` → `:x`).
|
||||
|
||||
::tip
|
||||
✨ **Alternative method:** Transfer the file via SFTP and move it:
|
||||
```sh
|
||||
sudo cp ~/wg0.conf /docker/wireguard/config/wg_confs
|
||||
```
|
||||
::
|
||||
|
||||
Create the `compose.yaml` file in `/docker/wireguard`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wireguard:
|
||||
image: lscr.io/linuxserver/wireguard:latest
|
||||
container_name: wireguard
|
||||
network_mode: host
|
||||
cap_add:
|
||||
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
environment:
|
||||
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
|
||||
- /docker/wireguard/config:/config
|
||||
- /lib/modules:/lib/modules
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
Start the container:
|
||||
```sh
|
||||
cd /docker/wireguard
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
Repeat this setup for each client.
|
||||
::
|
||||
|
||||
## Other Devices
|
||||
---
|
||||
|
||||
- **Mobile:** Install WireGuard and scan the QR code via the web UI (`http://server-ip:51821`)
|
||||
- **Desktop:** Install the WireGuard client and import the downloaded config file.
|
||||
|
||||
::warning
|
||||
|
||||
**Note:** If the client machine is on the same local network as the server, edit the `wg0.conf` file to use the local server IP:
|
||||
`Endpoint = server-local-ip:51820`
|
||||
::
|
||||
|
||||
And here’s the final setup overview:
|
||||
|
||||

|
||||
@@ -0,0 +1,588 @@
|
||||
---
|
||||
title: Authentik
|
||||
description: Install Authentik as a self-hosted identity provider — configure MFA and protect your services with SSO and reverse proxy authentication.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Authentik
|
||||
|
||||
::note
|
||||
🎯 __Objectives:__
|
||||
|
||||
- Install and expose Authentik
|
||||
- Configure Multi-Factor Authentication (MFA)
|
||||
- Protect a native app or an app behind a reverse proxy
|
||||
::
|
||||
|
||||
[Authentik](https://goauthentik.io) is a single sign-on (SSO) tool that allows you to log in once to all platforms compatible with OpenID. It can also secure access to your exposed services by injecting itself via SWAG into requests to those services.
|
||||
|
||||
For example, if you're exposing Dockge online at `dockge.mydomain.com`, you’ll first land on an Authentik login page when accessing it. If you've already authenticated with another Authentik-protected service, you won’t need to log in again. This allows you to authenticate only once per day for all protected services.
|
||||
|
||||
Authentik also supports multi-factor authentication, including TOTP (a code generated by the authentication app of your choice). Additionally, it allows login through Microsoft or Google accounts, provided you've configured one of those applications.
|
||||
|
||||
It's a great alternative to VPNs for securely exposing services, especially ones that lack MFA or login protection (e.g., the SWAG dashboard).
|
||||
|
||||
Authentik has [extensive documentation](https://docs.goauthentik.io/docs/installation/docker-compose) and [great tutorials from Cooptonian](https://www.youtube.com/@cooptonian). Here, we’ll cover the basics using Dockge as an example.
|
||||
|
||||
There are two main modes you should know:
|
||||
|
||||
- The first allows apps with native support for OpenID-compatible SSO to connect directly to Authentik. This is the preferred method, as the app itself decides what’s public and what’s protected.
|
||||
|
||||

|
||||
|
||||
- The second method injects Authentik authentication through SWAG before reaching the target service.
|
||||
|
||||

|
||||
|
||||
Both modes can be configured on a per-application basis.
|
||||
|
||||
## Installation
|
||||
---
|
||||
Folder structure:
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── authentik
|
||||
├── .env
|
||||
├── compose.yml
|
||||
├── media
|
||||
├── certs
|
||||
├── custom-template
|
||||
└── ssh
|
||||
```
|
||||
|
||||
Create the folders:
|
||||
|
||||
```sh
|
||||
sudo mkdir -p /docker/authentik/media /docker/authentik/certs /docker/authentik/custom-template /docker/authentik/ssh
|
||||
```
|
||||
|
||||
Navigate to the `authentik` folder via `cd /docker/authentik` and generate a password and secret key to include in the `.env` file:
|
||||
|
||||
```sh
|
||||
sudo echo "PG_PASS=$(openssl rand 36 | base64)" >> .env
|
||||
sudo echo "AUTHENTIK_SECRET_KEY=$(openssl rand 60 | base64)" >> .env
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
To generate the keys, we created the folders ahead of deployment using Dockge. Dockge will prevent you from creating a stack with the same name in these folders unless a `compose.yml` file exists. So, create an empty `compose.yml` so it appears as an inactive stack:
|
||||
```sh
|
||||
sudo vi /docker/authentik/compose.yml
|
||||
```
|
||||
::
|
||||
|
||||
Open Dockge and search for "authentik" in the inactive stacks.
|
||||
Name the stack `authentik` and paste the following configuration, replacing `{AUTHENTIK_TAG:-2026.2}`{lang=properties} with [the latest version of Authentik](https://goauthentik.io/docs/releases).
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
|
||||
postgresql:
|
||||
image: docker.io/library/postgres:16-alpine
|
||||
container_name: authentik-postgresql
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test:
|
||||
|
||||
- CMD-SHELL
|
||||
- pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}
|
||||
start_period: 20s
|
||||
interval: 30s
|
||||
retries: 5
|
||||
timeout: 5s
|
||||
volumes:
|
||||
|
||||
- database:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${PG_PASS:?database password required}
|
||||
POSTGRES_USER: ${PG_USER:-authentik}
|
||||
POSTGRES_DB: ${PG_DB:-authentik}
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
|
||||
redis:
|
||||
image: docker.io/library/redis:alpine
|
||||
container_name: authentik-redis
|
||||
command: --save 60 1 --loglevel warning
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test:
|
||||
|
||||
- CMD-SHELL
|
||||
- redis-cli ping | grep PONG
|
||||
start_period: 20s
|
||||
interval: 30s
|
||||
retries: 5
|
||||
timeout: 3s
|
||||
volumes:
|
||||
|
||||
- redis:/data
|
||||
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2}
|
||||
container_name: authentik-server
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
AUTHENTIK_REDIS__HOST: redis
|
||||
AUTHENTIK_POSTGRESQL__HOST: postgresql
|
||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||
volumes:
|
||||
|
||||
- ./media:/media
|
||||
- ./custom-templates:/templates
|
||||
- ./ssh:/authentik/.ssh
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
ports:
|
||||
|
||||
- ${COMPOSE_PORT_HTTP:-9000}:9000
|
||||
- ${COMPOSE_PORT_HTTPS:-9443}:9443
|
||||
depends_on:
|
||||
|
||||
- postgresql
|
||||
- redis
|
||||
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2}
|
||||
container_name: authentik-worker
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
AUTHENTIK_REDIS__HOST: redis
|
||||
AUTHENTIK_POSTGRESQL__HOST: postgresql
|
||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||
# `user: root` and the docker socket volume are optional.
|
||||
# See more for the docker socket integration here:
|
||||
# https://goauthentik.io/docs/outposts/integrations/docker
|
||||
# Removing `user: root` also prevents the worker from fixing the permissions
|
||||
# on the mounted folders, so when removing this make sure the folders have the correct UID/GID
|
||||
# (1000:1000 by default)
|
||||
user: root
|
||||
volumes:
|
||||
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./media:/media
|
||||
- ./certs:/certs
|
||||
- ./custom-templates:/templates
|
||||
- ./ssh:/authentik/.ssh
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
depends_on:
|
||||
|
||||
- postgresql
|
||||
- redis
|
||||
|
||||
volumes:
|
||||
database:
|
||||
driver: local
|
||||
redis:
|
||||
driver: local
|
||||
```
|
||||
|
||||
In the `.env` file, the `PG_PASS` and `AUTHENTIK_SECRET_KEY` variables are already set.
|
||||
Deploy the stack.
|
||||
|
||||
You can then begin the initial setup by visiting:
|
||||
`http://yourserverip:9000/if/flow/initial-setup/`
|
||||
|
||||
::warning
|
||||
|
||||
__Warning:__ It’s recommended to create a new admin account and **disable** the default `akadmin` account.
|
||||
::
|
||||
|
||||
## Exposing Authentik
|
||||
---
|
||||
To use Authentik outside your local network, you must expose it.
|
||||
|
||||
::note
|
||||
📋 __Prerequisites:__ <br/><br/>
|
||||
We assume you have already created a subdomain like `auth.mydomain.com` in your [DNS zone](/general/networking/dns), with a CNAME pointing to `mydomain.com`. Also, unless you're using [Cloudflare Zero Trust](/serveex/security/cloudflare), you must have already forwarded port `443` from your router to port `443` of your server in your [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
Open the `authentik-server.conf` file:
|
||||
|
||||
::tip
|
||||
✨ __Tip for those who dislike terminals:__
|
||||
You can use [File Browser](/serveex/files/file-browser) to navigate and edit files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/authentik-server.conf
|
||||
```
|
||||
|
||||
Verify that the following variables are set correctly:
|
||||
|
||||
```nginx
|
||||
set $upstream_authentik authentik-server;
|
||||
proxy_pass http://$upstream_authentik:9000;
|
||||
```
|
||||
|
||||
If not, press `i` to enter edit mode, make the necessary changes, then save and exit by pressing `Esc` followed by `:x`.
|
||||
|
||||
Create the `auth.subdomain.conf` file:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/auth.subdomain.conf
|
||||
```
|
||||
|
||||
Press `i` to enter edit mode and paste the following configuration:
|
||||
|
||||
```nginx
|
||||
## Version 2023/05/31
|
||||
# Ensure your authentik container is named authentik-server
|
||||
# Ensure your DNS has a CNAME for authentik
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name auth.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
location / {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app authentik-server;
|
||||
set $upstream_port 9000;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
|
||||
location ~ (/authentik)?/api {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app authentik-server;
|
||||
set $upstream_port 9000;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Save and exit by pressing `Esc` then `:x`.
|
||||
|
||||
Go to Dockge, and edit the SWAG compose file to add the Authentik network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Attach the container to the custom network
|
||||
# ...
|
||||
|
||||
- authentik # Name of the network declared in the stack
|
||||
|
||||
networks: # Define the custom network
|
||||
# ...
|
||||
authentik: # Name of the network declared in the stack
|
||||
name: authentik_default # Actual name of the external network
|
||||
external: true # Indicates it's an external network
|
||||
```
|
||||
|
||||
Restart the stack and wait for SWAG to be fully operational.
|
||||
|
||||
Done! You can now access Authentik via `https://auth.mydomain.com`
|
||||
|
||||
## Enable Multifactor Authentication
|
||||
---
|
||||
The main value of Authentik is using multifactor authentication for all protected apps.
|
||||
|
||||
- Go to `https://auth.mydomain.com`
|
||||
- Log in
|
||||
- Go to _Settings_
|
||||
- Click the _MFA_ section
|
||||
- Click _Register_
|
||||
- Choose a method like _TOTP device_ (you'll need an authenticator app like Google Authenticator)
|
||||
- Follow the steps
|
||||
|
||||
You’ll now be prompted to enter a one-time code at every login.
|
||||
|
||||
## Protecting a Native App
|
||||
---
|
||||
Authentik is natively compatible with several applications. You can find the list and [support here](https://docs.goauthentik.io/integrations/services/).
|
||||
|
||||
## Protecting an App via Reverse Proxy
|
||||
---
|
||||
SWAG lets you insert Authentik’s login page between a request and access to your service. To do this:
|
||||
|
||||
- Configure the authentication provider in Authentik.
|
||||
- Edit the domain proxy file so SWAG can intercept the request.
|
||||
|
||||
Why do this when Dockge already has authentication? Because Dockge uses weak HTTP authentication. With Authentik, you get strong MFA authentication and automatic login to all apps protected by Authentik. This secures access to Dockge and other apps without needing a VPN.
|
||||
|
||||
### Configuring Authentik
|
||||
|
||||
- Go to Authentik
|
||||
- Open the admin panel
|
||||
- Select _Applications_ then _Create with wizard_
|
||||
- Fill in the fields as shown:
|
||||
|
||||

|
||||
|
||||
- At the next step, choose "Forward authentication (single application)" and configure it as shown (flows are important):
|
||||
|
||||

|
||||
|
||||
- Next, go to the _Outposts_ menu on the left and edit _authentik Embedded Outpost_:
|
||||
|
||||

|
||||
|
||||
- Add the `dockge` application by moving it to the right column and save.
|
||||
|
||||
### Configuring SWAG
|
||||
|
||||
Edit the file `dockge.mydomain.com`:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/dockge.subdomain.conf
|
||||
```
|
||||
|
||||
Press `i` to enter edit mode and uncomment the two lines `#include /config/nginx/authentik-server.conf;`
|
||||
|
||||
Press `Esc`, type `:x`, and press `Enter` to save and exit.
|
||||
|
||||
Done! Now when accessing `https://dockge.mydomain.com`, you’ll be redirected to the Authentik login screen.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ In Dockge's settings, you can disable Dockge's authentication to avoid double login. **Warning**: this means if the port is open on your local network, there will be no authentication at all.
|
||||
::
|
||||
|
||||
::note
|
||||
|
||||
Repeat this process for each app you want to protect (unless it has native integration with Authentik).
|
||||
::
|
||||
|
||||
Your new architecture looks like this:
|
||||
|
||||

|
||||
|
||||
## Protecting a Remote Server Service
|
||||
---
|
||||
For a [native application](/serveex/security/authentik/#protecting-a-native-app) (via OAuth 2.0 or other), nothing changes.
|
||||
|
||||
For a non-native app behind a reverse proxy, you must deploy an __Outpost__. An Outpost is a container acting as a local proxy — it's the target of your app's auth requests and the only one authorized to communicate with your Authentik API.
|
||||
|
||||
::note
|
||||
Prerequisites:
|
||||
|
||||
- Install [Docker](/serveex/core/docker) on the remote server hosting the service.
|
||||
- If the app has no native integration, use a compatible reverse proxy. We will use [SWAG](/serveex/core/swag) here.
|
||||
::
|
||||
|
||||
This container will forward requests to your main [Authentik](/serveex/security/authentik#authentik) instance over the internet (or your local network). The server will perform checks and respond to the Outpost, which will allow or block access accordingly.
|
||||
|
||||

|
||||
|
||||
### Configuring Authentik
|
||||
|
||||
Create your [providers and applications](/serveex/security/authentik/#protecting-a-native-app) as shown earlier.
|
||||
|
||||
Then, in the admin panel, go to _Applications > Outposts_, and create a new outpost.
|
||||
|
||||
Fill in as follows:
|
||||
|
||||
| Field | Value |
|
||||
|----------------|------------------------------------------------------------------------|
|
||||
| `Name` | Your preferred name |
|
||||
| `Type` | `Proxy` |
|
||||
| `Integration` | Leave empty |
|
||||
| `Applications` | Select the applications you previously created |
|
||||
|
||||
In the `Advanced settings` section, clear the existing content and enter:
|
||||
|
||||
```yaml
|
||||
log_level: info
|
||||
docker_labels: null
|
||||
authentik_host: https://your_authentik_server_domain/
|
||||
object_naming_template: ak-outpost-%(name)s
|
||||
authentik_host_insecure: false
|
||||
container_image:
|
||||
docker_network: null
|
||||
docker_map_ports: true
|
||||
docker_labels: null
|
||||
```
|
||||
|
||||
Save and exit.
|
||||
|
||||
On the list of created outposts, locate the new one and click _Show details_ at the end of the line. Carefully copy the access token.
|
||||
|
||||
### Configuring the Remote Machine
|
||||
|
||||
We assume you’ve already installed [Docker](/serveex/core/docker) and [SWAG](/serveex/core/swag) on this remote machine.
|
||||
|
||||
On your remote machine, use [Dockge](/serveex/core/docker/#installer-dockge-pour-gérer-et-déployer-les-conteneurs) to create a stack named `authentik-outpost`.
|
||||
|
||||
If you haven’t installed [Dockge](/serveex/core/docker/#installer-dockge-pour-gérer-et-déployer-les-conteneurs), create a folder `/docker/authentik-outpost`, or directly via command line:
|
||||
|
||||
```sh
|
||||
sudo mkdir -P /docker/authentik-outpost
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip for terminal-averse users:__
|
||||
You can use [File Browser](/serveex/files/file-browser) to navigate and edit your files instead of using terminal commands.
|
||||
::
|
||||
|
||||
Create the `compose.yaml` file or paste the configuration directly into Dockge if installed.
|
||||
|
||||
Via command line:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/authentik-outpost/compose.yaml
|
||||
```
|
||||
Enter edit mode by pressing `i` and paste the following configuration, updating the version in `{AUTHENTIK_TAG:proxy:2024.2.3}`{lang=properties} to match your Authentik server version.
|
||||
|
||||
```yaml
|
||||
version: "3.5"
|
||||
services:
|
||||
authentik_proxy:
|
||||
container_name: authentik-outpost
|
||||
image: ghcr.io/goauthentik/proxy:2024.2.3
|
||||
# Optionally specify which networks the container should be
|
||||
# might be needed to reach the core authentik server
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
ports:
|
||||
|
||||
- 9000:9000
|
||||
- 9443:9443
|
||||
environment:
|
||||
AUTHENTIK_HOST: ${HOST}
|
||||
AUTHENTIK_INSECURE: "false"
|
||||
AUTHENTIK_TOKEN: ${TOKEN}
|
||||
```
|
||||
|
||||
Go to the SWAG stack on the remote machine (or edit directly using Dockge) and add the authentik-outpost network in the configuration file like this (see `networks` section):
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/compose.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: #...
|
||||
# ...
|
||||
networks: # Attach the container to the custom network
|
||||
|
||||
- authentik-outpost # Network name as declared in the stack
|
||||
|
||||
networks: # Define the custom network
|
||||
#...
|
||||
authentik-outpost: # Name of the network declared in the stack
|
||||
name: authentik-outpost_default # Actual name of the external network
|
||||
external: true # Marks it as an external network
|
||||
```
|
||||
|
||||
Press `Esc`, then type `:x` and press `Enter` to save and exit.
|
||||
|
||||
::note
|
||||
|
||||
We assume the Dockge network name is `authentik-outpost_default`.
|
||||
::
|
||||
|
||||
If using [Dockge](/serveex/core/docker/#installer-dockge-pour-gérer-et-déployer-les-conteneurs), restart SWAG.
|
||||
|
||||
Otherwise, via terminal:
|
||||
|
||||
```sh
|
||||
cd /docker/swag/
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
Create (or fill using Dockge) the `.env` file in the `authentik-outpost` directory:
|
||||
|
||||
Via command line:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/authentik-outpost/.env
|
||||
```
|
||||
|
||||
Enter edit mode with `i` and paste the following configuration:
|
||||
|
||||
```properties
|
||||
HOST=
|
||||
TOKEN=
|
||||
```
|
||||
|
||||
Fill in the values:
|
||||
|
||||
| Variable | Value | Example |
|
||||
|----------|-------|---------|
|
||||
| `HOST`{lang=properties} | The URL of your Authentik server | `https://auth.domain.com` |
|
||||
| `TOKEN`{lang=properties} | The previously copied access token | `Q2pVEqsTNRkJSO9SkJzU3KZ2` |
|
||||
|
||||
Press `Esc`, then type `:x` and press `Enter` to save and exit.
|
||||
|
||||
If using Dockge, deploy the stack.
|
||||
|
||||
Otherwise, via terminal:
|
||||
|
||||
```sh
|
||||
cd /docker/authentik-outpost/
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
The container is now running. You can verify its status from your Authentik instance admin panel under _Applications > Outposts_.
|
||||
|
||||
Now, let’s configure SWAG.
|
||||
|
||||
Open the `authentik-server.conf` file:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/authentik-server.conf
|
||||
```
|
||||
|
||||
In the file, press `i` to enter edit mode and change `authentik-server` to `authentik-outpost` as shown:
|
||||
|
||||
```nginx
|
||||
set $upstream_authentik authentik-outpost;
|
||||
proxy_pass http://$upstream_authentik:9000;
|
||||
```
|
||||
|
||||
Save and exit with `Esc`, then `:x` and `Enter`.
|
||||
|
||||
Then configure the applications to protect as you did on your main server, whether they are [native](/serveex/security/authentik/#protecting-a-native-app) or protected via [reverse proxy](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||
|
||||
## Migrating an Authentik Database
|
||||
---
|
||||
On the source machine, dump the database:
|
||||
|
||||
```sh
|
||||
sudo docker exec authentik-postgres pg_dump -U authentik -F t authentik > /path/to/mydb.tar
|
||||
```
|
||||
|
||||
Then transfer it to the target machine. On the target machine, copy the file into the Docker container:
|
||||
|
||||
```sh
|
||||
cp /path/to/mydb.tar authentik-postgres:/path/to/wherever
|
||||
```
|
||||
|
||||
(Optional) Purge existing tables:
|
||||
|
||||
```sh
|
||||
sudo docker exec -i authentik-postgres psql -U authentik -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'authentik' AND pid <> pg_backend_pid();" && sudo docker exec -i authentik-postgres psql -U authentik -d postgres -c "DROP DATABASE IF EXISTS authentik;" && sudo docker exec -i authentik-postgres psql -U authentik -d postgres -c "CREATE DATABASE authentik;"
|
||||
```
|
||||
|
||||
Restore the database:
|
||||
|
||||
```sh
|
||||
sudo docker exec authentik-postgresql pg_restore -U authentik -d authentik /path/to/wherever/mydb.tar
|
||||
```
|
||||
@@ -0,0 +1,272 @@
|
||||
---
|
||||
title: Cloudflare Zero Trust
|
||||
description: Use Cloudflare Tunnels and Zero Trust to expose homelab services without opening ports — configure SWAG and manage multiple tunnels.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Cloudflare Zero Trust
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Understand the concept of Cloudflare Tunnels
|
||||
- Configure your Cloudflare account
|
||||
- Configure SWAG
|
||||
- Manage multiple tunnels
|
||||
::
|
||||
|
||||

|
||||
|
||||
## Introduction
|
||||
---
|
||||
The _Zero Trust_ architecture is the practice of designing systems based on the principle of __"never trust, always verify"__, as opposed to the traditional principle of __"trust, but verify"__. This concept has become increasingly popular recently due to the growing number of attacks targeting user data. It’s a broad concept, but we’ll focus on how to apply _Zero Trust_ to the web services we host.
|
||||
|
||||
_Cloudflare tunnels_ offer a simple way to implement _Zero Trust_, using [SWAG](/serveex/core/swag) and [Authentik](/serveex/security/authentik).
|
||||
|
||||
Simply put, Cloudflare Tunnels allow you to:
|
||||
|
||||
- Hide your server’s IP (and your home IP if it's self-hosted)
|
||||
- Authenticate traffic
|
||||
- Benefit from Cloudflare protections (DDoS attacks, blacklists, malicious requests, etc.)
|
||||
- Use Cloudflare's CDN to cache and speed up your websites
|
||||
- Avoid opening router ports for services exposed by SWAG
|
||||
|
||||
Here we’ll explain how to integrate SWAG with Cloudflare tunnels.
|
||||
|
||||
::warning
|
||||
|
||||
- __Warning:__
|
||||
- Do not use Cloudflare tunnels to expose a mail server
|
||||
- Do not use Cloudflare tunnels to expose a video service like Plex (if you followed [this guide](/serveex/media/plex), Plex is not exposed, so it’s fine)
|
||||
- Do not use Cloudflare tunnels for the BitTorrent protocol (if you followed [this guide](/serveex/media/qbittorrent), everything is fine)
|
||||
::
|
||||
|
||||
## Cloudflare Configuration
|
||||
---
|
||||
### DNS Zone
|
||||
|
||||
First, you need to set Cloudflare as your [DNS zone](/general/networking/dns) manager. If you bought your domain from Cloudflare, that’s already done. Otherwise, check with your registrar how to add external DNS servers. Cloudflare provides [step-by-step documentation](https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/) on how to configure a DNS Zone, whether your domain is external or registered with Cloudflare.
|
||||
|
||||
If you only have one server to protect behind Cloudflare, you can delete all existing DNS records. By default, your domain and all its subdomains will be redirected to the tunnel.
|
||||
|
||||
If you have subdomains pointing to other servers, you can still define them in the DNS zone using A records.
|
||||
|
||||
If you have several servers and tunnels under one domain, [see here](http://192.168.7.80:8005/serveex/cloudflare/#gerer-plusieurs-tunnels-pour-plusieurs-serveurs).
|
||||
|
||||
### API Key
|
||||
|
||||
Start by creating a new Cloudflare API token and retrieving your zone and account IDs.
|
||||
|
||||
On your Cloudflare dashboard, on your domain overview page, you’ll see the `zone` and `account` IDs at the bottom right. Save both securely.
|
||||
|
||||

|
||||
|
||||
Just below that is a link titled _Get your API token_. Click it. The token scope must include `Zone:DNS:Edit` and `Account:Cloudflare Tunnel:Edit`. Your page should look like the screenshot below.
|
||||
|
||||

|
||||
|
||||
Once created, your token will only be shown once. Save it securely, as it cannot be viewed again later.
|
||||
|
||||
### Cloudflare Zero Trust
|
||||
|
||||
You must register for _Cloudflare Teams_ to access the _Zero Trust_ dashboard that manages tunnels and access policies. This is a premium service, but there’s a free plan for up to 50 users—perfect for a home lab. Keep in mind that a valid credit card is required to register, but the free plan incurs no charges.
|
||||
|
||||
Register [via this link](https://dash.teams.cloudflare.com/).
|
||||
|
||||
## SWAG Configuration
|
||||
---
|
||||
::note
|
||||
|
||||
This guide assumes you own `mondomaine.fr` and that its DNS is correctly pointing to Cloudflare, as described above.
|
||||
::
|
||||
|
||||
SWAG supports two Docker Mods:
|
||||
|
||||
- __Cloudflared__, the container used to create and manage tunnels
|
||||
- __Cloudflared Real IP__, which allows SWAG to receive the true source IP of incoming requests instead of Docker’s internal IP (important for IP geolocation mods like DBIP).
|
||||
|
||||
These two mods, merged into the SWAG container, require some configuration.
|
||||
|
||||
### Tunnel Configuration
|
||||
|
||||
Create a file `tunnelconfig.yml` to reference in your SWAG `compose.yaml`.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Use [File Browser](/serveex/files/file-browser) to navigate and edit files instead of using the terminal.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/tunnelconfig.yml
|
||||
```
|
||||
|
||||
Press `i` to enter insert mode and paste:
|
||||
|
||||
```yaml
|
||||
ingress:
|
||||
|
||||
- hostname: mondomaine.fr
|
||||
service: https://mondomaine.fr
|
||||
|
||||
- hostname: "*.mondomaine.fr"
|
||||
service: https://mondomaine.fr
|
||||
|
||||
- service: http_status:404
|
||||
```
|
||||
|
||||
Press `Esc`, then save and exit with `:x` and `Enter`.
|
||||
|
||||
### Cloudflare Real IP Configuration
|
||||
|
||||
Now configure _Cloudflare Real IP_.
|
||||
|
||||
Open the `nginx.conf` file:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/nginx.conf
|
||||
```
|
||||
|
||||
Press `i` and add the following at the end of the `http` section:
|
||||
|
||||
```nginx
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive on;
|
||||
include /config/nginx/cf_real-ip.conf;
|
||||
set_real_ip_from 127.0.0.1;
|
||||
```
|
||||
|
||||
Save and exit with `:x`.
|
||||
|
||||
### Docker Compose
|
||||
|
||||
In Dockge, edit your SWAG stack with this:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
swag:
|
||||
image: lscr.io/linuxserver/swag:latest
|
||||
container_name: swag
|
||||
cap_add:
|
||||
|
||||
- NET_ADMIN
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
environment:
|
||||
|
||||
- DOCKER_MODS=linuxserver/mods:swag-dbip|linuxserver/mods:swag-dashboard|linuxserver/mods:swag-auto-reload|linuxserver/mods:universal-cloudflared|linuxserver/mods:swag-cloudflare-real-ip
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=Europe/Paris
|
||||
- URL=${DOMAIN}
|
||||
- SUBDOMAINS=wildcard
|
||||
- VALIDATION=dns
|
||||
- DNSPLUGIN=${PLUGIN}
|
||||
- EMAIL=${EMAIL}
|
||||
- CF_ZONE_ID=${ZONE_ID}
|
||||
- CF_ACCOUNT_ID=${ACCOUNT_ID}
|
||||
- CF_API_TOKEN=${API_TOKEN}
|
||||
- CF_TUNNEL_NAME=${TUNNEL_NAME}
|
||||
- CF_TUNNEL_PASSWORD=${TUNNEL_PW}
|
||||
- FILE__CF_TUNNEL_CONFIG=/config/tunnelconfig.yml
|
||||
extra_hosts:
|
||||
|
||||
- ${DOMAIN}:127.0.0.1
|
||||
ports:
|
||||
|
||||
- 81:81
|
||||
volumes:
|
||||
|
||||
- /docker/swag/config:/config
|
||||
- /docker/swag/config/fail2ban/fail2ban.sqlite3:/dashboard/fail2ban.sqlite3:ro
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Add a Watchtower label to automate updates:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Fill in your `.env` file:
|
||||
|
||||
```properties
|
||||
PUID=
|
||||
PGID=
|
||||
DOMAIN=
|
||||
PLUGIN=
|
||||
EMAIL=
|
||||
ZONE_ID=
|
||||
ACCOUNT_ID=
|
||||
API_TOKEN=
|
||||
TUNNEL_NAME=
|
||||
TUNNEL_PW=
|
||||
```
|
||||
|
||||
| Variable | Value | Example |
|
||||
|----------------|-------------------------------------------------------------|--------------------------------|
|
||||
| `PUID` | User ID (`id username`) | `1000` |
|
||||
| `GUID` | Group ID (`id username`) | `1000` |
|
||||
| `DOMAIN` | Your reserved domain | `mondomaine.fr` |
|
||||
| `PLUGIN` | DNS provider (also configure `cloudflare.ini`) | `cloudflare` |
|
||||
| `EMAIL` | Email for the certificate | `[email protected]` |
|
||||
| `ZONE_ID` | Cloudflare Zone ID | `aNhcz1l3JfWbFZo2XMpzQlP2iOqk` |
|
||||
| `ACCOUNT_ID` | Cloudflare Account ID | `buKsjNHLyzKMM1qYnzOy4s7SHfly` |
|
||||
| `API_TOKEN` | API token | `53ydYus9TFFk1DOXNdP87iIcJtQjoW` |
|
||||
| `TUNNEL_NAME` | Tunnel name | `my_tunnel` |
|
||||
| `TUNNEL_PW` | Strong, random password | `iSzKRmP4VbnlsMvdSdgBEJiJi` |
|
||||
|
||||
Once done, deploy the stack. Check the logs—you should reach `server ready`.
|
||||
|
||||
Then confirm your tunnel appears under _Networks > Tunnels_ in [Cloudflare Zero Trust](https://one.dash.cloudflare.com/). By default, all subdomains will be routed through the tunnel—no need to define them [in your DNS zone](/general/networking/dns).
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ If you want to expose a service without a tunnel, just define an A record [in your DNS zone](/general/networking/dns). If resolution fails, disable the proxy function for that record—e.g., for `sub.mondomaine.fr`.
|
||||

|
||||
::
|
||||
|
||||
## Managing Multiple Tunnels for Multiple Servers
|
||||
---
|
||||
By default, all subdomains of your domain are routed through the single tunnel. But if you have a second server, just change the tunnel name in that SWAG instance.
|
||||
|
||||
In your DNS zone, redirect subdomains to the correct tunnel.
|
||||
|
||||
Go to _Networks > Tunnels_ in [Cloudflare Zero Trust](https://one.dash.cloudflare.com/).
|
||||
|
||||
Note the tunnel IDs:
|
||||
|
||||

|
||||
|
||||
Then in the [Cloudflare DNS dashboard](https://dash.cloudflare.com/), click your domain name.
|
||||
|
||||
Click `Add Record` and add these two CNAME records (include `.cfargotunnel.com`):
|
||||
|
||||
| Type | Name | Target |
|
||||
|---------|--------------|----------------------------------------|
|
||||
| `CNAME` | `subdomain1` | `yourtunnelid1.cfargotunnel.com` |
|
||||
| `CNAME` | `subdomain2` | `yourtunnelid2.cfargotunnel.com` |
|
||||
|
||||
If you have many subdomains, point them to the above reference subdomains.
|
||||
|
||||
This way, if a tunnel ID changes, you only update one DNS record.
|
||||
|
||||
Example:
|
||||
|
||||
- `sub1` and `sub2` also point to the server behind `subdomain1`:
|
||||
|
||||
| Type | Name | Target |
|
||||
|---------|--------|---------------|
|
||||
| `CNAME` | `sub1` | `subdomain1` |
|
||||
| `CNAME` | `sub2` | `subdomain1` |
|
||||
|
||||
- `sub3` and `sub4` point to the server behind `subdomain2`:
|
||||
|
||||
| Type | Name | Target |
|
||||
|---------|--------|---------------|
|
||||
| `CNAME` | `sub3` | `subdomain2` |
|
||||
| `CNAME` | `sub4` | `subdomain2` |
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Monitoring
|
||||
icon: i-lucide-chart-no-axes-column
|
||||
@@ -0,0 +1,204 @@
|
||||
---
|
||||
title: Uptime-Kuma
|
||||
description: Install Uptime-Kuma to monitor your self-hosted services uptime, set up alerts, and optionally protect the dashboard with Authentik.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Uptime-Kuma
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Install and deploy Uptime-Kuma
|
||||
- Expose Uptime-Kuma
|
||||
- (Optional) Protect Uptime-Kuma with Authentik
|
||||
::
|
||||
|
||||
[Uptime-Kuma](https://github.com/louislam/uptime-kuma) is a container dedicated to service monitoring. The principle is to regularly send requests to your services to determine if they are online, and alert you if not. Uptime-Kuma is developed by the same developer as Dockge.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
Folder structure
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── uptime-kuma
|
||||
├── date
|
||||
└── compose.yaml
|
||||
```
|
||||
|
||||
Open Dockge, click on `compose`, name the stack `uptime-kuma`, then copy and paste the following:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
uptime-kuma:
|
||||
image: louislam/uptime-kuma:2-slim
|
||||
container_name: uptime-kuma
|
||||
volumes:
|
||||
|
||||
- /docker/uptime-kuma/uptime-kuma-data:/app/data
|
||||
ports:
|
||||
|
||||
- 3200:3001 # <Host Port>:<Container Port>
|
||||
restart: always
|
||||
```
|
||||
::tip
|
||||
✨ __Tip:__ Add the Watchtower label to each container to automate updates
|
||||
|
||||
```yaml
|
||||
services:
|
||||
uptime-kuma:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
::
|
||||
|
||||
You can now access the tool via `http://yourserverip:3200`.
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
## Expose with Swag
|
||||
---
|
||||
::note
|
||||
📋 __Before you begin:__
|
||||
<br/><br/>
|
||||
We assume you have the subdomain `stats.mydomain.com` with a `CNAME` pointing to `mydomain.com` in your [DNS zone](/general/networking/dns). And of course, [unless you're using Cloudflare Zero Trust](/serveex/security/cloudflare), port `443` of your router should point to port `443` of your server via [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
::warning
|
||||
|
||||
Uptime-Kuma does not use multi-factor authentication. Exposing Uptime-Kuma on the internet could compromise the machines it monitors. Only do this if you're using an MFA system like [Authentik](/serveex/security/authentik/). Otherwise, don’t expose it with SWAG; use a VPN like [Wireguard](/serveex/security/wireguard) instead.
|
||||
::
|
||||
|
||||
In the Swag folders, create the `stats.subdomain.conf` file.
|
||||
|
||||
::tip
|
||||
✨ __Tip for those who dislike the terminal:__
|
||||
you can use [File Browser](/serveex/files/file-browser) to browse and edit your files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/stats.subdomain.conf
|
||||
```
|
||||
Enter insert mode with `i` and paste the following config:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name stats.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app uptime-kuma;
|
||||
set $upstream_port 3001;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Esc`, then save and exit with `:x` and `Enter`.
|
||||
|
||||
In Dockge, edit the SWAG compose and add the Uptime-Kuma network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Link container to custom network
|
||||
# ...
|
||||
|
||||
- uptime-kuma # Name of the declared network
|
||||
|
||||
networks: # Define custom network
|
||||
# ...
|
||||
uptime-kuma: # Name of the declared network
|
||||
name: uptime-kuma_default # Actual name of the external network
|
||||
external: true # Specifies it's an external network
|
||||
```
|
||||
|
||||
Restart the stack and wait until SWAG is fully operational.
|
||||
|
||||
::note
|
||||
|
||||
Here we assume that the network name of Uptime-Kuma is `uptime-kuma_default`. You can verify the connection by visiting SWAG's dashboard at `http://yourserverip:81`.
|
||||
::
|
||||
|
||||
That's it! Uptime-Kuma is now exposed, and you can access it via `https://stats.mydomain.com`.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__
|
||||
<br/><br>
|
||||
You can protect this app with Authentik by opening `stats.subdomain.conf` and uncommenting the lines:
|
||||
`include /config/nginx/authentik-server.conf;`
|
||||
and
|
||||
`include /config/nginx/authentik-location.conf;`.
|
||||
Don’t forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy). If you want the public stats page to be accessible without authentication:
|
||||
|
||||
- Edit the Uptime-Kuma provider
|
||||
- In *Advanced Protocol Settings > Authenticated Paths*, enter:
|
||||
|
||||
```properties
|
||||
^/$
|
||||
^/status
|
||||
^/assets/
|
||||
^/assets
|
||||
^/icon.svg
|
||||
^/api/.*
|
||||
^/upload/.*
|
||||
^/metrics
|
||||
::
|
||||
|
||||
Redeploy the stack.
|
||||
|
||||
Uptime-Kuma will then be publicly reachable via `https://stats.mydomain.com`.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ If you're using Authentik and don't mind exposing the admin panel to your local network, you can disable Uptime-Kuma's native authentication in its settings and rely solely on Authentik.
|
||||
::
|
||||
@@ -0,0 +1,185 @@
|
||||
---
|
||||
title: Dozzle
|
||||
description: Install Dozzle to monitor Docker container logs in real time from a clean web interface, exposed via SWAG.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Dozzle
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Install Dozzle
|
||||
- Expose Dozzle with Swag
|
||||
::
|
||||
|
||||
[Dozzle](https://dozzle.dev/) is a container that lets you access logs from your other containers and display them in real time through a user-friendly interface. It's a simple way to browse logs and retrieve information from the history.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
Folder structure
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── dozzle
|
||||
└── data
|
||||
```
|
||||
|
||||
Open Dockge, click on `compose`, name the stack `dozzle`, then copy and paste the following:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
dozzle:
|
||||
container_name: dozzle
|
||||
image: amir20/dozzle:latest
|
||||
ports:
|
||||
|
||||
- 9135:8080
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
environment:
|
||||
|
||||
- DOZZLE_HOSTNAME=${DOMAIN}
|
||||
volumes:
|
||||
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Add the watchtower label to each container to automate updates
|
||||
|
||||
```yaml
|
||||
services:
|
||||
dozzle:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Fill in your domain name in the `.env` file, for example:
|
||||
|
||||
```properties
|
||||
DOMAIN=dozzle.mydomain.com
|
||||
```
|
||||
|
||||
Deploy the container. Go to `http://yourserverip:9135`. Voilà, your Dozzle web UI is up and running!
|
||||
|
||||
## Exposing Dozzle with Swag
|
||||
---
|
||||
|
||||
::warning
|
||||
|
||||
Dozzle does not use multi-factor authentication. Exposing Dozzle to the internet could compromise the connected machines. Only do this if you use a multi-factor authentication system like [Authentik](/serveex/security/authentik/). Otherwise, do not expose it with SWAG and instead use a VPN like [Wireguard](/serveex/security/wireguard).
|
||||
::
|
||||
|
||||
You may want to access Dozzle remotely and on all your devices. To do so, we’ll expose Dozzle via Swag.
|
||||
|
||||
::note
|
||||
📋 __Before you begin:__
|
||||
<br/><br/>
|
||||
We assume you have created a subdomain like `dozzle.mydomain.com` in your [DNS zone](/general/networking/dns) with a `CNAME` pointing to `mydomain.com` and that, [unless you're using Cloudflare Zero Trust](/serveex/security/cloudflare), you’ve redirected port `443` from your router to port `443` on your server in your [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
Go to Dockge and edit the SWAG compose file to add Dozzle’s network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Connects the container to a custom network
|
||||
# ...
|
||||
|
||||
- dozzle # Network name declared in the stack
|
||||
|
||||
networks: # Defines the custom network
|
||||
# ...
|
||||
dozzle: # Network name declared in the stack
|
||||
name: dozzle_default # Actual name of the external network
|
||||
external: true # Indicates it's an externally defined network
|
||||
```
|
||||
|
||||
Redeploy the stack by clicking “Deploy” and wait for SWAG to be fully operational.
|
||||
|
||||
::note
|
||||
|
||||
We assume the Dozzle network name is `dozzle_default`. You can verify the connection is working by visiting the SWAG dashboard at `http://yourserverip:81`.
|
||||
::
|
||||
|
||||
In the Swag folder, create the `dozzle.subdomain.conf` file.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can use [File Browser](/serveex/files/file-browser) to browse and edit files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/dozzle.subdomain.conf
|
||||
```
|
||||
Enter edit mode by pressing `i` and paste the configuration below:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name dozzle.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app dozzle;
|
||||
set $upstream_port 8080;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Esc`, then save and exit by typing `:x` and pressing `Enter`.
|
||||
|
||||
And there you go, Dozzle is now exposed!
|
||||
|
||||
::tip
|
||||
✨ You can protect this app with Authentik by opening `dozzle.subdomain.conf` and removing the `#` in front of `include /config/nginx/authentik-server.conf;`{lang=nginx} and `include /config/nginx/authentik-location.conf;`{lang=nginx}. Don’t forget to [create an application and a provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||
::
|
||||
@@ -0,0 +1,197 @@
|
||||
---
|
||||
title: Speedtest Tracker
|
||||
description: Install Speedtest Tracker to automatically measure and log your internet connection speed over time, exposed with SWAG.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Speedtest Tracker
|
||||
|
||||
::note
|
||||
🎯 **Objectives:**
|
||||
|
||||
- Install Speedtest Tracker
|
||||
- Expose Speedtest Tracker with SWAG
|
||||
::
|
||||
|
||||
[Speedtest Tracker](https://docs.speedtest-tracker.dev/) is a container that allows you to schedule regular speed tests in order to log your server's internet connection status.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
::note
|
||||
|
||||
We will use the Docker image maintained by [LinuxServer.io](https://docs.linuxserver.io/images/docker-speedtest-tracker/)
|
||||
::
|
||||
|
||||
File structure:
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── speedtest-tracker
|
||||
└── data
|
||||
└── config
|
||||
```
|
||||
|
||||
In a terminal, generate a key using the following command:
|
||||
|
||||
```sh
|
||||
echo -n 'base64:'; openssl rand -base64 32;
|
||||
```
|
||||
|
||||
Take note of the key.
|
||||
|
||||
Open Dockge, click on `compose`, name the stack `speedtest-tracker`, then paste the following:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
speedtest-tracker:
|
||||
image: lscr.io/linuxserver/speedtest-tracker:latest
|
||||
restart: unless-stopped
|
||||
container_name: speedtest-tracker
|
||||
ports:
|
||||
|
||||
- ${PORT}:80
|
||||
environment:
|
||||
|
||||
- PUID=${PUID}
|
||||
- PGID=${GUID}
|
||||
- TZ=Europe/Paris
|
||||
- APP_KEY=${API_KEY}
|
||||
- DB_CONNECTION=sqlite
|
||||
- SPEEDTEST_SCHEDULE=${SCHEDULE}
|
||||
volumes:
|
||||
|
||||
- /docker/speedtest-tracker/data/config:/config
|
||||
```
|
||||
|
||||
Find your `PUID` and `GUID` by running the following command:
|
||||
|
||||
```sh
|
||||
id yourusername
|
||||
```
|
||||
|
||||
In the `.env` file, set the variable `API_KEY` with the key you generated and add a cron-style test schedule, as well as your `PUID` and `GUID`, for example:
|
||||
|
||||
```properties
|
||||
SCHEDULE=15 */6 * * * # every 6 hours
|
||||
API_KEY=base64:zihejehkj8_nzhY/OjeieR= # your key
|
||||
PUID=1000
|
||||
GUID=1000
|
||||
PORT=3225 # port to access the web UI
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ **Tip:** You can configure additional environment variables by referring to the [official documentation](https://docs.speedtest-tracker.dev/getting-started/environment-variables).
|
||||
::
|
||||
|
||||
Deploy the container and go to `http://yourserverip:3225`. Log in with the account `[email protected]` and the password `password`. Don’t forget to change your ID and password once logged in!
|
||||
|
||||
## Expose Speedtest Tracker
|
||||
---
|
||||
::note
|
||||
📋 **Prerequisites:**
|
||||
We assume that you've already created a subdomain like `speedtest.yourdomain.com` in your [DNS zone](/general/networking/dns) with a `CNAME` pointing to `yourdomain.com`, and [unless you’re using Cloudflare Zero Trust](/serveex/security/cloudflare), you've also forwarded port `443` from your router to port `443` of your server in your [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
Now we want to expose Speedtest Tracker to the internet so you can access it remotely. We assume you've set up the DNS `CNAME` for `speedtest.yourdomain.com` pointing to `yourdomain.com`.
|
||||
|
||||
::warning
|
||||
|
||||
Speedtest Tracker does not use multi-factor authentication. Exposing it on the internet could compromise connected devices. Do so only if you use a multi-factor system like [Authentik](/serveex/security/authentik/). Otherwise, avoid using SWAG and prefer a VPN like [Wireguard](/serveex/security/wireguard).
|
||||
::
|
||||
|
||||
Open the `speedtest.subdomain.conf` file:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/speedtest.subdomain.conf
|
||||
```
|
||||
|
||||
Configure it like this:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name speedtest.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# Authentication options (uncomment as needed)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# Basic auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# Per-location authentication
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
|
||||
set $upstream_app speedtest-tracker;
|
||||
set $upstream_port 3225;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Save and exit. The configuration will update in a few seconds.
|
||||
|
||||
::note
|
||||
|
||||
By default, SWAG doesn’t know the name "speedtest-tracker". To allow access, you need to add Speedtest Tracker’s network to SWAG’s `compose.yml`.
|
||||
::
|
||||
|
||||
Go to Dockge, and edit SWAG’s compose to include Speedtest Tracker’s network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks:
|
||||
# ...
|
||||
|
||||
- speedtest-tracker
|
||||
|
||||
networks:
|
||||
# ...
|
||||
speedtest-tracker:
|
||||
name: speedtest-tracker_default
|
||||
external: true
|
||||
```
|
||||
|
||||
Restart the stack by clicking "Deploy" and wait for SWAG to be fully up.
|
||||
|
||||
::note
|
||||
|
||||
This assumes the Speedtest Tracker network is named `speedtest-tracker_default`. You can verify the connection by visiting SWAG’s dashboard at `http://yourserverip:81`.
|
||||
::
|
||||
|
||||
Wait a moment, then visit `https://speedtest.yourdomain.com` in your browser — you should be redirected to Speedtest Tracker. You can check service status via the dashboard (`http://yourserverip:81` from the local network).
|
||||
|
||||
::tip
|
||||
✨ You can protect this app with Authentik by opening `speedtest.subdomain.conf` and uncommenting
|
||||
`include /config/nginx/authentik-server.conf;` and `include /config/nginx/authentik-location.conf;`.
|
||||
Don’t forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||
::
|
||||
@@ -0,0 +1,253 @@
|
||||
---
|
||||
title: Beszel
|
||||
description: Install Beszel to monitor server CPU, RAM, disk, and network metrics — including remote servers — with a lightweight web dashboard.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Beszel
|
||||
|
||||
::note
|
||||
🎯 __Objectives:__
|
||||
|
||||
- Install Beszel
|
||||
- Monitor the local server
|
||||
- Monitor a remote server
|
||||
- Expose Beszel with Swag
|
||||
::
|
||||
|
||||
[Beszel](https://beszel.dev/) is a container that gives you real-time access to hardware information from your servers and allows historical tracking. CPU activity, disk usage, temperatures, RAM—nothing escapes your monitoring. Beszel also lets you configure notifications and alerts when your predefined thresholds are exceeded.
|
||||
|
||||
Beszel includes a hub with a web UI and an agent that collects data from your server or a remote server.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
|
||||
Folder structure
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── beszel
|
||||
├── data
|
||||
└── socket
|
||||
```
|
||||
|
||||
Open Dockge, click `compose`, name the stack `beszel`, and paste the following:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
beszel:
|
||||
image: henrygd/beszel:latest
|
||||
container_name: beszel
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
||||
- ${PORT}:8090
|
||||
volumes:
|
||||
|
||||
- ./data:/beszel_data
|
||||
- ./socket:/beszel_socket
|
||||
|
||||
beszel-agent:
|
||||
image: henrygd/beszel-agent:latest
|
||||
container_name: beszel-agent
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
volumes:
|
||||
|
||||
- ./socket:/beszel_socket
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
LISTEN: /beszel_socket/beszel.sock
|
||||
# Do not remove quotes around the key
|
||||
KEY: ${KEY}
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Add the Watchtower label to each container to automate updates.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
beszel:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Fill out the `.env` file, for example:
|
||||
|
||||
```properties
|
||||
PORT=8090 # web UI port
|
||||
KEY= # private key to retrieve from Beszel when adding a system
|
||||
```
|
||||
|
||||
For the `KEY` value, you'll need to launch Beszel once to get it.
|
||||
|
||||
Deploy the container and go to `http://yourserverip:8090`. Your Beszel web UI is now accessible!
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
### Add local server information
|
||||
|
||||
Now that the web UI is accessible, you need to push local server information into it. Just add a machine via the web UI and configure it like this:
|
||||
|
||||

|
||||
|
||||
Note the private key and confirm. Enter the key in your `.env` file in Dockge and redeploy the stack. Once done, your server will appear in the web UI:
|
||||
|
||||

|
||||
|
||||
### Add a remote server
|
||||
|
||||
You can also monitor a remote server. To do so, run the agent on the remote server. Add a new machine in Beszel and fill in:
|
||||
|
||||
- The name displayed for your remote server
|
||||
- The IP address or domain name of the remote server
|
||||
- The listening port (e.g., `45876`)
|
||||
|
||||
Beszel will suggest a `compose.yaml` to deploy on the remote server, or you can use:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
beszel-agent:
|
||||
image: henrygd/beszel-agent
|
||||
container_name: beszel-agent
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
volumes:
|
||||
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
LISTEN: ${PORT}
|
||||
KEY: ${KEY}
|
||||
```
|
||||
|
||||
And in `.env`:
|
||||
|
||||
```properties
|
||||
PORT=45876 # communication port between hub and remote agent
|
||||
KEY= # private key from Beszel when adding the system
|
||||
```
|
||||
|
||||
Deploy the stack on the remote server. Data will begin flowing into the web UI after a few seconds.
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
## Expose Beszel with Swag
|
||||
---
|
||||
|
||||
::warning
|
||||
|
||||
Beszel does not support multi-factor authentication. Exposing it on the internet could compromise connected machines. Only do this if you're using a system like [Authentik](/serveex/security/authentik/). Otherwise, do not expose with SWAG—use a VPN like [Wireguard](/serveex/security/wireguard) instead.
|
||||
::
|
||||
|
||||
If you want to access Beszel remotely from all your devices, expose it using Swag.
|
||||
|
||||
::note
|
||||
📋 __Prerequisite:__
|
||||
<br/><br/>
|
||||
You must have created a DNS subdomain like `beszel.mydomain.com` with a `CNAME` pointing to `mydomain.com`, and—unless you're using Cloudflare Zero Trust—you must have forwarded port `443` on your router to your server’s `443` port via [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
In Dockge, edit Swag's compose file and add Beszel’s network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks:
|
||||
# ...
|
||||
|
||||
- beszel # network declared in the stack
|
||||
|
||||
networks:
|
||||
# ...
|
||||
beszel:
|
||||
name: beszel_default # actual external network name
|
||||
external: true
|
||||
```
|
||||
|
||||
Redeploy the stack and wait for Swag to become fully operational.
|
||||
|
||||
::note
|
||||
|
||||
We assume the network name is `beszel_default`. You can check connectivity by visiting Swag's dashboard at `http://yourserverip:81`.
|
||||
::
|
||||
|
||||
In Swag’s config folders, create `beszel.subdomain.conf`.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Use [File Browser](/serveex/files/file-browser) to browse and edit files instead of terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/beszel.subdomain.conf
|
||||
```
|
||||
|
||||
Press `i` to enter insert mode and paste:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name beszel.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app beszel;
|
||||
set $upstream_port 8090;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Esc`, type `:x`, and hit `Enter` to save and exit.
|
||||
|
||||
That’s it—Beszel is now exposed!
|
||||
|
||||
::tip
|
||||
✨ You can protect this app with Authentik by opening `beszel.subdomain.conf` and removing the `#` in front of `include /config/nginx/authentik-server.conf;` and `include /config/nginx/authentik-location.conf;`. Don’t forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||
::
|
||||
@@ -0,0 +1,198 @@
|
||||
---
|
||||
title: UpSnap
|
||||
description: Install UpSnap to remotely wake up machines on your local network via Wake-on-LAN, exposed with SWAG.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# UpSnap
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Install UpSnap
|
||||
- Expose UpSnap with Swag
|
||||
::
|
||||
|
||||
[UpSnap](https://github.com/seriousm4x/UpSnap) is a container that allows you to remotely power on, shut down, or put your machines to sleep. It mainly uses Wake-On-Lan (WoL) over the network and offers advanced features.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
|
||||
Folder structure
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── upsnap
|
||||
└── data
|
||||
```
|
||||
|
||||
Open Dockge, click on `compose`, name the stack `upsnap`, then copy and paste the following:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
upsnap:
|
||||
container_name: upsnap
|
||||
image: ghcr.io/seriousm4x/upsnap:5
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
|
||||
- /docker/upsnap/data:/app/pb_data
|
||||
environment:
|
||||
|
||||
- TZ=Europe/Paris
|
||||
- UPSNAP_SCAN_RANGE=${SCAN_RANGE}
|
||||
- UPSNAP_SCAN_TIMEOUT=500ms
|
||||
- UPSNAP_PING_PRIVILEGED=true
|
||||
dns:
|
||||
|
||||
- ${DNS}
|
||||
entrypoint: /bin/sh -c "./upsnap serve --http 0.0.0.0:8095"
|
||||
healthcheck:
|
||||
test: curl -fs "http://localhost:8095/api/health" || exit 1
|
||||
interval: 10s
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Add the watchtower label to each container to automate updates
|
||||
|
||||
```yaml
|
||||
services:
|
||||
upsnap:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Fill in the `.env`, for example:
|
||||
|
||||
```properties
|
||||
RANGE=192.168.1.0/24 # scans all devices on the local network with an IP between 192.168.0.1 and 192.168.1.255
|
||||
DNS=192.168.1.1 # DNS IP to resolve domain names, typically your router’s IP
|
||||
```
|
||||
|
||||
Deploy the container and go to `http://yourserverip:8095`. Just follow the steps to create your account!
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
## Exposing UpSnap with Swag
|
||||
---
|
||||
|
||||
::warning
|
||||
|
||||
UpSnap does not support multi-factor authentication. Exposing it on the internet could compromise connected machines. Do this only if you're using a multi-factor authentication system like [Authentik](/serveex/security/authentik/). Otherwise, avoid exposing it with SWAG and use a VPN like [Wireguard](/serveex/security/wireguard) instead.
|
||||
::
|
||||
|
||||
You may want to access it remotely from all your devices. To do so, we'll expose UpSnap via Swag.
|
||||
|
||||
::note
|
||||
📋 __Beforehand:__
|
||||
<br/><br/>
|
||||
We assume you've created a subdomain in your [DNS zone](/general/networking/dns), such as `upsnap.yourdomain.com` with a `CNAME` to `yourdomain.com`. Also, unless you're using Cloudflare Zero Trust, you should have already forwarded port `443` from your router to port `443` on your server in your [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
Go to Dockge, and edit the SWAG compose by adding the UpSnap network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Connects the container to the custom network
|
||||
# ...
|
||||
|
||||
- upsnap # Network name declared in the stack
|
||||
|
||||
networks: # Defines the custom network
|
||||
# ...
|
||||
upsnap: # Network name declared in the stack
|
||||
name: upsnap_default # Actual name of the external network
|
||||
external: true # Indicates it's an external network
|
||||
```
|
||||
|
||||
Restart the stack by clicking "deploy" and wait for SWAG to be fully operational.
|
||||
|
||||
::note
|
||||
|
||||
Here we assume the network name for upsnap is `upsnap_default`. You can check the connection in the SWAG dashboard at `http://yourserverip:81`.
|
||||
::
|
||||
|
||||
In the Swag folders, create the file `upsnap.subdomain.conf`.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can use [File Browser](/serveex/files/file-browser) to navigate your files and edit documents instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/upsnap.subdomain.conf
|
||||
```
|
||||
Enter edit mode by pressing `i`, and paste the following configuration:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name upsnap.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app upsnap;
|
||||
set $upstream_port 8095;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Escape`, then save and exit by typing `:x` and pressing `Enter`.
|
||||
|
||||
And that’s it — you’ve exposed UpSnap!
|
||||
|
||||
::tip
|
||||
✨ You can protect this app with Authentik by opening `upsnap.subdomain.conf` and removing the `#` in front of `include /config/nginx/authentik-server.conf;`{lang=nginx} and `include /config/nginx/authentik-location.conf;`{lang=nginx}. Don’t forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||
::
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Media & Seedbox
|
||||
icon: i-lucide-list-video
|
||||
@@ -0,0 +1,317 @@
|
||||
---
|
||||
title: Plex
|
||||
description: Install Plex Media Server with Tautulli on your homelab to stream movies and TV shows from anywhere on all your devices.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Plex
|
||||
|
||||
::note
|
||||
🎯 **Objectives:**
|
||||
|
||||
- Install Plex
|
||||
- Install Tautulli
|
||||
- Access media from outside your network
|
||||
::
|
||||
|
||||
[Plex](https://www.plex.tv/fr/) is a self-hosted video streaming platform for managing your movie or TV show library and playing them locally or remotely. Plex has apps for TV, Android, iOS, Windows, and macOS, allowing you to stream your library just like Netflix.
|
||||
|
||||
With *Plex Pass*, you can also organize and play your music content similar to Spotify, the difference being that it’s your content, hosted and streamed from your server.
|
||||
|
||||

|
||||
|
||||
We'll also install [Tautulli](https://docs.linuxserver.io/images/docker-tautulli/), a tool that provides detailed stats about Plex. As always, we'll use linuxserver.io images where possible.
|
||||
|
||||
- [More info on the Plex container](https://docs.linuxserver.io/images/docker-plex)
|
||||
- [More info on the Tautulli container](https://docs.linuxserver.io/images/docker-tautulli/)
|
||||
|
||||
::note
|
||||
|
||||
You’ll need to create a *Plex.tv* account. You don’t need to expose your Plex service; it will be accessible through the platform. Your Plex server will be manageable directly from your account.
|
||||
::
|
||||
|
||||
## Install Plex
|
||||
---
|
||||
Folder structure:
|
||||
```sh
|
||||
root
|
||||
├── docker
|
||||
│ ├── plex
|
||||
│ │ ├── compose.yml
|
||||
│ │ ├── .env
|
||||
│ │ ├── config
|
||||
│ │ └── transcode
|
||||
│ └── tautulli
|
||||
│ └── config
|
||||
└── media
|
||||
├── tvseries
|
||||
├── movies
|
||||
└── library
|
||||
```
|
||||
|
||||
Create the `movies`, `tvseries`, and `library` folders in `/media`:
|
||||
|
||||
```sh
|
||||
mkdir -p /media/movies /media/library /media/tvseries
|
||||
```
|
||||
|
||||
Open Dockge in your browser and click `compose`.
|
||||
Name the stack `plex` and add the following config:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
linuxserver_plex:
|
||||
image: ghcr.io/linuxserver/plex:amd64-latest
|
||||
container_name: plex
|
||||
network_mode: host
|
||||
environment:
|
||||
|
||||
- PUID=${PUID}
|
||||
- PGID=${GUID}
|
||||
- TZ=Europe/Paris
|
||||
- VERSION=docker
|
||||
volumes:
|
||||
|
||||
- /docker/plex/config:/config
|
||||
- /docker/plex/transcode:/transcode
|
||||
- /media:/media
|
||||
restart: unless-stopped
|
||||
mem_limit: 4096m
|
||||
mem_reservation: 2048m
|
||||
devices:
|
||||
|
||||
- /dev/dri:/dev/dri
|
||||
|
||||
tautulli:
|
||||
image: lscr.io/linuxserver/tautulli:latest
|
||||
container_name: tautulli
|
||||
environment:
|
||||
|
||||
- PUID=${PUID}
|
||||
- PGID=${GUID}
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
|
||||
- /docker/tautulli/config:/config
|
||||
ports:
|
||||
|
||||
- 8181:8181
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ Add the Watchtower label to each container to automate updates:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
plex:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
|
||||
tautulli:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Find your PUID and GUID by running:
|
||||
|
||||
```sh
|
||||
id username
|
||||
```
|
||||
|
||||
Fill in your `.env` file with the retrieved values, for example:
|
||||
|
||||
```properties
|
||||
PUID=1000
|
||||
GUID=1000
|
||||
```
|
||||
|
||||
Deploy the stack.
|
||||
|
||||
The local interface is available at `http://yourserverip:32400/web/index.html`.
|
||||
Tautulli is accessible at `http://yourserverip:8181`.
|
||||
|
||||
::warning
|
||||
|
||||
You must be on your local network during Plex's initial setup. Otherwise, the URL will redirect to your Plex account without detecting your server. A VPN won't help. If you have no choice, [you can handle the setup remotely via SSH tunnel](https://support.plex.tv/articles/200288586-installation/#toc-2).
|
||||
::
|
||||
|
||||
## Configure Plex
|
||||
---
|
||||
Plex offers a range of free movies/shows. After creating your account, I recommend disabling everything in the _Online Services_ section to keep your library clean.
|
||||
|
||||
Then go to the _Remote Access_ section and manually select a port (we’ll use `1234`). It's best not to use the default port.
|
||||
|
||||

|
||||
|
||||
- On your router, forward TCP port `1234` to port `32400` for your server’s IP using [NAT rules](/general/networking/nat).
|
||||
- Once done, return to Plex to verify that remote access is functional.
|
||||
|
||||
::caution
|
||||
|
||||
**If it fails:** check your firewall rules and allow port `32400` on your server.
|
||||
::
|
||||
|
||||
- If you have PlexPass and a GPU or iGPU, enable *hardware acceleration* in the _Transcoder_ section.
|
||||
- In _Settings > Library_, enable _Update my library automatically_.
|
||||
- In _Manage > Library_, add or edit libraries and point to `/media/movies` for movies and `/media/tvseries` for series.
|
||||
|
||||
And that’s it! You now have a working Plex server!
|
||||
|
||||
Simply add your media to `/media/movies` and `/media/tvseries` on your server. You can then install the Plex app on your devices and watch your favorite content locally or remotely.
|
||||
|
||||
::note
|
||||
|
||||
If your media is stored on a network disk (e.g. NAS or external hard drive over the network), refer to the [Samba mount guide](/general/networking/samba) so Plex can access it.
|
||||
::
|
||||
|
||||
## Expose Tautulli with Swag
|
||||
---
|
||||
You don’t need to expose Plex, as it's accessible via your Plex account on plex.tv.
|
||||
|
||||
However, you may want to expose Tautulli so you can view stats from a simple URL when you're not home.
|
||||
|
||||
::note
|
||||
|
||||
We assume you have the subdomain `tautulli.mydomain.com` with a `CNAME` pointing to `mydomain.com` in your [DNS zone](/general/networking/dns). And of course, [unless you use Cloudflare Zero Trust](/serveex/security/cloudflare), your box's port `443` must be forwarded to your server's port `443` in [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
Go to Dockge and edit SWAG’s compose file by adding Tautulli’s network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Attach container to custom network
|
||||
# ...
|
||||
|
||||
- tautulli # Name of the declared network
|
||||
|
||||
networks: # Define the custom network
|
||||
# ...
|
||||
tautulli: # Declared network name
|
||||
name: tautulli_default # Actual external network name
|
||||
external: true # Marks it as externally defined
|
||||
```
|
||||
|
||||
Redeploy the stack and wait for SWAG to be fully operational.
|
||||
|
||||
::note
|
||||
|
||||
Here we assume the Tautulli network name is `tautulli_default`. You can check the connection by visiting SWAG’s dashboard at `http://yourserverip:81`.
|
||||
::
|
||||
|
||||
Copy and rename the file `tautulli.subdomain.conf.sample` to `tautulli.subdomain.conf`, then edit it:
|
||||
|
||||
::tip
|
||||
✨ **Tip:** Use [File Browser](/serveex/files/file-browser) to navigate and edit files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo cp /docker/swag/config/nginx/proxy-confs/tautulli.subdomain.conf.sample /docker/swag/config/nginx/proxy-confs/tautulli.subdomain.conf
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/tautulli.subdomain.conf
|
||||
```
|
||||
|
||||
Ensure the configuration matches the following. If needed, press `i` to edit:
|
||||
|
||||
```nginx
|
||||
## Version 2023/05/31
|
||||
# make sure that your tautulli container is named tautulli
|
||||
# make sure that your dns has a cname set for tautulli
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name tautulli.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app tautulli;
|
||||
set $upstream_port 8181;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
|
||||
location ~ (/tautulli)?/api {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app tautulli;
|
||||
set $upstream_port 8181;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
|
||||
location ~ (/tautulli)?/newsletter {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app tautulli;
|
||||
set $upstream_port 8181;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
|
||||
location ~ (/tautulli)?/image {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app tautulli;
|
||||
set $upstream_port 8181;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ You can protect this app with Authentik by removing the `#` before `include /config/nginx/authentik-server.conf;` and `include /config/nginx/authentik-location.conf;`. Don’t forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||
::
|
||||
|
||||
Press `Esc` then save and quit by typing `:x`
|
||||
|
||||
Wait a few minutes, then open `http://tautulli.mydomain.com` in your browser.
|
||||
|
||||
::caution
|
||||
|
||||
**If it fails:** check your firewall rules.
|
||||
::
|
||||
|
||||
And you're done!
|
||||
@@ -0,0 +1,330 @@
|
||||
---
|
||||
title: Qbittorrent
|
||||
description: Install qBittorrent with Gluetun and ProtonVPN to download torrents securely behind a VPN on your self-hosted server.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Qbittorrent
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Install and configure Qbittorrent
|
||||
- Securely connect to the BitTorrent network using Gluetun and Proton VPN
|
||||
::
|
||||
|
||||

|
||||
|
||||
To safely download your favorite media, we'll build a system using:
|
||||
|
||||
- [Qbittorrent](https://github.com/linuxserver/docker-qbittorrent) as the BitTorrent client
|
||||
- [Proton VPN Plus](https://protonvpn.com/torrenting), a VPN to secure your traffic. You need a subscription (promos available) to access the BitTorrent protocol. You can also use another VPN as long as it supports BitTorrent.
|
||||
- [Gluetun](https://github.com/qdm12/gluetun)
|
||||
- [Qbittorrent port update](https://codeberg.org/TechnoSam/qbittorrent-gluetun-port-update) to automatically update the VPN port (which changes regularly).
|
||||
- The [VueTorrent](https://github.com/gabe565/linuxserver-mod-vuetorrent) mod for a modern and intuitive UI.
|
||||
|
||||
Here’s the system we’ll set up:
|
||||
|
||||

|
||||
|
||||
## Configuration
|
||||
---
|
||||
Folder structure
|
||||
|
||||
```sh
|
||||
root
|
||||
├── docker
|
||||
│ └── seedbox
|
||||
│ ├── qbittorrent
|
||||
│ │ └── config
|
||||
│ ├── gluetun
|
||||
│ ├── compose.yaml
|
||||
│ └── .env
|
||||
│
|
||||
└── media #linked to Plex and Qbittorrent
|
||||
├── downloads #generic downloads, selected in settings
|
||||
├── movies #used for downloading movies
|
||||
└── tvseries #used for downloading TV shows
|
||||
```
|
||||
|
||||
If not already done, create the `downloads` folder under `/media`:
|
||||
|
||||
```sh
|
||||
mkdir -P /media/downloads
|
||||
```
|
||||
|
||||
Open Dockge, click on `compose`, and name the stack `seedbox`. Paste the following config:
|
||||
|
||||
```yaml
|
||||
|
||||
services:
|
||||
qbit:
|
||||
image: ghcr.io/linuxserver/qbittorrent:libtorrentv1
|
||||
container_name: qbittorrent
|
||||
restart: unless-stopped
|
||||
network_mode: service:gluetun
|
||||
mem_limit: 4g
|
||||
environment:
|
||||
|
||||
- DOCKER_MODS=ghcr.io/gabe565/linuxserver-mod-vuetorrent|ghcr.io/t-anc/gsp-qbittorent-gluetun-sync-port-mod:main
|
||||
- TZ=Europe/Paris
|
||||
- PUID=${PUID}
|
||||
- PGID=${GUID}
|
||||
- WEBUI_PORT=${UI_PORT}
|
||||
- GSP_GTN_API_KEY=${GSP_KEY}
|
||||
- GSP_QBT_USERNAME=${ID}
|
||||
- GSP_QBT_PASSWORD=${PW}
|
||||
volumes:
|
||||
|
||||
- /docker/seedbox/qbittorrent/config:/config
|
||||
- /media:/media
|
||||
depends_on:
|
||||
|
||||
- gluetun
|
||||
|
||||
gluetun:
|
||||
image: qmcgaw/gluetun:v3.41.3
|
||||
container_name: gluetun
|
||||
restart: unless-stopped
|
||||
mem_limit: 4g
|
||||
volumes:
|
||||
|
||||
- /docker/gluetun/config.toml:/gluetun/auth/config.toml:ro
|
||||
devices:
|
||||
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
ports:
|
||||
|
||||
- ${UI_PORT}:5695 # Port de la web-ui
|
||||
- 8000:8000 # Port de controle de Gluetun
|
||||
cap_add:
|
||||
|
||||
- NET_ADMIN
|
||||
environment:
|
||||
|
||||
- TZ=Europe/Paris
|
||||
- VPN_SERVICE_PROVIDER=protonvpn
|
||||
- VPN_PORT_FORWARDING=on
|
||||
- VPN_PORT_FORWARDING_PROVIDER=protonvpn
|
||||
- VPN_TYPE=wireguard
|
||||
- WIREGUARD_PRIVATE_KEY=${PR_KEY}
|
||||
- SERVER_COUNTRIES=France
|
||||
- PORT_FORWARD_ONLY=on
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Add the Watchtower label in each container to automate updates
|
||||
|
||||
```yaml
|
||||
services:
|
||||
qbittorrent:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
gluetun:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Before editing the `.env` in Dockge, let's configure the download port update. Proton and most VPNs rotate the forwarding port, which must be communicated to Qbittorrent.
|
||||
|
||||
We’ve added the mod `ghcr.io/t-anc/gsp-qbittorent-gluetun-sync-port-mod` to the container.
|
||||
|
||||
We now need to allow the mod to fetch info from Gluetun, which only allows encrypted communication via its API.
|
||||
|
||||
Open a terminal to generate the authentication key:
|
||||
|
||||
```sh
|
||||
sudo docker run --rm qmcgaw/gluetun genkey
|
||||
```
|
||||
|
||||
Note the key, then create the `/docker/gluetun` folder:
|
||||
|
||||
```sh
|
||||
sudo mkdir /docker/gluetun
|
||||
```
|
||||
|
||||
Create the `config.toml` file:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/gluetun/config.toml
|
||||
```
|
||||
|
||||
Press `i` to edit and enter:
|
||||
|
||||
```toml
|
||||
[[roles]]
|
||||
name = "t-anc/GSP-Qbittorent-Gluetun-sync-port-mod"
|
||||
routes = ["GET /v1/portforward"]
|
||||
auth = "apikey"
|
||||
apikey = "your_key_here" # key you just generated
|
||||
```
|
||||
|
||||
Press `Esc` then type `:x` to save and exit.
|
||||
|
||||
In Dockge, fill in the variables in `.env`:
|
||||
|
||||
```properties
|
||||
PUID=
|
||||
GUID=
|
||||
UI_PORT=
|
||||
PR_KEY=
|
||||
GSP_KEY= # the key you generated and entered in config.toml
|
||||
ID=
|
||||
PW=
|
||||
```
|
||||
|
||||
Detailed info:
|
||||
|
||||
| Variable | Description | Example |
|
||||
|------------|-------------|---------|
|
||||
| `PUID` | User ID (`id yourusername`) | `1000` |
|
||||
| `GUID` | Group ID (`id yourusername`) | `1000` |
|
||||
| `UI_PORT` | Port for accessing the web UI | `5695` |
|
||||
| `PR_KEY` | Private key from Proton | `buKsjNHLyzKMM1qYnzOy4s7SHfly` |
|
||||
| `GSP_KEY` | Key you generated for port update | `MnBa47MeVmk7xiv` |
|
||||
| `ID` | Qbittorrent UI login username | `user` |
|
||||
| `PW` | Qbittorrent UI password | `password` |
|
||||
|
||||
## Deployment
|
||||
---
|
||||
Once done, deploy the container.
|
||||
|
||||
::warning
|
||||
|
||||
**Startup logs will show a temporary password for `admin` user**
|
||||
::
|
||||
|
||||
Login at `http://server-ip:5695` (or the port you set).
|
||||
|
||||
::caution
|
||||
|
||||
__If login fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
Change your username and password in the "webui" settings.
|
||||
|
||||
You're done! In Qbittorrent settings, under "Downloads", set `/media/downloads` as the default folder.
|
||||
|
||||
When adding a download, remember to select the proper directory so Plex can sync correctly (`/media/movies` or `/media/tvseries`). You can also automate this with categories and folders.
|
||||
|
||||
## Exposing the Web UI
|
||||
---
|
||||
|
||||
::warning
|
||||
|
||||
Qbittorrent does not support multi-factor authentication. Exposing it to the internet may put your system at risk. Only do this if you use MFA via [Authentik](/serveex/security/authentik/). Otherwise, don’t expose it with SWAG—use a VPN like [Wireguard](/serveex/security/wireguard) instead.
|
||||
::
|
||||
|
||||
To start downloads from outside your home, without a VPN, you can expose the Qbittorrent web UI.
|
||||
|
||||
::note
|
||||
|
||||
We assume you have the subdomain `seedbox.mydomain.com` with a `CNAME` pointing to `mydomain.com` in [DNS zone](/general/networking/dns). And that port `443` on your router is forwarded to your server in [NAT rules](/general/networking/nat), unless you’re using Cloudflare Zero Trust.
|
||||
::
|
||||
|
||||
In Dockge, edit the SWAG compose file and add Gluetun’s network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks:
|
||||
# ...
|
||||
|
||||
- seedbox
|
||||
|
||||
networks:
|
||||
# ...
|
||||
seedbox:
|
||||
name: seedbox_default
|
||||
external: true
|
||||
```
|
||||
|
||||
Click "Deploy" and wait for SWAG to fully initialize.
|
||||
|
||||
::note
|
||||
|
||||
We assume the network name is `seedbox_default`. You can confirm by checking the SWAG dashboard at http://server-ip:81.
|
||||
::
|
||||
|
||||
Now create/edit `seedbox.subdomain.conf`.
|
||||
|
||||
::tip
|
||||
✨ __Terminal-free tip:__ use [File Browser](/serveex/files/file-browser) to edit files instead of using the terminal.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/seedbox.subdomain.conf
|
||||
```
|
||||
|
||||
Press `i` and paste the following config (check the port):
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name seedbox.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app gluetun;
|
||||
set $upstream_port 5555;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ You can secure this app with Authentik by uncommenting the `authentik-server.conf` and `authentik-location.conf` lines. Don’t forget to [create an app and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||
::
|
||||
|
||||
Press `Esc`, type `:x` to save and quit.
|
||||
|
||||
Wait a few minutes, then go to `https://seedbox.mydomain.com`—you should land on the Qbittorrent interface.
|
||||
|
||||
And that’s it! You now have a ready-to-use media center.
|
||||
|
||||

|
||||
@@ -0,0 +1,534 @@
|
||||
---
|
||||
title: Automation
|
||||
description: Automate media downloads with the Servarr stack — Radarr, Sonarr, Bazarr, Prowlarr, and Overseerr for movies and TV shows.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Servarr
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
Automate movie and TV show downloads using Radarr, Sonarr, Bazarr, Prowlarr, and Overseerr.
|
||||
::
|
||||
|
||||
[Servarr](https://wiki.servarr.com/) is a suite of applications developed to automate the downloading, updating, and management of media. Here, we'll focus on movies and TV shows with the goal of:
|
||||
|
||||
- Selecting a movie from a catalog through a web interface.
|
||||
- Sitting back and enjoying it on Plex a few minutes later.
|
||||
|
||||
Simple.
|
||||
|
||||

|
||||
|
||||
We’ll start by deploying the stack and then proceed to configure each app and understand how they work.
|
||||
|
||||
## Install the Apps
|
||||
---
|
||||
|
||||
### Docker Compose
|
||||
|
||||
Folder structure:
|
||||
|
||||
```sh
|
||||
root
|
||||
├── docker
|
||||
│ ├── plex
|
||||
│ │ ├── compose.yml
|
||||
│ │ ├── config
|
||||
│ │ └── transcode
|
||||
│ ├── tautulli
|
||||
│ │ └── config
|
||||
│ ├── sonarr
|
||||
│ │ └── config
|
||||
│ ├── radarr
|
||||
│ │ └── config
|
||||
│ ├── bazarr
|
||||
│ │ └── config
|
||||
│ ├── prowlarr
|
||||
│ │ └── config
|
||||
│ └── overseerr
|
||||
│ └── config
|
||||
└── media
|
||||
├── downloads
|
||||
├── tvseries
|
||||
├── movies
|
||||
└── library
|
||||
```
|
||||
|
||||
::warning
|
||||
|
||||
__Warning:__ Make sure to follow this file structure carefully, especially the `media` folder. This folder must be mounted **exactly the same way** in both the _Qbittorrent_ compose file (`/your/path/media:/media`) and the _arr_ applications.
|
||||
If not, the _arr_ apps may not recognize the path provided by Qbittorrent and will fail to create _hardlinks_.
|
||||
Without hardlinks, the _arr_ apps will copy the files instead—**doubling the space used** on your storage.
|
||||
::
|
||||
|
||||
Open Docker and your `plex` stack. Modify the compose file as follows:
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
linuxserver_plex:
|
||||
image: ghcr.io/linuxserver/plex:latest
|
||||
container_name: plex
|
||||
network_mode: host
|
||||
environment:
|
||||
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=Europe/Paris
|
||||
- VERSION=docker
|
||||
- PLEX_CLAIM= #optional
|
||||
volumes:
|
||||
|
||||
- /docker/plex/config:/config
|
||||
- /docker/plex/transcode:/transcode #optional
|
||||
- ${MEDIA_PATH}:/media
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
restart: unless-stopped
|
||||
mem_limit: 4096m
|
||||
mem_reservation: 2048m
|
||||
devices:
|
||||
|
||||
- /dev/dri:/dev/dri
|
||||
|
||||
tautulli:
|
||||
image: lscr.io/linuxserver/tautulli:latest
|
||||
container_name: tautulli
|
||||
environment:
|
||||
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
|
||||
- /docker/tautulli/config:/config
|
||||
ports:
|
||||
|
||||
- 8181:8181
|
||||
restart: unless-stopped
|
||||
|
||||
sonarr:
|
||||
image: lscr.io/linuxserver/sonarr:latest
|
||||
container_name: sonarr
|
||||
environment:
|
||||
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
|
||||
- /docker/sonarr/config:/config
|
||||
- ${MEDIA_PATH}:/media
|
||||
ports:
|
||||
|
||||
- 8989:8989
|
||||
restart: unless-stopped
|
||||
|
||||
radarr:
|
||||
image: lscr.io/linuxserver/radarr:latest
|
||||
container_name: radarr
|
||||
environment:
|
||||
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
|
||||
- /docker/radarr/config:/config
|
||||
- ${MEDIA_PATH}:/media
|
||||
ports:
|
||||
|
||||
- 7878:7878
|
||||
restart: unless-stopped
|
||||
|
||||
prowlarr:
|
||||
image: lscr.io/linuxserver/prowlarr:latest
|
||||
container_name: prowlarr
|
||||
environment:
|
||||
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
|
||||
- /docker/prowlarr/data:/config
|
||||
ports:
|
||||
|
||||
- 9696:9696
|
||||
restart: unless-stopped
|
||||
|
||||
overseerr:
|
||||
image: lscr.io/linuxserver/overseerr:latest
|
||||
container_name: overseerr
|
||||
dns:
|
||||
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
environment:
|
||||
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
|
||||
- /docker/overseerr/config:/config
|
||||
ports:
|
||||
|
||||
- 5055:5055
|
||||
restart: unless-stopped
|
||||
|
||||
bazarr:
|
||||
image: lscr.io/linuxserver/bazarr:latest
|
||||
container_name: bazarr
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
|
||||
- /docker/bazarr/config:/config
|
||||
- ${MEDIA_PATH}:/media
|
||||
ports:
|
||||
|
||||
- 6767:6767
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ Add the Watchtower label to each container to automate updates
|
||||
|
||||
```yaml
|
||||
services:
|
||||
plex:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
|
||||
tautulli:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Set your `.env` file with the variables below:
|
||||
|
||||
```properties
|
||||
PUID=
|
||||
GUID=
|
||||
MEDIA_PATH=
|
||||
```
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------------|-------------------------------------------------------------------------------------------------|-------------|
|
||||
| `PUID` | Set using your user info (check with `id yourusername`) | `1000` |
|
||||
| `GUID` | Same as above | `1000` |
|
||||
| `MEDIA_PATH` | Path to your media folder, here: `/media`. It must match the one used by Qbittorrent. | `/media` |
|
||||
|
||||
Deploy the stack.
|
||||
|
||||
### Configure Radarr
|
||||
---
|
||||
|
||||
Radarr queries your torrent sources and lets you define the type of releases to prioritize. It can also upgrade your movies if a better version is available.
|
||||
|
||||
Once deployed, visit `http://yourserverip:7878`.
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
Create an account and choose *forms login*.
|
||||
|
||||
##### Add a *root folder*
|
||||
|
||||
- Go to *Settings > Media Management*.
|
||||
- Add a root folder and select `/media/movies`.
|
||||
|
||||
::warning
|
||||
|
||||
__Warning:__ If you already have movies in `movies` from Qbittorrent, do not let Radarr add them. Radarr might modify them, which could stop seeding in Qbittorrent.
|
||||
::
|
||||
|
||||
##### Configure Profiles
|
||||
|
||||
Go to *Settings > Profiles*. These are your default quality profiles. When you make a request, you're selecting one of these. For example, configure the “any” profile by unchecking everything except what is shown in the image and ordering them accordingly. This makes Radarr search for 4K REMUX first, then go down the list if unavailable.
|
||||
|
||||

|
||||
|
||||
##### Add Qbittorrent
|
||||
|
||||
In *Settings > Download Clients*, add Qbittorrent.
|
||||
|
||||
- Use your server IP as *Host* and port `5695` if following this guide.
|
||||
- Provide your Qbittorrent *Username* and *Password*.
|
||||
- Click *Test*.
|
||||
- If successful, click *Save*.
|
||||
|
||||
##### Connect to Plex
|
||||
|
||||
Go to *Settings > Connect*, add a new connection and choose *Plex Media Server*.
|
||||
|
||||
- Use `plex` or your server IP for *Host*.
|
||||
- Port: `32400`
|
||||
- Click the blue "authenticate with Plex.tv" button and log into Plex.
|
||||
- Press *Test*, then *Save* if successful.
|
||||
|
||||
##### Get API Key for Prowlarr and Overseerr
|
||||
|
||||
- Go to *Settings > General* and copy your *API Key* for later use.
|
||||
|
||||
### Configure Sonarr
|
||||
---
|
||||
|
||||
Sonarr queries torrent sources and defines what kind of TV series releases to prioritize. It also upgrades series when better versions are available.
|
||||
|
||||
- Visit `http://yourserverip:8989`.
|
||||
- Follow the same steps as for Radarr, but use `/media/tvseries` as the root folder.
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
### Configure Prowlarr
|
||||
---
|
||||
|
||||
Prowlarr acts as a proxy to manage your torrent indexers and link them to Radarr and Sonarr.
|
||||
|
||||
Go to `http://yourserverip:9696` and create an account, using *forms login*.
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
##### Add an Indexer
|
||||
|
||||
- Go to the *Indexers* section and add your torrent indexer.
|
||||
|
||||
##### Add Radarr and Sonarr
|
||||
|
||||
In *Settings > Apps*, add Radarr and Sonarr with the following details:
|
||||
|
||||
- Prowlarr Server: `http://prowlarr:9696` (or use server IP)
|
||||
- Sonarr / Radarr Server: `http://sonarr:8989` or `http://radarr:7878`
|
||||
- API Key: use the one copied from Radarr and Sonarr.
|
||||
- Click *Test*, then *Save* if all goes well.
|
||||
|
||||
### Configuring Bazarr
|
||||
---
|
||||
Bazarr is an app that automatically searches for the correct subtitles in your preferred languages for all the movies and TV shows added by Radarr and Sonarr.
|
||||
|
||||
Go to `http://yourserverip:6767`.
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
Go to *Settings > General* and create a username and password using *forms login*.
|
||||
|
||||
#### Add a Language Profile
|
||||
|
||||
- In *Settings > Languages*, click the pink *Add new profile* button and name it.
|
||||
- Click the pink *Add Languages* button and add your preferred languages, e.g., *French* and *English*.
|
||||
- Save and exit.
|
||||
- At the bottom of the screen under *Default Language For Newly Added Show*, check both boxes and select the profile you just created.
|
||||

|
||||
|
||||
- Save using the button at the top of the screen.
|
||||
|
||||
#### Add Subtitle Providers
|
||||
|
||||
- In *Settings > Providers*, add your preferred providers, for example:
|
||||
|
||||

|
||||
|
||||
- Save using the button at the top of the screen.
|
||||
|
||||
#### Add Radarr and Sonarr
|
||||
|
||||
- Go to *Settings > Sonarr*
|
||||
- In *Address*, enter `sonarr` or your server's IP address.
|
||||
- In *Port*, enter `8989`.
|
||||
- In *API Key*, enter Sonarr’s API key.
|
||||
- Click *Test*.
|
||||
- Save using the button at the top of the screen.
|
||||
|
||||
Repeat the same steps for Radarr.
|
||||
|
||||
### Configuring Overseerr
|
||||
---
|
||||
|
||||
[Overseerr](https://overseerr.dev/) is an app that lets you browse a movie catalog and send requests to Sonarr and Radarr. Just browse movies or series, click *Request*, and the media will automatically be downloaded according to your Radarr or Sonarr settings. If the title hasn’t been released yet, it will be downloaded automatically when available. This way, episodes of a series appear in Plex without any manual intervention.
|
||||
|
||||

|
||||
|
||||
Go to `http://yourserverip:5055` and log in with your Plex account.
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
#### Add Radarr and Sonarr
|
||||
|
||||
When prompted, add a Radarr server:
|
||||
|
||||
- Check *Default server*.
|
||||
- __Server name:__ Radarr
|
||||
- __Hostname or IP address:__ `radarr` or your server's IP
|
||||
- __Port:__ `7878`
|
||||
- __API Key:__ Radarr’s API key
|
||||
- Click *Test* at the bottom.
|
||||
|
||||
If the test succeeds, continue filling in the fields:
|
||||
|
||||
- __Quality Profile:__ the one you configured (e.g., `any`)
|
||||
- __Root Folder:__ the Plex folder. In our examples: `/media/movies`
|
||||
- __Minimum Availability:__ `Announced`. This allows requesting unreleased content and downloads it upon release.
|
||||
- Check all 3 boxes at the bottom.
|
||||
- Save and continue.
|
||||
|
||||
Now do the same for Sonarr:
|
||||
|
||||
- Check *Default server*.
|
||||
- __Server name:__ Sonarr
|
||||
- __Hostname or IP address:__ `sonarr` or your server's IP
|
||||
- __Port:__ `8989`
|
||||
- __API Key:__ Sonarr’s API key
|
||||
- Click *Test* at the bottom.
|
||||
|
||||
If the test succeeds, continue filling in the fields:
|
||||
|
||||
- __Quality Profile:__ the one you configured (e.g., `any`)
|
||||
- __Root Folder:__ the Plex folder. In our examples: `/media/tvseries`
|
||||
- __Language Profile:__ `Deprecated`
|
||||
- Check all 4 boxes at the bottom.
|
||||
- Save and continue.
|
||||
|
||||
And that’s it! Just request a movie or series, then check in qBittorrent or Radarr/Sonarr. Within a few minutes, your media will be available on Plex!
|
||||
|
||||
## Exposing Overseerr with SWAG
|
||||
---
|
||||
|
||||
It can be useful to expose Overseerr if you want to send requests from outside your network without a VPN, or if you've shared your Plex library with others and want them to have Overseerr access.
|
||||
|
||||
::note
|
||||
|
||||
We assume you have the subdomain `films.mydomain.com` with a `CNAME` pointing to `films.fr` in your [DNS zone](/general/networking/dns). And that [unless you’re using Cloudflare Zero Trust](/serveex/security/cloudflare), port `443` on your router is forwarded to port `443` on your server via [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
Go to Dockge, edit the SWAG compose file, and add the Overseerr network, which is the same as Plex (since it’s in the Plex stack):
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Connects the container to a custom network
|
||||
# ...
|
||||
|
||||
- plex # Name of the network declared in the stack
|
||||
|
||||
networks: # Defines the custom network
|
||||
# ...
|
||||
plex: # Name of the declared network
|
||||
name: plex_default # Actual name of the external network
|
||||
external: true # Indicates it’s an external network
|
||||
```
|
||||
|
||||
Restart the stack by clicking “Deploy” and wait until SWAG is fully operational.
|
||||
|
||||
::note
|
||||
|
||||
Here we assume the Tautulli network is named `plex_default`. You can verify the connection works by visiting the SWAG dashboard at `http://yourserverip:81`.
|
||||
::
|
||||
|
||||
Create and edit the file `films.subdomain.conf`:
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ you can use [File Browser](/serveex/files/file-browser) to browse and edit files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/films.subdomain.conf
|
||||
```
|
||||
|
||||
Enter insert mode by pressing `i`:
|
||||
|
||||
```nginx
|
||||
## Version 2024/07/16
|
||||
# make sure that your overseerr container is named overseerr
|
||||
# make sure that your dns has a cname set for overseerr
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name films.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app overseerr;
|
||||
set $upstream_port 5055;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
|
||||
location ~ (/overseerr)?/api {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app overseerr;
|
||||
set $upstream_port 5055;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Escape`, then type `:x` and press `Enter` to save and exit.
|
||||
|
||||
Wait a few minutes, then visit `http://films.mydomain.com` in your browser.
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
And there you go, Overseerr is now publicly accessible!
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Cloud Drive & Photos
|
||||
icon: i-lucide-cloud-upload
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: Immich
|
||||
description: Install Immich, a self-hosted alternative to Google Photos and iCloud with face recognition, geolocation, and multi-device sync.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Immich
|
||||
|
||||
::note
|
||||
🎯 __Goals:__ Install [Immich](https://immich.app/docs/overview/introduction) to manage your photos across all your devices.
|
||||
::
|
||||
|
||||
[Immich](https://immich.app/docs/overview/introduction) is a self-hosted photo and video management solution that replaces cloud services like Google Photos or iCloud. It offers powerful features like face recognition and geolocation.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
Folder structure
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── immich
|
||||
├── library
|
||||
├── compose.yaml
|
||||
└── .env
|
||||
```
|
||||
|
||||
Open Dockge, click on `compose`, name the stack `immich`, then copy and paste the latest `docker-compose.yml` [published here](https://github.com/immich-app/immich/blob/main/docker/docker-compose.yml).
|
||||
|
||||
::warning
|
||||
|
||||
__Warning__: Do not add the Watchtower label to the Immich stack. Immich evolves rapidly, and automatic updates may break your installation.
|
||||
::
|
||||
|
||||
Configure the `.env` file by copying the latest version [from here](https://github.com/immich-app/immich/blob/main/docker/example.env) and follow the comments in the file.
|
||||
|
||||
::note
|
||||
|
||||
If you're using a NAS or a network-shared drive via [Samba](/general/networking/samba/) to store your data, replace the value of `UPLOAD_LOCATION`{lang=properties} with the path to your shared folder.
|
||||
::
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ If your CPU/iGPU/GPU supports it, Immich can use hardware acceleration for video playback and image recognition. This can triple performance. Learn more about [Transcoding](https://immich.app/docs/features/hardware-transcoding/) and [Machine Learning](https://immich.app/docs/features/ml-hardware-acceleration).
|
||||
::
|
||||
|
||||
Deploy the container.
|
||||
|
||||
You're done! You can connect and follow the setup instructions at `http://yourserverip:2283`.
|
||||
|
||||
## Exposing Immich with SWAG
|
||||
---
|
||||
The main benefit of this setup is being able to access Immich remotely on all your devices. We'll expose Immich using SWAG.
|
||||
|
||||
::note
|
||||
📋 __Before you begin:__
|
||||
<br/><br/>
|
||||
We assume that you have a subdomain `immich.yourdomain.com` with a `CNAME` pointing to `yourdomain.com` in your [DNS zone](/general/networking/dns). Also, unless you're using [Cloudflare Zero Trust](/serveex/security/cloudflare), make sure port `443` on your router is forwarded to port `443` on your server via [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
In Dockge, open the SWAG stack and edit the compose file to add Immich's network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Connects the container to the custom network
|
||||
# ...
|
||||
|
||||
- immich # Network name defined in the stack
|
||||
|
||||
networks: # Defines the custom network
|
||||
# ...
|
||||
immich: # Network name defined in the stack
|
||||
name: immich_default # Actual external network name
|
||||
external: true # Indicates it's an external network
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
We're assuming Immich's network is named `immich_default`. You can check connectivity by visiting the SWAG dashboard at http://yourserverip:81.
|
||||
::
|
||||
|
||||
Restart the stack by clicking "deploy" and wait for SWAG to fully initialize.
|
||||
|
||||
In the SWAG folders, create a file named `immich.subdomain.conf`.
|
||||
|
||||
::tip
|
||||
|
||||
__Tip:__ You can use [File Browser](/serveex/files/file-browser) to navigate and edit your files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/immich.subdomain.conf
|
||||
```
|
||||
Press `i` to enter insert mode, then paste the following configuration:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name immich.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app immich_server;
|
||||
set $upstream_port 3001;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
|
||||
location ~ (/immich)?/api {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app immich_server;
|
||||
set $upstream_port 3001;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Esc`, type `:x`, then hit `Enter` to save and exit.
|
||||
|
||||
That's it! Immich is now accessible from the internet. Don’t forget to install the [iOS](https://apps.apple.com/us/app/immich/id1613945652) / [Android](https://play.google.com/store/apps/details?id=app.alextran.immich) apps to sync your devices.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can protect this app with Authentik natively by [following these instructions](https://docs.goauthentik.io/integrations/services/immich/).
|
||||
::
|
||||
@@ -0,0 +1,197 @@
|
||||
---
|
||||
title: Nextcloud
|
||||
description: Install Nextcloud to self-host your files, photos, and calendar — a privacy-friendly alternative to Google Drive, OneDrive, and iCloud.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Nextcloud
|
||||
|
||||
::note
|
||||
🎯 __Goals:__ Install [Nextcloud](https://nextcloud.com/) to manage your photos and files across all your devices.
|
||||
::
|
||||
|
||||
[Nextcloud](https://nextcloud.com/) is a self-hosted solution that allows you to access and synchronize your data across all your devices. It also includes collaboration features, calendar, and more. It’s a great alternative to services like Google Drive, iCloud, or OneDrive.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
::note
|
||||
|
||||
We'll be using the Docker image maintained by [LinuxServer.io](https://docs.linuxserver.io/images/docker-nextcloud/)
|
||||
::
|
||||
|
||||
File structure:
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── nextcloud
|
||||
├── config
|
||||
├── data
|
||||
├── compose.yaml
|
||||
└── .env
|
||||
```
|
||||
|
||||
Open Dockge, click on `compose`, name the stack `nextcloud` and paste the following:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
nextcloud:
|
||||
image: lscr.io/linuxserver/nextcloud:latest
|
||||
container_name: nextcloud
|
||||
environment:
|
||||
|
||||
- PUID=${PUID}
|
||||
- PGID=${GUID}
|
||||
- TZ=Etc/UTC
|
||||
volumes:
|
||||
|
||||
- /docker/nextcloud/config:/config
|
||||
- /docker/nextcloud/data:/data
|
||||
ports:
|
||||
|
||||
- ${PORT}:443
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
If you’re using a NAS or network-shared drive via [Samba](/general/networking/samba), replace `/docker/nextcloud/data` with the path to your shared folder.
|
||||
::
|
||||
|
||||
Find your `PUID` and `GUID` by running the following command:
|
||||
|
||||
```sh
|
||||
id username
|
||||
```
|
||||
|
||||
Then fill out the `.env` file with your preferred port and the values found above, for example:
|
||||
|
||||
```properties
|
||||
PUID=1000
|
||||
GUID=1000
|
||||
PORT=4545
|
||||
```
|
||||
|
||||
Deploy the stack and visit `http://yourserverip:4545` to complete the setup.
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
## Exposing Nextcloud with Swag
|
||||
---
|
||||
The goal of this setup is to access Nextcloud remotely from all your devices. We’ll use Swag to expose the app.
|
||||
|
||||
::note
|
||||
|
||||
We assume you have a subdomain `nextcloud.yourdomain.com` with a `CNAME` pointing to `yourdomain.com` in your [DNS zone](/general/networking/dns). And unless you’re using [Cloudflare Zero Trust](/serveex/security/cloudflare), port `443` on your router must be forwarded to port `443` on your server using [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
In Dockge, go to your SWAG stack and edit the compose to add Nextcloud's network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks:
|
||||
# ...
|
||||
|
||||
- nextcloud
|
||||
|
||||
networks:
|
||||
# ...
|
||||
nextcloud:
|
||||
name: nextcloud_default
|
||||
external: true
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
We assume the Nextcloud network is named `nextcloud_default`. You can confirm connectivity by visiting the SWAG dashboard at http://yourserverip:81.
|
||||
::
|
||||
|
||||
Redeploy the stack and wait for SWAG to become fully operational.
|
||||
|
||||
In Nextcloud’s files, edit the `config.php` file:
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can use [File Browser](/serveex/files/file-browser) to navigate and edit files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/nextcloud/config/www/nextcloud/config/config.php
|
||||
```
|
||||
|
||||
Enter edit mode with `i` and paste the following before the final `);`:
|
||||
|
||||
```php
|
||||
'trusted_proxies' => [gethostbyname('swag')],
|
||||
'overwrite.cli.url' => 'https://nextcloud.example.com/',
|
||||
'overwritehost' => 'nextcloud.example.com',
|
||||
'overwriteprotocol' => 'https',
|
||||
```
|
||||
|
||||
Also add your domain in the `array` section. It should look like this:
|
||||
|
||||
```php
|
||||
array (
|
||||
0 => '192.168.0.1:444', # This line may differ—don’t change it!
|
||||
1 => 'nextcloud.yourdomain.com', # Add your domain here
|
||||
),
|
||||
```
|
||||
|
||||
Press `Esc`, then save and exit by typing `:x` and hitting Enter.
|
||||
|
||||
In Swag’s folders, create the file `nextcloud.subdomain.conf`:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/nextcloud.subdomain.conf
|
||||
```
|
||||
|
||||
Enter edit mode with `i` and paste the following:
|
||||
|
||||
```nginx
|
||||
## Version 2024/04/25
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name nextcloud.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
location / {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app nextcloud;
|
||||
set $upstream_port 443;
|
||||
set $upstream_proto https;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
# Hide proxy response headers from Nextcloud that conflict with ssl.conf
|
||||
proxy_hide_header Referrer-Policy;
|
||||
proxy_hide_header X-Content-Type-Options;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header X-XSS-Protection;
|
||||
|
||||
# Disable proxy buffering
|
||||
proxy_buffering off;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Esc`, save and exit with `:x` then Enter.
|
||||
|
||||
That’s it—you’ve exposed Nextcloud! Don’t forget to install [the desktop and mobile apps](https://nextcloud.com/install/).
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can natively protect this app with Authentik by [following these instructions](https://docs.goauthentik.io/integrations/services/nextcloud/).
|
||||
::
|
||||
@@ -0,0 +1,2 @@
|
||||
title: File & share
|
||||
icon: i-lucide-folder-tree
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
title: File Browser
|
||||
description: Install File Browser to browse and manage your server files from a web interface, exposed securely with SWAG.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# File Browser
|
||||
|
||||
::note
|
||||
🎯 __Objectives:__
|
||||
|
||||
- Install File Browser
|
||||
- Expose File Browser using Swag
|
||||
::
|
||||
|
||||
[File Browser](https://github.com/filebrowser/filebrowser) is a web-based interface that lets you access and edit the files on your server.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
Open Dockge, click on `compose`, name the stack `filebrowser`, then copy and paste the following:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
filebrowser:
|
||||
container_name: filebrowser
|
||||
volumes:
|
||||
|
||||
- /docker/filebrowser/config:/config/
|
||||
- /path/to/your/folders:/yourfolders #add your folders to browse as /docker:/docker for exemple
|
||||
ports:
|
||||
|
||||
- 8010:80
|
||||
image: filebrowser/filebrowser:s6
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Add the watchtower label to each container to automate updates.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
filebrowser:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Deploy the container and go to `http://yourserverip:8010`. That’s it—your File Browser web UI is up and running!
|
||||
|
||||
::caution
|
||||
|
||||
__If it doesn’t work:__ check your firewall rules.
|
||||
::
|
||||
|
||||
## Exposing File Browser with Swag
|
||||
---
|
||||
|
||||
::warning
|
||||
|
||||
File Browser does not support multi-factor authentication. Exposing it publicly could put your systems at risk. Only do this if you’re using a secure authentication solution like [Authentik](/serveex/security/authentik/). Otherwise, do not expose it with SWAG—use a VPN like [Wireguard](/serveex/security/wireguard) instead.
|
||||
::
|
||||
|
||||
You may want to access File Browser remotely from all your devices. To do that, we’ll expose it through Swag.
|
||||
|
||||
::note
|
||||
|
||||
__Pre-requisite:__ We assume you've already created a subdomain like `files.yourdomain.com` in your [DNS zone](/general/networking/dns) pointing to `yourdomain.com` with a `CNAME`, and—unless you're using Cloudflare Zero Trust—have already forwarded port `443` on your router to port `443` on your server using [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
In Dockge, go to the SWAG stack and edit the compose file to add File Browser’s network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Connects the container to the custom network
|
||||
# ...
|
||||
|
||||
- filebrowser # Name of the network declared in the stack
|
||||
|
||||
networks: # Defines the custom network
|
||||
# ...
|
||||
filebrowser: # Name of the network declared in the stack
|
||||
name: filebrowser_default # Actual name of the external network
|
||||
external: true # Specifies it's an external network
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
Here, we assume the network name for File Browser is `filebrowser_default`. You can confirm the connection is working by accessing the SWAG dashboard at http://yourserverip:81.
|
||||
::
|
||||
|
||||
Restart the stack by clicking "deploy" and wait for SWAG to fully initialize.
|
||||
|
||||
In the Swag folders, create the file `files.subdomain.conf`.
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/files.subdomain.conf
|
||||
```
|
||||
|
||||
Enter insert mode by pressing `i`, and paste the following configuration:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name files.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app filebrowser;
|
||||
set $upstream_port 80;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Esc`, then save and exit with `:x` followed by `Enter`.
|
||||
|
||||
That’s it—File Browser is now exposed!
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can protect this app with Authentik by opening `files.subdomain.conf` and uncommenting `include /config/nginx/authentik-server.conf;`{lang=nginx} and `include /config/nginx/authentik-location.conf;`{lang=nginx}. Don’t forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||
::
|
||||
@@ -0,0 +1,211 @@
|
||||
---
|
||||
title: Pingvin
|
||||
description: Install Pingvin Share, a self-hosted file sharing platform to send files securely without relying on WeTransfer or Google Drive.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Pingvin
|
||||
|
||||
::note
|
||||
🎯 __Objectives:__
|
||||
|
||||
- Install Pingvin
|
||||
- Expose Pingvin
|
||||
::
|
||||
|
||||
[Pingvin](https://github.com/stonith404/pingvin-share) is a tool for quickly sharing files, similar to WeTransfer. Its many sharing options (password, expiration time, custom link, etc.) make it the ideal tool for sharing files quickly. Pingvin can also create _upload requests_, i.e. a shareable link you can send to someone so they can upload their files for you to retrieve.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
Open Dockge, click `compose`, name the stack `pingvin`, then copy-paste this:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
pingvin-share:
|
||||
container_name: pingvin
|
||||
image: stonith404/pingvin-share
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
||||
- 3600:3000
|
||||
volumes:
|
||||
|
||||
- /docker/pingvin/data:/opt/app/backend/data
|
||||
- /docker/pingvin/data/img:/opt/app/frontend/public/img
|
||||
- /docker/pingvin/uploads:/opt/app/backend/uploads # path to the folder where you want to store files uploaded to pingvin. Change to your preference.
|
||||
depends_on:
|
||||
clamav:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
|
||||
- swag
|
||||
|
||||
clamav: #antivirus for the files
|
||||
restart: unless-stopped
|
||||
image: clamav/clamav
|
||||
```
|
||||
::note
|
||||
|
||||
From here on, we assume the network name for Swag is `swag_default`.
|
||||
::
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Add the watchtower label to each container to automate updates.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
pingvin-share:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
|
||||
clamav:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Deploy the container and go to `http://yourserverip:3600`. That's it—your Pingvin web UI instance is up and running!
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
## Exposing Pingvin with Swag
|
||||
---
|
||||
The whole point of a solution like this is being able to access it remotely, from all your devices. To do this, we'll expose Pingvin through Swag.
|
||||
|
||||
::note
|
||||
📋 __Prerequisite:__ <br/><br/>
|
||||
We assume you have the subdomain `pingvin.mydomain.com` with a `CNAME` pointing to `mydomain.com` in your [DNS zone](/general/networking/dns). And of course, [unless you're using Cloudflare Zero Trust](/serveex/security/cloudflare), port `443` on your router is forwarded to port `443` on your server via [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
In Dockge, go to the SWAG stack and edit the compose file to add the pingvin network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Connects the container to the custom network
|
||||
# ...
|
||||
|
||||
- pingvin # Name of the network declared in the stack
|
||||
|
||||
networks: # Defines the custom network
|
||||
# ...
|
||||
pingvin: # Name of the network declared in the stack
|
||||
name: pingvin_default # Actual name of the external network
|
||||
external: true # States that it's a network to look up externally
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
From here on, we assume the pingvin network name is `pingvin_default`. You can verify the connection is working by visiting the SWAG dashboard at http://yourserverip:81.
|
||||
::
|
||||
|
||||
Redeploy the stack by clicking "deploy" and wait for SWAG to be fully up.
|
||||
|
||||
In the Swag folders, create the `pingvin.subdomain.conf` file.
|
||||
|
||||
::tip
|
||||
|
||||
__Tip:__ you can use [File Browser](/serveex/files/file-browser) to browse your files and edit your documents instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/pingvin.subdomain.conf
|
||||
```
|
||||
Press `i` to enter edit mode and paste the configuration below:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name pingvin.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app pingvin;
|
||||
set $upstream_port 3000;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Press `Escape`, then save and quit by typing `:x` and pressing `Enter`.
|
||||
|
||||
That's it, you've exposed Pingvin!
|
||||
|
||||
## Securing Pingvin with Authentik
|
||||
|
||||
You can protect this app natively with Authentik by following the instructions below.
|
||||
|
||||
1. In your Authentik admin area, create an OAuth2/OpenID provider.
|
||||
|
||||
2. Fill in each section as follows, replacing `mydomain.com` with your own domain. Copy the `Client ID` and `Client Secret` fields somewhere safe.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
3. Save and create a `pingvin` application as follows.
|
||||
|
||||

|
||||
|
||||
4. Save and go to your list of outposts. Add the pingvin provider to your outpost.
|
||||
|
||||
5. Leave Authentik, and go to Pingvin's admin interface.
|
||||
|
||||
6. In the _"OAuth"_ section, fill in the following fields:
|
||||
- `OpenID discovery URI` with `https://pingvin.mydomain.com/application/o/pingvin/.well-known/openid-configuration` (don't forget to replace `mydomain.com` with your own domain)
|
||||
- `OpenID username claim` with `preferred_username`
|
||||
- `OpenID client ID` with the ID you copied in step 2.
|
||||
- `OpenID client secret` with the token you copied in step 2.
|
||||
|
||||
That's it—from now on, when you log in to Pingvin, an "Open ID" button will be available below the login form.
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Developpement
|
||||
icon: i-lucide-code-xml
|
||||
@@ -0,0 +1,226 @@
|
||||
---
|
||||
title: Code-Server
|
||||
description: Install code-server to run VS Code in your browser from your homelab — mount folders and expose it securely with SWAG.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Code-Server
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Install code-server
|
||||
- Mount folders into VS Code
|
||||
- Expose code-server with Swag
|
||||
::
|
||||
|
||||
[code-server](https://github.com/linuxserver/docker-code-server) is a container that lets you access [VS Code](https://code.visualstudio.com/) via a web UI in a Linux environment. It's literally VS Code and your projects in your pocket, available anywhere.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
::note
|
||||
|
||||
For this setup, we’ll use the [image maintained by LinuxServer.io](https://docs.linuxserver.io/images/docker-code-server/).
|
||||
::
|
||||
|
||||
Folder structure
|
||||
|
||||
```sh
|
||||
root
|
||||
├── docker
|
||||
│ └── code-server
|
||||
│ └── config
|
||||
└── #any folder you want to mount in VS Code
|
||||
```
|
||||
|
||||
Open Dockge, click on `compose`, name the stack `code-server`, and paste the following:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
code-server:
|
||||
image: lscr.io/linuxserver/code-server:latest
|
||||
container_name: code-server
|
||||
environment:
|
||||
|
||||
- PUID=${PUID}
|
||||
- PGID=${GUID}
|
||||
- TZ=Etc/UTC
|
||||
- HASHED_PASSWORD=${PW}
|
||||
volumes:
|
||||
|
||||
- /docker/code-server/config:/config
|
||||
# add folders to mount in VS Code
|
||||
# - /path/to/folder:/folder
|
||||
ports:
|
||||
|
||||
- 8443:8443
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ Add the Watchtower label to each container to automate updates
|
||||
|
||||
```yaml
|
||||
services:
|
||||
code-server:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Choose a password and generate its hash:
|
||||
|
||||
```sh
|
||||
echo -n "yourpassword" | npx argon2-cli -e
|
||||
```
|
||||
|
||||
Save the result carefully. Find your PUID and GUID with:
|
||||
|
||||
```sh
|
||||
id yourusername
|
||||
```
|
||||
|
||||
Fill in the `.env` file with the values you found, for example:
|
||||
|
||||
```properties
|
||||
PW='$argon2i$v=19$m=4096,t=3,p=1$wST5QhBgk2lu1ih4DMuxvg$LS1alrVdIWtvZHwnzCM1DUGg+5DTO3Dt1d5v9XtLws4'
|
||||
PUID=1000
|
||||
GUID=1000
|
||||
```
|
||||
|
||||
::warning
|
||||
|
||||
__Note:__ Make sure to wrap the hash in single quotes `'`
|
||||
::
|
||||
|
||||
Deploy the container and go to `http://yourserverip:8443`. Voilà, your code-server instance is up and running in the browser!
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
## Mount Folders
|
||||
---
|
||||
You can mount folders into VS Code by adding the relevant volumes in `compose.yaml` (or via Dockge), then redeploy the container.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
code-server:
|
||||
#...
|
||||
volumes:
|
||||
|
||||
- /path/to/folder:/folder
|
||||
```
|
||||
Once inside VS Code, you'll have access to the mounted folder.
|
||||
|
||||
## Expose code-server with Swag
|
||||
---
|
||||
The whole point of such a solution is to access it remotely from any device. To do this, we’ll expose code-server via Swag.
|
||||
|
||||
::note
|
||||
|
||||
__Preliminary:__ We assume you’ve created a subdomain like `code.yourdomain.com` with a `CNAME` pointing to `yourdomain.com` in your [DNS zone](/general/networking/dns), and—unless you're using [Cloudflare Zero Trust](/serveex/security/cloudflare)—that you’ve forwarded port `443` from your router to port `443` on your server using [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
In Dockge, go to the SWAG stack and edit the compose file to add code-server’s network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Connects the container to a custom network
|
||||
# ...
|
||||
|
||||
- code-server # Name of the network defined in the stack
|
||||
|
||||
networks: # Defines the custom network
|
||||
# ...
|
||||
code-server: # Name of the network defined in the stack
|
||||
name: code-serveur # Actual name of the external network
|
||||
external: true # Indicates it’s an external network
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
We assume the network name is `code-server_default`. You can verify that the connection works by visiting the SWAG dashboard at http://yourserverip:81.
|
||||
::
|
||||
|
||||
Redeploy the stack by clicking “deploy” and wait until SWAG is fully operational.
|
||||
|
||||
Inside the Swag config folders, create the file `code.subdomain.conf`.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can use [File Browser](/serveex/files/file-browser) to navigate and edit your files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/code.subdomain.conf
|
||||
```
|
||||
|
||||
Enter insert mode with `i` and paste the following configuration:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name code.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app code-server;
|
||||
set $upstream_port 8443;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Esc`, then save and exit by typing `:x` and pressing `Enter`.
|
||||
|
||||
That’s it — code-server is now exposed!
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can protect this app with Authentik by opening `code.subdomain.conf` and uncommenting the lines `include /config/nginx/authentik-server.conf;` and `include /config/nginx/authentik-location.conf;`. Don’t forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||
::
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
title: Gitea
|
||||
description: Install Gitea, a lightweight self-hosted Git service to manage your code repositories privately on your own server.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Gitea
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Install Gitea
|
||||
- Expose Gitea using Swag
|
||||
::
|
||||
|
||||
[Gitea](https://about.gitea.com/) is a self-hosted DevOps platform that allows you to manage repositories much like GitHub, but on your own infrastructure.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
Folder structure
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── gitea
|
||||
└── data
|
||||
```
|
||||
|
||||
Open Dockge, click on `compose`, name the stack `gitea`, and paste the following content:
|
||||
|
||||
```yaml
|
||||
---
|
||||
networks:
|
||||
gitea:
|
||||
external: false
|
||||
services:
|
||||
server:
|
||||
image: gitea/gitea:1.22.0
|
||||
container_name: gitea
|
||||
environment:
|
||||
|
||||
- USER_UID=${UID}
|
||||
- USER_GID=${GID}
|
||||
- TZ=Europe/Paris
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
|
||||
- gitea
|
||||
volumes:
|
||||
|
||||
- ./data:/data
|
||||
ports:
|
||||
|
||||
- 3333:3000
|
||||
- 222:22
|
||||
```
|
||||
|
||||
Fill out the `.env` file with the required information, for example:
|
||||
|
||||
```properties
|
||||
UID=1000
|
||||
GID=1000
|
||||
```
|
||||
|
||||
Deploy the container and go to `http://yourserverip:3333`. Your Gitea instance is now up and running!
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
## Exposing Gitea with Swag
|
||||
---
|
||||
The benefit of this setup is being able to access it remotely from any of your devices. To do so, we’ll expose Gitea through Swag.
|
||||
|
||||
::note
|
||||
|
||||
__Prerequisite:__ We assume you have created a subdomain such as `gitea.yourdomain.com` in your [DNS zone](/general/networking/dns) with `CNAME` pointing to `yourdomain.com`, and [unless you're using Cloudflare Zero Trust](/serveex/security/cloudflare), you have already forwarded port `443` from your router to your server’s port `443` in the [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
In Dockge, go to the SWAG stack and edit the compose file by adding Gitea's network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Connect the container to the custom network
|
||||
# ...
|
||||
|
||||
- gitea # Name of the declared network
|
||||
|
||||
networks: # Define the custom network
|
||||
# ...
|
||||
gitea: # Name of the declared network
|
||||
name: gitea_default # Actual external network name
|
||||
external: true # Indicates it's an external network
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
We assume the Gitea network name is `gitea_default`. You can verify connectivity by visiting the SWAG dashboard at http://yourserverip:81.
|
||||
::
|
||||
|
||||
Redeploy the stack by clicking "Deploy" and wait until SWAG is fully operational.
|
||||
|
||||
Inside the Swag folders, create the file `gitea.subdomain.conf`.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can use [File Browser](/serveex/files/file-browser) to navigate and edit your files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/gitea.subdomain.conf
|
||||
```
|
||||
Press `i` to enter edit mode and paste the configuration below:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name gitea.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app gitea;
|
||||
set $upstream_port 3000;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
|
||||
location ~ (/gitea)?/info/lfs {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app gitea;
|
||||
set $upstream_port 3000;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Esc`, then save and exit by typing `:x` and hitting `Enter`.
|
||||
|
||||
Now open the `app.ini` file from the container's file system:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/gitea/data/gitea/conf/app.ini
|
||||
```
|
||||
|
||||
Press `i` to edit, then modify the server section with your domain information:
|
||||
|
||||
```properties
|
||||
[server]
|
||||
DOMAIN = gitea.yourdomain.com
|
||||
SSH_DOMAIN = gitea.yourdomain.com
|
||||
ROOT_URL = https://gitea.yourdomain.com/
|
||||
```
|
||||
|
||||
Press `Esc`, save and exit with `:x`, then restart the container.
|
||||
|
||||
And that’s it! Gitea is now exposed to the web.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can natively protect this app with Authentik by [following these instructions](https://docs.goauthentik.io/integrations/services/gitea/).
|
||||
::
|
||||
@@ -0,0 +1,167 @@
|
||||
---
|
||||
title: IT Tools
|
||||
description: Install IT Tools, a self-hosted collection of handy utilities for developers — converters, encoders, formatters, and more.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# IT Tools
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Install IT Tools
|
||||
- Expose IT Tools with Swag
|
||||
::
|
||||
|
||||
[IT Tools](https://github.com/CorentinTh/it-tools) is a container exposing a web page that provides access to a wide range of development tools.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
|
||||
Open Dockge, click on `compose`, name the stack `it-tools`, and paste the following:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
it-tools:
|
||||
container_name: it-tools
|
||||
restart: unless-stopped
|
||||
image: corentinth/it-tools:latest
|
||||
ports:
|
||||
|
||||
- 3222:80
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Add the Watchtower label to each container to enable automatic updates.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
it-tools:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Deploy the container and visit `http://yourserverip:3222`. That’s it, your IT Tools web UI instance is up and running!
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
## Expose IT Tools with Swag
|
||||
---
|
||||
You might want to access it remotely on all your devices. To do that, we'll expose IT Tools using Swag.
|
||||
|
||||
::note
|
||||
|
||||
__Pre-requisite:__ We assume you’ve created a subdomain like `tools.yourdomain.com` in your [DNS zone](/general/networking/dns) with `CNAME` set to `yourdomain.com`. Also, unless you’re using [Cloudflare Zero Trust](/serveex/security/cloudflare), make sure you’ve already forwarded port `443` from your router to port `443` on your server in the [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
In Dockge, go to the SWAG stack and edit the compose file to add the IT Tools network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Connects the container to the custom network
|
||||
# ...
|
||||
|
||||
- it-tools # Network name as defined in the IT Tools stack
|
||||
|
||||
networks: # Defines the custom network
|
||||
# ...
|
||||
it-tools: # Network name as defined in the IT Tools stack
|
||||
name: it-tools_default # Actual name of the external network
|
||||
external: true # Indicates it's an external network
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
We assume the IT Tools network is named `it-tools_default`. You can check connectivity by visiting the SWAG dashboard at http://yourserverip:81.
|
||||
::
|
||||
|
||||
::note
|
||||
|
||||
We also assume the SWAG network is named `swag_default`.
|
||||
::
|
||||
|
||||
Restart the stack by clicking "deploy" and wait for SWAG to be fully operational.
|
||||
|
||||
Inside the Swag folders, create the file `tools.subdomain.conf`.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can use [File Browser](/serveex/files/file-browser) to navigate and edit your files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/tools.subdomain.conf
|
||||
```
|
||||
|
||||
Enter edit mode by pressing `i` and paste the configuration below:
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name tools.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app it-tools;
|
||||
set $upstream_port 80;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Esc`, then save and exit by typing `:x` and pressing `Enter`.
|
||||
|
||||
And that’s it — IT Tools is now exposed!
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can secure this app with Authentik by opening `tools.subdomain.conf` and uncommenting the lines `include /config/nginx/authentik-server.conf;` and `include /config/nginx/authentik-location.conf;`. Don’t forget to [create an application and a provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||
::
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Useful Apps
|
||||
icon: i-lucide-award
|
||||
@@ -0,0 +1,304 @@
|
||||
---
|
||||
title: Adguard Home
|
||||
description: Install AdGuard Home for network-wide ad and tracker blocking with DNS-over-HTTPS, client management, and custom filtering rules.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Adguard Home
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Install and deploy Adguard
|
||||
- Expose Adguard
|
||||
- Secure DNS queries with SSL/TLS
|
||||
- Configure client devices
|
||||
::
|
||||
|
||||
[AdGuard Home](https://github.com/AdguardTeam/AdGuardHome) is a DNS server that blocks ads and tracking at the system level. Once configured, it will protect ALL your home devices without the need for any client-side software.
|
||||
|
||||
It works as a DNS server that redirects tracking domains to a “black hole,” preventing your devices from connecting to them.
|
||||
|
||||
In practice, once it's in place, all you need to do is set your devices to use Adguard as their DNS server.
|
||||
|
||||
**Quick reminder of how DNS works:**
|
||||
|
||||
When you visit a site or use an app, it makes requests to various domains to load content—ads in particular. Your device doesn’t know the IP addresses of these domains, so it contacts a _Domain Name Server_ (DNS), which returns the current IP address.
|
||||
|
||||
By default, your device uses your ISP's DNS server, which is usually configured in your router or, for mobile devices, at the carrier’s CGNAT level. You can change this in your browser settings, your device’s system settings, or even directly in your router, depending on your ISP.
|
||||
|
||||
Adguard will act as a middleman between your device and the upstream DNS servers. If you configure your devices to use Adguard:
|
||||
|
||||
- If the domain is not in a blocklist, Adguard queries the upstream DNS servers and returns the correct IP to your device.
|
||||
- If the domain *is* in a blocklist, Adguard will block the request and return nothing, so the associated content won’t load.
|
||||
|
||||
This is how ads and malicious domains are blocked—Adguard blocks only the bad domains, allowing the rest of the page to load normally.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
Folder structure:
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── adguard
|
||||
├── confdir
|
||||
├── workdir
|
||||
├── compose.yaml
|
||||
└── .env
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
We will also mount the `/docker/swag/config/etc/letsencrypt` folder to access Swag's SSL certificate.
|
||||
::
|
||||
|
||||
Open Dockge and click `compose`
|
||||
|
||||
Name the stack `adguardhome` and paste the configuration below:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
adguardhome:
|
||||
container_name: adguard
|
||||
image: adguard/adguardhome
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
||||
- 53:53/udp
|
||||
- 8080:80/tcp
|
||||
- 4443:443/tcp
|
||||
- 853:853/tcp
|
||||
- 3000:3000/tcp
|
||||
volumes:
|
||||
|
||||
- /docker/adguardhome/confdir:/opt/adguardhome/conf
|
||||
- /docker/adguardhome/workdir:/opt/adguardhome/work
|
||||
- /docker/swag/config/etc/letsencrypt:/swag-ssl:ro
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Add the watchtower label to each container to automate updates
|
||||
|
||||
```yaml
|
||||
services:
|
||||
adguardhome:
|
||||
# ...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Deploy the stack.
|
||||
|
||||
Go to `http://yourserverip:3000` and follow the setup instructions.
|
||||
|
||||
That’s it! Adguard is deployed.
|
||||
|
||||
## Exposing AdGuard with SWAG
|
||||
---
|
||||
To make AdGuard usable from outside your home network, you need to expose it.
|
||||
|
||||
::note
|
||||
|
||||
__Prerequisites:__ We assume you've created a subdomain like `adguard.mydomain.com` in your [DNS zone](/general/networking/dns) with a `CNAME` pointing to `mydomain.com`, and that you’ve already forwarded port `443` from your router to port `443` on your server in your [NAT rules](/general/networking/nat). Also forward port `53` and port `853` to your server. These ports are used to route DNS requests.
|
||||
::
|
||||
|
||||
::warning
|
||||
|
||||
Do not use Cloudflare tunnels to expose AdGuard, and make sure any proxying is disabled.
|
||||
::
|
||||
|
||||
In Dockge, go to the SWAG stack and edit the compose file to add the AdGuard network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Connect the container to the custom network
|
||||
# ...
|
||||
|
||||
- adguard # Name of the network declared in the stack
|
||||
|
||||
networks: # Define the custom network
|
||||
# ...
|
||||
adguard: # Name of the network declared in the stack
|
||||
name: adguard_default # Actual name of the external network
|
||||
external: true # Specifies that this is an external network
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
We assume here that the AdGuard network is named `adguard_default`. You can verify the connection is working by visiting the SWAG dashboard at http://yourserverip:81.
|
||||
::
|
||||
|
||||
Restart the stack by clicking "Deploy" and wait for SWAG to be fully operational.
|
||||
|
||||
Create and open the file `adguard.subdomain.conf`
|
||||
|
||||
::tip
|
||||
✨ __Tip for terminal haters:__
|
||||
You can use [File Browser](/serveex/files/file-browser) to browse and edit files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/adguard.subdomain.conf
|
||||
```
|
||||
|
||||
Edit the file by pressing `i` and then pasting the configuration below:
|
||||
|
||||
```nginx
|
||||
## Version 2023/05/31
|
||||
# make sure that your adguard container is named adguard
|
||||
# make sure that your dns has a cname set for adguard
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name adguard.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app adguard;
|
||||
set $upstream_port 3000;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
|
||||
location /control {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app adguard;
|
||||
set $upstream_port 3000;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
|
||||
location /dns-query {
|
||||
# to properly use this please set `allow_unencrypted_doh: true` and `force_https: false` in adguard
|
||||
# see https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#configuration-file
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app adguard;
|
||||
set $upstream_port 3000;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__
|
||||
<br/><br/>
|
||||
You can protect this app with Authentik by opening `adguard.subdomain.conf` and removing the `#` in front of `include /config/nginx/authentik-server.conf;`{lang=nginx} and `include /config/nginx/authentik-location.conf;`{lang=nginx}. Don’t forget to [create an application and a provider in Authentik](/serveex/security/authentik/#protéger-une-app-par-reverse-proxy). You’ll need to exclude the URL `https://adguard.mydomain.com/dns-query` from authentication:
|
||||
|
||||
- Edit the AdGuard provider
|
||||
- Under *Advanced Protocol Settings > Authenticated Paths*, enter `^/dns-query`
|
||||
::
|
||||
|
||||
Press `Esc`, then save and exit by typing `:x`
|
||||
|
||||
And that's it! AdGuard is now exposed!
|
||||
|
||||
## Configure SSL/TLS Encryption
|
||||
---
|
||||
Encryption is essential if you want to keep your queries to AdGuard private. Encrypting your queries ensures that no one—not even your ISP—can see your history. It also ensures that only your server can respond to you.
|
||||
|
||||
To configure encryption:
|
||||
|
||||
- Go to _Settings_ then _Encryption_.
|
||||
- Set the options as follows:
|
||||
|
||||

|
||||
|
||||
- Below, in the _Certificates_ section, check _Use file path for certificate_
|
||||
- In the input field, enter `/swag-ssl/live/mydomain.com/fullchain.pem`, replacing `mydomain.com` with your actual domain.
|
||||
- For _Private Key_, check _Use file path for private key_
|
||||
- In the input field, enter `/swag-ssl/live/mydomain.com/privkey.pem`, replacing `mydomain.com` accordingly.
|
||||
- Save
|
||||
|
||||
Done! Your future DNS queries are now protected!
|
||||
|
||||
## Configure Devices
|
||||
---
|
||||
You have several options (which you can combine) to configure your devices.
|
||||
### Secure the Local Network
|
||||
You can secure your local network with AdGuard by configuring your router to direct all DNS queries by default to AdGuard instead of your ISP’s DNS. Note: your router must allow DNS changes (Orange routers do not).
|
||||
|
||||
This option is usually in the _DHCP_ settings. Make sure to add a fallback DNS server such as:
|
||||
|
||||
- Cloudflare: `1.1.1.1`
|
||||
- Google: `8.8.8.8`
|
||||
|
||||
Without this, if your server goes down, your devices will lose internet access.
|
||||
|
||||
::note
|
||||
|
||||
Some devices may have a separate DNS configured and may not use the router’s DNS.
|
||||
::
|
||||
|
||||
### Force a Browser to Use AdGuard
|
||||
|
||||
In your browser, you can configure a DNS to force it to use AdGuard Home.
|
||||
In the settings, specify the address `https://adguard.mydomain.com/dns-query`
|
||||
|
||||
### Configure AdGuard at the System Level on Windows
|
||||
|
||||
In Windows, you need to configure AdGuard for each network adapter you want to use.
|
||||
|
||||
- Go to _Home > Network & Internet_, then select the network adapter to modify
|
||||
- Click _Edit DNS_ (sometimes under _Hardware Properties_)
|
||||
- Choose `Manual`
|
||||
- Enable IPv4
|
||||
- Enter your server’s public IP (the one accessible from the internet)
|
||||
- Enable _DNS over HTTPS (manual template)_
|
||||
- Disable _Fallback to plaintext_
|
||||
- Save
|
||||
|
||||
All programs using that network adapter will now be filtered by AdGuard.
|
||||
|
||||
## Add Filters
|
||||
---
|
||||
|
||||
- Go to the settings and change the filters.
|
||||
@@ -0,0 +1,248 @@
|
||||
---
|
||||
title: Vaultwarden
|
||||
description: Install Vaultwarden, a self-hosted Bitwarden-compatible password manager to replace Google or Apple password managers across all your devices.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Vaultwarden
|
||||
|
||||
::note
|
||||
🎯 __Goals:__ Install [Vaultwarden](https://github.com/dani-garcia/vaultwarden) to manage your passwords across all your devices (a replacement for Google or Apple password managers).
|
||||
::
|
||||
|
||||

|
||||
|
||||
[Vaultwarden](https://github.com/dani-garcia/vaultwarden) is a password management solution (generation, autofill...) that you can host directly on your server. This replaces managers like Google, Apple, or Keepass. Vaultwarden synchronizes your passwords across all your devices with end-to-end encryption.
|
||||
|
||||
Vaultwarden is a fork of [Bitwarden](https://bitwarden.com/fr-fr/help/).
|
||||
|
||||
## Installation
|
||||
---
|
||||
Folder structure:
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── vaultwarden
|
||||
├── data
|
||||
├── compose.yaml
|
||||
└── .env
|
||||
```
|
||||
|
||||
Open Dockge, click on `compose`, name the stack `vaultwarden`, and paste the following:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
vaultwarden:
|
||||
container_name: vaultwarden
|
||||
image: vaultwarden/server:latest
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
volumes:
|
||||
|
||||
- ./data/:/data/
|
||||
ports:
|
||||
|
||||
- 3050:80
|
||||
environment:
|
||||
|
||||
- DOMAIN=${URL}
|
||||
- LOGIN_RATELIMIT_MAX_BURST=10
|
||||
- LOGIN_RATELIMIT_SECONDS=60
|
||||
- ADMIN_RATELIMIT_MAX_BURST=10
|
||||
- ADMIN_RATELIMIT_SECONDS=60
|
||||
- ADMIN_TOKEN=${TOKEN}
|
||||
- SENDS_ALLOWED=true
|
||||
- EMERGENCY_ACCESS_ALLOWED=true
|
||||
- WEB_VAULT_ENABLED=true
|
||||
- SIGNUPS_ALLOWED=false
|
||||
- SIGNUPS_VERIFY=true
|
||||
- SIGNUPS_VERIFY_RESEND_TIME=3600
|
||||
- SIGNUPS_VERIFY_RESEND_LIMIT=5
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Add the Watchtower label in each container to automate updates
|
||||
|
||||
```yaml
|
||||
services:
|
||||
vaultwarden:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Next, generate a password hash to put in the `TOKEN` variable in `.env`:
|
||||
|
||||
```sh
|
||||
echo -n 'yourpassword' | argon2 "$(openssl rand -base64 32)" -e -id -k 65540 -t 3 -p 4
|
||||
```
|
||||
|
||||
Copy the result securely.
|
||||
|
||||
In the `.env` file, enter the following variables:
|
||||
|
||||
```properties
|
||||
URL=
|
||||
TOKEN=
|
||||
```
|
||||
|
||||
| Variable | Value | Example |
|
||||
|----------|-------|---------|
|
||||
| `URL` | The URL of your Vaultwarden server | `https://vault.yourdomain.com` |
|
||||
| `TOKEN` | The token you previously copied | `'$argon2id$v=19$m=65540,t=3,p=4$bXBGME...` |
|
||||
|
||||
Then deploy the container.
|
||||
|
||||
Recently, Vaultwarden requires SSL to be accessed, which prevents access via a local IP. We'll expose it with SWAG, which provides an SSL certificate.
|
||||
|
||||
::caution
|
||||
|
||||
__If it fails:__ check your firewall rules.
|
||||
::
|
||||
|
||||
## Exposing Vaultwarden with SWAG
|
||||
---
|
||||
The main benefit of Vaultwarden is being able to access it remotely from any device. We'll expose it through [SWAG](/serveex/core/swag).
|
||||
|
||||
::note
|
||||
✨ __Before you start:__ Make sure you've created a DNS subdomain like `vault.yourdomain.com` with `CNAME` pointing to `yourdomain.com` and (unless using Cloudflare Zero Trust) that you've forwarded port `443` from your router to your server's `443` via [NAT rules](/general/networking/nat).
|
||||
::
|
||||
|
||||
In Dockge, go to the SWAG stack and edit the compose file to add the Vaultwarden network:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: # ...
|
||||
# ...
|
||||
networks: # Connects container to custom network
|
||||
# ...
|
||||
|
||||
- vaultwarden # Name of the declared network
|
||||
|
||||
networks: # Defines the custom network
|
||||
# ...
|
||||
vaultwarden: # Name of the declared network
|
||||
name: vaultwarden_default # Actual name of the external network
|
||||
external: true
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
We're assuming the network name is `vaultwarden_default`. You can check connectivity by visiting the SWAG dashboard at http://yourserverip:81.
|
||||
::
|
||||
|
||||
Restart the stack by clicking "Deploy" and wait for SWAG to be fully operational.
|
||||
|
||||
In SWAG's config folder, create the file `vault.subdomain.conf`:
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Use [File Browser](/serveex/files/file-browser) to navigate and edit files instead of using terminal commands.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/vault.subdomain.conf
|
||||
```
|
||||
|
||||
Press `i` to edit, and paste the following configuration:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name vault.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 128M;
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app vaultwarden;
|
||||
set $upstream_port 80;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
|
||||
location ~ ^(/vaultwarden)?/admin {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app vaultwarden;
|
||||
set $upstream_port 80;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
|
||||
location ~ (/vaultwarden)?/api {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app vaultwarden;
|
||||
set $upstream_port 80;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
|
||||
location ~ (/vaultwarden)?/notifications/hub {
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
set $upstream_app vaultwarden;
|
||||
set $upstream_port 80;
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Press `Esc`, then type `:x` and press `Enter` to save and exit.
|
||||
|
||||
And there you go — Vaultwarden is now exposed! Visit `https://vault.yourdomain.com/admin` to access the admin panel and paste the password you specified when generatique the `ADMIN_TOKEN`. For more information, see the [Bitwarden documentation](https://bitwarden.com/help/).
|
||||
|
||||
Don't forget to install Bitwarden browser extensions (they work with Vaultwarden) for [Chrome](https://chromewebstore.google.com/detail/gestionnaire-de-mots-de-p/nngceckbapebfimnlniiiahkandclblb) and [Firefox](https://addons.mozilla.org/fr/firefox/addon/bitwarden-password-manager/), as well as [iOS](https://apps.apple.com/fr/app/bitwarden/id1137397744) and [Android](https://play.google.com/store/apps/details?id=com.x8bit.bitwarden&hl=fr) apps to sync your passwords.
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ You can protect this app with Authentik by opening `tools.subdomain.conf` and removing the `#` in front of `include /config/nginx/authentik-server.conf;` and `include /config/nginx/authentik-location.conf;`. Don't forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
|
||||
::
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Stockeex
|
||||
icon: i-noto-computer-disk
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: Introduction to Stockeex — a personal project for stock and inventory management. Documentation coming soon.
|
||||
navigation:
|
||||
icon: i-lucide-bookmark
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Stockeex
|
||||
|
||||
```console
|
||||
sudo systemctl status stockeex-article
|
||||
currently writing, come back later...
|
||||
--
|
||||
```
|
||||
<div align="center">
|
||||
<img src="/img/stockeex/stockeex-raid.svg" alt="Image" style="max-width: 60%;">
|
||||
</div>
|
||||
@@ -0,0 +1,2 @@
|
||||
title: My nonsense
|
||||
icon: i-noto-test-tube
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Python
|
||||
icon: i-lucide-file-code-2
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Nvidia Stock Bot
|
||||
description: A Python bot that monitors GPU stock availability in real time and sends Discord alerts — built during the RTX 5000 series launch shortage.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# 🤖 Nvidia Stock Bot
|
||||
---
|
||||
|
||||
For the past four years, the electronics hardware shortage has been relentless. Graphics cards are no exception. In 2020, I had to wait two months to get my RTX 3080. To manage it, I joined [JV Hardware](https://discord.gg/gxffg3GA96), where a small group of geeks had set up a bot that pinged users when GPUs became available.
|
||||
|
||||
Four years later and with 5,000 members on the server, the RTX 5000 series is being released. Yet, no working stock bot seems to exist. Not to mention a certain “influencer” who charges users for access to a bot that doesn’t even work. He manually copies alerts from other servers like ours, which have already solved the issue.
|
||||
|
||||
Anyway, eager to get an RTX 5090 for my AI-dedicated machine, I decided it was time to dive into Python—with a little help from ChatGPT. Along with another member, KevOut, who helped guide me through the APIs and initial architecture, I ended up building a clean and functional bot that sends different kinds of Discord alerts—all deployable in a simple Docker container.
|
||||
|
||||
After many setbacks, I went from this:
|
||||
|
||||

|
||||
|
||||
To this:
|
||||
|
||||

|
||||
|
||||
And more recently :
|
||||
|
||||

|
||||
|
||||
And I was also lucky enough to be referenced in the famous [selfhost newsletter](https://selfh.st/weekly/2025-07-11/) !
|
||||
|
||||
More info directly on the repo:
|
||||
|
||||
::card{title="🐋 __Nvidia Stock Bot__" to="https://git.djeex.fr/Djeex/nvidia-stock-bot" target="_blank"}
|
||||
Nvidia GPU stock alert bot
|
||||
::
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Adguard CIDRE
|
||||
description: A Python script to sync AdGuard Home CIDR allowlists automatically, securing your self-hosted DNS server exposed to the internet.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# 🤖 Adguard CIDRE Sync
|
||||
---
|
||||
|
||||
Adguard Home is a fantastic solution for DNS-level ad blocking and rewriting requests—perfect for removing ISP DNS trackers or intrusive ads.
|
||||
|
||||
It works great locally, but if you want all your devices (even on the go) to benefit, you’ll need to expose Adguard to the internet. Unfortunately, that means anyone can use it, potentially overloading your €1/month remote VPS.
|
||||
|
||||
Adguard allows whitelisting or blacklisting clients. The problem? To whitelist a client, you need their IP—but for mobile phones, that IP changes often. Instead of trying to whitelist ever-changing IPs, the better approach is to block broader IP ranges by region.
|
||||
|
||||
CIDRE is a tool that syncs geo-targeted IP ranges with firewalls. Instead of running CIDRE with a full firewall stack on the remote server, I figured I could just import those regularly updated IP ranges into Adguard’s blocklist.
|
||||
|
||||
Thus, Adguard CIDRE Sync was born: a container that syncs Adguard’s blocklist with CIDRE’s updated IP ranges on a schedule of your choosing.
|
||||
|
||||
The idea is to:
|
||||
|
||||
- Backup Adguard’s config file on first run (original untouched version saved)
|
||||
- Download selected country IP ranges via an environment variable
|
||||
- Let you manually add custom IPs via a file
|
||||
- Concatenate, backup the config again (as the updated version), and inject the list into the correct blocklist section
|
||||
- Reload Adguard by restarting the container (using Docker socket proxy for limited permissions)
|
||||
|
||||
All fully autonomous, with frequency set via environment variable in the `docker-compose` config.
|
||||
|
||||
More info directly on the repo:
|
||||
|
||||
::card{title="🐋 __Adguard CIDRE Sync__" to="https://git.djeex.fr/Djeex/adguard-cidre" target="_blank"}
|
||||
Adguard blocklist sync bot
|
||||
::
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: Lumeex
|
||||
description: Lumeex is a static photo gallery site generator built with Python — minimalist, lightweight, and fully customizable without a CMS.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
<div align="center">
|
||||
<img src="https://git.djeex.fr/Djeex/lumeex/raw/branch/main/illustration/logo.svg" alt="Lumeex Screenshot" width="300"/>
|
||||
</div>
|
||||
<div align="center">
|
||||
<p>Yet another minimalist, lightweight photo gallery static site generator.</p>
|
||||
</div>
|
||||
<div align="center">
|
||||
<img src="https://git.djeex.fr/Djeex/lumeex/raw/branch/main/illustration/lumeex.png" alt="Lumeex Screenshot" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
Amateur photographer that I am, I spent several weeks looking for a framework with a photo gallery that could outshine Instagram. I wanted something that showcased the photos rather than the author, and that made every visit unique by loading the images in random order—while still allowing filtering and sorting by tag or combinations of tags.
|
||||
|
||||
In the end, I found nothing that did exactly what I wanted. And when something came close, it was always through heavy, bloated CMS platforms. So I decided to make a static site by hand, the old-school way, with Notepad++. Being fairly comfortable with HTML/CSS and a bit of JavaScript, I quickly came up with something nice during my vacation, between beach sessions. After all, a good craftsman should have good tools—and there’s no better tool than one you make yourself.
|
||||
|
||||
Then I thought it might be a good idea to automate certain tasks—like generating favicon formats, resizing and converting images, creating the gallery automatically instead of entering everything by hand, and generating `robots.txt` and `sitemap` files… so I turned back to Python.
|
||||
|
||||
Eventually, after getting good results, I figured I might as well go all the way: build a complete framework for generating a static site photo gallery, where all you need to do is fill in your site’s information in a config file and tweak the visuals a bit—without touching the code.
|
||||
|
||||
That’s how **Lum[eex]{style="color: #1ad6ff"}** was born.
|
||||
|
||||
<div align="center">
|
||||
<img src="https://git.djeex.fr/Djeex/lumeex/raw/branch/main/illustration/lumeex-webui.png" alt="Lumeex Screenshot" />
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Et voilà!
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-noto-open-book" title="Documentation" to="https://lumeex.djeex.fr" target="_blank"}
|
||||
Check out the doc
|
||||
::
|
||||
|
||||
::card{icon="i-simple-icons-gitea" title="Repository" to="https://git.djeex.fr/Djeex/lumeex" target="_blank"}
|
||||
See the repo
|
||||
::
|
||||
|
||||
::card{icon="i-fluent-color-design-ideas-48" title="Demo" to="https://modern.djeex.fr" target="_blank"}
|
||||
Explore the demo
|
||||
::
|
||||
::
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Instameex
|
||||
description: Instameex is a Docker-based tool to merge SDR and HDR photo exports into a proper gain-map JPEG ready for Instagram HDR upload.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
<div align="center">
|
||||
<img src="https://git.djeex.fr/Djeex/instameex/raw/branch/main/src/assets/img/logo-long.svg" alt="Instameex Screenshot" width="300"/>
|
||||
</div>
|
||||
<div align="center">
|
||||
<p>Mix your SDR and HDR exports into an Instagram-ready HDR photo.</p>
|
||||
</div>
|
||||
<div align="center">
|
||||
<img src="https://git.djeex.fr/Djeex/instameex/raw/branch/main/illustration/instameex-illustration.png" width="640" alt="Instameex Screenshot" />
|
||||
</div>
|
||||
---
|
||||
|
||||
Nothing is more frustrating than Instagram's HDR handling. It compresses and destroys gain maps, and the slightest change in aspect ratio or size simply strips HDR out entirely. As for Lightroom, its "SDR preview" system is frankly unacceptable, it makes it impossible to get consistent results. Until now, posting on Instagram meant choosing between decent SDR with broken HDR, or the other way around.
|
||||
|
||||
Why not simply edit your SDR file to perfection on one side, your HDR file on the other, and then recalculate a gain map from those two perfect files?
|
||||
A few pioneers have already gone down that road, notably with an [Adobe Lightroom Classic](https://github.com/karachungen/lightroom-plugin-export-hdr) plugin. Judge me if you want, but I only use Lightroom CC, which does not support plugins.
|
||||
|
||||
I drew inspiration from a [fork of the original project](https://github.com/kostis-kounadis/instagram-hdr-assembler), the one that eventually became the LrC plugin, to build a frontend that can be easily deployed with Docker. Let's be honest: it was also a great excuse to put my Claude Code subscription to the test. And I have to say, watching it spin up its own environments, run end-to-end tests, self-correct its code, and write detailed summaries is genuinely impressive. I still reviewed everything myself, don't worry. I also learned a great deal about HDR fundamentals, gain maps, HLG/PQ tone curves, color spaces, and more.
|
||||
|
||||
In short, here is what my workflow now looks like for posting on Instagram:
|
||||
|
||||

|
||||
|
||||
Allow me to introduce **Instam[eex]{style="color: #1ad6ff"}**
|
||||
|
||||
---
|
||||
### And here is the result
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{title="🐋 __Instameex__" to="https://git.djeex.fr/Djeex/instameex" target="_blank"}
|
||||
Open the repository
|
||||
::
|
||||
|
||||
::card{title="🌍 __Online version__" to="https://instameex.djeex.fr" target="_blank"}
|
||||
Convert online
|
||||
::
|
||||
::
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Bash
|
||||
icon: i-lucide-file-terminal
|
||||
@@ -0,0 +1,142 @@
|
||||
---
|
||||
title: Servarr corrector
|
||||
description: A bash script to detect and fix duplicate media files in Sonarr and Radarr libraries by replacing copies with hardlinks to reclaim disk space.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Servarr duplicates corrector
|
||||
---
|
||||
|
||||
Six months after downloading terabytes of media, I realized that Sonarr and Radarr were copying them into my Plex library instead of creating hardlinks. This happens due to a counterintuitive mechanism: if you mount multiple folders in Sonarr/Radarr, it sees them as different filesystems and thus cannot create hardlinks. That’s why you should mount only one parent folder containing all child folders (like `downloads`, `movies`, `tvseries` inside a `media` parent folder).
|
||||
|
||||
So I restructured my directories, manually updated every path in Qbittorrent, Plex, and others. The last challenge was finding a way to detect existing duplicates, delete them, and automatically create hardlinks instead—to save space.
|
||||
|
||||
My directory structure:
|
||||
|
||||
```sh
|
||||
.
|
||||
└── media
|
||||
├── seedbox
|
||||
├── radarr
|
||||
│ └── tv-radarr
|
||||
├── movies
|
||||
└── tvseries
|
||||
```
|
||||
|
||||
The originals are in `seedbox` and must not be modified to keep seeding. The copies (duplicates) are in `movies` and `tvseries`. To complicate things, there are also unique originals in `movies` and `tvseries`. And within those, there can be subfolders, sub-subfolders, etc.
|
||||
|
||||
So the idea is to:
|
||||
|
||||
- list the originals in seedbox
|
||||
- list files in movies and tvseries
|
||||
- compare both lists and isolate duplicates
|
||||
- delete the duplicates
|
||||
- hardlink the originals to the deleted duplicate paths
|
||||
|
||||
Yes, I asked ChatGPT and Qwen3 (which I host on a dedicated AI machine). Naturally, they suggested tools like rfind, rdfind, dupes, rdupes, rmlint... But hashing 30TB of media would take days, so I gave up quickly.
|
||||
|
||||
In the end, I only needed to find `.mkv` files, and duplicates have the exact same name as the originals, which simplifies things a lot. A simple Bash script would do the job.
|
||||
|
||||
Spare you the endless Q&A with ChatGPT—I was disappointed. Qwen3 was much cleaner. ChatGPT kept pushing awk-based solutions, which fail on paths with spaces. With Qwen’s help and dropping awk, the results improved significantly.
|
||||
|
||||
To test, I first asked for a script that only lists and compares:
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
|
||||
# Create an associative array to store duplicates
|
||||
declare -A seen
|
||||
|
||||
# Find all .mkv files only (exclude directories)
|
||||
find /media/seedbox /media/movies /media/tvseries -type f -name "*.mkv" -print0 | \
|
||||
while IFS= read -r -d '' file; do
|
||||
# Get the file's inode and name
|
||||
inode=$(stat --format="%i" "$file")
|
||||
filename=$(basename "$file")
|
||||
|
||||
# If the filename has been seen before
|
||||
if [[ -n "${seen[$filename]}" ]]; then
|
||||
# Check if the inode is different from the previous one
|
||||
if [[ "${seen[$filename]}" != "$inode" ]]; then
|
||||
# Output the duplicates with full paths
|
||||
echo "Duplicates for \"$filename\":"
|
||||
echo "${seen["$filename"]} ${seen["$filename:full_path"]}"
|
||||
echo "$inode $file"
|
||||
echo
|
||||
fi
|
||||
else
|
||||
seen[$filename]="$inode"
|
||||
seen["$filename:full_path"]="$file"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
This gave me outputs like:
|
||||
|
||||
```
|
||||
Duplicates for "episode1.mkv":
|
||||
1234567 /media/seedbox/sonarr/Serie 1/Season1/episode1.mkv
|
||||
2345678 /media/tvseries/Serie 1/Season1/episode1.mkv
|
||||
```
|
||||
|
||||
With `awk`, it would’ve stopped at `/media/seedbox/sonarr/Serie`. I’m far from an expert, but Qwen3 performed better and explained everything clearly.
|
||||
|
||||
Once I verified the output, I asked for a complete script: compare, delete duplicates, create hardlinks.
|
||||
|
||||
Again, ChatGPT disappointed. Despite my requests, it created hardlinks *before* deleting the duplicates—effectively linking and then deleting the link (though the original is kept). Not helpful.
|
||||
|
||||
Quick stopover to Qwen3, RTX 5090 in overdrive, and bam—much better result. Yes, it kept ChatGPT-style emojis, but here it is:
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
|
||||
echo "🔍 Step 1: Indexing original files in /media/seedbox..."
|
||||
declare -A seen
|
||||
|
||||
# Index all .mkv files in seedbox
|
||||
while IFS= read -r -d '' file; do
|
||||
filename=$(basename "$file")
|
||||
seen["$filename"]="$file"
|
||||
done < <(find /media/seedbox -type f -name "*.mkv" -print0)
|
||||
|
||||
echo "📦 Step 2: Automatically replacing duplicates..."
|
||||
total_doublons=0
|
||||
total_ko_saved=0
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
filename=$(basename "$file")
|
||||
original="${seen[$filename]}"
|
||||
|
||||
if [[ -n "$original" && "$original" != "$file" ]]; then
|
||||
inode_orig=$(stat -c %i "$original")
|
||||
inode_dupe=$(stat -c %i "$file")
|
||||
|
||||
if [[ "$inode_orig" != "$inode_dupe" ]]; then
|
||||
size_kb=$(du -k "$file" | cut -f1)
|
||||
echo "🔁 Replacing:"
|
||||
echo " Duplicate : $file"
|
||||
echo " Original : $original"
|
||||
echo " Size : ${size_kb} KB"
|
||||
|
||||
rm "$file" && ln "$original" "$file" && echo "✅ Hardlink created."
|
||||
|
||||
total_doublons=$((total_doublons + 1))
|
||||
total_ko_saved=$((total_ko_saved + size_kb))
|
||||
fi
|
||||
fi
|
||||
done < <(find /media/movies /media/tvseries -type f -name "*.mkv" -print0)
|
||||
|
||||
echo ""
|
||||
echo "🧾 Summary:"
|
||||
echo " 🔗 Duplicates replaced by hardlink: $total_doublons"
|
||||
echo " 💾 Approx. disk space saved: ${total_ko_saved} KB (~$((total_ko_saved / 1024)) MB)"
|
||||
echo "✅ Done."
|
||||
```
|
||||
|
||||
So, in conclusion, I:
|
||||
|
||||
- Learned many Bash subtleties
|
||||
- Learned never to blindly copy-paste a ChatGPT script without understanding and dry-running it
|
||||
- Learned that Qwen on a RTX 5090 is more coherent than ChatGPT-4o on server farms (not even mentioning “normal” ChatGPT)
|
||||
- Learned that even with 100TB of storage, monitoring it would’ve alerted me much earlier to the 12TB of duplicates lying around
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: LUKS Backup
|
||||
description: A bash script to automatically dump LUKS headers from all encrypted disks, identify them by serial number, and store them in an encrypted archive.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Backup of LUKS Headers for Encrypted Disks/Volumes
|
||||
---
|
||||
|
||||
I recently realized that having just the password is not enough to unlock a LUKS volume after a failure or corruption. I learned how to dump the LUKS headers from disks/volumes and to use the serial numbers along with partition names to accurately identify which header corresponds to which disk/partition (I have 10 of them!).
|
||||
|
||||
After struggling to do this manually, I asked Qwen3 (an LLM running on my RTX 5090) to create a script that automates the listing and identification of disks, dumps the headers, and stores them in an encrypted archive ready to be backed up on my backup server.
|
||||
|
||||
This script:
|
||||
|
||||
* Lists and identifies disks with their serial numbers
|
||||
* Lists partitions
|
||||
* Dumps headers into a secured folder under `/root`
|
||||
* Creates a temporary archive
|
||||
* Prompts for a password
|
||||
* Encrypts the archive with that password
|
||||
* Deletes the unencrypted archive
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
|
||||
# Directory where LUKS headers will be backed up
|
||||
DEST="/root/luks-headers-backup"
|
||||
mkdir -p "$DEST"
|
||||
|
||||
echo "🔍 Searching for LUKS containers on all partitions..."
|
||||
|
||||
# Loop through all possible disk partitions (including NVMe and SATA)
|
||||
for part in /dev/sd? /dev/sd?? /dev/nvme?n?p?; do
|
||||
# Skip if the device doesn't exist
|
||||
if [ ! -b "$part" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if the partition is a LUKS encrypted volume
|
||||
if cryptsetup isLuks "$part"; then
|
||||
# Find the parent disk device (e.g. nvme0n1p4 → nvme0n1)
|
||||
disk=$(lsblk -no pkname "$part" | head -n 1)
|
||||
full_disk="/dev/$disk"
|
||||
|
||||
# Get the serial number of the parent disk
|
||||
SERIAL=$(udevadm info --query=all --name="$full_disk" | grep ID_SERIAL= | cut -d= -f2)
|
||||
if [ -z "$SERIAL" ]; then
|
||||
SERIAL="unknown"
|
||||
fi
|
||||
|
||||
# Extract the partition name (e.g. nvme0n1p4)
|
||||
PART_NAME=$(basename "$part")
|
||||
|
||||
# Build the output filename with partition name and disk serial
|
||||
OUTPUT="$DEST/luks-header-${PART_NAME}__${SERIAL}.img"
|
||||
|
||||
echo "🔐 Backing up LUKS header of $part (Serial: $SERIAL)..."
|
||||
|
||||
# Backup the LUKS header to the output file
|
||||
cryptsetup luksHeaderBackup "$part" --header-backup-file "$OUTPUT"
|
||||
if [[ $? -eq 0 ]]; then
|
||||
echo "✅ Backup successful → $OUTPUT"
|
||||
else
|
||||
echo "❌ Backup failed for $part"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Create a timestamped compressed tar archive of all header backups
|
||||
ARCHIVE_NAME="/root/luks-headers-$(date +%Y%m%d_%H%M%S).tar.gz"
|
||||
echo "📦 Creating archive $ARCHIVE_NAME..."
|
||||
tar -czf "$ARCHIVE_NAME" -C "$DEST" .
|
||||
|
||||
# Encrypt the archive symmetrically using GPG with AES256 cipher
|
||||
echo "🔐 Encrypting the archive with GPG..."
|
||||
gpg --symmetric --cipher-algo AES256 "$ARCHIVE_NAME"
|
||||
if [[ $? -eq 0 ]]; then
|
||||
echo "✅ Encrypted archive created: ${ARCHIVE_NAME}.gpg"
|
||||
# Remove the unencrypted archive for security
|
||||
rm -f "$ARCHIVE_NAME"
|
||||
else
|
||||
echo "❌ Encryption failed"
|
||||
fi
|
||||
```
|
||||
|
||||
**Don’t forget to back up `/etc/fstab` and `/etc/crypttab` as well!**
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Socat Proxy
|
||||
description: Use socat to proxy the Docker socket through Docker Socket Proxy, allowing Beszel to collect container stats without exposing the full Docker socket.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Socat Proxy
|
||||
---
|
||||
|
||||
This project addresses a common use case:
|
||||
|
||||
- I have [Beszel](https://beszel.dev/), a monitoring container running in host mode, which requires access to the Docker socket to collect container statistics.
|
||||
- To avoid exposing the Docker socket fully to Beszel, I use [Docker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy), a container that sits between the Docker socket and the consuming container. It filters requests by setting appropriate permissions, preventing full exposure of the Docker socket.
|
||||
|
||||
The problem arises when **Beszel** runs in host mode. In that case, it must connect directly to **Docker Socket Proxy** on a host port, meaning the proxy’s port is exposed. This allows any container or application on the host to access it and use the Docker socket.
|
||||
|
||||
This is where [Socat Proxy](https://git.djeex.fr/Djeex/socat-proxy) comes in. It is a container that:
|
||||
|
||||
- Creates a UNIX socket
|
||||
- Listens on this socket
|
||||
- Forwards requests to Docker Socket Proxy and back
|
||||
- Replaces the real Docker socket by exposing the proxy socket in the target container via a bind mount (in this case, Beszel)
|
||||
|
||||
With this setup, Docker Socket Proxy communicates with Socat Proxy in their isolated bridge network, while the UNIX socket bind-mounted on the host has restricted permissions, preventing access from other containers or applications.
|
||||
|
||||
In short:
|
||||
|
||||

|
||||
|
||||
For example, with Beszel, the configuration would look like this:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
socat-proxy:
|
||||
image: git.djeex.fr/djeex/socat-proxy:latest
|
||||
container_name: socat-proxy-beszel
|
||||
environment:
|
||||
|
||||
- TARGET_HOST=${TARGET_HOST}
|
||||
- TARGET_PORT=${TARGET_PORT}
|
||||
- UNIX_SOCKET_PATH=${UNIX_SOCKET_PATH}
|
||||
- HOST_SOCKET_PATH=${HOST_SOCKET_PATH}
|
||||
- UNIX_SOCKET_NAME=${UNIX_SOCKET_NAME}
|
||||
volumes:
|
||||
|
||||
- ${HOST_SOCKET_PATH}:${UNIX_SOCKET_PATH}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
|
||||
- ${TARGET_HOST}
|
||||
|
||||
socket-proxy:
|
||||
image: lscr.io/linuxserver/socket-proxy:latest
|
||||
container_name: ${TARGET_HOST}
|
||||
security_opt:
|
||||
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
|
||||
- CONTAINERS=1
|
||||
- INFO=1
|
||||
volumes:
|
||||
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
restart: unless-stopped
|
||||
read_only: true
|
||||
tmpfs:
|
||||
|
||||
- /run
|
||||
|
||||
beszel-agent:
|
||||
image: henrygd/beszel-agent:latest
|
||||
container_name: beszel-agent
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
security_opt:
|
||||
|
||||
- no-new-privileges:true
|
||||
volumes:
|
||||
|
||||
- ${HOST_SOCKET_PATH}/${UNIX_SOCKET_NAME}:/var/run/docker.sock:ro
|
||||
environment:
|
||||
|
||||
- #... your Beszel environment variables
|
||||
depends_on:
|
||||
|
||||
- socat-proxy
|
||||
```
|
||||
|
||||
More information is available on the repository:
|
||||
|
||||
::card{title="🐋 **Socat Proxy**" to="https://git.djeex.fr/Djeex/socat-proxy" target="_blank"}
|
||||
A lightweight bind-mount socket proxy
|
||||
::
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: HotDisk
|
||||
description: A bash script that monitors hard drive temperatures and automatically shuts down the server when disks stay above a safe threshold for too long.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# HotDisk
|
||||
---
|
||||
|
||||
When you have a NAS with several drives sitting in a laundry room, temperatures can quickly rise.
|
||||
Hard drives are very sensitive to heat and can suffer serious damage if they exceed a certain temperature threshold for too long.
|
||||
After a particularly hot summer that caused a few cold sweats while monitoring my drives’ temperatures, I started looking for a way to automatically shut down the server when disk temperatures stay above their safe limit for an extended period.
|
||||
|
||||
Since I couldn’t find a convincing solution, I decided to build my own.
|
||||
|
||||
- The script reads SMART temperature data from all SATA drives every minute.
|
||||
- It counts the number of consecutive minutes the temperature stays above or below the threshold.
|
||||
- It sends Discord notifications if the threshold is exceeded or when the temperature cools down.
|
||||
- It triggers a system shutdown if the temperature stays above the limit for the configured duration.
|
||||
- It logs all temperatures and counter states, and automatically rotates log files.
|
||||
|
||||
While I was at it, I also added an installation script that installs the main script, makes it executable, creates a systemd service and timer, and enables them automatically.
|
||||
The installer also lets you configure various parameters:
|
||||
|
||||
| Variable | Description | Default Value |
|
||||
|-----------------------|------------------------------------------------------------------------------|-----------------------------------------------|
|
||||
| `MAX_TEMP` | Maximum allowed temperature (°C) before the shutdown countdown starts | `60` |
|
||||
| `HOT_DURATION` | Consecutive minutes above `MAX_TEMP` before shutdown | `5` |
|
||||
| `COOL_RESET_DURATION` | Consecutive minutes below `MAX_TEMP` to reset all counters | `5` |
|
||||
| `LOG_FILE` | Path to the main log file | `/var/log/hdd_temp_monitor.log` |
|
||||
| `LOG_ROTATE_COUNT` | Number of log files to keep | `7` |
|
||||
| `LOG_ROTATE_PERIOD` | Log rotation period (`daily` or `weekly`) | `daily` |
|
||||
| `DISCORD_WEBHOOK` | Discord webhook URL for notifications | _Required_ |
|
||||
|
||||
It also runs another script that configures **logrotate** with the parameters defined above.
|
||||
Finally, the installer can even be executed directly via a simple `curl` command followed by one last setup script — perfect for the laziest of us.
|
||||
|
||||
I also had to handle several tricky cases: running as root without sudo, using sudo directly, running as a non-sudo user, missing dependencies, permission issues, file creation errors, disk data reading errors, and more.
|
||||
|
||||
Concurrent access to the status file also had to be managed carefully.
|
||||
|
||||
More details are available directly on the repository:
|
||||
|
||||
::card{title="📜 __HotDisk__" to="https://git.djeex.fr/Djeex/hotdisk" target="_blank"}
|
||||
Keep your drives cool!
|
||||
::
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
title: Backrest Docker Stop
|
||||
description: A bash script that stops Docker containers before a Backrest backup runs and restarts them after — ensuring safe database backups without complex dumps.
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Backrest Docker Stop
|
||||
---
|
||||
|
||||
[Backrest](https://github.com/garethgeorge/backrest) is a fantastic backup tool. In the case of [Serveex](https://docu.djeex.fr/en/serveex/introduction), most of the data that needs to be backed up consists of containers, and those containers often include databases.
|
||||
|
||||
The problem? You can’t safely back up a database while it’s running. There are plenty of complex solutions involving database dumps, but often the simplest method is to stop the containers, perform the backup, and then restart them.
|
||||
|
||||
**Backrest** doesn’t natively provide this functionality, but it does allow the execution of custom scripts triggered by events, for example, at the start and end of a backup plan. Our goal is to stop the containers whose databases need to be backed up when the backup plan starts, and restart them when the backup plan finishes.To achieve this, we’ll need a small Bash script and a secure connection between Backrest and the Docker socket, to enable the following sequence:
|
||||
|
||||
- The backup plan starts
|
||||
- The event triggers the execution of a custom script
|
||||
- The script contacts Docker and retrieves a list of containers labeled `backrest.backup.stop=true`
|
||||
- It stops those containers
|
||||
- The backup plan completes
|
||||
- The event triggers another custom script
|
||||
- The script contacts Docker again, retrieves the same list, and restarts those containers
|
||||
|
||||
## Securely Connecting Backrest and Docker
|
||||
|
||||
To allow **Backrest** to communicate securely with Docker, we’ll use [Docker Socket Proxy](https://github.com/linuxserver/docker-socket-proxy).
|
||||
This avoids exposing the full Docker socket and grants only the necessary permissions.
|
||||
Here’s an example Docker stack:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
backrest:
|
||||
image: garethgeorge/backrest:latest
|
||||
container_name: backrest
|
||||
hostname: backrest
|
||||
security_opt:
|
||||
|
||||
- no-new-privileges:true
|
||||
volumes:
|
||||
|
||||
- ... # your volumes
|
||||
environment:
|
||||
|
||||
- ... # your environment variables
|
||||
- DOCKER_HOST=tcp://socket-proxy-backrest:2375
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
||||
- ... # your ports
|
||||
depends_on:
|
||||
|
||||
- socket-proxy
|
||||
|
||||
socket-proxy:
|
||||
image: lscr.io/linuxserver/socket-proxy:latest
|
||||
container_name: socket-proxy-backrest
|
||||
security_opt:
|
||||
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
|
||||
- CONTAINERS=1
|
||||
- ALLOW_START=1
|
||||
- ALLOW_STOP=1
|
||||
volumes:
|
||||
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
restart: unless-stopped
|
||||
read_only: true
|
||||
tmpfs:
|
||||
|
||||
- /run
|
||||
```
|
||||
|
||||
With this setup, Backrest can communicate with Docker safely and securely.
|
||||
|
||||
## The Scripts
|
||||
|
||||
Below are the scripts to use for **Backrest**’s *start* and *end* backup events.
|
||||
|
||||
::code-group
|
||||
```sh [Stop]
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BACKUP_LABEL="backrest.backup.stop=true"
|
||||
BACKUP_CONTAINERS=$(docker ps -aqf "label=$BACKUP_LABEL")
|
||||
for BC in $BACKUP_CONTAINERS
|
||||
do
|
||||
docker stop "$BC"
|
||||
done
|
||||
sleep 10
|
||||
```
|
||||
|
||||
```sh [Start]
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BACKUP_LABEL="backrest.backup.stop=true"
|
||||
BACKUP_CONTAINERS=$(docker ps -aqf "label=$BACKUP_LABEL")
|
||||
for BC in $BACKUP_CONTAINERS
|
||||
do
|
||||
docker start "$BC"
|
||||
done
|
||||
sleep 10
|
||||
```
|
||||
::
|
||||
|
||||
## The Label
|
||||
|
||||
Once the scripts are in place and configured for the proper **Backrest** hooks, you just need to add the label `backrest.backup.stop=true` to the `compose.yaml` files of the containers that should stop and restart during backups:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
your_service:
|
||||
...
|
||||
labels:
|
||||
|
||||
- backrest.backup.stop=true
|
||||
```
|
||||
|
||||
And that’s it!
|
||||
At the next backup, all containers with the correct label will automatically stop during the backup and restart once it’s finished.
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Recycled
|
||||
icon: i-noto-recycling-symbol
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Deprecated
|
||||
icon: i-lucide-trash-2
|
||||
@@ -0,0 +1,264 @@
|
||||
---
|
||||
title: Wireguard 14
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Wireguard
|
||||
|
||||
::note
|
||||
🎯 __Goals:__
|
||||
|
||||
- Install Wireguard
|
||||
- Configure clients
|
||||
- Access the secure network
|
||||
::
|
||||
|
||||
## Introduction
|
||||
---
|
||||
Using a VPN allows remote access to a server’s local resources without exposing them to the internet. It’s a clean and secure way to access services like SSH without exposing the port publicly. With a VPN, you can securely connect to your network from anywhere and make devices on different networks communicate.
|
||||
|
||||
Here we will use [Wireguard](https://www.wireguard.com/), a secure and high-performance VPN server, using containers:
|
||||
|
||||
- [wg-easy](https://github.com/wg-easy/wg-easy) as the server, providing a very simple web UI to manage connections and download config files (including QR codes for phones)
|
||||
- [Wireguard](https://docs.linuxserver.io/images/docker-wireguard/?h=wireguard) as the client for Linux systems
|
||||
|
||||
Clients are also available for Windows, macOS, iOS, and Android.
|
||||
|
||||
The concept:
|
||||
|
||||
- On the internet, anyone can reach any internet box and thus any exposed server.
|
||||
- Your server is on your local network. It is accessible only locally unless services are explicitly exposed (as we did with Dockge). To access non-exposed resources, you must be on the same local network.
|
||||
- We want to securely access these unexposed services (like SSH) from anywhere.
|
||||
- We also want to connect services between servers, like linking two Dockge instances securely.
|
||||
|
||||
To achieve this, we’ll create a **Virtual Private Network** (VPN), i.e., a secure tunnel that only connected machines can use. They’ll appear to be on the same private network.
|
||||
|
||||
Additionally, you can add your phone, laptop, or other devices to the VPN and securely access your server resources wherever you are.
|
||||
|
||||

|
||||
|
||||
In this diagram, machine 1 is part of two networks:
|
||||
|
||||
- Its local network (devices behind the same router, e.g. `192.168.x.x` – machines 1 and 2)
|
||||
- The VPN network (VPN devices with a second IP, e.g. `10.8.x.x` – machines 1 and 4)
|
||||
|
||||
You *can* allow VPN clients to share access to their local networks, but we won’t do that here for security and subnet conflict reasons (e.g., if two remote machines use the same local IP like `192.168.1.1`).
|
||||
|
||||
So only VPN-connected devices can communicate with each other on the VPN, not with other local devices outside the VPN.
|
||||
|
||||
## Server Side
|
||||
---
|
||||
::note
|
||||
📋 __Checklist:__
|
||||
|
||||
- Ensure port `51820 UDP` is available and properly forwarded through your router to the server (`Source 51820 UDP -> Destination 51820 UDP -> Server`).
|
||||
- Ensure port `51821 TCP` is available for the web UI.
|
||||
::
|
||||
|
||||
::warning
|
||||
|
||||
__Warning:__ This guide uses version `14` of [wg-easy](https://wg-easy.github.io/wg-easy/latest/). Version `15` introduces breaking changes incompatible with this configuration.
|
||||
::
|
||||
|
||||
Folder structure:
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── wg-easy
|
||||
├── config
|
||||
│ └── etc_wireguard
|
||||
├── compose.yaml
|
||||
└── .env
|
||||
```
|
||||
|
||||
The container runs in `HOST` mode, meaning it uses the host’s network stack directly.
|
||||
|
||||
Open Dockge, click `compose`, and name the stack `wg_easy`.
|
||||
|
||||
Paste the following configuration:
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
wg-easy:
|
||||
network_mode: host
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
environment:
|
||||
|
||||
- LANG=en
|
||||
- WG_HOST=${HOST}
|
||||
- PASSWORD_HASH=${PW}
|
||||
- WG_DEFAULT_ADDRESS=${ADDRESS}
|
||||
- WG_HIDE_KEYS=never
|
||||
- WG_ALLOWED_IPS=${IPS}
|
||||
- WG_DEFAULT_DNS=
|
||||
- UI_TRAFFIC_STATS=true
|
||||
- UI_CHART_TYPE=1
|
||||
image: ghcr.io/wg-easy/wg-easy:14
|
||||
container_name: wg-easy
|
||||
volumes:
|
||||
|
||||
- /docker/wg_easy/config/etc_wireguard:/etc/wireguard
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Tip:__
|
||||
|
||||
- You can also specify your own wireguard port with `WG_PORT`
|
||||
- Add the Watchtower label to enable automatic updates
|
||||
|
||||
```yaml
|
||||
services
|
||||
wg-easy:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
In `.env`:
|
||||
|
||||
```properties
|
||||
HOST=
|
||||
PW=
|
||||
ADDRESS=
|
||||
IPS=
|
||||
```
|
||||
|
||||
| Variable | Description | Example |
|
||||
|--------------|-------------|---------|
|
||||
| `HOST` | IP of public access of your host (router ISP's IP if it's at home) | `80.75.137.27` |
|
||||
| `PW` | Bcrypt password hash, [generate here](https://bcrypt-generator.com/). **NOTE:** Double the `$` characters | `$$2a$$12$$FF6T4QqSP9Ho` |
|
||||
| `ADDRESS` | VPN DHCP address range, the `x` must remain, others can vary | `10.8.0.x` |
|
||||
| `IPS` | IPs routed by clients through the VPN. Use `10.8.0.0/24` to only route VPN traffic. To include local LAN, add `192.168.0.0/16` separated by commas. | `10.8.0.0/24` |
|
||||
|
||||
Deploy the stack.
|
||||
|
||||
### Enable Forwarding on Host
|
||||
|
||||
To allow communication between VPN clients, enable:
|
||||
|
||||
```sh
|
||||
sudo sysctl net.ipv4.ip_forward=1
|
||||
sudo sysctl net.ipv4.conf.all.src_valid_mark=1
|
||||
```
|
||||
|
||||
### Retrieve Configuration Files
|
||||
|
||||
To configure clients, download the config files from the server:
|
||||
|
||||
- Visit `http://your-server-ip:51821`
|
||||
- Create a client
|
||||
- Download the config file
|
||||
- Rename it to `wg0.conf`
|
||||
|
||||
::caution
|
||||
|
||||
If it fails, check firewall rules.
|
||||
::
|
||||
|
||||
## On the Client Server
|
||||
---
|
||||
::note
|
||||
|
||||
Assumes the client is a Linux server with Docker installed
|
||||
::
|
||||
|
||||
Folder structure:
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── wireguard
|
||||
└── config
|
||||
│ └── wg_confs
|
||||
└── compose.yaml
|
||||
```
|
||||
|
||||
Create the folder `/docker/wireguard/config/wg_confs`:
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Use [File Browser](/serveex/files/file-browser) to browse and edit files without terminal
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo mkdir -p /docker/wireguard/config/wg_confs
|
||||
```
|
||||
|
||||
Copy the `wg0.conf` file downloaded earlier:
|
||||
|
||||
::tip
|
||||
✨ __Tip:__ Easiest way is to transfer the file via SFTP to `/home/youruser`, then move it:
|
||||
|
||||
```sh
|
||||
sudo cp ~/wg0.conf /docker/wireguard/config/wg_confs
|
||||
```
|
||||
::
|
||||
|
||||
Create `compose.yaml` in `/docker/wireguard`:
|
||||
|
||||
```sh
|
||||
sudo vi /docker/wireguard/compose.yaml
|
||||
```
|
||||
|
||||
Press `i` to enter insert mode and paste:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wireguard:
|
||||
image: lscr.io/linuxserver/wireguard:latest
|
||||
container_name: wireguard
|
||||
network_mode: host
|
||||
cap_add:
|
||||
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE #optional
|
||||
environment:
|
||||
|
||||
- TZ=Europe/Paris
|
||||
volumes:
|
||||
|
||||
- /docker/wireguard/config:/config
|
||||
- /lib/modules:/lib/modules #optional
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
Press `Esc` then type `:x` to save and exit.
|
||||
|
||||
Start the container:
|
||||
|
||||
```sh
|
||||
cd /docker/wireguard
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
Repeat for each client
|
||||
::
|
||||
|
||||
## Other Devices
|
||||
---
|
||||
|
||||
- **Phone:** Install Wireguard and scan the QR code from the web UI (`http://your-server-ip:51821`)
|
||||
- **PC:** Install the Wireguard client and import the config file
|
||||
|
||||
::warning
|
||||
|
||||
__Warning:__ If a client device is on the same LAN as the server, edit `wg0.conf` and change the endpoint to the local server IP:
|
||||
`Endpoint = your-server-ip:51820`
|
||||
::
|
||||
|
||||
And this is the result:
|
||||
|
||||

|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: Home
|
||||
description: Homelab documentation by Djeex — self-hosting guides for Debian, Docker, networking, storage, and more.
|
||||
---
|
||||
|
||||
:ellipsis{right=0px width=75% blur=150px zIndex=60}
|
||||
|
||||
::u-container{class="min-h-[calc(100vh-var(--ui-header-height))] flex flex-col pt-32 pb-32"}
|
||||
:::div{class="grid lg:grid-cols-3 gap-8 items-stretch flex-1"}
|
||||
::::div{class="lg:col-span-2 flex flex-col justify-between"}
|
||||
:::::div{class="flex flex-col gap-8"}
|
||||
<h1 class="text-6xl font-bold mb-0">Welcome to docu<span style="color: #1ad6ff">·</span>djeex</h1>
|
||||
|
||||
<p class="text-lg my-0!">Docs, more docs. Tips and experiments. Build your homelab and your own NAS.</p>
|
||||
:::::
|
||||
|
||||
:::::div{class="my-6"}
|
||||
{.w-full}
|
||||
:::::
|
||||
|
||||
:::::div{class="flex items-center gap-3"}
|
||||
::::::u-button{class="text-white font-bold"}
|
||||
---
|
||||
color: primary
|
||||
size: xl
|
||||
to: /en/about/welcome
|
||||
---
|
||||
Access the Docs
|
||||
::::::
|
||||
|
||||
::::::u-button
|
||||
---
|
||||
color: neutral
|
||||
variant: outline
|
||||
size: xl
|
||||
to: https://docu.djeex.fr/fr/
|
||||
---
|
||||
🇫🇷 →
|
||||
::::::
|
||||
:::::
|
||||
::::
|
||||
|
||||
::::div{class="flex flex-col gap-5 justify-start self-start"}
|
||||
<h2 class="text-sm font-semibold text-muted uppercase tracking-wide mt-0 mb-0">And Other dumb things</h2>
|
||||
|
||||
:::::card
|
||||
---
|
||||
icon: i-cib-gitea
|
||||
title: git.djeex.fr
|
||||
to: https://git.djeex.fr
|
||||
target: _blank
|
||||
class: my-0!
|
||||
ui:
|
||||
icon: text-[#1ad6ff]
|
||||
---
|
||||
Check my nonsense projects
|
||||
:::::
|
||||
|
||||
:::::card
|
||||
---
|
||||
icon: i-brand-lumeex
|
||||
title: Lumeex
|
||||
to: https://lumeex.djeex.fr
|
||||
target: _blank
|
||||
class: my-0!
|
||||
---
|
||||
Yet another minimalist, lightweight photo gallery static site generator
|
||||
:::::
|
||||
|
||||
:::::card
|
||||
---
|
||||
icon: i-brand-instameex
|
||||
title: Instameex
|
||||
to: https://instameex.djeex.fr
|
||||
target: _blank
|
||||
class: my-0!
|
||||
---
|
||||
Mix your SDR and HDR exports into an Instagram-ready HDR photo.
|
||||
:::::
|
||||
::::
|
||||
:::
|
||||
::
|
||||
@@ -0,0 +1,2 @@
|
||||
title: À propos
|
||||
icon: i-noto-star
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: Bienvenue
|
||||
description: Introduction à Docudjeex — un site de documentation personnelle sur les services auto-hébergés, Debian et l'infrastructure Docker.
|
||||
navigation:
|
||||
icon: i-lucide-home
|
||||
hideHeader: true
|
||||
hideCopyPage: true
|
||||
hideToc: true
|
||||
---
|
||||
|
||||
|
||||
:ellipsis{right=0px width=75% blur=150px zIndex=60}
|
||||
# docu[·]{style="color: #1ad6ff"}quoi ?
|
||||
|
||||
__Docu[·]{style="color: #1ad6ff"}djeex__ est le site regroupant la documentation de mes serveurs personnels, pensé à l'origine pour retrouver facilement mes configurations et commandes.
|
||||
Mon infrastructure est construite autour du duo Debian 13 + Docker, pour plus de simplicité à l'export et au déploiement.
|
||||
Un grand merci à __Nipah__, __Xenio__, et d'autres pour leur patience et leur soutien. La majeure partie de ce contenu vient directement d'eux.
|
||||
|
||||
## À propos de la documentation
|
||||
|
||||
La documentation fournie ici est expérimentale et partagée dans un esprit de partage de connaissances et d'expérience.
|
||||
Elle n'est en aucun cas destinée à construire une infrastructure de production ou industrialisée.
|
||||
Il est possible qu'elle contienne des erreurs et/ou des approximations.
|
||||
|
||||
Évidemment, l'usage de cette documentation doit strictement se limiter au cadre légal.
|
||||
|
||||
### Documentation disponible ou à venir
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-noto-open-book" title="Généralités" to="/fr/general/networking/nat"}
|
||||
Bases réseau, stockage et matériel
|
||||
::
|
||||
|
||||
::card{icon="i-noto-microscope" title="Serveex" to="/fr/serveex/introduction"}
|
||||
Guide de déploiement de votre Homelab, pas à pas
|
||||
::
|
||||
|
||||
::card{icon="i-noto-computer-disk" title="Stockeex"}
|
||||
*(à venir)* Créez votre propre NAS maison pour stocker vos données et médias
|
||||
::
|
||||
|
||||
::card{icon="i-noto-test-tube" title="Mes bêtises" to="/fr/nonsense/python/nvidia-stock-bot"}
|
||||
Scripts personnels et projets annexes
|
||||
::
|
||||
|
||||
::card{icon="i-noto-recycling-symbol" title="Poubelle" to="/fr/recycled/deprecated/wireguard-14"}
|
||||
Pages dépréciées, conservées pour archive
|
||||
::
|
||||
::
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Généralités
|
||||
icon: i-noto-open-book
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Réseau
|
||||
icon: i-lucide-network
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: NAT & DHCP
|
||||
description: Comprendre le NAT, la redirection de ports et le DHCP sur un routeur domestique. Configurer des baux IP fixes et exposer des services locaux.
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Routeur et NAT
|
||||
::note
|
||||
🎯 __Objectifs :__
|
||||
|
||||
- Comprendre le principe de la redirection de port
|
||||
- Savoir configurer le NAT de son routeur
|
||||
- Savoir émettre des baux DHCP (IP fixes)
|
||||
::
|
||||
|
||||

|
||||
|
||||
## Qu'est-ce qu'un "port" ?
|
||||
---
|
||||
Les ports sont différents canaux par lesquels votre routeur envoie et reçoit des données, ce qui permet d'utiliser plusieurs services en meme temps. Lorsqu'il reçoit une donnée via un port, otre routeur transmet ensuite les données à la machine qui :
|
||||
|
||||
- soit a émis la requête de départ
|
||||
- soit est configurée pour recevoir les données reçues par un port spécifique du routeur
|
||||
|
||||
Votre routeur dispose de plus de 65 000 ports à utiliser.
|
||||
|
||||
Certains programmes et applications sont conçus pour utiliser des ports spécifiques. Par exemple, lorsque votre réseau envoie des données à partir d'une page HTML, le routeur les recevra via le port numéro 80 (non sécurisé) ou `443` (sécurisé via SSL).
|
||||
|
||||
Le routeur sert donc de plateforme d'aiguillage des données entre internet et votre machine.
|
||||
|
||||
## La redirection de port
|
||||
---
|
||||
Rediriger un `port`, c'est émettre une règle qui spécifie que telle `source` peut envoyer des données à tel `port` de votre routeur, qui redirigera les données sur tel `port` de telle `machine`. Les `sources` et la `machine de destination` sont identifiées par leur `adresse IP`.
|
||||
|
||||
| Variable | Description | exemple |
|
||||
|--------------------------|----------------------------------------------------------|-------------------------|
|
||||
| `machine source` | IP de la machine source (sur internet) | `All`<br>`123.45.67.89` |
|
||||
| `port source` | Port d'arrivée sur le routeur | `443` |
|
||||
| `port de destination` | Port d'arrivée sur la machine de destination | `3000` |
|
||||
| `machine de destination` | IP de la machine de destination (sur votre réseau local) | `192.168.1.50` |
|
||||
|
||||
Selon ce tableau, si on enlève le `All` et que l'on garde l'ip `123.45.67.89` en provenance d'internet, tout le traffic envoyé depuis cette IP sur le port `443` du routeur sera redirigé vers le port `3000` de l'IP locale `192.168.1.50`.
|
||||
|
||||
Si on enlève l'IP de l'exemple et qu'on laisse le `All`, tout le traffic d'internet envoyé au port `443` du routeur sera redirigé vers le port `3000` de l'IP locale `192.168.1.50`.
|
||||
|
||||
C'est utile si par exemple vous avez un serveur qui a un service qui nécessite d'etre accessible par internet. Par exemple, un site web. Le web utilise le port `80` (non sécurisé) et le port 443 (sécurisé par certificat SSL) pour communiquer. Ainsi, si je veux que mon site internet soit accessible, je vais faire en sorte que lorsqu'on tape le nom de domaine de mon site, le routeur redirige bien vers mon serveur local (avec l'exemple de l'IP locale du tableau). Par exemple, imaginons que mon service est sur le port `3000` de mon routeur (accessible en local via `http://192.168.1.50:3000`), je vais donc rediriger comme dans l'exemple toutes les sources (All) qui passent par le port `443` du routeur vers le port `3000` de mon serveur local.
|
||||
|
||||
::warning
|
||||
|
||||
__Attention :__ Si vous avez plusieurs services à rendre accessible, avec par exemple `sousdomaine1.mondomaine.fr` et `sousdomaine2.mondomaine.fr`, votre routeur ne peut pas rediriger vers plusieurs port selon la requête. Vous devrez utiliser un [Reverse Proxy](../../serveex/coeur/swag) qui selon la requete redirigera vers le bon service de votre serveur.
|
||||
::
|
||||
|
||||
## Le DHCP
|
||||
---
|
||||
A chaque fois que vous connectez un appareil sur votre réseau local, votre routeur lui attribue une adresse IP via les règles DHCP. Celle-ci est aléatoire selon des règles prédéfinies. A chaque redémarrage de l'appareil, l'IP peut changer. C'est embetant si vous exposez un service et que vous avez une redirection de port dans votre routeur car si l'IP change, la redirection enverra les données dans le vide. Le serveur DHCP de votre box permet d'attribuer une IP fixe à un appareil.
|
||||
|
||||
Chaque appareil a une adresse physique dite "adressse MAC". Pour fixer l'IP, vous devez connaitre l'adresse physique de votre appareil (visible dans votre routeur si votre machine est connectée au réseau), et lui attribuer une adresse IP fixe, ce qu'on appel un "bail DHCP fixe".
|
||||
|
||||
Ainsi, l'IP de votre machine ne changera jamais et la redirection de port sera toujours effective.
|
||||
|
||||
| Variable | Description | Exemple |
|
||||
|---------------|--------------------------------|---------------------|
|
||||
| `IP` | IP locale fixe à attribuer | `192.168.1.50` |
|
||||
| `Adresse Mac` | Adresse physique de la machine | `5E:FF:56:A2:AF:15` |
|
||||
|
||||
Pour plus d'information sur ces sujets, consultez la documentation de votre routeur.
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: Zone DNS
|
||||
description: Comprendre le fonctionnement du DNS, lire et éditer une zone DNS, et configurer des noms de domaine pour vos services auto-hébergés.
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Noms de domaines et zone DNS
|
||||
|
||||
::note
|
||||
🎯 __Objectifs :__
|
||||
|
||||
- Comprendre le fonctionnement d'un serveur DNS
|
||||
- Comprendre comment modifier une zone DNS
|
||||
::
|
||||
|
||||
## Introduction
|
||||
---
|
||||
Lorsque vous naviguez sur un site, ou une application, des requêtes sont émises vers un ou des domaines afin d'afficher le contenu de votre page. Votre appareil ne connait pas les adresses IP de ces serveurs à joindre. Pour les connaitre, il va contacter un _serveur de nom_ (Domain Name Server) qui lui va lui répondre avec l'adresse IP la plus à jour pour le domaine de la requête.
|
||||
|
||||
La zone DNS, c'est une sorte de registre avec des panneaux qui redirige vos requêtes vers la bonne destination.
|
||||
|
||||

|
||||
|
||||
## La zone DNS
|
||||
---
|
||||
Lorsque vous réservez un domaine chez votre registrar (cloudflare, ovh...), ce registrar vous attribue une zone DNS que vous pouvez personnaliser.
|
||||
|
||||
Vous pouvez rentrer des _enregistrements_ dans cette zone DNS qui permettront d'orienter les requêtes au bon endroit. Vous trouverez [plus d'information ici](https://help.ovhcloud.com/csm/fr-dns-servers-general-information?id=kb_article_view&sysparm_article=KB0051661).
|
||||
|
||||
Exemple d'une zone DNS du domaine mondomaine.fr:
|
||||
|
||||
```
|
||||
@ IN SOA ns1.dns.me. dns.net. (2024051800 86400 3600 3600000 60)
|
||||
IN NS ns1.dns.me.
|
||||
IN NS ns2.dns.me.
|
||||
IN A 203.0.113.0
|
||||
www IN CNAME mondomaine.fr
|
||||
sousdomaine IN CNAME mondomaine.fr
|
||||
```
|
||||
|
||||
Dans cet exemple :
|
||||
|
||||
- `$TTL 3600` indique aux différents serveurs de noms de la planète que les enregistrement sont valides 1h (et qu'au-delà il faudra rev"rifier).
|
||||
- `IN SOA ns1.dns.me. dns.net. (2024051800 86400 3600 3600000 60)` indique que `ns1.dns.me` est le serveur dns principal, et les nombres sont des indications de rafraichissement.
|
||||
- `IN NS ns1.dns.me.` et `IN NS ns2.dns.me.` indique que `ns1.dns.me` et `ns2.dns.me` sont des serveurs de noms pour ce domaine.
|
||||
- `IN A 203.0.113.0` signifie que `mondomaine.fr` pointe vers l'IP `203.0.113.0`
|
||||
- `sousdomaine IN CNAME mondomaine.fr` signifie que `sousdomaine.mondomaine.fr` pointe vers `mondomaine.fr` et donc vers l'IP `203.0.113.0`.
|
||||
|
||||
Ainsi, si vous choisissez de pointer le domaine `mondomaine.fr` vers votre serveur, vous pouvez le faire en ajoutant un enregistrement `A` pointant vers l'IP publique de votre serveur.
|
||||
|
||||
::warning
|
||||
|
||||
- __Attention,__ Si votre serveur est chez vous :
|
||||
- l'IP publique est celle de votre box internet. Assurez-vous auprès de votre opérateur que cette IP est fixe ou configurez un [DDNS](https://aws.amazon.com/fr/what-is/dynamic-dns/).
|
||||
- assurez-vous d'avoir [redirigé le port 443 vers le port d'écoute de votre serveur](/fr/general/networking/nat).
|
||||
::
|
||||
|
||||
Et si vous ajoutez un sous-domaine qui doit pointer vers votre serveur, vous pouvez utiliser un enregistrement `CNAME` vers `mondomaine.fr`.
|
||||
|
||||
::note
|
||||
|
||||
__Pourquoi ne pas utiliser un enregistrement `A` pour le sous-domaine ?__ Si votre sous domaine pointe sur le meme serveur que `mondomaine.fr`, il vaut mieux utiliser un enregistrement `CNAME` car en cas de changement d'IP du serveur, il n'y aura aucune autre manipulation à faire.
|
||||
::
|
||||
|
||||
La plupart des registrar proposent des interfaces plus lisbles pour modifier ces informations. Renseignez-vous auprès de la documentation de votre registrar.
|
||||
@@ -0,0 +1,223 @@
|
||||
---
|
||||
title: Samba
|
||||
description: Configurer Samba sur Debian pour partager des dossiers sur votre réseau local et y accéder depuis Windows, macOS ou Linux.
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Samba
|
||||
|
||||
Samba est un protocole permettant d'accèder à un dossier situé sur un disque réseau. Il peut être configuré sous Mac, Windows ou Linux.
|
||||
|
||||
De nombreux tutorials existent pour configurer Samba sous windows ou sur un NAS type Synology, ici nous nous concentrons sur Debian.
|
||||
|
||||
::note
|
||||
🎯 __Objectifs :__
|
||||
|
||||
- Créer un dossier réseau sur une machine distante
|
||||
- Accéder au dossier réseau sur notre serveur
|
||||
::
|
||||
|
||||

|
||||
|
||||
## Partager un dossier réseau
|
||||
---
|
||||
::note
|
||||
|
||||
Ici, nous allons partager le dossier `/video` d'une machine distant que nous appelerons `machine-distante`. Nous accéderons à ce dossier par la machine nommée `machine-locale`. L'utilisateur de connexion au disque réseau sera `sambauser`.
|
||||
::
|
||||
|
||||
### Installer le serveur samba
|
||||
|
||||
```sh
|
||||
sudo apt update && sudo apt upgrade
|
||||
sudo apt install samba smbclient cifs-utils
|
||||
```
|
||||
|
||||
### Créer le dossier `/video`
|
||||
|
||||
```sh
|
||||
sudo mkdir /video
|
||||
```
|
||||
|
||||
### Configuration du partage
|
||||
|
||||
Ensuite nous allons éditer le fichier `/etc/samba/smb.conf`
|
||||
|
||||
::tip
|
||||
✨ __Astuce :__ vous pouvez utiliser [File Browser](/fr/serveex/files/file-browser) pour naviguer dans vos fichier et éditer vos documents au lieu d'utiliser les commandes du terminal.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vim /etc/samba/smb.conf
|
||||
```
|
||||
|
||||
Localisez la variable `workgroup` puis passez en mode modification en appuyant sur `i` et nommez votre worgroup, par exemple `::::properties workgroup = WORKGROUP`
|
||||
|
||||
Puis allez à la fin du fichier et collez la configuration suivante
|
||||
|
||||
```properties
|
||||
[video]
|
||||
comment = Dossier video
|
||||
path = /video
|
||||
writable = yes
|
||||
guest ok = no
|
||||
valid users = @smbshare
|
||||
force create mode = 770
|
||||
force directory mode = 770
|
||||
inherit permissions = yes
|
||||
```
|
||||
Appuyez sur `Echap` pour quitter le mode notification puis tapez `:x` et appuyez sur `Entrée` pour sauvegarder et quitter.
|
||||
|
||||
### Créer un utilisateur et un groupe pour Samba
|
||||
|
||||
Comme nous avons configfuré un partage sécurisé, nous allons devoir créer un utilisateur et un groupe pour pouvoir y accéder à distance.
|
||||
|
||||
Creez le groupe.
|
||||
```sh
|
||||
sudo groupadd smbshare
|
||||
```
|
||||
|
||||
Nous allons maintenant permettre au groupe d'avoir le controle sur le dossier `/video`.
|
||||
|
||||
```sh
|
||||
sudo chgrp -R smbshare /video
|
||||
```
|
||||
|
||||
Et maintenant nous allons donner les permissions nécessaires aux dossiers et fichier hérités.
|
||||
|
||||
```sh
|
||||
sudo chmod 2775 /video
|
||||
```
|
||||
|
||||
A présent nous allons ajouter un utilisateur nologin c'est à dire que cet utilisateur ne pourra pas se connecter sur le serveur pour faire des opérations, mais pourra tout de meme se connecter au service samba.
|
||||
|
||||
```sh
|
||||
sudo useradd -M -s /sbin/nologin sambauser
|
||||
```
|
||||
|
||||
Puis nous ajoutons l'utilisateur au groupe `sambashare` que nous avons créé précédemment.
|
||||
|
||||
```sh
|
||||
sudo usermod -aG smbshare sambauser
|
||||
```
|
||||
Et nous allons configurer un mot de passe.
|
||||
|
||||
```sh
|
||||
sudo smbpasswd -a sambauser
|
||||
```
|
||||
Et enfin nous allons activer le compte que nous venons de créer.
|
||||
|
||||
```sh
|
||||
sudo smbpasswd -e sambauser
|
||||
```
|
||||
::warning
|
||||
|
||||
__Attention :__ Si vous utilisez un pare-feu, comme ufw, n'oubliez pas d'autoriser les IP des machines qui accéderont à votre dossier partagé :
|
||||
```sh
|
||||
sudo ufw allow from ipdelamachine to any app Samba
|
||||
```
|
||||
::
|
||||
|
||||
## Accéder à un dossier partagé
|
||||
---
|
||||
::note
|
||||
|
||||
A présent, nous sommes sur votre `machine-locale` qui nécessite d'accéder au dossier partagé `/video` présent sur la `machine-distante`.
|
||||
::
|
||||
|
||||
### Installer les package nécessaires
|
||||
|
||||
```sh
|
||||
sudo apt update && sudo apt upgrade
|
||||
sudo apt install cifs-utils
|
||||
```
|
||||
### Créer le dossier de destination
|
||||
|
||||
Nous allons créer un dossier sur notre serveur sur lequel sera monté le dossier partagé de notre `machine-distante. C'est à dire que dans ce dossier nous retrouverons le contenu du dossier partagé de notre `machine-distante`. Ici nous appellerons ce dossier `/mnt/video`.
|
||||
|
||||
```sh
|
||||
sudo mkdir /mnt/video
|
||||
```
|
||||
|
||||
### Préparer le fichier .credentials
|
||||
|
||||
Afin de ne pas avoir systématiquement à rentrer notre utilisateur et mot de passe, nous allons créer un fichier .credentials` stockant ces informations.
|
||||
|
||||
Nous allons le créer dans le dossier `/smb`.
|
||||
|
||||
```sh
|
||||
sudo mkdir /smb
|
||||
sudo vi /smb/.credentials
|
||||
```
|
||||
Passez en mode modification en appuyant sur `i` et configurez comme suit :
|
||||
|
||||
```properties
|
||||
username=smbuser
|
||||
password=motdepasse
|
||||
```
|
||||
|
||||
- `smbuser` : L'utilisateur que nous avons configuré sur la `machine-distante`
|
||||
- `motdepasse` : Le mot de passe que nous avons configuré sur la `machine-distante`
|
||||
|
||||
Appuyez sur `Echap` afin de quitter le mode modification, puis tapez `:x` et appuyez sur `Entrée` pour sauvegarder et quitter.
|
||||
|
||||
Nous allons modifier les permissions du dossier afin que seul le propriétaire puis lire et écrire dans ce fichier.
|
||||
|
||||
```sh
|
||||
sudo chmod 600 /smb/.credentials
|
||||
```
|
||||
|
||||
### Monter le dossier partager
|
||||
|
||||
A présent nous allons monter le dossier.
|
||||
|
||||
```sh
|
||||
sudo mount -t cifs -o credentials=/smb/.credentials //ip-machine-distante/video /mnt/video
|
||||
```
|
||||
|
||||
Remplacez `ip-machine-distante` par l'adresse IP de votre `machine-distante`
|
||||
|
||||
Vérifiez que cela a fonctionné en tapant :
|
||||
|
||||
```sh
|
||||
sudo mount -t cifs
|
||||
```
|
||||
Vous verrez différentes informations qui confirmerons le succès du montage.
|
||||
|
||||
Et voilà, à présent vous accédez au dossier /video de `votre machine-distante`, depuis votre `machine-locale` !
|
||||
|
||||
### Automatiser le montage au boot
|
||||
|
||||
Par défaut, les dossiers pattagés ne sont pas connectés automatiquement au redémarrage. Pour autoamtiser cet aspect, nous allons modifier le fichier `/etc/fstab`.
|
||||
|
||||
D'abord, sauvegardons notre fichier `fstab`.
|
||||
|
||||
```sh
|
||||
sudo cp /etc/fstab /etc/fstab.bak
|
||||
```
|
||||
|
||||
Puis nous allons ajouter une ligne à la fin du fichier comportant les informations de montages dans le fichier `fstab`.
|
||||
|
||||
```sh
|
||||
sudo echo //ip-machine-distante/video /mnt/video cifs _netdev,nofail,credentials=/smb/.credentials,x-systemd.automount,x-systemd.device-timeout=15 0 0 >> /etc/fstab
|
||||
```
|
||||
|
||||
Redémarrez.
|
||||
|
||||
```sh
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
Une fois redémarré, vérifiez que le montage est correct
|
||||
|
||||
```sh
|
||||
sudo mount -t cifs
|
||||
```
|
||||
|
||||
Et voilà !
|
||||
|
||||
### Démonter le dossier partagé
|
||||
|
||||
```sh
|
||||
sudo umount -t cifs /mnt/video
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Stockage
|
||||
icon: i-lucide-hard-drive
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
title: RAID
|
||||
description: Comprendre le RAID — matériel vs logiciel, niveaux RAID, et comment mettre en place des grappes de disques redondants pour votre homelab.
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# RAID
|
||||
|
||||
_Redundant Array of Independent Disks_
|
||||
|
||||
::note
|
||||
🎯 __Objectifs :__
|
||||
|
||||
- Comprendre ce qu'est un système RAID
|
||||
- Comprendre quel type de RAID est adapté aux différents usages
|
||||
::
|
||||
|
||||
En informatique, le RAID (Redundant Array of Independent Disks) est un système permettant de combiner plusieurs disques durs pour améliorer les performances et/ou la fiabilité. Il fonctionne en restructurant et en répartissant les blocs de données entre les disques.
|
||||
|
||||
À l’origine, les systèmes RAID étaient matériels, ce qui signifiait qu’un contrôleur dédié (une puce spécifique) gérait la distribution des données et les opérations RAID. Aujourd’hui, la plupart des systèmes RAID (ou équivalents) sont logiciels. En fait, de nombreuses technologies logicielles peuvent créer des systèmes de type RAID avec des fonctionnalités indisponibles dans les RAID matériels, comme la réparation automatique (data scrubbing), les instantanés (snapshots), etc.
|
||||
|
||||
## Différents types de RAID
|
||||
|
||||
Il existe plusieurs types de RAID, chacun ayant ses avantages et inconvénients. Les facteurs qui les influencent sont les suivants :
|
||||
|
||||
- Nombre de disques
|
||||
- Capacité totale de stockage
|
||||
- Vitesse de lecture
|
||||
- Vitesse d’écriture
|
||||
- Tolérance aux pannes (résistance aux défaillances matérielles)
|
||||
|
||||
::warning
|
||||
|
||||
Le RAID n’est pas un système de sauvegarde mais un système de continuité de service ! Il permet seulement le remplacement à chaud des disques sans interruption du serveur ou restauration depuis une sauvegarde. Vous avez toujours besoin d’un système de sauvegarde externe.
|
||||
::
|
||||
|
||||
### Sans RAID
|
||||
---
|
||||
|
||||
<div style="display: flex; align-items: center;">
|
||||
<img src="/img/global/no-raid.svg" alt="Image" style="max-width: 30%; max-height:230px; margin-right: 20px;">
|
||||
<ul>
|
||||
<li>Vos disques sans RAID. Les données sont stockées disque par disque.</li>
|
||||
<li>Si vous perdez un disque, seules ses données sont perdues.</li>
|
||||
<li>La capacité totale est la somme de tous les disques.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Utilisez vos disques sans RAID si vous n’avez pas peur de perdre des données et pouvez tolérer une interruption de service entre la panne et la restauration.
|
||||
|
||||
### RAID 0
|
||||
---
|
||||
|
||||
<div style="display: flex; align-items: center;">
|
||||
<img src="/img/global/raid0.svg" alt="Image" style="max-width: 30%; max-height:230px; margin-right: 20px;">
|
||||
<ul>
|
||||
<li>Le système voit un seul disque.</li>
|
||||
<li>Les données sont entrelacées entre tous les disques.</li>
|
||||
<li>Si un disque est perdu, toutes les données le sont.</li>
|
||||
<li>Excellentes performances en lecture et écriture (multipliées par le nombre de disques).</li>
|
||||
<li>La capacité totale est la somme de tous les disques.</li>
|
||||
<li>Minimum de 2 disques requis.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Utilisez RAID 0 si vous souhaitez privilégier la performance et que la perte de données n’est pas un problème. Idéal pour le stockage temporaire à haute vitesse (montage vidéo, IA, etc). Pas adapté au stockage à long terme.
|
||||
|
||||
### RAID 1
|
||||
---
|
||||
|
||||
<div style="display: flex; align-items: center;">
|
||||
<img src="/img/global/raid1.svg" alt="Image" style="max-width: 30%; max-height:230px; margin-right: 20px;">
|
||||
<ul>
|
||||
<li>Le système voit un seul disque.</li>
|
||||
<li>Tous les disques contiennent les mêmes données.</li>
|
||||
<li>Vous pouvez perdre tous les disques tant qu'un seul est en bonne santé.</li>
|
||||
<li>Vitesse de lecture améliorée (selon le nombre de disques).</li>
|
||||
<li>Capacité totale égale à un seul disque (ex. : 2×10 To = 10 To).</li>
|
||||
<li>Minimum de 2 disques requis.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Utilisez RAID 1 pour une redondance maximale. Chaque disque contient toutes les données, donc les performances ne sont pas affectées en cas de panne. Une fois les disques remplacés, les données sont rapidement restaurées. Mais la capacité utilisable est limitée à un seul disque, ce qui en fait une solution coûteuse.
|
||||
|
||||
::tip
|
||||
✨ __Astuce :__ Vous pouvez combiner le RAID 1 avec d’autres types de RAID pour créer des grappes miroir.
|
||||
::
|
||||
|
||||
### RAID 5
|
||||
---
|
||||
<p align="center">
|
||||
<img src="/img/global/raid5.svg" alt="Image" style="max-width: 40%; margin-right: 20px;">
|
||||
</p>
|
||||
|
||||
- Le système voit un seul disque.
|
||||
- Les données sont entrelacées avec des blocs de parité.
|
||||
- Vous pouvez perdre un disque et récupérer les données.
|
||||
- Vitesse de lecture améliorée (selon le nombre de disques).
|
||||
- Capacité totale = somme des disques – 1 (ex. : 3×10 To = 20 To).
|
||||
- Minimum de 3 disques (4 recommandés pour limiter la perte de capacité).
|
||||
|
||||
Utilisez RAID 5 pour un stockage fiable avec 3 à 5 disques et une perte minimale d’espace. Il tolère une panne, mais la reconstruction peut durer plusieurs jours avec des performances dégradées.
|
||||
|
||||
### RAID 6
|
||||
---
|
||||
<p align="center">
|
||||
<img src="/img/global/raid6.svg" alt="Image" style="max-width: 50%; margin-right: 20px;">
|
||||
</p>
|
||||
|
||||
- Le système voit un seul disque.
|
||||
- Les données sont entrelacées avec deux blocs de parité.
|
||||
- Vous pouvez perdre 2 disques et récupérer les données.
|
||||
- Vitesse de lecture améliorée (selon le nombre de disques).
|
||||
- Capacité totale = somme des disques – 2 (ex. : 4×10 To = 20 To).
|
||||
- Minimum de 4 disques (6 recommandés pour minimiser la perte d’espace).
|
||||
|
||||
Utilisez RAID 6 dans les mêmes cas que RAID 5, surtout avec 6 disques ou plus. Plus de disques = plus de risque de panne. RAID 6 vous apportera plus de fiabilité en cas de panne simultanée de deux disques. Il n'y a rien de plus frustrant que de perdre un second disque en pleine reconstruction du remplacement du premier.
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: ZFS
|
||||
description: Introduction à ZFS — système de fichiers et gestionnaire de volumes combinés avec snapshots, checksums et redondance intégrée pour un stockage fiable.
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# ZFS
|
||||
|
||||
::note
|
||||
🎯 __Objectifs :__
|
||||
|
||||
Comprendre ce qu'est ZFS et son utilité
|
||||
::
|
||||
|
||||
ZFS est populaire dans le monde des serveurs, des NAS (comme FreeNAS / TrueNAS), de la virtualisation, et même chez les particuliers qui veulent un stockage résilient. C'est est un _système de fichier_ (à l'instar de NTFS pour windows, EXT4, FAT32 etc...) mais également un __gestionnaire de volume__ (comme LVM par exemple). Pour le dire (très) rapidement, un gestionnaire de volume arrange l'espace sur des espaces physiques comme un ou plusieurs disques durs, et un gestionnaire de fichier arrange la maniere dont sont organisés les blocs de données dans ces volumes pour écrire, lire et supprimer les données.
|
||||
|
||||
ZFS dépasse largement les limites des autres systèmes de fichiers, que cela soit en terme de performance ou de fonctionnalité.
|
||||
Ce qui nous intéresse le plus :
|
||||
|
||||
- ses fonctionnalités de __gestion des instantanés__ (snaphsot), permettant de rapidement revenir en arrière en cas de problème
|
||||
- sa gestion des grappes de disques et [__ses équivalent au RAID__](/fr/general/storage/raid) (Z-Mirror, RAIDZ1, RAIDZ2, RAIDZ3)
|
||||
- sa __reconstruction automatique des données corrumpues__ (via le scrubbing)
|
||||
- ses performance avec son cache RAM (ZFS ARC)
|
||||
- ses notifications en cas d'erreur
|
||||
|
||||
## Structure
|
||||
|
||||

|
||||
|
||||
ZFS dispose d'une structure particulière :
|
||||
|
||||
- __vdev__ (virtual device) : une grappe de disques (physiques ou virtuel)
|
||||
- __zpool__ : un ensemble de disques physiques ou virtuels en volume simple, Z-mirror ou RAIDZ. Un _zpool_ peut englober plusieurs _vdev_ mais pas l'inverse.
|
||||
- __dataset__ : un système de de donnée dans un _zpool_. Chaque dataset peut avoir ses propres options (compression, quotas, permissions, etc.).
|
||||
|
||||
Il existe plusieurs types de dataset :
|
||||
|
||||
- __file system__ : un système de fichier, ZFS par défaut, monté comme un volume sans quota de stockage.
|
||||
- __zvol__ : un "disque virtuel" avec un quota d'espace, que vous pouvez formater/partitionner comme vous le souhaitez. Il sera utilisable comme un disque physique.
|
||||
- __snapshot__ : un état figé d’un autre dataset à un instant donné. Ils peuvent etre créés manuellement ou via des outils de sauvegarde. Ils peuvent etre montés pour parcourir les données dans leur état au moment du snapshot.
|
||||
|
||||
## Pourquoi ZFS vs les autres ?
|
||||
|
||||
### Intégrité des données
|
||||
|
||||
ZFS vérifie automatiquement que les données stockées ne sont pas corrompues. Chaque bloc de données est accompagné d’une empreinte (checksum) qui permet à ZFS de détecter toute altération, même minime. Et s’il y a un problème, il peut souvent le réparer automatiquement, si une copie saine existe ailleurs dans le système.
|
||||
|
||||
### RAID intégré
|
||||
|
||||
ZFS propose son propre système de gestion de volumes (vdev). Vous pouvez créer un pool de stockage (zpool) en combinant plusieurs disques, un peu comme avec le [RAID](/fr/general/storage/raid) classique, mais de façon plus souple. Par exemple :
|
||||
|
||||
- __Z-mirror__ => equivalent du RAID 1
|
||||
- __RAIDZ1__ => équivalent du RAID 5 (tolérance à 1 panne disque)
|
||||
- __RAIDZ2__ => équivalent du RAID 6 (tolérance à 2 pannes disque)
|
||||
- __RAIDZ3__ => (tolérance à 3 pannes disque)
|
||||
|
||||
ZFS gère cela directement, pas besoin de logiciel RAID externe.
|
||||
|
||||
::note
|
||||
|
||||
Consultez [l'article sur le RAID](/fr/general/storage/raid) pour en savoir plus sur la solution qui vous convient.
|
||||
::
|
||||
|
||||
### Snapshots et clones
|
||||
|
||||
ZFS permet de créer des snapshots, c’est-à-dire des captures instantanées de l’état d’un système de fichiers à un moment donné. Ces snapshots prennent très peu d’espace et peuvent être créés automatiquement et fréquemment. Vous pouvez aussi faire des clones : des copies modifiables d’un snapshot.
|
||||
|
||||
### Compression et déduplication
|
||||
|
||||
ZFS peut compresser les données à la volée (transparente pour l’utilisateur), ce qui permet d’économiser de l’espace disque. Il propose aussi la déduplication (éliminer les doublons), mais cette fonctionnalité consomme beaucoup de mémoire et n’est pas recommandée pour tous les usages.
|
||||
|
||||
Voilà, à présent vous savez pourquoi ZFS est Ze systeme de fichier à déployer sur votre NAS.
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Matériel
|
||||
icon: i-lucide-server
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
title: Les bases
|
||||
description: Les fondamentaux du matériel serveur — CPU, RAM, stockage et facteurs de forme pour bien démarrer votre homelab.
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Les bases d'un serveur
|
||||
|
||||
::note
|
||||
🎯 __Objectifs :__
|
||||
|
||||
Comprendre les bases du hardware d'un serveur
|
||||
::
|
||||
|
||||

|
||||
|
||||
Un __serveur__ n'est rien d'autre qu'un ordinateur dédié à des taches particulières, ayant vocation à rester accessible en permanence. En soi, sa structure ne diffère pas d'un ordinateur classique, si ce n'est que selon l'usage cible, on fera évoluer certains composants dans un sens ou dans un autre. Dans cet article, vous trouverez un aide mémoire pour comprendre quels sont les composants essentiels d'un serveur et comprendre leur fonction selon vos usages.
|
||||
|
||||
## La carte mère
|
||||
---
|
||||
La __carte mère__ est le socle de votre machine. C'est le composant qui relie tous les autres composants. Elle sert à les faire communiquer et à interagir avec vos périphériques (clavier, souris, etc...). Il faut donc la choisir en fonction de vos besoins en terme d'entrées/sorties (I/O) comme le nombre de ports USB, de ports réseau, leur vitesse, etc... Mais il faut également veiller à sa compatibilité avec les autres composants que vous allez brancher dessus.
|
||||
|
||||
Les composants importants à brancher dessus sont :
|
||||
|
||||
- le CPU
|
||||
- la RAM
|
||||
- le stockage (HDD et/ou SSD)
|
||||
- l'eventuel GPU dédié
|
||||
|
||||
Il existe plusieurs formats de carte mère grand public :
|
||||
|
||||
- E-ATX : les plus grosses
|
||||
- ATX : le standard
|
||||
- Micro-ATX : plus petit
|
||||
- Mini-ITX : le plus petit
|
||||
|
||||
Bien sûr, selon la taille, le nombre de ports et de fonctionnalité diffère. D'autre part, les constructeurs de machines déjà assemblées peuvent aussi profiter de formats plus personnalisés.
|
||||
|
||||
## Le CPU
|
||||
---
|
||||
|
||||
<div style="display: flex; align-items: center;">
|
||||
<img src="/img/global/cpu.svg" alt="Image" style="max-width: 25%; max-height:230px; margin-right: 20px;">
|
||||
<p>
|
||||
Le <strong>CPU</strong> (Central processor Unit) est la supercalculette de l’ordinateur. Il traite la plupart des tâches logicielles. Aujourd’hui les processeurs comportent plusieurs cœurs, parfois eux même divisé en deux de façon logicielle, afin de mieux répartir la charge de travail et optimiser son fonctionnement. Il nécessite d'etre refroidit avec un dissipateur actif (avec ventilateur) ou passif (sans ventilateur) selon la chaleur qu'iil dégage, et donc selon la puissance en Watt qu'il requiert. Dans le cadre d'un serveur, on veillera donc à le choisir selon les usages que l'on aura. </p>
|
||||
</div>
|
||||
|
||||
::warning
|
||||
|
||||
__Attention :__ N'oubliez pas que pour installer un dissipateur tiers, vous devez vous assurer qu'il soit compatible avec le socket de votre processeur, c'est à dire le socle sur lequel il est installé sur la carte mère, et que vous devez également appliquer de la pate thermique sur le CPU avant d'installer le dissipateur.
|
||||
::
|
||||
|
||||
Il faut les juger selon :
|
||||
|
||||
- Leur nombre de coeurs (plus ils en ont, plus il peuvent paralléliser les tâches)
|
||||
- La fréquence de ces coeurs en Giga Hertz _Ghz_
|
||||
- Leur consommation en Watt _W_
|
||||
|
||||
Dans le cadre d'un homelab/NAS sans calcul intensif, aujourd'hui on se dirigera facilement vers les Intel N100/150 (4 coeurs) et N305/N355 (8 coeurs) qui sont des processeurs performants à très faible consommation (rappelez vous que ces machines sont censées tourner H24).
|
||||
|
||||
## La RAM
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/global/ram.svg" alt="Image" style="max-width: 65%; margin-right: 20px;">
|
||||
</p>
|
||||
|
||||
La __RAM__ (Random Access Memory), est une zone de stockage éphémère ultra rapide utilisée par le CPU (et l'iGPU le cas échéant) pour stocker des informations et calculs le temps de les éxecuter. Elle se vide régulièrement selon les sollicitation et se vide également à chaque extinction de la machine. Plus elle est performante, plus les calculs du processeur sont efficaces.
|
||||
|
||||
Elle prend généralement la forme de barettes, à brancher sur la carte mère. Il en existe plusieurs format selon le type de carte mère, et de plusieurs générations (DDR5 de nos jours).
|
||||
|
||||
## Le GPU
|
||||
---
|
||||
|
||||
Le __GPU__ (Graphical Processor Unit) est un processeur dédié aux calculs graphiques, vidéo, et parfois d'intelligence artificielle. Dans le cadre d'un serveur, il aura son utilité pour tout ce qui touche au media center (Streaming de video comme [Plex](/fr/serveex/media/plex) par exemple), mais aussi en terme d'accélération matérielle pour les calculs d'IA comme la reconnaissance faciale ou la recherche sur des photos (comme via [Immich](/fr/serveex/cloud/immich) par exemple).
|
||||
|
||||
Selon la puissance requise, on choisira un GPU dédié avec sa propre VRAM (une carte graphique à brancher sur un port PCIe de la carte mère), ou un iGPU, c'est à dire un GPU compris dans le CPU (comme les N100/N150 et N305/N355), qui utilisera la RAM globale de la machine
|
||||
|
||||
### Le ou les HDD
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/global/hdd.svg" alt="Image" style="max-width: 50%; margin-right: 20px;">
|
||||
</p>
|
||||
|
||||
Un __HDD__ (Hard Disk Drive) ou disque dur, est un composant servant à stocker des données. Autrefois, c'etait le stockage standard des machines informatiques. Ils sont constitués d'un ou plusieurs disques superposés en plateau, et de têtes de lecture, presque comme une platine vinyle. Si aujourd'hui on arrive à stocker des quantités extraordinaire de données dans un HDD (jusqu'à 30To soit 30 000 Giga-octet de nos jours pour le grand public), ils sont limités dans leur vitesse de lecture et d'écriture par leur caractère mécaniques. Ils sont également volumineux et très lourds.
|
||||
|
||||
De manière générale, on les privilégiera pour stocker des données qui ne servent pas à des calculs et ne nécessitent pas d'ecriture rapide, comme les media (videos, photos...) ou les cloud drive, stockage d'archives, etc. Ils sont performants dans ces scenario et surtout coutent beaucoup moins cher que des SSD à espace de stockage égal.
|
||||
|
||||
::tip
|
||||
✨ __Astuce :__ Vous pouvez combiner plusieurs HDD ensemble en [RAID](/fr/general/storage/raid) afin d'accroitre les performances de votre machine.
|
||||
::
|
||||
|
||||
Il existe plusieurs formats de HDD : 3.5" et 2.5". De manière générale dans un serveur on privilégiera les 3.5, plus fiables.
|
||||
|
||||
### Le ou les SSD
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img src="/img/global/nvme.svg" alt="Image" style="max-width: 50%; margin-right: 20px;">
|
||||
</p>
|
||||
|
||||
Un __SSD__ (Solid State Drive) est une petite carte sur laquelle sont soudées des puces de mémoires servant à stocker de l'information. Contrairement à la RAM, ces puces conservent les informations même lorsqu'elles ne sont pas alimentées et donc les conservent après un redémarrage. C'est ce qui sert globalement de stockage pour votre serveur. Contrairement aux HDD, ils ne disposent pas de parties mécaniques, sont très miniaturisés et surtout sont extrêmement rapides, de l'ordre de plusieurs Giga-octets par seconde pour les plus performants.
|
||||
|
||||
On les trouve dans plusieurs formats, aujourd'hui on priviligiera les versions M.2 NVMe, car ce sont les plus petits et plus rapides, et sont devenu un standard sur les cartes mères.
|
||||
|
||||
Ils sont en revanche beaucoup plus chers que les disque durs à capacité de stockage égale. Généralement, on y stockera au moins le système d'exploitation de la machine (Operating System ou OS) pour garantir une certaine rapidité d'execution. Dans le cadre d'un serveur, on y stockera aussi si possible les conteneurs type [docker](/fr/serveex/core/docker) et les bases de données. De manière générale, toute données dont un a besoin régulièrement et rapidement pour des calculs (site web, applications, etc...).
|
||||
|
||||
### La carte réseau
|
||||
---
|
||||
|
||||
Une __carte réseau__ sert à faire communiquer votre machine avec votre réseau (dont internet). Elle est composée d'une puce de controle et d'un ou plusieurs port réseau. Ces ports peuvent dits _ports ethernet_ peuvent être dans plusieurs formats physique et dans plusieurs normes de débit :
|
||||
|
||||
- RJ45 Gigabit ethernet 10/100/1000 : le format standard de prise RJ45 permettant de dialoguer à des débit allant de 10Mbps (soit 0,125Mo/s) à 1000Mbps (soit 125Mo/s)
|
||||
- RJ45 2.5G : Même prise, pouvant dialoguer jusqu'à 2,5Gpbs soit 2 500Mbps (donc 312,5Mo/s)
|
||||
- RJ45 5G : Même prise, pouvant dialoguer jusqu'à 5Gpbs (donc 625Mo/s)
|
||||
- RJ45 10G Base T : Même prise pouvant dialoguer jusqu'à 10Gbs (soit 1,25Go/s)
|
||||
- SFP 1G : Prise SFP, généralement utilisée pour la fibre optique, pouvant dialoguer jusqu'à 1Gpbs
|
||||
- SFP+ 10G : Prise SFP amélioriée, aussi utilisée pour la fibre optique, pouvant dialoguer jusqu'à 10Gbps.
|
||||
|
||||
::warning
|
||||
|
||||
__Attention :__ Pensez bien à dimensionner vos appareils réseaux (routeurs, switch, cables...) en fonction du débit que vous souhaitez entre vos appareils. D'autre part, il existe plusieurs normes de cables RJ45 selon la vitesse permise. On privilegiera dans la plupart des cas des cables RJ45 CAT5E, et au delà de 10Gbps, on privilégiera du CAT6A, bien que le CAT5E soit utilisable à ces vitesses à courte distance. Quant à la fibre, c'est tout un sujet (simplex, duplex, transceiver...).
|
||||
::
|
||||
|
||||
La carte réseau est généralement directement intégrée à la carte mère, mais vous pouvez utiliser des cartes réseau dédiées comme par USB par exemple ou via un port d'extension PCIe. De manière générale, sur un serveur, on appréciera d'avoir au moins deux ports ethernet afin d'avoir de la redondance en cas de panne.
|
||||
|
||||
### Entrées et sorties
|
||||
---
|
||||
|
||||
Les __ports d'entrées et sorties__ (Input/Outpout) sont l'ensemble des ports de la machine permettant d'échanger de l'information avec des appareils externes (écrans, clavier, souris, réseau...). Généralement, sur une carte mère on retrouve le ou les ports réseaux, des ports USB de plusieurs formats et vitesse différentes, un ou plusieurs port vidéo, et des ports audio.
|
||||
|
||||
Selon vos usages, vous devrez choisir votre carte mere et ses éventuelles cartes d'extension en fonction de vos besoin à ce niveau.
|
||||
|
||||
### L'alimentation
|
||||
---
|
||||
|
||||
__L'alimentation__ est le composant qui permet d'alimenter électriquement les composants de votre machine. Elle se compose d'un cable secteur en entrée, et de plusieurs types de cables en sorties. Ces cables se branchent sur la carte mère et différents périphériques le nécessitant, comme les disques dur, ou certaines cartes graphiques dédiées. L'alimentation se caractérise par sa puissance, en Watt, sa modularité (cables attachés ou détachables), et son rendement, en pourcentage. Comprendre qu'une alimentation de 500W avec un rendement de 80% consommera en fait 625W pour fournir ces 500W.
|
||||
|
||||
Une autre caractéristiques des alimentations sont leur format. Il en existe plusieurs standard, comme ATX L pour les plus grandes jusqu'à SFX pour les plus petites. Il en existe aussi des spécifiques aux serveurs rackables, par définition assez plats.
|
||||
|
||||
Pour choisir votre alimentation, la convention est d'estimer la puissance electrique demandée par votre machine à usage et de multiplier par deux cette puissance. En effet, le rendement des alimentations se situe environ à 50% de sa charge totale.
|
||||
|
||||
### Le boitier
|
||||
---
|
||||
|
||||
<div style="display: flex; align-items: center;">
|
||||
<img src="/img/global/case.svg" alt="Image" style="max-width: 25%; max-height:230px; margin-right: 20px;">
|
||||
<p>Le <strong>boitier</strong> est également un composant essentiel pour votre machine. Il conditionne le refroidissement de cette dernière, avec ses ventilateurs et sa conception gérant les flux d'air, mais aussi le format de votre carte mere, de votre alimentation et de votre éventuel GPU dédié. D'autre part, il conditionne également le nombre de HDD et leur format. Certains boitiers sont dit rackables, c'est à dire qu'ils peuvent etre fixés dans des racks pour des armoires informatiques (baies serveurs). Choisissez consciencieusement votre boitier en fonction de vos besoins.</p>
|
||||
</div>
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
title: Réseau
|
||||
description: Aperçu du matériel réseau pour homelab — switches, cartes réseau, câbles et comment connecter efficacement vos serveurs.
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Réseau
|
||||
|
||||
::note
|
||||
🎯 __Objectifs :__
|
||||
|
||||
Comprendre les bases du matériel réseau
|
||||
::
|
||||
|
||||

|
||||
|
||||
Un réseau informatique est indissociable du matériel nécessaire à le mettre en place. Le matériel va conditionner les dimension du réseau, les vitesses de communication et les performances du réseau. Dans cet article, nous nous limiterons au réseau les plus simples, composant généralement ceux que l'on peut retrouver chez soi.
|
||||
|
||||
## Le routeur
|
||||
---
|
||||
Le __routeur__ est le point central de votre réseau. C'est lui qui dirige les __paquets__, c'est à dire les blocs d'informations qui transitent sur votre réseau, de l'émetteur vers le bon destinataire. Il permet à la fois de conditionner le routage de l'information au sein de votre réseau mais aussi vers ou depuis l'exterieur. Globalement, il fait communiquer les appareils entre eux et avec internet.
|
||||
|
||||
Vous avez tous un routeur chez vous, c'est la __box__ de votre _FAI_ (Fournisseur d'Accès à Internet).
|
||||
|
||||
Plus généralement, un routeur est composé :
|
||||
|
||||
- d'un port WAN (Wide Area Network) recevant les informations depuis l'internet (ou un réseau de hiérarchie supérieure). Par exemple un port recevant la fibre optique de votre opérateur, ou un port SFP+/RJ45 pour un routeur tiers.
|
||||
- d'un switch, c'est à dire d'un hub composé de plusieurs ports __LAN__ (Local Area Network) permettant de connecter plusieurs lignes et appareils à votre routeur. Ils peuvent etre RJ45 ou SFP/SFP+.
|
||||
- parfois d'un emetteur/recepteur WiFi
|
||||
|
||||
Le routeur peut posséder des capacité de _firewall_, c'est à dire de limiter le traffic d'appareils en particulier, et de _[NAT (Network Adress Translation)](/fr/general/networking/nat)_, c'est à dire de redirection de port. Il possède aussi généralement un _[DHCP (Dynamic Host Configuration Protocol)](/fr/general/networking/nat#le-dhcp)_, servant à attribuer dynamiquement des _adresses IP_ à votre matériel branché au réseau.
|
||||
|
||||
Le routeur conditionne directement la vitesse de communication entre vos appareils. En effet, le port WAN conditionne le débit qu'il peut recevoir de la part de votre FAI. Si vous avez un abonnement de 5 Gb/s, il vous faudra un port WAN d'au moins 5 Gb/s. Mais il conditionne également la vitesse de communication entre vos équipements. Si vous avez des appareils qui communiquent à 5 Gb/s, il faudra que la partie _switch_ du routeur disposent de ports 5 Gb/s. Enfin, si vous avez du matériel WiFi 7 et que vous souhaitez profiter de ces débits, il faudra également que votre routeur le supporte. Et dans le cas d'une borne Wifi tierce, n'oubliez pas que son port réseau doit disposer d'un débit au moins égale au WiFi qu'il diffuse, et le routeur également.
|
||||
|
||||
Débit internet, nombre d'équipements à brancher, débit WiFi, débit réseau, ce sont quatre points à regarder avec attention lorsque vous souscrivez à une offre avec une box internet ou lorsque vous achetez votre propre routeur.
|
||||
|
||||
::tip
|
||||
✨ __Astuce :__
|
||||
Vous pouvez utiliser sans difficulté un routeur tiers pour votre réseau qui remplacera la gestion de votre box internet si celle-ci surpporte le mode _Bridge_. En France seul l'opérateur Free le permet. C'est également possible avec les opérateurs ne disposant pas de ce mode, mais avec de grandes difficultés et sans toutes les fonctionnalités qu'un routeur tiers pourrait vous apporter.
|
||||
::
|
||||
|
||||
## Le Switch
|
||||
---
|
||||
|
||||
Le __switch__ ou commutateur, est un appareil qui permet de brancher plusieurs appareils au réseau. C'est littéralement un hub, qui se connecte directement au routeur ou à un autre switch, jusqu'au routeur. Il permet d'éviter de saturer toutes les prises switch du routeur, ou de délocaliser le matériel dans une autre pièce, sans tirer un cable par appareil vers le routeur. Un autre cas d'usage est de pouvoir séparer plusieurs réseaux gérés par un meme routeur.
|
||||
|
||||
Il en existe globalement de deux types :
|
||||
|
||||
- Les switch non managés, les plus courants. Ils sont plug-n-play, c'est à dire que vous les branchez et tout est réglé tout seul.
|
||||
- Les switchs managés. Ils disposent d'une interface de configuration (en ligne de commande ou via une interface web), servant à affiner les règles de routages asservies au routeur. C'est très efficace pour séparer des réseaux virtuels entre vos appareils, mais généralement nécessite beaucoup de temps de configuration et sont moins pratiques à utiliser qu'un simple switch non managé.
|
||||
|
||||
::warning
|
||||
|
||||
__Attention :__ Veillez à bien utiliser un switch avec des ports disposant des débits cohérents avec le matériel de votre réseau
|
||||
::
|
||||
|
||||
## Les cables
|
||||
---
|
||||
|
||||
Les cables sont des composants essentiels de votre réseau. Selon leur type et catégorie, ils limiteront le débit entre vos appareils et nécessitent donc d'etre dimensionnés de manière cohérente avec votre réseau. Ils doivent être compatibles avec les ports de vos apapreils. Pour rappel, voici les normes de ports les plus utilisées:
|
||||
|
||||
- RJ45 Gigabit ethernet 10/100/1000 : le format standard de prise RJ45 permettant de dialoguer à des débit allant de 10Mbps (soit 0,125Mo/s) à 1000Mbps (soit 125Mo/s)
|
||||
- RJ45 2.5G : Même prise, pouvant dialoguer jusqu'à 2,5Gpbs soit 2 500Mbps (donc 312,5Mo/s)
|
||||
- RJ45 5G : Même prise, pouvant dialoguer jusqu'à 5Gpbs (donc 625Mo/s)
|
||||
- RJ45 10G Base T : Même prise pouvant dialoguer jusqu'à 10Gb/s (soit 1,25Go/s)
|
||||
- SFP 1G : Prise SFP, généralement utilisée pour la fibre optique, pouvant dialoguer jusqu'à 1Gpbs
|
||||
- SFP+ 10G : Prise SFP amélioriée, aussi utilisée pour la fibre optique, pouvant dialoguer jusqu'à 10Gb/s.
|
||||
|
||||
### Les cables Ethernet
|
||||
|
||||
Ces cables en cuivre disposent généralement d'une prise dite `RJ45`. C'est la prise la plus standard du matériel réseau, que vous retrouvez sur vos routeurs et vos switch.
|
||||
|
||||
Ils sont définis en plusieurs catégorie, définissant le débit maximal selon la distance :
|
||||
|
||||
| Débit | Type de câble | Distance max |
|
||||
|----------|----------------|--------------|
|
||||
| 10 Gb/s | CAT 6A | 100 m |
|
||||
| | CAT 6 | 55 m |
|
||||
| | CAT 5e | 30 m |
|
||||
| 5 Gb/s | CAT 6 | 100 m |
|
||||
| | CAT 5e | 30 m |
|
||||
| 2.5 Gb/s | CAT 5e | 100 m |
|
||||
| 1 Gb/s | CAT 5e | 100 m |
|
||||
| 100 Mbs | CAT 5 | 100 m |
|
||||
|
||||
Certains de ces cables sont plats, ronds, blindés (à relier à la terre), etc. Choisissez en fonction de votre installation. ce qu'il faut comprendre, c'est que pour relier un appareil qui dispose d'une prise RJ45 ethernet 2.5 Gb/s sur un routeur 2.5G b/s, il faut au moins un cable `CAT 5e`.
|
||||
En revanche, si votre appareil est limité à 100 Mb/s, vous n'avez besoin que d'un cable `CAT 5`.
|
||||
|
||||
Aujourd'hui, dans les nouvelles construction, la norme est d'installer des cables `CAT 6A` dans les murs. Ainsi, les prises murales sont prêtes à accepter du 10 Gb/s sur 100 m
|
||||
|
||||
### Les cables optiques
|
||||
Très fins mais très fragile, on commence à les voir de plus en plus dans les installations chez soi. A commencer par le cable opérateur qui est relié entre votre prise fibre et votre box/routeur. Ils ont l'avantage de prendre peu de place, de ne rien consommer comparé à un cable en cuivre qui subit des déperdition d'energie liée à la chauffe, de n'emettre aucun rayonnement (pas besoin de blindage, pas de perturbation du signal) et d'accepter des débits très élevés sur de longue distance.
|
||||
|
||||
Pour du réseau local, il faut comprendre qu'il existe plusieurs types de cables fibres, conditionnant le débit selon la distance, et conditionnant le type de `transceiver` à utiliser. En effet, les cables fibres se branchent sur les ports SFP+ de vos appareils, via un petit objet qui traduit le signal lumineux en information électrique, et qui, dans le sens inverse, traduit les informations électrique en signal lumineux.
|
||||
|
||||
Globalement, pour du réseau local, on évitera de choisir autre chose qu'un cable multimode OM3 avec prise LC, avec un transceiver LC SFP+ 10G. Cela permet de relier des appareils en 10 Gb/s et est compatible avec la plupart des appareils disposant d'un port SFP+.
|
||||
|
||||
::warning
|
||||
|
||||
__Attention :__ Veillez à bien utiliser des `transceiver` compatibles avec vos materiels (routeur, switch ou appareil). Il n'existe pas encore de standard absolu, les constructeurs précisent généralement les marques compatibles.
|
||||
::
|
||||
|
||||
### Les cables DAC
|
||||
Ce sont des cables en cuivre fichés dans des `transceiver`. Ils permettent à deux ports SFP/SFP+ de dialoguer, à courte distance, sans utiliser une fibre plus fragile ou d'adapteteur RJ45. Cependant, ils demandent plus d'énergie, la deperdition dans le cuivre etant non négligeable.
|
||||
|
||||
### Les transceiver SFP+
|
||||
Il permettent de connecter différents typle de cable à vos ports SFP/SFP+. Il en existe pour fibre optique, pour DAC et pour RJ45.
|
||||
|
||||
::warning
|
||||
|
||||
Un transceiver RJ45 consommera beaucoup d'energie due à la deperdition naturelle dans le cuivre, et donc génerera beaucoup de chaleur. Il existe des transceiver basse consommation, consommant moins de 2W. De manière peu intuitive, ils sont généralement indiqués pour des cables plus long (80m au lieu de 30m). Ils sont conseillés plutot que ceux indiqués pour les cables plus court car ces derniers dégagent plus de chaleur et demandent plus d'energie, ce qui peut poser des problemes de compatibilité avec votre matériel, et dégrader votre réseau, voir le couper.
|
||||
::
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Le ProloNAS
|
||||
description: Construire un serveur domestique performant avec un mini PC Intel N100 — guide pratique pour démarrer l'auto-hébergement pour moins de 130 €.
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Le ProloNAS
|
||||
|
||||
Lorsque l'on se lance dans l'aventure de son serveur chez soi, on se pose souvent la question _« Avec quoi dois-je commencer ? »_, _« Cela coûte cher non ? »_, et on finit soit par ne pas le faire, soit par acheter un NAS propriétaire qu'on jettera à la poubelle au bout d'un an quand on aura compris que cela n'apporte rien à part des problèmes et de l'argent jeté par la fenêtre.
|
||||
|
||||
Un serveur, ce n'est pas une armoire. C'est n'importe quel ordinateur capable de faire tourner linux. C'est pour cela que les mini PC à base de processeurs N100 sont populaires. Pour 100-130€ sur des plateformes chinoises on a une machine sous linux capable de tourner H24 des années durant, et d'executer tout ce qu'on attend d'un serveur/cloud chez soi sans perdre en performances.
|
||||
|
||||
Ce n'est objectivement pas cher et n'importe qui s'intéressant au sujet peut se lancer dans l'aventure.
|
||||
|
||||
Un Mini PC à 100 balles + un dock USB à 50 balles accueillant des disques durs cela donne une plateforme à 150 balles versus entre 350 et 1200€ chez les constructeurs.
|
||||
|
||||
C'est tout simplement ça le **ProloNAS, un NAS accessible pour les prolo**. Charge à vous ensuite de calibrer votre besoin en Disques Durs.
|
||||
|
||||
En exemple :
|
||||
|
||||
- Mini PC - **Attention : prendre 16GB - 512GB** : [Aliexpress](https://fr.aliexpress.com/item/1005008477986765.html)
|
||||
- DAS (Direct Attached Storage) - **Attention : prendre "EU plug"** : [Aliexpress](https://fr.aliexpress.com/item/1005007933987260.html)
|
||||
|
||||
- Alternatives avec ventilateur et plus soigné : [Amazon](https://www.amazon.fr/Boîtier-Disque-Ventilateur-Supportant-Capacité/dp/B0DD3GSSCX)
|
||||
|
||||
Je précise que cela ne sont pas des liens affiliés, prenez ce que vous voulez.
|
||||
|
||||

|
||||
|
||||
## Pourquoi un NAS ?
|
||||
|
||||
Un NAS (Network Attached Storage), c'est une machine conçue autour d'un espace de stockage ayant vocation a être partagé sur un réseau. L'idée du NAS, c'est d'avoir un stockage fiable et sécurisé sur lequel s'appuie nos usages et nos applicatifs comme des données cloud par exemple (un drive comme [Nextcloud](/fr/serveex/cloud/nextcloud), une synchro photo comme [Immich](/fr/serveex/cloud/immich). Mais aussi vos media comme vos films (par exemple avec [Plex](/fr/serveex/media/plex)) ou les videos de vos caméra de surveillance.
|
||||
|
||||
**Mais pourquoi un Mini PC sans DAS ne suffirait il pas ?** Bien sûr, un simple mini PC avec ses 1 à 2To de stockage devraient suffire pour la plupart des gens. Et les films pourraient tenir dans un disque dur externe de quelques TB supplémentaire. En revanche ce n'est pas une solution fiable ni extensible de faire tourner ses applications et usages sur du stockage qui au moindre choc, au moindre probleme, fait perdre vos données définitivement.
|
||||
|
||||
Le vrai NAS est construit autour de la fiabilité du support qui contient vos données. Il nécessite de mettre en place des stratégies de stockages type [RAID](/fr/general/storage/raid) afin de préserver vos données en cas de panne et de la sauvegarde en cas de corruption (comme les snapshot [ZFS](/fr/general/storage/zfs)).
|
||||
|
||||
Vous l'aurez compris, l'idée du NAS c'est de déporter chez vous tout ce que vous confiez actuellement à des tiers, tout en assurant la fiabilité du support et la sauvegarde des données stockées.
|
||||
|
||||
## Le problème des NAS grands publics
|
||||
|
||||
De nombreuses marques proposent des plateformes prêtes à accueillir vos disques, avec une palanquée de logiciels en Web UI reluisants. Synology, QNAP, Ugreen, j'en passe et des meilleurs. Cela semble plus facile, pourquoi ne pas se laisser tenter ?
|
||||
|
||||
### Beh le prix déjà !
|
||||
|
||||
350€, c'est le prix de départ d'un NAS 2 baies (sans les disques) chez Syno, avec un processeur qui date de 2019, pas de logement pour un SSD pour l'OS, et 2 pauvres giga-octets de RAM.
|
||||
|
||||
Rappelez vous le **ProloNAS** : un N100 4 coeurs, 16Go de RAM, un SSD de 512Go, pour 100-130€ et un DAS 4 baies à 55€. Soit deux fois moins que le Syno 2 baies, 4 fois moins que le Syno 4 baies.
|
||||
|
||||
### OS limités
|
||||
|
||||
« Oui mais ok l'argument du prix, mais au moins tu lances le truc et c'est bon, t'as de jolies applications qui tournent toute seule sans maintenance ».
|
||||
|
||||
Une année. Une année c'est le temps qu'il m'a fallu pour jeter à la poubelle mon Syno en me disant que j'aurais du dès le départ partir sur mon **ProloNAS** (devenu un vrai serveur, depuis).
|
||||
|
||||
Les constructeurs vous mettent à disposition des OS linux customisés à leur sauce, ne partageant pas les catalogues d'applications linux des distributions majeures, avec des noyaux linux datant souvent de l'antiquité, et surtout des OS tres fermés ne vous permettant pas de développer votre NAS exactement selon vos besoins.
|
||||
|
||||
En gros, soit vous utilisez leurs applications, soit vous rentrer dans un monde de souffrance. Pour donner un exemple, de nombreux conteneurs docker ne fonctionnent pas sur ces NAS constructeurs car le kernel est tellement vieux qu'il ne peut supporter les versions pourtant pas toujours récentes de docker.
|
||||
|
||||
### Dépendance des mises à jours propriétaires
|
||||
|
||||
« Oui mais moi j'ai pas d'autre usages que ce que leurs applicatifs natifs proposent ». Oui oui, moi aussi j'ai cru ça. Mais 1 an après mon Syno, mes usages ont explosés. Media center, gestion de mot de passe, plateforme git, plateforme de développement, Authentification forte et SSO hébergement web... Pourquoi etre bloqués par les solutions constructeurs jamais excellentes, souvent baclées et compatibles avec rien ?
|
||||
|
||||
D'autre part, ces applicatifs, vous devenez dépendant de la bonne volonté du constructeur, en terme de sécurité, de mises à jour, et de disponibilités. Je préfère confier cette dépendance à des projets open-source, mis à jour plus régulièrement et ne dépendant pas de la bonne volonté d'un board commercial.
|
||||
|
||||
Et cela ne s'arrête pas là, qui vous dit que les disques dur que vous avez inséré dans ces NAS proproietaires vont etre supportés ? Synology a rendu incompatible son matériel avec les marques n'ayant pas un partenariat avec eux, afin de booster la vente de leurs propres disques.
|
||||
|
||||
Qui vous dit que votre media center pourra profiter des performances de l'iGPU de votre processeur pour streamer vos media vers vos appareils ? [Synology a désactivé le transcodage matériel de ses NAS](https://www.cachem.fr/synology-desactive-transcodage-materiel-nas/).
|
||||
|
||||
Bref, vous n'avez aucun pouvoir sur un produit qui n'est pas réellement ni ouvert ni à vous.
|
||||
|
||||
## OK mais omment je passe de mon Mini PC Serveex à un ProloNAS ?
|
||||
|
||||
Comme dit plus haut, avec un DAS (hub disque) + de quoi faire du [RAID](/fr/general/storage/raid) avec [ZFS](/fr/general/storage/zfs) par exemple.
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Serveex
|
||||
icon: i-noto-microscope
|
||||
@@ -0,0 +1,193 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: Introduction à Serveex — un projet homelab pour auto-héberger ses services du quotidien avec Debian et Docker, sans dépendre de Google, Apple ou Netflix.
|
||||
navigation:
|
||||
icon: i-lucide-bookmark
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
## Un home lab par un débutant pour les débutants
|
||||
|
||||

|
||||
|
||||
**Serveex** c'est avant tout un projet personnel visant à héberger chez soi le plus de services possibles du quotidien sans passer par des plateformes propriétaires (Google, Apple, Netflix...). L'idée était d'expérimenter, d'apprendre, et de documenter chaque étape. C'est un projet purement pour la science, et n'a pas vocation à être utilisé en production.
|
||||
|
||||
Un grand merci à **Nipah**, pour le partage de ses connaissances infinies, et surtout pour sa patience.
|
||||
|
||||
::note
|
||||
**Pré-requis :**
|
||||
|
||||
- Posséder [un VPS en ligne](https://www.it-connect.fr/les-serveurs-prives-virtuels-vps-pour-les-debutants/) ou une machine locale : idéalement un mini PC (on trouve des N100 pour 100€), mais fonctionne aussi sur laptop ou [une machine virtuelle](https://openclassrooms.com/fr/courses/2035806-virtualisez-votre-architecture-et-vos-environnements-de-travail/6313946-installez-virtualbox). Les [Freebox Delta/Ultra proposent des machines virtuelles](https://next.ink/3493/machines-virtuelles-et-freebox-delta-comment-heberger-votre-premiere-page-web/).
|
||||
- Savoir configurer les [règles NAT d'un routeur et attribuer des baux DHCP](/fr/general/networking/nat)
|
||||
- Savoir configurer la [zone DNS d'un nom de domaine](/fr/general/networking/dns)
|
||||
::
|
||||
|
||||
<div align="center">
|
||||
<img src="/img/serveex/serveex.svg" align="center" width="700">
|
||||
</div>
|
||||
|
||||
L'objectif étant d'être facilement déployable et facile à migrer, voici sa structure :
|
||||
|
||||
### Le coeur du serveur
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-logos-debian" title="Système d'exploitation"}
|
||||
[Installer et configurer Debian 13](/fr/serveex/core/installation)
|
||||
::
|
||||
|
||||
::card{icon="i-logos-docker-icon" title="Moteur de conteneur"}
|
||||
[Installer Docker](/fr/serveex/core/docker)
|
||||
::
|
||||
|
||||
::card{icon="i-carbon-container-registry" title="Docker GUI"}
|
||||
[Installer et déployer Dockge](/fr/serveex/core/docker#installer-dockge-pour-gérer-et-déployer-les-conteneurs)
|
||||
::
|
||||
|
||||
::card{icon="i-noto-globe-showing-americas" title="Reverse Proxy"}
|
||||
[Exposez vos services avec SWAG](/fr/serveex/core/swag)
|
||||
::
|
||||
::
|
||||
|
||||
### La sécurité
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-simple-icons-wireguard" title="VPN"}
|
||||
[Installer et déployer Wireguard](/fr/serveex/security/wireguard)
|
||||
::
|
||||
|
||||
::card{icon="i-noto-key" title="SSO & MFA"}
|
||||
[Installer et déployer Authentik](/fr/serveex/security/authentik)
|
||||
::
|
||||
|
||||
::card{icon="i-logos-cloudflare-icon" title="Zero Trust"}
|
||||
[Installer et déployer Cloudflared](/fr/serveex/security/cloudflare)
|
||||
::
|
||||
::
|
||||
|
||||
### Monitoring
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-solar-pulse-linear" title="Etat des services"}
|
||||
[Installer et déployer Uptime-Kuma](/fr/serveex/monitoring/uptime-kuma)
|
||||
::
|
||||
|
||||
::card{icon="i-lucide-logs" title="Gestion des logs"}
|
||||
[Installer et déployer Dozzle](/fr/serveex/monitoring/dozzle)
|
||||
::
|
||||
|
||||
::card{icon="i-noto-rabbit" title="Gestion de la connexion"}
|
||||
[Installer et déployer Speedtest Tracker](/fr/serveex/monitoring/speedtest-tracker)
|
||||
::
|
||||
|
||||
::card{icon="i-lucide-chart-column-decreasing" title="Etat des ressources"}
|
||||
[Installer et déployer Beszel](/fr/serveex/monitoring/beszel)
|
||||
::
|
||||
|
||||
::card{icon="i-lucide-circle-power" title="Wake on Lan"}
|
||||
[Installer et déployer UpSnap](/fr/serveex/monitoring/upsnap)
|
||||
::
|
||||
::
|
||||
|
||||
### Media
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-cbi-plex-alt" title="Media"}
|
||||
[Installer et déployer Plex](/fr/serveex/media/plex)
|
||||
::
|
||||
|
||||
::card{icon="i-cbi-qbittorrent" title="Seedbox"}
|
||||
[Installer et déployer Qbittorrent](/fr/serveex/media/qbittorrent)
|
||||
::
|
||||
::
|
||||
|
||||
### Cloud Drive & Photos
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-cib-nextcloud" title="Drive"}
|
||||
[Installer et déployer Nextcloud](/fr/serveex/cloud/nextcloud)
|
||||
::
|
||||
|
||||
::card{icon="i-simple-icons-immich" title="Photos"}
|
||||
[Installer et déployer Immich](/fr/serveex/cloud/immich)
|
||||
::
|
||||
::
|
||||
|
||||
### Fichiers & partage
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-noto-open-file-folder" title="Explorateur de fichier"}
|
||||
[Installer et déployer file-browser](/fr/serveex/files/file-browser)
|
||||
::
|
||||
|
||||
::card{icon="i-carbon-share" title="Partage"}
|
||||
[Installer et déployer Pingvin](/fr/serveex/files/pingvin)
|
||||
::
|
||||
::
|
||||
|
||||
### Outils de développement
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-logos-visual-studio-code" title="Visual Studio Code"}
|
||||
[Installer et déployer code-server](/fr/serveex/development/code-server)
|
||||
::
|
||||
|
||||
::card{icon="i-simple-icons-gitea" title="Git Repository"}
|
||||
[Installer et déployer Gitea](/fr/serveex/development/gitea)
|
||||
::
|
||||
|
||||
::card{icon="i-noto-hammer-and-wrench" title="Outils"}
|
||||
[Installer et déployer IT Tools](/fr/serveex/development/it-tools)
|
||||
::
|
||||
::
|
||||
|
||||
### Applications utiles
|
||||
|
||||
:::div{class="relative"}
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px}
|
||||
:::
|
||||
|
||||
::card-group
|
||||
::card{icon="i-cbi-adguard" title="DNS anti-pub et filtres"}
|
||||
[Installer et déployer Adguard Home](/fr/serveex/apps/adguard)
|
||||
::
|
||||
|
||||
::card{icon="i-cbi-bitwarden" title="Gestionnaire de mots de passe"}
|
||||
[Installer et déployer Vaultwarden](/fr/serveex/apps/vaultwarden)
|
||||
::
|
||||
::
|
||||
|
||||
## A venir
|
||||
---
|
||||
|
||||
- Homepage, pour avoir tout vos services en un coup d'oeil et y accéder facilement
|
||||
- Mkdocs pour votre documentation
|
||||
- Docus, alternative à Mkdocs
|
||||
- UpSnap pour réveiller vos machines à distance
|
||||
@@ -0,0 +1,2 @@
|
||||
title: Cœur du serveur
|
||||
icon: i-lucide-server-cog
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
title: Debian 13
|
||||
description: Guide pas à pas pour installer Debian 13 sur un serveur et configurer l'accès SSH, les paquets essentiels et un système de base prêt à l'emploi.
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Debian 13
|
||||
::note
|
||||
🎯 __Objectif :__ Installer Debian 13 et les dépendances principales pour avoir un OS pret à l'emploi, joignable en SSH.
|
||||
::
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
|
||||
1. [Paramètrage BIOS](https://www.debian.org/releases/stable/amd64/ch03s06.fr.html#bios-setup)
|
||||
2. [Téléchargement de l'image Debian 13](https://www.debian.org/download.fr.html)
|
||||
3. [USB bootable (Rufus)](https://dev.to/devops2808/how-to-create-bootable-usb-installer-for-debian-12-4f66)
|
||||
4. [Installer Debian et configurer SSH](https://www.howtoforge.com/tutorial/debian-minimal-server/)
|
||||
5. Installer sudo et ajouter un utilisateur au groupe sudo, pour les privilèges administrateurs
|
||||
Se connecter en root :
|
||||
```sh
|
||||
su -
|
||||
```
|
||||
mettre son mot de passe puis taper :
|
||||
```sh
|
||||
apt install sudo
|
||||
```
|
||||
Ajouter l'utilisateur au groupe sudo :
|
||||
```sh
|
||||
adduser <nomdutilisateur> sudo
|
||||
```
|
||||
|
||||
La prochaine fois que l'utilisateur se connectera, il pourra utiliser la commande `sudo` et ainsi executer des commandes avec les privilèges administrateurs.
|
||||
|
||||
6. [Tout savoir sur la connexion à distance à la console (SSH)](https://www.digitalocean.com/community/tutorials/ssh-essentials-working-with-ssh-servers-clients-and-keys)
|
||||
7. Optionnel - [UPS client en cas de coupure](https://www.sindastra.de/p/2078/how-to-connect-linux-server-to-synology-ups-server) / [et aussi](https://www.reddit.com/r/synology/comments/gtkjam/use_synology_nas_as_ups_server_to_safely_power/)
|
||||
8. Optionnel - Réveil en cas de coupure de courant -> régler le BIOS S0 state
|
||||
9. Optionnel - [Réveiller le serveur à distance (WoW - WoL)](https://dev.to/zakery1369/enable-wake-on-lan-on-debian-4ljd)
|
||||
|
||||
## CLI apps à avoir près de soi
|
||||
---
|
||||
Quelques app que vous utiliserez forcément à un moment donné, autant les installer dès le départ
|
||||
```sh
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
sudo apt install vim btop ranger git duf neofetch samba cifs-utils tree unzip
|
||||
```
|
||||
|
||||
En plus :
|
||||
|
||||
- [gping](https://www.linode.com/docs/guides/how-to-use-gping-on-linux/) - Outil graphique de ping
|
||||
- [lazydocker](https://github.com/jesseduffield/lazydocker) - Gestion de conteneurs docker en CLI
|
||||
|
||||
## Fonctions utiles
|
||||
---
|
||||
### Pare-feu
|
||||
|
||||
- [ufw](https://www.zenarmor.com/docs/network-security-tutorials/how-to-set-up-a-firewall-with-ufw-on-debian)
|
||||
- [Firewalld](https://linuxcapable.com/how-to-install-firewalld-on-debian-linux/)
|
||||
|
||||
### Partage Samba (accès à un disque réseau distant)
|
||||
|
||||
- [Créer et accéder à un partage Samba](/fr/general/networking/samba)
|
||||
|
||||
### Transfert de fichier via rsync
|
||||
|
||||
```sh
|
||||
sudo rsync -avhHSP /source /destination
|
||||
```
|
||||
::note
|
||||
|
||||
Ajoutez ` --exclude @eaDir`{lang=shell} si la source est un NAS Synology
|
||||
::
|
||||
@@ -0,0 +1,181 @@
|
||||
---
|
||||
title: Docker
|
||||
description: Installer Docker et Dockge sur Debian pour déployer et gérer des services auto-hébergés avec de simples stacks de conteneurs.
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# Docker
|
||||
|
||||
Docker, pour installer des services déployables en quelques secondes, et les manager en quelques commandes/clics.
|
||||
|
||||
::note
|
||||
🎯 __Objectifs :__
|
||||
|
||||
- Installer [Docker](https://www.docker.com/)
|
||||
- Installer [Dockge](https://github.com/louislam/dockge) pour manager les stacks
|
||||
- Installer [Watchtower](https://github.com/containrrr/watchtower) pour mettre à jour les conteneurs
|
||||
::
|
||||
|
||||

|
||||
|
||||
## Installer docker
|
||||
---
|
||||
Installez les repo Docker et la clé GPG
|
||||
|
||||
```sh
|
||||
# Add Docker's official GPG key:
|
||||
sudo apt-get update
|
||||
sudo apt-get install ca-certificates curl
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||||
|
||||
# Add the repository to Apt sources:
|
||||
echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
|
||||
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
```
|
||||
Installez les package
|
||||
|
||||
```sh
|
||||
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
```
|
||||
Et voilà !
|
||||
|
||||
**Plus d'options** [Installer docker pour Debian 12](https://docs.docker.com/engine/install/debian/)
|
||||
|
||||
::note
|
||||
|
||||
Dans toute la suite, on part du principe que les stacks sont installées dans le dossier `/docker`, créé grace à la commande :
|
||||
```sh
|
||||
sudo mkdir /docker
|
||||
```
|
||||
::
|
||||
|
||||
## Installer [dockge](https://github.com/louislam/dockge) pour gérer et déployer les conteneurs
|
||||
---
|
||||
[Dockge](https://github.com/louislam/dockge) est un outil web permettant de créer, configurer, lancer et gérer des conteneurs pour Docker. C'est une interface simple, intuitive, qui est plus légère et plus facile pour les débutants que d'utiliser docker en CLI ou Portainer.
|
||||
|
||||

|
||||
|
||||
### Configuration
|
||||
|
||||
Plan des fichiers que nous allons modifier :
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── dockge
|
||||
└── compose.yml
|
||||
```
|
||||
|
||||
Créez le dossier de la stack :
|
||||
|
||||
```sh
|
||||
cd /docker
|
||||
sudo mkdir dockge
|
||||
```
|
||||
|
||||
Puis créez le fichier `compose.yml` dans ce dossier avec l'outil vim que vous avez installé préalablement (dans les outils CLI)
|
||||
|
||||
```sh
|
||||
cd /docker/dockge
|
||||
sudo vi compose.yml
|
||||
```
|
||||
Appuyer sur `i` pour rentrer en modif et copiez-collez ceci.
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
dockge:
|
||||
image: louislam/dockge:1
|
||||
restart: unless-stopped
|
||||
container_name: dockge
|
||||
ports:
|
||||
|
||||
- 3555:5001 # Le port joignable depuis le LAN sera 3555
|
||||
|
||||
volumes:
|
||||
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /docker/dockge/data:/app/data
|
||||
- /docker:/docker
|
||||
environment:
|
||||
|
||||
- DOCKGE_STACKS_DIR=/docker
|
||||
```
|
||||
|
||||
Appuyez sur `Echap` pour quitter le mode modif et tapez `:x` pour enregistrer et quitter
|
||||
|
||||
Pour lancer le conteneur, tapez :
|
||||
|
||||
```sh
|
||||
cd /docker/dockge
|
||||
sudo docker compose up -d
|
||||
```
|
||||
Une fois lancé, tapez dans votre navigateur `http://ipduserveur:3555` et vous tomberez sur la page de login.
|
||||
|
||||
Plus d'info sur [dockge et comment l'utiliser](https://github.com/louislam/dockge)
|
||||
|
||||
Et voilà, vous avez installé docker et un outil facile pour manager vos conteneurs !
|
||||
|
||||
## [Watchtower](https://watchtower.nickfedor.com/), pour mettre à jour automatiquement les conteneurs
|
||||
---
|
||||
Watchtower est un conteneur qui permet de vérifier les mise à jour et d'installer les nouvelles images sans effort, en ajoutant un simple label dans les fichiers `compose.yml` de vos conteneurs.
|
||||
|
||||
### Configuration
|
||||
|
||||
- Ouvrez Dockge dans votre navigateur
|
||||
- Cliquez sur `compose`
|
||||
- Nommez la stack `watchtower`
|
||||
- Copiez collez la configuration ci-dessous à la place de la configuration par défaut dans Dockge
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
watchtower:
|
||||
container_name: watchtower
|
||||
image: ghcr.io/nicholas-fedor/watchtower:latest
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
environment:
|
||||
|
||||
- TZ=Europe/Paris
|
||||
- WATCHTOWER_SCHEDULE=${SCHEDULE}
|
||||
- WATCHTOWER_LABEL_ENABLE=true # watchtower scan tous les conteneurs qui ont le label com.centurylinklabs.watchtower.enable=true
|
||||
- WATCHTOWER_CLEANUP=true
|
||||
- WATCHTOWER_REMOVE_VOLUMES=true
|
||||
#Notifications Discord - décommenter si utilisé
|
||||
#- WATCHTOWER_NOTIFICATIONS=slack
|
||||
#- WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=Watchtower
|
||||
#- WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL=${WH_URL}
|
||||
volumes:
|
||||
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
```
|
||||
|
||||
Puis remplissez les données ci-dessous dans l'encart ".env" de Dockge
|
||||
|
||||
```properties
|
||||
SCHEDULE=
|
||||
WH_URL=
|
||||
```
|
||||
|
||||
| Propriété | Valeur | Exemples |
|
||||
|-------------------------|---------------------------------------------------------------------|----------------------------------------------|
|
||||
| `SCHEDULE`{lang=properties} | Format cron | `0 0 6 * * *` (tous les jours à 6h du matin) |
|
||||
| `WH_URL`{lang=properties} | URL du webhook de votre serveur Discord - ajouter `/slack` à la fin | `https://serveurdiscord/valeur/slack` |
|
||||
|
||||
Pour que Watchtower surveille vos autres conteneurs, ajoutez ceci à vos conteneurs dans leur compose.yml :
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
|
||||
Puis relancez les stacks modifiés. Et voilà, vous avez une bonne carcasse pour commencer à installer les services qui vous plaisent !
|
||||
@@ -0,0 +1,396 @@
|
||||
---
|
||||
title: SWAG
|
||||
description: Configurer SWAG comme reverse proxy avec SSL automatique, exposer vos services en toute sécurité et activer le blocage géographique sur votre homelab.
|
||||
---
|
||||
|
||||
:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
|
||||
# SWAG
|
||||
|
||||
::note
|
||||
🎯 __Objectifs :__
|
||||
|
||||
- Installer Swag
|
||||
- Activer le SSL
|
||||
- Accéder au tableau de bord
|
||||
- Configurer le blocage régional
|
||||
- Exposer Dockge
|
||||
::
|
||||
|
||||
[Swag](https://docs.linuxserver.io/general/swag/) est le noyau de ce homelab. C'est un reverse proxy puissant qui permet d'exposer des services sur le net via un ou des noms de domaines, en se chargeant de l'émission des certificats SSL (pour garder des connexions chiffrées), du routage des requêtes et de la sécurisation des accès (par authent HTTP ou par SSO comme Authelia ou Authentik). Toute la doc nécessaire se [situe ici](https://docs.linuxserver.io/general/swag).
|
||||
|
||||
::warning
|
||||
|
||||
SWAG n'a pour utilité que l'exposition de vos services sur internet. C'est à dire, y accéder via une url publique du type `https://service.mondomaine.fr`. Si vous ne souhaitez pas exposer vos services et plutôt utiliser systématiquement un VPN pour vous connecter à vos services à distance, vous pouvez directement aller [par ici](/fr/serveex/security/wireguard).
|
||||
::
|
||||
|
||||
Ci-dessous, vous trouverez un exemple, exposant Dockge. Nous installerons SWAG, ainsi que le mod dbip servant à bloquer les connexions en fonction de la géoloc, ainsi que le mod dashboard qui permet de piloter le fonctionnement de swag, fail2ban et la géoloc.
|
||||
|
||||
**Principe d'un reverse proxy et application dans notre cas :**
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
---
|
||||
|
||||
::note
|
||||
|
||||
Ce tutoriel part du principe que vous avez un nom de domaine qui pointe vers votre serveur, et que votre box a une règle NAT qui redirige le port `443` vers l'adresse IP et le port `443` de votre serveur. Le nom de domaine d'exemple sera `mondomaine.fr`.
|
||||
::
|
||||
|
||||
Plan des fichiers que nous allons modifier :
|
||||
|
||||
```sh
|
||||
root
|
||||
└── docker
|
||||
└── swag
|
||||
├── config
|
||||
│ ├── dns-conf
|
||||
│ │ └── ovh.ini
|
||||
│ └── nginx
|
||||
│ ├── dbip.conf
|
||||
│ ├── nginx.conf
|
||||
│ └── proxy-confs
|
||||
│ └── dockge.subdomain.conf
|
||||
├── compose.yml
|
||||
└── .env
|
||||
```
|
||||
|
||||
Ouvrez Dockge dans votre navigateur, cliquez sur `compose`, nommez la stack `swag` et copiez la conf ci-dessous
|
||||
|
||||
``` yaml
|
||||
---
|
||||
services:
|
||||
swag:
|
||||
image: lscr.io/linuxserver/swag:latest
|
||||
container_name: swag
|
||||
cap_add:
|
||||
|
||||
- NET_ADMIN
|
||||
env_file:
|
||||
|
||||
- .env
|
||||
environment:
|
||||
|
||||
- TZ=Europe/Paris
|
||||
- URL=${DOMAIN}
|
||||
- EXTRA_DOMAINS=${DOMAINS}
|
||||
- SUBDOMAINS=wildcard # couvre les sous-domaines
|
||||
- VALIDATION=dns
|
||||
- DNSPLUGIN=${PLUGIN}
|
||||
- EMAIL=${EMAIL}
|
||||
- DOCKER_MODS=linuxserver/mods:swag-dbip|linuxserver/mods:swag-dashboard|linuxserver/mods:swag-auto-reload
|
||||
volumes:
|
||||
|
||||
- /docker/swag/config:/config
|
||||
ports:
|
||||
|
||||
- 80:80
|
||||
- 443:443
|
||||
- 81:81 # Nécessaire pour le dashboard
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
|
||||
- swag
|
||||
|
||||
networks:
|
||||
swag:
|
||||
name: swag_default
|
||||
|
||||
```
|
||||
|
||||
::tip
|
||||
✨ __Astuce :__
|
||||
ajoutez le label de watchtower dans chaque conteneur afin d'automatiser les mises à jour
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
#...
|
||||
labels:
|
||||
|
||||
- com.centurylinklabs.watchtower.enable=true
|
||||
```
|
||||
::
|
||||
|
||||
Puis dans le `.env` :
|
||||
|
||||
```properties
|
||||
DOMAIN=
|
||||
DOMAINS=
|
||||
EMAIL=
|
||||
PLUGIN=
|
||||
```
|
||||
|
||||
Remplissez comme suit
|
||||
|
||||
| Propriété | Valeur | Exemples |
|
||||
|--------------------------|---------------------------------------------------------------------------|-----------------------|
|
||||
| ` DOMAIN`{lang=properties} | Votre domaine (cela couvre aussi tous les sous-domaines) | `mondomaine.fr` |
|
||||
| ` DOMAINS`{lang=properties} | Vos éventuels autres domaines | `monsecondomaine.fr` |
|
||||
| ` EMAIL`{lang=properties} | Votre email, pour générer le certificat | `votre@email.fr` |
|
||||
| ` PLUGIN`{lang=properties} | Le plugin pour générer le certificat, lié à votre [fournisseur de zone DNS](https://docs.linuxserver.io/general/swag/) | `ovh`<br>`cloudflare` |
|
||||
|
||||
Ici nous partons du principe que votre zone DNS est chez OVH. Déployez la stack une premiere fois. Dans les logs vous verrez qu'il n'arrivera pas à créer de certificat SSL car le fichier ovh.ini renvoi une erreur. Arretez la stack.
|
||||
|
||||
En CLI, allez dans le dossier dns-conf et éditez le fichier `ovh.ini` :
|
||||
|
||||
::tip
|
||||
✨ __Astuce pour les allergiques au terminal :__
|
||||
vous pouvez utiliser [File Browser](/fr/serveex/files/file-browser) pour naviguer dans vos fichier et éditer vos documents au lieu d'utiliser les commandes du terminal.
|
||||
::
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/dns-conf/ovh.ini
|
||||
```
|
||||
|
||||
Voici ce qui s'affiche :
|
||||
|
||||
```properties
|
||||
# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-ovh/certbot_dns_ovh/__init__.py#L20
|
||||
# Replace with your values
|
||||
dns_ovh_endpoint = ovh-eu
|
||||
dns_ovh_application_key =
|
||||
dns_ovh_application_secret =
|
||||
dns_ovh_consumer_key =
|
||||
```
|
||||
Authentifiez vous et créez [votre token ici](https://www.ovh.com/auth/?onsuccess=https%3A%2F%2Fwww.ovh.com%2Fauth%2Fapi%2FcreateToken).
|
||||
|
||||
Les permissions à configurer sont les suivantes :
|
||||
|
||||
* ``GET /domain/zone/*``
|
||||
* ``PUT /domain/zone/*``
|
||||
* ``POST /domain/zone/*``
|
||||
* ``DELETE /domain/zone/*``
|
||||
|
||||
Notez les 3 clés temporairement et renseignez le fichier `ovh.ini`. (avec vim, `i` pour passer en modif, `Echap` quand c'est fini, `:x` pour sauvegarder et quitter)
|
||||
|
||||
Sauvegardez et quittez le fichier.
|
||||
|
||||
Configurez aussi swag pour qu'il accède à DBIP, le module de gestion des accès par géolocalisation /Ouvrez le fichier nginx.conf
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/nginx.conf
|
||||
```
|
||||
|
||||
Et ajoutez la ligne suivante en dessous de la section `http` :
|
||||
|
||||
```nginx
|
||||
include /config/nginx/dbip.conf
|
||||
```
|
||||
|
||||
Relancez la stack dans Dockge, cette fois le certificat SSL est bien émis ! Vérifiez dans les logs que le serveur est bien ready.
|
||||
|
||||
## Dashboard
|
||||
---
|
||||
Accedez au dashboard via votre réseau local en tapant `http//ipdevotreserveur:81`
|
||||
A gauche, vous trouverez la liste des services actuellement "proxied" (aucun pour le moment). A droite, les IP bannies. En-dessous, une liste d'indicateurs. pour le détail, [c'est par ici](https://www.linuxserver.io/blog/introducing-swag-dashboard).
|
||||
|
||||

|
||||
|
||||
## DBIP
|
||||
---
|
||||
DBIP permet de bloquer les connexions en fonction des pays. Il s'appuie sur le fichier de config nommé `dbip.conf` dans `/docker/swag/config/nginx`. [Plus d'info ici](https://virtualize.link/secure/).
|
||||
|
||||
Dans cet exemple, nous allons le configurer pour bloquer une liste de pays connus pour etre à l'origine de la plupart des connexions malveillantes. Nous allons également configurer une variable au cas où nous souhaiterions permettre au réseau interne du serveur, au réseau local de votre box ainsi qu'à un éventuel vpn en 10.x.x.x de pouvoir accéder à vos services, mais pas directement à internet.
|
||||
|
||||
La configuration est activable ou désactivable pour chaque service qui sera proxied (voir exemple de Dockge plus bas).
|
||||
|
||||
Ouvrez `dbip.conf` :
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/dbip.conf
|
||||
```
|
||||
|
||||
Faites vos modifications ([voir documentation](https://github.com/linuxserver/docker-mods/tree/swag-dbip)), ou prenez l'exemple suivant:
|
||||
|
||||
```nginx
|
||||
geoip2 /config/geoip2db/dbip-country-lite.mmdb {
|
||||
auto_reload 1w;
|
||||
$geoip2_data_continent_code continent code;
|
||||
$geoip2_data_country_iso_code country iso_code;
|
||||
}
|
||||
|
||||
# Country Codes: https://en.wikipedia.org/wiki/ISO_3166-2
|
||||
|
||||
map $geoip2_data_country_iso_code $geo-whitelist {
|
||||
# default yes;
|
||||
# Example for whitelisting a country, comment out 'default yes;' above and uncomment 'default no;' and the whitelisted country below
|
||||
default no;
|
||||
FR yes;
|
||||
}
|
||||
|
||||
map $geoip2_data_country_iso_code $geo-blacklist {
|
||||
default yes;
|
||||
# Example for blacklisting a country, uncomment the blacklisted country below
|
||||
CN no; #China
|
||||
RU no; #Russia
|
||||
HK no; #Hong Kong
|
||||
IN no; #India
|
||||
IR no; #Iran
|
||||
VN no; #Vietnam
|
||||
TR no; #Turkey
|
||||
EG no; #Egypt
|
||||
MX no; #Mexico
|
||||
JP no; #Japan
|
||||
KR no; #South Korea
|
||||
KP no; #North Korea
|
||||
PE no; #Peru
|
||||
BR no; #Brazil
|
||||
UA no; #Ukraine
|
||||
ID no; #Indonesia
|
||||
TH no; #Thailand
|
||||
}
|
||||
|
||||
geo $lan-ip {
|
||||
default no;
|
||||
10.0.0.0/8 yes;
|
||||
172.16.0.0/12 yes;
|
||||
192.168.0.0/16 yes;
|
||||
127.0.0.1 yes;
|
||||
}
|
||||
```
|
||||
|
||||
Sauvegardez et quittez. Redémarrez la stack.
|
||||
|
||||
Dans les fichiers de conf des domaines (section suivante), vous pourrez activer ou désactiver la whitelist ou la blacklist ([voir documentation ici](https://www.forum-nas.fr/threads/tuto-installer-swag-en-docker-reverse-proxy.15057/)). Dans notre cas, la whitelist laisse uniquement passer les requêtes françaises. La blacklist laisse passer tout le monde sauf la liste de pays mentionnée. On utilisera donc la blacklist, sur ce modèle :
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name some-app.*;
|
||||
include /config/nginx/ssl.conf;
|
||||
client_max_body_size 0;
|
||||
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
location / {
|
||||
```
|
||||
|
||||
## Exposer Dockge
|
||||
---
|
||||
::note
|
||||
📋 __Prérequis :__ <br/></br>
|
||||
Nous partons du principe que vous avez créé dans votre [zone DNS](/fr/general/networking/dns) un sous domaine du type `dockge.mondomaine.fr` avec pour `CNAME` `mondomaine.fr` et [à moins que vous utilisiez Cloudflare Zero Trust](/fr/serveex/security/cloudflare), que vous avez déjà redirigé le port `443` de votre box vers le `443` de votre serveur dans [les règles NAT](/fr/general/networking/nat).
|
||||
::
|
||||
|
||||
Il s'agit maintenant d'exposer Dockge sur internet, afin de pouvoir y accéder et gérer vos conteneurs sans que vous soyez chez vous. Pour cela, nous partons du principe que vous avez configuré un sous domaine `dockge.mondomaine.fr` dans votre zone DNS dont le `CNAME` pointe sur `mondomaine.fr`.
|
||||
|
||||
::warning
|
||||
|
||||
Dockge n'utilise pas d'authentification multifacteur. Exposer Dockge sur internet pourrait compromettre les machines auxquelles il est relié. Ne le faite que si vous utilisez un systeme d'authentification multifacteur comme [Authentik](/fr/serveex/security/authentik). Sinon, n'exposez pas avec SWAG et utilisez plutôt un VPN comme [Wireguard](/fr/serveex/security/wireguard).
|
||||
::
|
||||
|
||||
Ouvrez le fichier dockge.subdomain.conf :
|
||||
|
||||
```sh
|
||||
sudo vi /docker/swag/config/nginx/proxy-confs/dockge.subdomain.conf
|
||||
```
|
||||
|
||||
Paramétrez le comme tel :
|
||||
|
||||
```nginx
|
||||
## Version 2023/12/19
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
# indique que le sous-domaine doit être dirigé
|
||||
server_name dockge.*;
|
||||
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
#if ($lan-ip = yes) { set $geo-whitelist yes; }
|
||||
#if ($geo-whitelist = no) { return 404; }
|
||||
# indique que les pays dans la blacklist sont intedits
|
||||
if ($geo-blacklist = no) { return 404; }
|
||||
|
||||
# enable for ldap auth (requires ldap-location.conf in the location block)
|
||||
#include /config/nginx/ldap-server.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-location.conf in the location block)
|
||||
#include /config/nginx/authelia-server.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-location.conf in the location block)
|
||||
#include /config/nginx/authentik-server.conf;
|
||||
|
||||
location / {
|
||||
# enable the next two lines for http auth
|
||||
#auth_basic "Restricted";
|
||||
#auth_basic_user_file /config/nginx/.htpasswd;
|
||||
|
||||
# enable for ldap auth (requires ldap-server.conf in the server block)
|
||||
#include /config/nginx/ldap-location.conf;
|
||||
|
||||
# enable for Authelia (requires authelia-server.conf in the server block)
|
||||
#include /config/nginx/authelia-location.conf;
|
||||
|
||||
# enable for Authentik (requires authentik-server.conf in the server block)
|
||||
#include /config/nginx/authentik-location.conf;
|
||||
|
||||
include /config/nginx/proxy.conf;
|
||||
include /config/nginx/resolver.conf;
|
||||
|
||||
set $upstream_app dockge; # Nom du conteneur
|
||||
set $upstream_port 5001; # Port interne conteneur
|
||||
set $upstream_proto http;
|
||||
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sauvegardez et quittez. La configuration va se mettre à jour en quelques secondes.
|
||||
::note
|
||||
|
||||
Par défaut, SWAG ne connait pas le nom "dockge". Pour qu'il puisse y accéder, vous devez rajouter le réseau de dockge dans le `compose.yml` de SWAG.
|
||||
::
|
||||
|
||||
Rendez-vous sur la stack de SWAG, puis cliquez sur `éditer`, et ajouter le réseau de dockge dans le fichier de conf sur ce modele (les champs `networks`) :
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
container_name: #...
|
||||
# ...
|
||||
networks: # Relie le conteneur au réseau custom
|
||||
|
||||
- dockge # Nom du réseau déclaré dans la stack
|
||||
|
||||
networks: # Définit le réseau custom
|
||||
#...
|
||||
dockge: # Nom du réseau déclaré dans la stack
|
||||
name: dockge_default # Nom véritable du réseau externe
|
||||
external: true # Précise que c'est un réseau à rechercher en externe
|
||||
```
|
||||
|
||||
::note
|
||||
|
||||
Ici nous partons du principe que le nom du réseau de dockge est `dockge_default`. Vous pouvez vérifier que la connexion est opérationnelle en visitant le dashboard de SWAG en tapant `http://ipduserveur:81`.
|
||||
::
|
||||
|
||||
Déployez à nouveau la stack de SWAG.
|
||||
|
||||
Patientez puis tapez `https://dockge.mondomaine.fr` dans votre navigateur, vous devriez être redirigé vers dockge. Vous pouvez vérifier le statut du service via le dashboard (depuis votre réseau local, http://ipdevotreserveur:81)
|
||||
|
||||
## Exposer un autre service avec SWAG
|
||||
---
|
||||
Swag dispose de modeles pour la plupart des services connus, nommés `nomduservice.subdomain.conf.sample`. Il vous suffit de créer le sous-domaine dans votre zone DNS chez votre registrar (comme OVH par exemple), de le faire pointer sur votre domaine principale (via un enregistrement CNAME) et de copier en renommant `nomduservice.subdomain.conf.sample` en `nomduservice.subdomain.conf`.
|
||||
|
||||
```sh
|
||||
cd /docker/swag/config/proxy-confs
|
||||
sudo cp nomduservice.subdomain.conf.sample nomduservice.subdomain.conf
|
||||
```
|
||||
::caution
|
||||
|
||||
- __Si le sous domaine n'est pas redirigé correctement__
|
||||
- éditez le fichier et vérifiez notamment le nom du conteneur dans `set $upstream_app nomduconteneur;`{lang=nginx}
|
||||
|
||||
- vérifiez que vous avez bien ajouté le réseau du conteneur dans le `compose.yml` de SWAG.
|
||||
::
|
||||
|
||||
Vous pouvez aussi choisir le sous-domaine en changeant la variable `server_name votresousdomaine.*;`{lang=nginx} et en renommant le fichier `votresousdomaine.subdomain.conf`.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user