Compare commits

48 Commits

Author SHA1 Message Date
Djeex 5d872ab3c6 Fixed bazarr compose 2025-08-28 20:46:27 +00:00
Djeex 8cfc044087 Lumeex update 2025-08-28 20:41:58 +00:00
Djeex d08e86d04f Lumeex 2025-08-12 14:50:47 +00:00
Djeex 4722c5c1a8 Fixed tips + fixed social 2025-08-11 12:17:19 +00:00
Djeex 876d0a6ff5 Better sh hl 2025-08-10 22:59:47 +00:00
Djeex 6eaf8a5c94 Network 2025-07-27 16:36:50 +00:00
Djeex 45bca17862 Maintenance 2025-07-27 16:22:17 +00:00
Djeex 66d51c4010 Typo 2025-07-27 14:27:10 +00:00
Djeex da67053e3b Better translation 2025-07-26 21:38:50 +00:00
Djeex 10040814e1 Cleaning 2025-07-26 21:28:25 +00:00
Djeex 4ff886bab4 Hardware 2025-07-26 21:07:35 +00:00
Djeex 0b70a6f693 Fixed links 2025-07-20 17:58:30 +00:00
Djeex 4af8bbe1e4 New directory and icons 2025-07-20 17:49:48 +00:00
Djeex c89b16d0ae Fixed typo 2025-07-19 18:07:50 +00:00
Djeex d2ae627ec6 Fixed wrong env var description 2025-07-19 18:02:54 +00:00
Djeex dff7947b1b Fixed scheme 2025-07-18 15:26:51 +00:00
Djeex f72a4eefc9 Some fix 2025-07-18 15:02:16 +00:00
Djeex a4d7ac0182 Some fix 2025-07-18 14:43:25 +00:00
Djeex cf9c3a2b14 Different updates 2025-07-18 10:18:36 +00:00
Djeex ebce70c352 RAID 2025-07-14 22:14:20 +00:00
Djeex f50ce5472c Home update + Serveex introduction new illustration 2025-07-14 15:50:05 +00:00
Djeex e82eaab851 Fixed doc about using ' instead of " for password using $ caractere in argon hash generation command, preventing working password for admin panel. Please notice Vaultwarden own doc isn't correct. 2025-07-13 16:54:19 +00:00
Djeex 211107e2ff Fixed wrong compose + added WG_PORT 2025-07-13 11:21:06 +00:00
Djeex d318c65d6c Nvidia-stock-bot v4 2025-07-13 10:44:46 +00:00
Djeex ad52d4a654 Fixed typo 2025-07-12 23:03:00 +00:00
Djeex d72fb9f1ea Fixed typo 2025-07-12 10:39:06 +00:00
Djeex 71f8ce40d4 Fixed Dozzle 2025-07-11 16:39:16 +00:00
Djeex 1d424bd197 Script backup luks header 2025-07-11 16:35:14 +00:00
Djeex c1d3d35e24 Filebrowser + Dozzle updates 2025-07-11 14:13:18 +00:00
Djeex 7059821f1c updated link 2025-07-11 07:27:21 +00:00
Djeex ba5047030a updated version 2025-07-11 07:17:55 +00:00
Djeex 4fd1c8db1e updated version 2025-07-11 07:03:46 +00:00
Djeex 208f95c5ab updated compose 2025-07-11 07:01:33 +00:00
Djeex e30693d39e Fixed translation 2025-07-09 16:34:59 +00:00
Djeex 80465d7398 Removed french discord link 2025-07-07 14:16:00 +00:00
Djeex f5da0b4eb3 Github social added - removed discord link (french) 2025-07-07 08:20:26 +00:00
Djeex ccda4aa34f Fixed pic 2025-07-04 16:03:30 +00:00
Djeex fd66282a60 Minor fixes 2025-07-04 15:07:38 +00:00
Djeex 6046093b24 'excurity" urls fixed + language switcher 2025-07-04 14:54:28 +00:00
Djeex 7aadc4378c Minor fixe 2025-07-04 14:31:19 +00:00
Djeex 1654fd9287 Minor fixes 2025-07-04 14:23:25 +00:00
Djeex a9284d1d85 Removed discord link (french) 2025-07-04 13:44:10 +00:00
Djeex 5c207add10 Fully translated + good linking 2025-07-04 13:37:55 +00:00
Djeex e9ccda2e14 Fully translated (but some work to do about urls) 2025-07-04 12:54:44 +00:00
Djeex acdebcb682 Translation #2 2025-07-03 20:22:12 +00:00
Djeex 675ecaee3a First translated batch 2025-06-04 15:04:16 +00:00
Djeex 7630f970fe Added license 2025-06-04 14:30:04 +00:00
Djeex 3e6bb5aa92 First commit -clean 2025-06-04 14:19:33 +00:00
261 changed files with 24195 additions and 21689 deletions
+14
View File
@@ -0,0 +1,14 @@
module.exports = {
root: true,
extends: ['@nuxt/eslint-config'],
ignorePatterns: [
'dist',
'node_modules',
'.output',
'.nuxt'
],
rules: {
'vue/max-attributes-per-line': 'off',
'vue/multi-word-component-names': 'off'
}
}
Regular → Executable
+9 -36
View File
@@ -1,39 +1,12 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
*.iml
.idea
.eslintcache
# Local env files
*.log*
.nuxt
.vscode
.DS_Store
coverage
dist
sw.*
.env
.env.*
!.env.example
# Template
template/pnpm-lock.yaml
# npm pack
*.tgz
# Temp files
.tmp
.profile
*.0x
#VSC
.history
.output
+1
View File
@@ -0,0 +1 @@
strict-peer-dependencies=false
-24
View File
@@ -1,24 +0,0 @@
{
"git": {
"commitMessage": "chore(release): v${version}",
"tagName": "v${version}"
},
"github": {
"release": true,
"releaseName": "v${version}",
"web": true
},
"hooks": {
"before:init": ["pnpm lint"]
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": {
"name": "conventionalcommits"
},
"infile": "CHANGELOG.md",
"header": "# Changelog",
"ignoreRecommendedBump": true
}
}
}
-40
View File
@@ -1,40 +0,0 @@
# 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
# Template
template/pnpm-lock.yaml
# npm pack
*.tgz
# Temp files
.tmp
.profile
*.0x
#VSC
.history
.wrangler
@@ -1,2 +0,0 @@
title: Getting Started
icon: false
@@ -1,54 +0,0 @@
---
title: Introduction
description: Welcome to Docus theme documentation.
navigation:
icon: i-lucide-house
seo:
title: Introduction
description: Discover how to create, manage, and publish documentation
effortlessly with Docus.
---
Welcome to **Docus**, a fully integrated documentation solution built with [Nuxt UI Pro](https://ui.nuxt.com/pro).
## What is Docus?
Docus is a theme based on the [UI Pro documentation template](https://docs-template.nuxt.dev/). While the visual style comes ready out of the box, your focus should be on writing content using the Markdown and [MDC syntax](https://content.nuxt.com/docs/files/markdown#mdc-syntax) provided by [Nuxt Content](https://content.nuxt.com).
We use this theme across all our Nuxt module documentations, including:
::card-group
:::card
---
icon: i-simple-icons-nuxtdotjs
target: _blank
title: Nuxt Image
to: https://image.nuxt.com
---
The documentation of `@nuxt/image`
:::
:::card
---
icon: i-simple-icons-nuxtdotjs
target: _blank
title: Nuxt Supabase
to: https://supabase.nuxtjs.org
---
The documentation of `@nuxt/supabase`
:::
::
## Key Features
This theme includes a range of features designed to improve documentation management:
- **Powered by** [**Nuxt 3**](https://nuxt.com): Utilizes the latest Nuxt framework for optimal performance.
- **Built with** [**Nuxt UI**](https://ui.nuxt.com) **and** [**Nuxt UI Pro**](https://ui.nuxt.com/pro): Integrates a comprehensive suite of UI components.
- [**MDC Syntax**](https://content.nuxt.com/usage/markdown) **via** [**Nuxt Content**](https://content.nuxt.com): Supports Markdown with component integration for dynamic content.
- [**Nuxt Studio**](https://content.nuxt.com/docs/studio) **Compatible**: Write and edit your content visually. No Markdown knowledge is required!
- **Auto-generated Sidebar Navigation**: Automatically generates navigation from content structure.
- **Full-Text Search**: Includes built-in search functionality for content discovery.
- **Optimized Typography**: Features refined typography for enhanced readability.
- **Dark Mode**: Offers dark mode support for user preference.
- **Extensive Functionality**: Explore the theme to fully appreciate its capabilities.
@@ -1,65 +0,0 @@
---
title: Installation
description: Get started with Docus.
navigation:
icon: i-lucide-download
seo:
description: Get started with Docus documentation theme.
---
## Local development
::steps
### Create your docs directory
Use the `docus` CLI to create a new Docus project in the `docs/` directory:
```bash [Terminal]
npx docus init docs
```
We recommend using the `npm` package manager.
### Start your docs server in development
Move to the `docs/` directory and start your docs server in development mode:
```bash [Terminal]
npm run dev
```
A local preview of your documentation will be available at <http://localhost:4000>
### Write your documentation
Head over the [Edition](https://docus.dev/concepts/edition) section to learn how to write your documentation.
::
## Online Edition with [Nuxt Studio](https://content.nuxt.com/studio)
::prose-steps
### Create a new project on [Nuxt Studio](https://nuxt.studio)
Choose `Start from a template` and select **Docus.** Clone it on your GitHub personal account or any organisation of your choice.
### Deploy in one click
Once your project has been created and you're in the project dashboard, navigate to the `Deploy` section, choose the `GitHub Pages` tab and set your [Nuxt UI Pro license](https://ui.nuxt.com/pro/pricing) (`NUXT_UI_PRO_LICENSE` ) in the environment variables then click on the **Deploy** button.
:::prose-note
---
to: https://content.nuxt.com/docs/studio/setup#enable-the-full-editing-experience
---
This is a one click static deployment available with [GitHub Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site) but you can also handle deployment yourself and use the `Selfhosted` tab.
:::
### Write your documentation in the editor
Once the deployment is achieved, you'll be able to display the preview of your documentation. You can browse your content pages to edit them or create new ones.
:video{controls loop poster="https://res.cloudinary.com/nuxt/video/upload/v1747230893/studio/wzt9zfmdvk7hgmdx3cnt.jpg" src="https://res.cloudinary.com/nuxt/video/upload/v1747230893/studio/wzt9zfmdvk7hgmdx3cnt.mp4"}
::
::prose-tip{to="https://content.nuxt.com/docs/studio/debug"}
If you want to try Docus and Nuxt Studio in develoment mode without an UI Pro license, you can check the Nuxt Content documentation for local setup with Nuxt Studio.
::
@@ -1,52 +0,0 @@
---
navigation:
icon: i-lucide-folder-tree
title: Project Structure
---
Docus provides a ready-to-use [documentation website starter](https://github.com/nuxtlabs/docus/tree/.starter).
This is the minimal directory structure to get an up and running Docus website.
```bash
content/
index.md
public/
favicon.ico
package.json
```
### `content/` directory
This is where you [write pages](https://docus.dev/concepts/edition) in Markdown.
### `public/` directory
Files contained within the `public/` directory are served at the root and are not modified by the build process of your documentation. This is where you can locate your medias.
### `package.json`
This file contains all the dependencies and scripts for your application. The `package.json` of a Docus application si really minimal and looks like:
```json [package.json]
{
"name": "docus-starter",
"scripts": {
"dev": "docus dev",
"build": "docus build"
},
"devDependencies": {
"docus": "latest"
}
}
```
### `app.config.ts`
*This file is not mandatory to start a Docus application.*
This is where you can [configure Docus](https://docus.dev/concepts/configuration) to fit your branding, handle SEO and adapt links and socials.
::prose-tip{to="https://docus.dev/concepts/nuxt"}
Docus uses a layer system, you can go further and use any feature or file of a classical Nuxt project from `nuxt.config.ts` to `app/components` or `server/` directory.
::
@@ -1,123 +0,0 @@
---
title: Web Editor
description: Build your documentation using Nuxt Studio web editor
navigation:
icon: i-lucide-mouse-pointer-2
---
## **Introduction**
The [Nuxt Studio](https://nuxt.studio) **web editor** is a browser-based visual interface for creating, editing, and reviewing your documentation. It provides a preview experience while keeping your work in sync with your Git repository.
:video{controls loop src="https://res.cloudinary.com/nuxt/video/upload/v1747230893/studio/wzt9zfmdvk7hgmdx3cnt.mp4"}
::prose-tip{to="https://content.nuxt.com/studio"}
Learn more about Nuxt Studio in the Nuxt Content documentation.
::
## **Web Editor vs. CLI**
The **web editor** of [Nuxt Studio](https://nuxt.studio) allows you to manage your documentation entirely from your browser. There is no need for local development tools or terminal commands. Its ideal for maintaining your docs in one centralised place, with an easy tool without any Markdown skills required.
The **CLI (Command Line Interface)**, on the other hand, is a local tool designed for developers who prefer working in their own IDE.
::prose-note
Both tools are fully integrated with Git, so you can switch between them as needed. Team members can choose whichever method suits their workflow best.
::
## **Two distinct editors**
Nuxt Studio offers a versatile workspace for both developers and content writers, giving them the freedom to choose between two distinct editors for content creation and management: the **Markdown editor** and the **Visual editor**.
You can select your favorite editor from the settings page of your project.
::prose-note
Each editor serves its own purpose, some users are used to Markdown edition, while others prefer a non-technical, visual approach. At the end, **Markdown syntax is the final output** for both editors.
::
## **Markdown editor**
The Markdown editor in Nuxt Studio provides full control over your content, allowing you to write directly you documentation in **Markdown** and integrate Vue components with the [MDC syntax](https://content.nuxt.com/docs/files/markdown#mdc-syntax).
When your file is saved with the Markdown editor, the content is stored exactly as you've written it, preserving all specific syntax and formatting. This editor is ideal for users comfortable with Markdown who want precise control over the layout and structure of their content.
## **Visual editor**
The Nuxt Studio editor is heavily inspired by Notion, well known for its intuitive design and flexibility. Much like a standard text editor, the Studio editor is designed to be familiar and easy to use.
However, it stands out with its additional features that improve the writing experience:
### **Toolbar**
Highlight your text to reveal the toolbar, giving you access to all the standard text editing features provided by the [Markdown syntax](/essentials/markdown-syntax):
- Title formatting
- Bold
- Italic
- Strike-through
- Code
- Link
- Class
- Bullet list
- Numerated list
### **The** `/` **shortcut**
Simply type `/` anywhere in the editor to access all Studio features.
#### **Formatting features**
- Title formatting
- Line break
- Horizontal rule
- Code-block
- Paragraph
- Bold & italic
#### **Components**
One of Studio's standout features is its ability to integrate and customize any complex component directly within the editor.
In other terms, all [Nuxt UI components](/essentials/components) are usable and can be integrated directly from the editor. An editor can also tweak the component properties, slots and styles.
::prose-note
You can also create custom components and let the user integrate them from the visual editor.
::
Just type `/` to access the list of all the components available.
#### **Images**
Using the `/`shortcut, you can quickly insert an image by selecting the `Image` option. A modal will open to let you choose the media you want to insert.
From the media modal, you can set the **alt attribute** for SEO and accessibility purpose.
#### **Videos**
Using the `/` shortcut, you can quickly insert a video by selecting the `Video` choice and filling up the Video URL.
As soon as a video is added, a tab will automatically open with all the props field **available by default**, for you to fill out the URL and customize your media.
## **Live Preview**
Once your documentation is deployed, it provides a live preview feature that lets you instantly see updates to your project.
We're using your production website to override contents and display the new visual. This is why we need the URL to be set in the **deploy** section.
When you are editing your website with Studio, the live preview can be displayed on the right part of your screen. You get an instant feedback when typing. It syncs the preview based on your draft updates.
## **Making Changes**
To edit your documentation:
1. **Browse files** using the file explorer.
2. **Open a file** by clicking on it.
3. **Edit content** in either visual or Markdown mode. All edits are automatically saved as drafts.
4. **Preview your changes** to see how theyll appear when published.
## **Publishing Changes**
When youre ready to publish:
- Click the **Publish** button in the top-right corner of the editor.
- Your changes will be pushed directly to your deployment branch and go live immediately.
@@ -1,24 +0,0 @@
---
title: Migration
description: " How to migrate your documentation from an existing Markdown
solution to Docus"
navigation:
icon: i-lucide-replace
---
## **Migrating to Docus**
Already using a Markdown-based solution for your documentation? Whether its **Docus v1**, the **Nuxt UI Pro docs template**, or another static site setup, migrating to Docus is simple and straightforward.
Docus offers a clean and maintainable solution with a single dependency: the Docus library itself. Theres no need to manage multiple .dependencies With everything built-in and maintained together, keeping your documentation up to date is easier than ever.
To migrate, just move your existing Markdown files into the `content/` directory of the Docus starter.
From there, you have two scenarios:
- **If your current docs already use Nuxt Content and the MDC syntax**, make sure the components used in your content exist in Nuxt UI. If any components are missing, you can easily create your own custom ones.
- **If youre using standard Markdown**, you can copy your files as is. Then, enhance your documentation progressively using the [built-in components](https://docus.dev/essentials/components) provided by Nuxt UI.
Once your content has been moved to the `content/` folder, you can go through the [configuration section](https://docus.dev/concepts/configuration) to easily customize your app.
Docus is designed to focus on writing content, so if you're already using Markdown, you can easily switch to it.
@@ -1 +0,0 @@
title: Essentials
@@ -1,223 +0,0 @@
---
title: Markdown Syntax
description: Text, title, and styling in standard markdown.
navigation:
icon: i-lucide-heading-1
---
## Titles
Use titles to introduce main sections. They structure your documentation and help users navigate content.
::code-preview
---
class: "[&>div]:*:my-0"
---
## Titles
#code
```mdc
## Titles
```
::
### Subtitles
Use subtitles to divide sections further. They create a more detailed content hierarchy for better readability.
::code-preview
---
class: "[&>div]:*:my-0"
---
### Subtitles
#code
```mdc
### Subtitles
```
::
::tip
Each title and subtitle creates an anchor and shows up automatically in the table of contents.
::
## Text Formatting
Docus supports most Markdown formatting options.
| Style | How to use | Result |
| ------ | ------------ | ---------- |
| Bold | `**bold**` | **Bold** |
| Italic | `*italic*` | *Italic* |
| Strike | `~~strike~~` | ~~Strike~~ |
Combine formatting for richer text styles and visual emphasis.
| Style | How to use | Result |
| ------------- | ------------------- | ----------------- |
| Bold Italic | `**_bold italic_**` | ***Bold Italic*** |
| Bold Strike | `~~**bold**~~` | ~~**Bold**~~ |
| Italic Strike | `~~*italic*~~` | ~~*Italic*~~ |
## Links
Links connect different parts of your documentation and external resources, essential for user navigation and providing references.
To create a link, wrap the link text in brackets `[]()`.
::code-preview
---
class: "[&>div]:*:my-0"
---
[Nuxt UI Pro](https://ui.nuxt.com/getting-started/installation/pro/nuxt)
#code
```mdc
[Nuxt UI Pro](https://ui.nuxt.com/getting-started/installation/pro/nuxt)
```
::
### Internal links
For linking within your documentation, use root-relative paths like `/getting-started/installation`.
::code-preview
---
class: "[&>div]:*:my-0"
---
[Installation](/getting-started/installation)
#code
```mdc
[Installation](/getting-started/installation)
```
::
## Lists
Organize related items in a structured, readable format. Markdown supports unordered, ordered, and nested lists for various content needs.
### Unordered
Use unordered lists for items without a specific sequence. Start each item with a `-` symbol.
::code-preview
---
class: "[&>div]:*:my-0"
---
- I'm a list item.
- I'm another list item.
- I'm the last list item.
#code
```mdc
- I'm a list item.
- I'm another list item.
- I'm the last list item.
```
::
### Ordered
Use ordered lists when item order matters, like steps in a process. Start each item with a number.
::code-preview
---
class: "[&>div]:*:my-0"
---
1. I'm a list item.
2. I'm another list item.
3. I'm the last list item.
#code
```mdc
1. I'm a list item.
2. I'm another list item.
3. I'm the last list item.
```
::
### Nested
Create hierarchical lists with sub-items for complex structures. Indent sub-items by four spaces for nesting.
::code-preview
---
class: "[&>div]:*:my-0"
---
- I'm a list item.
- I'm a nested list item.
- I'm another nested list item.
- I'm another list item.
#code
```mdc
- I'm a list item.
- I'm a nested list item.
- I'm another nested list item.
- I'm another list item.
```
::
## Tables
Present structured data in rows and columns clearly. Tables are ideal for comparing data or listing properties.
::code-preview
---
class: "[&>div]:*:my-0 [&>div]:*:w-full"
---
| Prop | Default | Type |
| ------- | --------- | -------- |
| `name` | | `string` |
| `size` | `md` | `string` |
| `color` | `neutral` | `string` |
#code
```mdc
| Prop | Default | Type |
|---------|-----------|--------------------------|
| `name` | | `string`{lang="ts-type"} |
| `size` | `md` | `string`{lang="ts-type"} |
| `color` | `neutral` | `string`{lang="ts-type"} |
```
::
## Blockquotes
Highlight important quotations, citations, or emphasized text. Blockquotes visually distinguish quoted content.
### Singleline
Single-line blockquotes are best for short, impactful quotes or citations that fit within a single line. To create a single-line blockquote, add a `>` in front of a paragraph. Ideal for short and impactful quotes.
::code-preview
---
class: "[&>div]:*:my-0"
---
> Nuxt UI Pro is a collection of Vue components, composables and utils built on top of Nuxt UI, oriented on structure and layout and designed to be used as building blocks for your app.
#code
```mdc
> Nuxt UI Pro is a collection of Vue components, composables and utils built on top of Nuxt UI, oriented on structure and layout and designed to be used as building blocks for your app.
```
::
### Multiline
Multi-line blockquotes are suitable for longer quotes or when you need to include multiple paragraphs within a single quotation.
::code-preview
---
class: "[&>div]:*:my-0"
---
> Nuxt UI Pro is a collection of Vue components, composables and utils built on top of Nuxt UI, oriented on structure and layout and designed to be used as building blocks for your app.
>
> Create beautiful, responsive, and accessible Vue applications with Nuxt UI Pro.
#code
```mdc
> Nuxt UI Pro is a collection of Vue components, composables and utils built on top of Nuxt UI, oriented on structure and layout and designed to be used as building blocks for your app.
>
> Create beautiful, responsive, and accessible Vue applications with Nuxt UI Pro.
```
::
@@ -1,396 +0,0 @@
---
title: Code Blocks
description: Display inline code and code blocks in your documentation.
navigation:
icon: i-lucide-code-xml
---
## Basic
### Inline Code
Use inline code to display code snippets within text paragraphs. It's ideal for referencing code elements directly in sentences.
::code-preview
---
class: "[&>div]:*:my-0"
---
`inline code`
#code
```mdc
`inline code`
```
::
### Code Blocks
Use code blocks to display multi-line code snippets with syntax highlighting. Code blocks are essential for presenting code examples clearly.
::code-preview
---
class: "[&>div]:*:my-0 [&>div]:*:w-full"
---
```ts
export default defineNuxtConfig({
modules: ['@nuxt/ui-pro']
})
```
#code
````mdc
```ts
export default defineNuxtConfig({
modules: ['@nuxt/ui-pro']
})
```
````
::
When writing a code-block, you can specify a filename that will be displayed on top of the code block. An icon will be automatically displayed based on the extension or the name.
Filenames help users understand the code's location and purpose within a project.
::code-preview
---
class: "[&>div]:*:my-0 [&>div]:*:w-full"
---
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui-pro']
})
```
#code
````mdc
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui-pro']
})
```
````
::
Every code-block has a built-in copy button that will copy the code to your clipboard.
::tip{to="https://ui.nuxt.com/getting-started/icons/nuxt#theme"}
Icons are already defined by default, but you can customize them in your `app.config.ts`:
```ts [app.config.ts]
export default defineAppConfig({
uiPro: {
prose: {
codeIcon: {
terminal: 'i-ph-terminal-window-duotone'
}
}
}
})
```
::
## Advanced
### CodeGroup
Group code blocks in tabs using `code-group`. `code-group` is perfect for showing code examples in multiple languages or package managers.
::code-preview
---
class: "[&>div]:*:my-0 [&>div]:*:w-full"
---
:::code-group{.w-full}
```bash [pnpm]
pnpm add @nuxt/ui-pro@next
```
```bash [yarn]
yarn add @nuxt/ui-pro@next
```
```bash [npm]
npm install @nuxt/ui-pro@next
```
```bash [bun]
bun add @nuxt/ui-pro@next
```
:::
#code
````mdc
:::code-group
```bash [pnpm]
pnpm add @nuxt/ui-pro@next
```
```bash [yarn]
yarn add @nuxt/ui-pro@next
```
```bash [npm]
npm install @nuxt/ui-pro@next
```
```bash [bun]
bun add @nuxt/ui-pro@next
```
::
````
::
### CodeTree
Display code blocks in a file tree view using `code-tree`. `code-tree` is excellent for showcasing project structures and file relationships.
::code-preview{class="[&>div]:*:my-0 [&>div]:*:w-full"}
:::code-tree{default-value="app/app.config.ts"}
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui-pro'],
future: {
compatibilityVersion: 4
},
css: ['~/assets/css/main.css']
})
```
```css [app/assets/css/main.css]
@import "tailwindcss";
@import "@nuxt/ui-pro";
```
```ts [app/app.config.ts]
export default defineAppConfig({
ui: {
colors: {
primary: 'sky',
colors: 'slate'
}
}
})
```
```vue [app/app.vue]
<template>
<UApp>
<NuxtPage />
</UApp>
</template>
```
```json [package.json]
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"lint": "eslint .",
"lint:fix": "eslint --fix ."
},
"dependencies": {
"@iconify-json/lucide": "^1.2.18",
"@nuxt/ui-pro": "3.0.0-alpha.10",
"nuxt": "^3.15.1"
},
"devDependencies": {
"eslint": "9.20.1",
"typescript": "^5.7.2",
"vue-tsc": "^2.2.0"
}
}
```
```json [tsconfig.json]
{
"extends": "./.nuxt/tsconfig.json"
}
```
````md [README.md]
# Nuxt 3 Minimal Starter
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install the dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm run dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm run build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm run preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
````
:::
::
### `CodePreview`
Use `code-preview` to show code output alongside the code. `code-preview` is ideal for interactive examples and demonstrating code results.
Write the code to be previewed in a the `default` slot and the actual code in the `code` slot.
::code-preview
---
class: "[&>div]:*:my-0 [&>div]:*:w-full"
label: Preview
---
:::code-preview
---
class: "[&>div]:*:my-0"
---
`inline code`
#code
```mdc
`inline code`
```
:::
#code
````mdc
::code-preview
`inline code`
#code
```mdc
`inline code`
```
::
````
::
### `CodeCollapse`
Use `code-collapse` for long code blocks to keep pages clean. `code-collapse` allows users to expand code blocks only when needed, improving readability.
::code-preview
---
class: "[&>div]:*:my-0 [&>div]:*:w-full"
---
:::code-collapse{class="[&>div]:my-0"}
```css [main.css]
@import "tailwindcss";
@import "@nuxt/ui-pro";
@theme {
--font-sans: 'Public Sans', sans-serif;
--breakpoint-3xl: 1920px;
--color-green-50: #EFFDF5;
--color-green-100: #D9FBE8;
--color-green-200: #B3F5D1;
--color-green-300: #75EDAE;
--color-green-400: #00DC82;
--color-green-500: #00C16A;
--color-green-600: #00A155;
--color-green-700: #007F45;
--color-green-800: #016538;
--color-green-900: #0A5331;
--color-green-950: #052E16;
}
```
:::
#code
````mdc
::code-collapse
```css [main.css]
@import "tailwindcss";
@import "@nuxt/ui-pro";
@theme {
--font-sans: 'Public Sans', sans-serif;
--breakpoint-3xl: 1920px;
--color-green-50: #EFFDF5;
--color-green-100: #D9FBE8;
--color-green-200: #B3F5D1;
--color-green-300: #75EDAE;
--color-green-400: #00DC82;
--color-green-500: #00C16A;
--color-green-600: #00A155;
--color-green-700: #007F45;
--color-green-800: #016538;
--color-green-900: #0A5331;
--color-green-950: #052E16;
}
```
::
````
::
@@ -1,424 +0,0 @@
---
title: Markdown Components
description: Use Markdown components to help you structure your content, with
the help of Nuxt UI Pro.
navigation:
icon: i-lucide-component
title: Components
---
Prose components are replacements for HTML typography tags. They provide a simple way to customize your UI when using Markdown.
**Docus and Nuxt UI Pro** provides a set of styled and beautiful prose components to help you write your documentation using the [MDC syntax](https://content.nuxt.com/docs/files/markdown#mdc-syntax).
::prose-note{to="https://ui.nuxt.com/getting-started"}
This page highlights only the prose components best suited for writing documentation. However, you can use **any Nuxt UI or Nuxt UI Pro component** in your Markdown. For the full list of available components, visit the Nuxt UI documentation.
::
### `Accordion`
Use the `accordion` and `accordion-item` components to display an [Accordion](https://ui.nuxt.com/components/accordion) in your content.
::tabs
:::tabs-item{icon="i-lucide-eye" label="Preview"}
::::accordion
:::::accordion-item
---
icon: i-lucide-circle-help
label: What is Docus and what are its key features??
---
Docus is a fully integrated documentation solution built with Nuxt UI Pro. It's a theme based on the UI Pro documentation template that provides a ready-to-use visual. User can focus on content using Markdown and MDC syntax.
:::::
:::::accordion-item
---
icon: i-lucide-circle-help
label: How do I get started with Docus?
---
The only thing you need to start a Docus project is a `content/` folder. You can have a check at the starter for a quick start.
:::::
:::::accordion-item{icon="i-lucide-circle-help" label="What is Nuxt UI Pro?"}
Nuxt UI Pro is a collection of premium Vue components, composables and utils built on top of [Nuxt UI](https://ui.nuxt.com/). Nuxt UI Pro is free in development, but you need a license to use it in production.
:::::
::::
:::
:::tabs-item{icon="i-lucide-code" label="Code"}
```mdc
::accordion
:::accordion-item{label="What is Docus and what are its key features??" icon="i-lucide-circle-help"}
Docus is a fully integrated documentation solution built with Nuxt UI Pro. It's a theme based on the UI Pro documentation template that provides a ready-to-use visual. User can focus on content using Markdown and MDC syntax.
:::
:::accordion-item{label="How do I get started with Docus?" icon="i-lucide-circle-help"}
The only thing you need to start a Docus project is a `content/` folder. You can have a check at the starter for a quick start.
:::
:::accordion-item{label="What is Nuxt UI Pro?" icon="i-lucide-circle-help"}
Nuxt UI Pro is a collection of premium Vue components, composables and utils built on top of [Nuxt UI](https://ui.nuxt.com/). Nuxt UI Pro is free in development, but you need a license to use it in production.
:::
::
```
:::
::
### `Badge`
Use markdown in the default slot of the `badge` component to display a [Badge](https://ui.nuxt.com/components/badge) in your content.
::tabs
:::tabs-item{.my-5 icon="i-lucide-eye" label="Preview"}
::::badge
**v3.0.0**
::::
:::
:::tabs-item{icon="i-lucide-code" label="Code"}
```mdc
::badge
**v3.0.0**
::
```
:::
::
### `Callout`
Use markdown in the default slot of the `callout` component to add eye-catching context to your content.
Use the `icon` and `color` props to customize it. You can also pass any property from the [`<NuxtLink>`](https://nuxt.com/docs/api/components/nuxt-link) component.
You can also use the `note`, `tip`, `warning` and `caution` shortcuts with pre-defined icons and colors.
::tabs
:::tabs-item{.my-5 icon="i-lucide-eye" label="Preview"}
::::div{.flex.flex-col.gap-4.w-full}
:::::note{.w-full.my-0}
Here's some additional information for you.
:::::
:::::tip{.w-full.my-0}
Here's a helpful suggestion.
:::::
:::::warning{.w-full.my-0}
Be careful with this action as it might have unexpected results.
:::::
:::::caution{.w-full.my-0}
This action cannot be undone.
:::::
::::
:::
:::tabs-item{icon="i-lucide-code" label="Code"}
```mdc
::note
Here's some additional information.
::
::tip
Here's a helpful suggestion.
::
::warning
Be careful with this action as it might have unexpected results.
::
::caution
This action cannot be undone.
::
```
:::
::
### `Card` and `CardGroup`
Use markdown in the default slot of the `card` component to highlight your content.
Use the `title`, `icon` and `color` props to customize it. You can also pass any property from the [`<NuxtLink>`](https://nuxt.com/docs/api/components/nuxt-link).
Wrap your `card` components with the `card-group` component to group them together in a grid layout.
::tabs
:::tabs-item{.my-5 icon="i-lucide-eye" label="Preview"}
::::card-group{.w-full.my-0}
:::::card
---
icon: i-simple-icons-github
target: _blank
title: Dashboard
to: https://github.com/nuxt-ui-pro/dashboard
---
A dashboard with multi-column layout.
:::::
:::::card
---
icon: i-simple-icons-github
target: _blank
title: SaaS
to: https://github.com/nuxt-ui-pro/saas
---
A template with landing, pricing, docs and blog.
:::::
:::::card
---
icon: i-simple-icons-github
target: _blank
title: Docs
to: https://github.com/nuxt-ui-pro/docs
---
A documentation with `@nuxt/content`.
:::::
:::::card
---
icon: i-simple-icons-github
target: _blank
title: Landing
to: https://github.com/nuxt-ui-pro/landing
---
A landing page you can use as starting point.
:::::
::::
:::
:::tabs-item{.my-5 icon="i-lucide-eye" label="Preview"}
```mdc
:::card-group
::card
---
title: Dashboard
icon: i-simple-icons-github
to: https://github.com/nuxt-ui-pro/dashboard
target: _blank
---
A dashboard with multi-column layout.
::
::card
---
title: SaaS
icon: i-simple-icons-github
to: https://github.com/nuxt-ui-pro/saas
target: _blank
---
A template with landing, pricing, docs and blog.
::
::card
---
title: Docs
icon: i-simple-icons-github
to: https://github.com/nuxt-ui-pro/docs
target: _blank
---
A documentation with `@nuxt/content`.
::
::card
---
title: Landing
icon: i-simple-icons-github
to: https://github.com/nuxt-ui-pro/landing
target: _blank
---
A landing page you can use as starting point.
::
:::
```
:::
::
### `Collapsible`
Wrap your content with the `collapsible` component to display a [Collapsible](https://ui.nuxt.com/components/collapsible) in your content.
::tabs
:::tabs-item{.my-5 icon="i-lucide-eye" label="Preview"}
::::collapsible
| Prop | Default | Type |
| ------- | --------- | -------- |
| `name` | | `string` |
| `size` | `md` | `string` |
| `color` | `neutral` | `string` |
::::
:::
:::tabs-item{icon="i-lucide-code" label="Code"}
```mdc
::collapsible
| Prop | Default | Type |
|---------|-----------|--------------------------|
| `name` | | `string`{lang="ts-type"} |
| `size` | `md` | `string`{lang="ts-type"} |
| `color` | `neutral` | `string`{lang="ts-type"} |
::
```
:::
::
### `Field` and `FieldGroup`
A `field`is a prop or parameter to display in your content. You can group them by `field-group` in a list.
::tabs
:::tabs-item{.my-5 icon="i-lucide-eye" label="Preview"}
::::field-group{.my-0}
:::::field{name="analytics" type="boolean"}
Default to `false` - Enables analytics for your project (coming soon).
:::::
:::::field{name="blob" type="boolean"}
Default to `false` - Enables blob storage to store static assets, such as images, videos and more.
:::::
:::::field{name="cache" type="boolean"}
Default to `false` - Enables cache storage to cache your server route responses or functions using Nitro's `cachedEventHandler` and `cachedFunction`
:::::
:::::field{name="database" type="boolean"}
Default to `false` - Enables SQL database to store your application's data.
:::::
::::
:::
:::tabs-item{icon="i-lucide-code" label="Code"}
```mdc
::field-group
::field{name="analytics" type="boolean"}
Default to `false` - Enables analytics for your project (coming soon).
::
::field{name="blob" type="boolean"}
Default to `false` - Enables blob storage to store static assets, such as images, videos and more.
::
::field{name="cache" type="boolean"}
Default to `false` - Enables cache storage to cache your server route responses or functions using Nitro's `cachedEventHandler` and `cachedFunction`
::
::field{name="database" type="boolean"}
Default to `false` - Enables SQL database to store your application's data.
::
::
```
:::
::
### `Icon`
Use the `icon` component to display an [Icon](https://ui.nuxt.com/components/icon) in your content.
::code-preview
:icon{name="i-simple-icons-nuxtdotjs"}
#code
```mdc
:icon{name="i-simple-icons-nuxtdotjs"}
```
::
### `Kbd`
Use the `kbd` component to display a [Kbd](https://ui.nuxt.com/components/kbd) in your content.
::code-preview
#code
```mdc
:kbd{value="meta"} :kbd{value="K"}
```
::
### `Tabs`
Use the `tabs` and `tabs-item` components to display [Tabs](https://ui.nuxt.com/components/tabs) in your content.
::code-preview
:::tabs{.w-full}
::::tabs-item{icon="i-lucide-code" label="Code"}
```mdc
::callout
Lorem velit voluptate ex reprehenderit ullamco et culpa.
::
```
::::
::::tabs-item{icon="i-lucide-eye" label="Preview"}
:::::callout
Lorem velit voluptate ex reprehenderit ullamco et culpa.
:::::
::::
:::
#code
````mdc
::tabs{.w-full}
:::tabs-item{icon="i-lucide-code" label="Code"}
```mdc
::::callout
Lorem velit voluptate ex reprehenderit ullamco et culpa.
::::
```
::::
:::tabs-item{icon="i-lucide-eye" label="Preview"}
:::::callout
Lorem velit voluptate ex reprehenderit ullamco et culpa.
:::::
:::
::
````
::
### `Steps`
Wrap your headings with the Steps component to display a list of steps.
Use the `level` prop to define which heading will be used for the steps.
::tabs
:::tabs-item{.my-5 icon="i-lucide-eye" label="Preview"}
::::steps{level="4"}
#### Start a fresh new project
```bash [Terminal]
npx nuxi init -t github:nuxt-ui-pro/docus
```
#### Run docus CLI to run your dev server
```bash [Terminal]
docus dev
```
::::
:::
:::tabs-item{icon="i-lucide-code" label="Code"}
````mdc
::steps{level="4"}
#### Start a fresh new project
```bash [Terminal]
npx nuxi init -t github:nuxt-ui-pro/docus
```
#### Run docus CLI to run your dev server
```bash [Terminal]
docus dev
```
::
````
:::
::
@@ -1,53 +0,0 @@
---
title: Images and Embeds
description: Add image, video, and other HTML elements
navigation:
icon: i-lucide-image
seo:
description: Add image, video, and other HTML elements using Docus theme
---
## Markdown
Display images or videos using standard Markdown syntax.
### Images
::code-preview
![Nuxt Social Image](https://nuxt.com/new-social.jpg)
#code
```mdc
![Nuxt Social Image](https://nuxt.com/new-social.jpg)
```
::
Or with your local images
::code-preview
![Snow-capped mountains in a sea of clouds at sunset](/mountains.webp)
#code
```mdc
![Snow-capped mountains in a sea of clouds at sunset](/mountains.webp)
```
::
::note{to="https://image.nuxt.com/"}
Docus will use `<NuxtImg>` component under the hood instead of the native `img` tag.
::
### Videos
::prose-code-preview
:video{autoplay controls loop src="https://res.cloudinary.com/dcrl8q2g3/video/upload/v1745404403/landing_od8epr.mp4"}
#code
```mdc
:video{autoplay controls loop src="https://res.cloudinary.com/dcrl8q2g3/video/upload/v1745404403/landing_od8epr.mp4"}
```
::
###
-123
View File
@@ -1,123 +0,0 @@
---
seo:
title: Write beautiful docs with Markdown
description: Ship fast, flexible, and SEO-optimized documentation with beautiful
design out of the box. Docus brings together the best of the Nuxt ecosystem.
Powered by Nuxt UI Pro.
---
::u-page-hero
#title
Write beautiful docs with Markdown
#description
Ship fast, flexible, and SEO-optimized documentation with beautiful design out of the box.
Docus brings the best of the Nuxt ecosystem into one CLI.
#links
:::u-button
---
color: neutral
size: xl
to: /getting-started/installation
trailing-icon: i-lucide-arrow-right
---
Get started
:::
:::u-button
---
color: neutral
icon: simple-icons-github
size: xl
to: https://github.com/nuxt-ui-pro/docus
variant: outline
---
Star on GitHub
:::
::
::u-page-section
#title
Shipped with many features
#features
:::u-page-feature
---
icon: i-simple-icons-nuxt
target: _blank
to: https://nuxt.com
---
#title
Built with [Nuxt 3]{.text-primary}
#description
Optimized by the most famous Vue framework. Docus gives you everything you need to build fast, performant, and SEO-friendly websites.
:::
:::u-page-feature
---
icon: i-simple-icons-nuxt
target: _blank
to: https://ui.nuxt.com/
---
#title
Powered by [Nuxt UI Pro]{.text-primary}
#description
Beautiful out of the box, minimal by design but highly customizable. Docus leverages Nuxt UI Pro to give you the best docs writing experience with zero boilerplate, just focus on your content.
:::
:::u-page-feature
---
icon: i-simple-icons-nuxt
target: _blank
to: https://content.nuxt.com
---
#title
Enhanced Markdown syntax by [Nuxt Content]{.text-primary}
#description
The only thing you need to take care about is writing your content. Write your pages in Markdown and extend with MDC syntax to embed Nuxt UI or custom Vue components. Structure, routing, and rendering are handled for you.
:::
:::u-page-feature
---
icon: i-simple-icons-nuxt
target: _blank
to: https://nuxt.com/docs/guide/directory-structure/app-config
---
#title
Customize with [Nuxt App Config]{.text-primary}
#description
Update colors, social links, header logos and component styles globally using the `app.config.ts`, no direct code modifications required.
:::
:::u-page-feature
---
icon: i-simple-icons-nuxt
target: _blank
to: https://content.nuxt.com/studio
---
#title
Collaborate on [Nuxt Studio]{.text-primary}
#description
Write and manage your content visually, with zero Markdown knowledge required. Let your non technical colleagues collaborate on the documentation and integrate Vue components without code skills.
:::
:::u-page-feature
---
icon: i-simple-icons-nuxt
target: _blank
to: https://ui.nuxt.com/components/content-search
---
#title
Built-in navigation and [full-text search]{.text-primary}
#description
Only focus on ordering your content, Docus handles the search modal and auto-generates the side navigation for you.
:::
::
-12
View File
@@ -1,12 +0,0 @@
{
"name": "docus-starter",
"scripts": {
"dev": "docus dev",
"build": "docus build"
},
"dependencies": {
"docus": "latest",
"better-sqlite3": "^11.10.0",
"nuxt": "^3.17.6"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

-139
View File
@@ -1,139 +0,0 @@
# Changelog
## [3.0.5](https://github.com/nuxtlabs/docus/compare/v3.0.4...v3.0.5) (2025-07-01)
### Features
* **app:** add `bash` & `diff` highlight langs ([55e0fa0](https://github.com/nuxtlabs/docus/commit/55e0fa0408e5d0656fa36cb49554668dbc288082))
* **app:** handle `github.rootDir` app config ([0698787](https://github.com/nuxtlabs/docus/commit/06987870962d6c3b604f35126c10018388930c37))
### Bug Fixes
* **app:** add `highlight` on content toc ([a511b50](https://github.com/nuxtlabs/docus/commit/a511b508cd2140f86ba7e045244cf59b68f84c68))
* **app:** allow content navigation variant override ([ccc1340](https://github.com/nuxtlabs/docus/commit/ccc1340faa0da10ac8e17d909739b987b337fcd4))
* **app:** display page links in header ([9acc755](https://github.com/nuxtlabs/docus/commit/9acc75565f1f92013371319a2506edbba7dd415c))
* **app:** import from `@nuxt/kit` ([d8dbee4](https://github.com/nuxtlabs/docus/commit/d8dbee4c804754b94ff3abc9e0d0225f5112688a))
* **app:** improve app config type ([246c16a](https://github.com/nuxtlabs/docus/commit/246c16a984e1e837c19bdd22c439bb6fb5bbf813))
* **app:** use `useClipboard` to copy page ([a8cd48b](https://github.com/nuxtlabs/docus/commit/a8cd48b063679b1c58142842ef857abf15fc8630))
## [3.0.4](https://github.com/nuxtlabs/docus/compare/v3.0.3...v3.0.4) (2025-06-24)
### Bug Fixes
* **prerender:** redirect issue with static deployment ([5f8fbb3](https://github.com/nuxtlabs/docus/commit/5f8fbb32c5cba8479b2562495d0fb7c49291c6de))
## [3.0.3](https://github.com/nuxtlabs/docus/compare/v3.0.2...v3.0.3) (2025-06-20)
### Features
* **nav:** handle nav for docs folder ([65a477a](https://github.com/nuxtlabs/docus/commit/65a477a0974ced0cae7aed6d5fd498ec4e7e0687))
### Bug Fixes
* **landing:** conditionally add prose ([1931668](https://github.com/nuxtlabs/docus/commit/19316680c2c2035d7d72b4628d2caa901b3a01a6))
* **landing:** put back prose ([73edf2a](https://github.com/nuxtlabs/docus/commit/73edf2a417802b5a366af17d17961f4e9a900564))
* **prerender:** add mardown raw content ([e35b7aa](https://github.com/nuxtlabs/docus/commit/e35b7aaab954f69b7b8edd67d92a37ba6678c9d4))
## [3.0.2](https://github.com/nuxtlabs/docus/compare/v3.0.1...v3.0.2) (2025-06-17)
### Features
* **llms:** enable full by default ([677078f](https://github.com/nuxtlabs/docus/commit/677078f0d1e432d7b25e876374e36eeb2796d5f2))
### Bug Fixes
* **setup:** docs layer ([d407155](https://github.com/nuxtlabs/docus/commit/d40715593adecf5e8421e100e897687a28a56e39))
* **starter:** prerender issues ([2facbea](https://github.com/nuxtlabs/docus/commit/2facbeaa3c8c9287c2048c754602063912fe5a49))
## [3.0.1](https://github.com/nuxtlabs/docus/compare/v3.0.0...v3.0.1) (2025-06-17)
### Bug Fixes
* **configs:** handle llms and site default configs in a module ([c642df9](https://github.com/nuxtlabs/docus/commit/c642df95c0a3a8b98eddaa33f00b5b1187eeaba8))
* improve async data key for SEO ([2de5ffe](https://github.com/nuxtlabs/docus/commit/2de5ffe22ccfc9fb46c802d0fbc77f4a764f78a5))
* **llms:** missing deps to enable full ([6d354ce](https://github.com/nuxtlabs/docus/commit/6d354ceafa7792880f50487d2ad392172df10d87))
* **setup:** define default app config in module ([b250a1b](https://github.com/nuxtlabs/docus/commit/b250a1b677c6cf1bf68794615c86599864ce9fd5))
## [3.0.0](https://github.com/nuxtlabs/docus/compare/v3.0.0-alpha.3...v3.0.0) (2025-06-13)
### Features
* **llms:** enable full ([65374af](https://github.com/nuxtlabs/docus/commit/65374af2bc44c42cb35fa66055bd65d092dcd32e))
## [3.0.0-alpha.3](https://github.com/nuxtlabs/docus/compare/v3.0.0-alpha.2...v3.0.0-alpha.3) (2025-06-12)
## [3.0.0-alpha.2](https://github.com/nuxtlabs/docus/compare/v3.0.0-alpha.1...v3.0.0-alpha.2) (2025-06-12)
### Bug Fixes
* **ci:** nightly ([26b92a7](https://github.com/nuxtlabs/docus/commit/26b92a71a2abd1e0216f6d7433edfde696c25264))
* **deps:** add brace-expansion as optimized deps ([32a5589](https://github.com/nuxtlabs/docus/commit/32a5589d0abaec0a4566778fa154e1eee28c014a))
* **deps:** remove brace-expansion optimization ([355ac39](https://github.com/nuxtlabs/docus/commit/355ac39d60674aec4e99234a6e73502db00ce4a8))
## [3.0.0-alpha.1](https://github.com/nuxtlabs/docus/compare/v3.0.0-alpha.0...v3.0.0-alpha.1) (2025-06-12)
### Bug Fixes
* **deps:** refine optimizeDeps of `@nuxt/content` ([#1080](https://github.com/nuxtlabs/docus/issues/1080)) ([ba0f6ef](https://github.com/nuxtlabs/docus/commit/ba0f6effa28b25135719746da17991453ffd678d))
## [3.0.0-alpha.0](https://github.com/nuxtlabs/docus/compare/v2.0.0-alpha.1...v3.0.0-alpha.0) (2025-06-12)
### Bug Fixes
* **docs:** use docus instead of @larbish/docus ([0dee9ec](https://github.com/nuxtlabs/docus/commit/0dee9ec484f4f097c68f2236cb2f927dcbd1db30))
## [2.0.0-alpha.1](https://github.com/nuxtlabs/docus/compare/v2.0.0-alpha.0...v2.0.0-alpha.1) (2025-06-10)
### Bug Fixes
* **cli:** init command ([cfa6290](https://github.com/nuxtlabs/docus/commit/cfa6290f5aa38da852dad57780a277c502df3daf))
## [2.0.0-alpha.0](https://github.com/nuxtlabs/docus/compare/v2.0.0...v2.0.0-alpha.0) (2025-06-10)
### Features
* animate the menu toggle icon ([1cb0273](https://github.com/nuxtlabs/docus/commit/1cb0273d68e8b28b76c22f07cb40ca74c8034189))
* **app:** build command ([cb505bb](https://github.com/nuxtlabs/docus/commit/cb505bbfffc50654a4634846ff39510401c93bf2))
* **app:** compat nuxt version 4 ([594ac08](https://github.com/nuxtlabs/docus/commit/594ac0834738effb6752f3b3efc25d1dd9f0b3d7))
* **app:** init docus v2 ([bc3a9d1](https://github.com/nuxtlabs/docus/commit/bc3a9d101052fb787e562744d5d9b3d87799c24b))
* **app:** rename DocsHeaderRight to DocsPageHeaderLinks ([82a2ca8](https://github.com/nuxtlabs/docus/commit/82a2ca87aefdf446c62564010c6839b76a57de89))
* **ci:** publish nightly ([4753cd4](https://github.com/nuxtlabs/docus/commit/4753cd450f8f6741572911169ea21c88e1f75915))
* **components:** AppHeaderBody ([ae5ecc8](https://github.com/nuxtlabs/docus/commit/ae5ecc86d00918c9fb35f235bc2b72c322932f9d))
* **components:** customizable app header and docs asides ([4ca262b](https://github.com/nuxtlabs/docus/commit/4ca262b247f01ad4b8041bf106886f3d506525fa))
* **config:** handle app.config.ts ([739cbb1](https://github.com/nuxtlabs/docus/commit/739cbb151a5d8ff510662d1ee534a827e05cb21f))
* **docs:** docs header right ([843527c](https://github.com/nuxtlabs/docus/commit/843527cb6f909fd9cf5492b014b659d45490f3ef))
* **docs:** links from app config from docs aside right bottom ([28f521d](https://github.com/nuxtlabs/docus/commit/28f521dd51aa4ce5b7354416bd3bf626f9f79cd0))
* **docs:** trigger nightly ([4493e33](https://github.com/nuxtlabs/docus/commit/4493e33d6cce899d4eacb4f0191cbfe40b6554d9))
* improvements ([b1af212](https://github.com/nuxtlabs/docus/commit/b1af212900712223673617749eecb227378cb3e3))
* **llms:** integrate nuxt llms by default ([3f060d8](https://github.com/nuxtlabs/docus/commit/3f060d85bca006e8cea412144fdfda7eec481d1f))
* **seo:** og images landing ([a81f07b](https://github.com/nuxtlabs/docus/commit/a81f07b49ee21b6bb1e944178f596065ce8b0ff2))
* **seo:** site name and title template ([19fb325](https://github.com/nuxtlabs/docus/commit/19fb32542036ff943bc1ad532ce182d9fe036a5b))
* **social:** update og image ([87f87e4](https://github.com/nuxtlabs/docus/commit/87f87e4cb2905267feb2bd66fe8c744d7ace53af))
* **starter:** update ([6ddff7f](https://github.com/nuxtlabs/docus/commit/6ddff7fd3909c746c86ac6a82b6bbc350c3e987e))
### Bug Fixes
* add docs dir only if not found as layer ([05fdaf3](https://github.com/nuxtlabs/docus/commit/05fdaf3a87edf1b9470918259e7792b91a82d1a1))
* **app:** config ([af15911](https://github.com/nuxtlabs/docus/commit/af15911b054c9d7c3c22902f4d44860da3510f12))
* **cli:** layers ([370740c](https://github.com/nuxtlabs/docus/commit/370740c4231d147bac5c5f5f90702fc9f0b3a74e))
* **cli:** rename to docus ([ef17013](https://github.com/nuxtlabs/docus/commit/ef1701359be87390ceae4b064970269f4bd206b3))
* **cli:** update init cmd ([b902db7](https://github.com/nuxtlabs/docus/commit/b902db7ce6293c778577747905718e62e1e4d4cd))
* **config:** update toc links schema ([5ce2c70](https://github.com/nuxtlabs/docus/commit/5ce2c70aa7c52be341c9484d5fd10427c8320d09))
* **docs:** copy page ([63f1088](https://github.com/nuxtlabs/docus/commit/63f1088a7b4efe0cf12213df899554cb8e820a86))
* **docs:** safari copy to clipboard ([4761858](https://github.com/nuxtlabs/docus/commit/47618586a94169e9e4f75158ffd2e62539735f01))
* **git:** fetch info ([98dae4b](https://github.com/nuxtlabs/docus/commit/98dae4bf59829313cd630f7bf4eaffb6003cbe95))
* **git:** vercel branch name variable ([966a1b9](https://github.com/nuxtlabs/docus/commit/966a1b9369957e76019efdc3dc0c48c8d3c99a07))
* **icon:** use iconify provider ([f41113c](https://github.com/nuxtlabs/docus/commit/f41113c1f767db3f26830070db245fcd542caa5e))
* **landing:** neutral and primary in iframe command menu ([e16717d](https://github.com/nuxtlabs/docus/commit/e16717df0ae90cea71bf9c83eb8056c4a3d59202))
* **package:** add repository ([cebd917](https://github.com/nuxtlabs/docus/commit/cebd91740a797b7c4f94dd360b335a289f18e2e6))
* **package:** dev without rebuild ([67fad9e](https://github.com/nuxtlabs/docus/commit/67fad9ec773caeac517d737dfc8370b1302d4de5))
* **package:** set pnpm version ([b96e34b](https://github.com/nuxtlabs/docus/commit/b96e34b6cfb6a070667e812925c5058585710169))
* **pages:** possibility to set gh edit url ([85a01e7](https://github.com/nuxtlabs/docus/commit/85a01e79817c53142472c49a4e1db684c71c7b3e))
* **schema:** toc ([00b90d8](https://github.com/nuxtlabs/docus/commit/00b90d89fe16db9ee92894e8bb1e797674c9cd93))
* **seo:** default title and description ([0321956](https://github.com/nuxtlabs/docus/commit/03219562b9fa02eed1ee1bbf4cbab31092028911))
* **seo:** site name and url ([afc59b6](https://github.com/nuxtlabs/docus/commit/afc59b678287caf53b39ac69d65165a67a4840b8))
* **seo:** use seo key instead of site ([68bece7](https://github.com/nuxtlabs/docus/commit/68bece7bdbb9b9ef7d9ed7408a37ffb8445a0453))
* **seo:** use site title ([9e9df32](https://github.com/nuxtlabs/docus/commit/9e9df3264efec4fd6f4cf9843894c05f3f77ce15))
* **setup:** default header title ([1a4ee1d](https://github.com/nuxtlabs/docus/commit/1a4ee1d80f00d673b5b58048c28d673e977b0204))
* **setup:** infer URL ([105e1ca](https://github.com/nuxtlabs/docus/commit/105e1ca4ac7b33fd6589cc533c75bed561c06ca4))
* source app config ([abc5c35](https://github.com/nuxtlabs/docus/commit/abc5c355daa47665c064a19834455c1e57c5799c))
+1 -2
View File
@@ -1,6 +1,5 @@
MIT License
Copyright (c) NuxtLabs
Copyright (c) 2025 > Djeex
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Regular → Executable
+28 -38
View File
@@ -1,53 +1,43 @@
[![docus](https://docus-puce.vercel.app/__og-image__/static/og.png)](https://docus.dev)
<p align="center">
<img src="https://git.djeex.fr/Djeex/DjeexLab/raw/branch/main/docs/files/img/global/lab.svg" align="center" width="700">
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![License][license-src]][license-href]
[![Nuxt][nuxt-src]][nuxt-href]
[![docu.djeex.fr](https://img.shields.io/badge/Docu·djeex-00b0f0?style=for-the-badge&logoColor=white&logo=materialformkdocs)](https://docu.djeex.fr/)
[![Uptime-Kuma](https://stats.djeex.fr/api/badge/23/status?style=for-the-badge)](https://docu.djeex.fr/)
</p>
# Docus
# 🔧 Docs, More Docs
Documentation Theme and CLI to write beautiful docs with Markdown.
**Docu·djeex** is first and foremost a personal project aimed at self-hosting as many everyday services as possible — without relying on proprietary platforms (Google, Apple, Netflix, etc.).
This documentation site is built using [Nuxt.js](https://nuxt.com/).
Ship fast, flexible, and SEO-optimized documentation with beautiful design out of the box. Docus brings together the best of the Nuxt ecosystem:
- [Nuxt 3](https://nuxt.com)
- [Nuxt Content](https://content.nuxt.com/)
- [Nuxt UI](https://ui.nuxt.com/)
- [Nuxt Image](https://image.nuxt.com/)
- [Nuxt LLMs](https://github.com/nuxtlabs/nuxt-llms)
- [Nuxt SEO](https://nuxtseo.com/)
- [UnJS ecosystem](https://unjs.io/)
- [Nuxt Studio](https://content.nuxt.com/studio)
This repository contains everything you need to edit pages, apply your changes, and redeploy the site.
## Contribution
## Setup
<details>
<summary>Local development</summary>
Install dependencies:
- Clone this repository
- Install the latest LTS version of [Node.js](https://nodejs.org/en/)
- Install dependencies using `pnpm install`
- Run prepare command using `pnpm run dev:prepare`
- Run dev documentation built on top of Docus using `pnpm run dev`
```sh
npm install
```
</details>
## Development Environment (port 3000)
## License
```sh
npm run dev
```
Published under the [MIT](https://github.com/unjs/undocs/blob/main/LICENSE) license.
## Generate Static Pages
Docus v3 has been entirely rewritten from scratch and is inspired and copied from [undocs](https://github.com/unjs/undocs) made by [@pi0](https://github.com/pi0) 💚
```sh
npm run generate
```
The HTML files will be generated in the `.output/public` folder and are ready to be deployed on any static-compatible hosting.
<!-- Badges -->
[npm-version-src]: https://img.shields.io/npm/v/docus/latest.svg?style=flat&colorA=020420&colorB=EEEEEE
[npm-version-href]: https://npmjs.com/package/docus
## Preview Build
[npm-downloads-src]: https://img.shields.io/npm/dm/docus.svg?style=flat&colorA=020420&colorB=EEEEEE
[npm-downloads-href]: https://npm.chart.dev/docus
If you'd like to immediately see the result of your static site build, you can launch a preview server:
[license-src]: https://img.shields.io/npm/l/docus.svg?style=flat&colorA=020420&colorB=EEEEEE
[license-href]: https://npmjs.com/package/docus
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
[nuxt-href]: https://nuxt.com
```sh
npm run preview
```
+81
View File
@@ -0,0 +1,81 @@
// https://github.com/nuxt-themes/docus/blob/main/nuxt.schema.ts
export default defineAppConfig({
css: ['~/assets/css/extra.css'],
colorMode: {
preference: 'dark',
fallback:'dark',
},
content: {
highlight: {
langs: [
'console',
'nginx',
]
}
},
mdc: {
highlight: {
theme: 'github-dark',
langs: ['ts','console','nginx'],
wrapperStyle: true
}
},
docus: {
title: 'Docudjeex',
description: 'Homelab documentation',
url: 'http://docus.dev',
image: '/img/social.png',
socials: {
github:'',
Language: {
label: '🇫🇷',
icon:'material-symbols:language-french',
href: 'https://docu.djeex.fr/fr/',
},
Gitea: {
label: 'Gitea',
icon: 'cib:gitea',
href: 'https://git.djeex.fr/Djeex/docudjeex',
},
Github: {
label: 'Github',
icon:'cib:github',
href: 'https://github.com/Djeex',
}
},
github: {
baseUrl:'https://git.djeex.fr',
dir: 'content',
branch: 'src/branch/master',
repo: 'docudjeex',
owner: 'Djeex',
edit: false
},
aside: {
level: 0,
collapsed: false,
exclude: []
},
main: {
padded: true,
fluid: true
},
header: {
logo: true,
showLinkIcon: true,
exclude: [],
fluid: false
},
footer: {
credits: {
icon: '',
text: '',
href: '',
}
}
},
})
-36
View File
@@ -1,36 +0,0 @@
export default defineAppConfig({
ui: {
colors: {
primary: 'emerald',
neutral: 'zinc',
},
},
uiPro: {
contentNavigation: {
slots: {
linkLeadingIcon: 'size-4 mr-1',
listWithChildren: 'border-(--ui-bg-elevated)',
linkTrailing: 'hidden',
},
variants: {
active: {
false: {
link: 'text-toned hover:after:bg-accented',
},
},
},
defaultVariants: {
variant: 'link',
},
},
pageLinks: {
slots: {
linkLeadingIcon: 'size-4',
linkLabelExternalIcon: 'size-2.5',
},
},
},
toc: {
title: 'On this page',
},
})
-56
View File
@@ -1,56 +0,0 @@
<script setup lang="ts">
const { seo } = useAppConfig()
const site = useSiteConfig()
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs'), {
transform: data => data.find(item => item.path === '/docs')?.children || data || [],
})
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs'), {
server: false,
})
useHead({
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
link: [
{ rel: 'icon', href: '/favicon.ico' },
],
htmlAttrs: {
lang: 'en',
},
})
useSeoMeta({
titleTemplate: seo.titleTemplate,
title: seo.title,
description: seo.description,
ogSiteName: site.name,
twitterCard: 'summary_large_image',
})
provide('navigation', navigation)
</script>
<template>
<UApp>
<NuxtLoadingIndicator color="var(--ui-primary)" />
<AppHeader />
<UMain>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</UMain>
<AppFooter />
<ClientOnly>
<LazyUContentSearch
:files="files"
:navigation="navigation"
/>
</ClientOnly>
</UApp>
</template>
-5
View File
@@ -1,5 +0,0 @@
@import "tailwindcss";
@import "@nuxt/ui-pro";
@source "../../../content/**/*";
@source "../../app.config.ts";
-83
View File
@@ -1,83 +0,0 @@
<script setup lang="ts">
import { motion } from 'motion-v'
import type { VariantType } from 'motion-v'
const props = defineProps<{
open: boolean
}>()
const variants: { [k: string]: VariantType | ((custom: unknown) => VariantType) } = {
normal: {
rotate: 0,
y: 0,
opacity: 1,
},
close: (custom: unknown) => {
const c = custom as number
return {
rotate: c === 1 ? 45 : c === 3 ? -45 : 0,
y: c === 1 ? 6 : c === 3 ? -6 : 0,
opacity: c === 2 ? 0 : 1,
transition: {
type: 'spring',
stiffness: 260,
damping: 20,
},
}
},
}
const state = computed(() => props.open ? 'close' : 'normal')
</script>
<template>
<UButton
size="sm"
variant="ghost"
color="neutral"
class="-me-1.5"
square
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="size-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<motion.line
x1="4"
y1="6"
x2="20"
y2="6"
:variants="variants"
:animate="state"
:custom="1"
class="outline-none"
/>
<motion.line
x1="4"
y1="12"
x2="20"
y2="12"
:variants="variants"
:animate="state"
:custom="2"
class="outline-none"
/>
<motion.line
x1="4"
y1="18"
x2="20"
y2="18"
:variants="variants"
:animate="state"
:custom="3"
class="outline-none"
/>
</svg>
</UButton>
</template>
@@ -1,76 +0,0 @@
<script lang="ts" setup>
const props = withDefaults(defineProps<{ title?: string, description?: string, headline?: string }>(), {
title: 'title',
description: 'description',
})
const title = computed(() => (props.title || '').slice(0, 60))
const description = computed(() => (props.description || '').slice(0, 200))
</script>
<template>
<div class="w-full h-full flex flex-col justify-center bg-neutral-900">
<svg
class="absolute right-0 top-0 opacity-50"
width="629"
height="593"
viewBox="0 0 629 593"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g filter="url(#filter0_f_199_94966)">
<path
d="M628.5 -578L639.334 -94.4223L806.598 -548.281L659.827 -87.387L965.396 -462.344L676.925 -74.0787L1087.69 -329.501L688.776 -55.9396L1160.22 -164.149L694.095 -34.9354L1175.13 15.7948L692.306 -13.3422L1130.8 190.83L683.602 6.50012L1032.04 341.989L668.927 22.4412L889.557 452.891L649.872 32.7537L718.78 511.519L628.5 36.32L538.22 511.519L607.128 32.7537L367.443 452.891L588.073 22.4412L224.955 341.989L573.398 6.50012L126.198 190.83L564.694 -13.3422L81.8734 15.7948L562.905 -34.9354L96.7839 -164.149L568.224 -55.9396L169.314 -329.501L580.075 -74.0787L291.604 -462.344L597.173 -87.387L450.402 -548.281L617.666 -94.4223L628.5 -578Z"
fill="white"
/>
</g>
<defs>
<filter
id="filter0_f_199_94966"
x="0.873535"
y="-659"
width="1255.25"
height="1251.52"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood
flood-opacity="0"
result="BackgroundImageFix"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feGaussianBlur
stdDeviation="40.5"
result="effect1_foregroundBlur_199_94966"
/>
</filter>
</defs>
</svg>
<div class="pl-[100px]">
<p
v-if="headline"
class="uppercase text-[24px] text-emerald-500 mb-4 font-semibold"
>
{{ headline }}
</p>
<h1
v-if="title"
class="m-0 text-[75px] font-semibold mb-4 text-white flex items-center"
>
<span>{{ title }}</span>
</h1>
<p
v-if="description"
class="text-[32px] text-neutral-300 leading-tight w-[700px]"
>
{{ description }}
</p>
</div>
</div>
</template>
@@ -1,73 +0,0 @@
<script lang="ts" setup>
const props = withDefaults(defineProps<{ title?: string, description?: string, headline?: string }>(), {
title: 'title',
description: 'description',
})
const title = computed(() => (props.title || '').slice(0, 60))
const description = computed(() => (props.description || '').slice(0, 200))
</script>
<template>
<div class="w-full h-full flex items-center justify-center bg-neutral-900">
<svg
class="absolute right-0 top-0 opacity-50 "
width="629"
height="593"
viewBox="0 0 629 593"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g filter="url(#filter0_f_199_94966)">
<path
d="M628.5 -578L639.334 -94.4223L806.598 -548.281L659.827 -87.387L965.396 -462.344L676.925 -74.0787L1087.69 -329.501L688.776 -55.9396L1160.22 -164.149L694.095 -34.9354L1175.13 15.7948L692.306 -13.3422L1130.8 190.83L683.602 6.50012L1032.04 341.989L668.927 22.4412L889.557 452.891L649.872 32.7537L718.78 511.519L628.5 36.32L538.22 511.519L607.128 32.7537L367.443 452.891L588.073 22.4412L224.955 341.989L573.398 6.50012L126.198 190.83L564.694 -13.3422L81.8734 15.7948L562.905 -34.9354L96.7839 -164.149L568.224 -55.9396L169.314 -329.501L580.075 -74.0787L291.604 -462.344L597.173 -87.387L450.402 -548.281L617.666 -94.4223L628.5 -578Z"
fill="white"
/>
</g>
<defs>
<filter
id="filter0_f_199_94966"
x="0.873535"
y="-659"
width="1255.25"
height="1251.52"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood
flood-opacity="0"
result="BackgroundImageFix"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feGaussianBlur
stdDeviation="40.5"
result="effect1_foregroundBlur_199_94966"
/>
</filter>
</defs>
</svg>
<div class="flex flex-col justify-center p-8">
<div class="flex justify-center mb-8">
<AppHeaderLogo white />
</div>
<h1
v-if="title"
class="flex justify-center m-0 text-5xl font-semibold mb-4 text-white"
>
<span>{{ title }}</span>
</h1>
<p
v-if="description"
class="text-center text-2xl text-neutral-300 leading-tight"
>
{{ description }}
</p>
</div>
</div>
</template>
-40
View File
@@ -1,40 +0,0 @@
<script setup lang="ts">
const appConfig = useAppConfig()
const links = computed(() => [
...Object.entries(appConfig.socials || {}).map(([key, url]) => ({
'icon': `i-simple-icons-${key}`,
'to': url,
'target': '_blank',
'aria-label': `${key} social link`,
})),
appConfig.github?.url && {
'icon': 'i-simple-icons-github',
'to': appConfig.github.url,
'target': '_blank',
'aria-label': 'GitHub repository',
},
].filter(Boolean))
</script>
<template>
<UFooter>
<template #left>
<div class="text-sm text-muted">
Copyright © {{ new Date().getFullYear() }}
</div>
</template>
<template #right>
<template v-if="links.length">
<UButton
v-for="(link, index) of links"
:key="index"
size="sm"
v-bind="{ color: 'neutral', variant: 'ghost', ...link }"
/>
</template>
<UColorModeButton />
</template>
</UFooter>
</template>
-57
View File
@@ -1,57 +0,0 @@
<script setup lang="ts">
const appConfig = useAppConfig()
const site = useSiteConfig()
const links = computed(() => appConfig.github?.url
? [
{
'icon': 'i-simple-icons-github',
'to': appConfig.github.url,
'target': '_blank',
'aria-label': 'GitHub',
},
]
: [])
</script>
<template>
<UHeader
:ui="{ center: 'flex-1' }"
to="/"
:title="appConfig.header?.title || site.name"
>
<AppHeaderCenter />
<template #title>
<AppHeaderLogo class="h-6 w-auto shrink-0" />
</template>
<template #right>
<AppHeaderCTA />
<UContentSearchButton class="lg:hidden" />
<UColorModeButton />
<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>
</UHeader>
</template>
-13
View File
@@ -1,13 +0,0 @@
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
</script>
<template>
<UContentNavigation
highlight
variant="link"
:navigation="navigation"
/>
</template>
-3
View File
@@ -1,3 +0,0 @@
<template>
<div />
</template>
@@ -1,6 +0,0 @@
<template>
<UContentSearchButton
:collapsed="false"
class="w-full"
/>
</template>
-16
View File
@@ -1,16 +0,0 @@
<script setup lang="ts">
const appConfig = useAppConfig()
</script>
<template>
<UColorModeImage
v-if="appConfig.header?.logo?.dark || appConfig.header?.logo?.light"
:light="appConfig.header?.logo?.light || appConfig.header?.logo?.dark"
:dark="appConfig.header?.logo?.dark || appConfig.header?.logo?.light"
:alt="appConfig.header?.logo?.alt || appConfig.header?.title"
class="h-6 w-auto shrink-0"
/>
<span v-else>
{{ appConfig.header?.title || '{appConfig.header.title}' }}
</span>
</template>
@@ -1,3 +0,0 @@
<template>
<div />
</template>
@@ -1,17 +0,0 @@
<script setup lang="ts">
const appConfig = useAppConfig()
</script>
<template>
<div
v-if="appConfig.toc?.bottom?.links?.length"
class="hidden lg:block space-y-6"
>
<USeparator type="dashed" />
<UPageLinks
:title="appConfig.toc?.bottom?.title || 'Links'"
:links="appConfig.toc?.bottom?.links"
/>
</div>
</template>
@@ -1,77 +0,0 @@
<script setup lang="ts">
import { useClipboard } from '@vueuse/core'
const route = useRoute()
const toast = useToast()
const { copy, copied } = useClipboard()
const markdownLink = computed(() => `${window?.location?.origin}/raw${route.path}.md`)
const items = [
{
label: 'Copy Markdown link',
icon: 'i-lucide-link',
onSelect() {
copy(markdownLink.value)
toast.add({
title: 'Markdown link copied to clipboard',
icon: 'i-lucide-check-circle',
color: 'success',
})
},
},
{
label: 'View as Markdown',
icon: 'i-simple-icons:markdown',
target: '_blank',
to: markdownLink.value,
},
{
label: 'Open in ChatGPT',
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: 'Open in 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.`)}`,
},
]
</script>
<template>
<UButtonGroup size="sm">
<UButton
label="Copy page"
:icon="copied ? 'i-lucide-copy-check' : 'i-lucide-copy'"
color="neutral"
variant="outline"
:ui="{
leadingIcon: [copied ? 'text-primary' : 'text-neutral', 'size-3.5'],
}"
@click="copy(markdownLink)"
/>
<UDropdownMenu
size="sm"
:items="items"
:content="{
align: 'end',
side: 'bottom',
sideOffset: 8,
}"
:ui="{
content: 'w-48',
}"
>
<UButton
icon="i-lucide-chevron-down"
color="neutral"
variant="outline"
/>
</UDropdownMenu>
</UButtonGroup>
</template>
-42
View File
@@ -1,42 +0,0 @@
<script setup lang="ts">
import type { NuxtError } from '#app'
defineProps<{
error: NuxtError
}>()
useHead({
htmlAttrs: {
lang: 'en',
},
})
useSeoMeta({
title: 'Page not found',
description: 'We are sorry but this page could not be found.',
})
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs'))
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs'), {
server: false,
})
provide('navigation', navigation)
</script>
<template>
<UApp>
<AppHeader />
<UError :error="error" />
<AppFooter />
<ClientOnly>
<LazyUContentSearch
:files="files"
:navigation="navigation"
/>
</ClientOnly>
</UApp>
</template>
-24
View File
@@ -1,24 +0,0 @@
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
</script>
<template>
<UContainer>
<UPage>
<template #left>
<UPageAside>
<DocsAsideLeftTop />
<UContentNavigation
highlight
:navigation="navigation"
/>
</UPageAside>
</template>
<slot />
</UPage>
</UContainer>
</template>
-136
View File
@@ -1,136 +0,0 @@
<script setup lang="ts">
import { kebabCase } from 'scule'
import type { ContentNavigationItem } from '@nuxt/content'
import { findPageHeadline } from '@nuxt/content/utils'
import { addPrerenderPath } from '../utils/prerender'
definePageMeta({
layout: 'docs',
})
const route = useRoute()
const appConfig = useAppConfig()
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const [{ data: page }, { data: surround }] = await Promise.all([
useAsyncData(kebabCase(route.path), () => queryCollection('docs').path(route.path).first()),
useAsyncData(`${kebabCase(route.path)}-surround`, () => {
return queryCollectionItemSurroundings('docs', route.path, {
fields: ['description'],
})
}),
])
if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
// Add the page path to the prerender list
addPrerenderPath(`/raw${route.path}.md`)
const title = page.value.seo?.title || page.value.title
const description = page.value.seo?.description || page.value.description
useSeoMeta({
title,
ogTitle: title,
description,
ogDescription: description,
})
const headline = computed(() => findPageHeadline(navigation?.value, page.value?.path))
defineOgImageComponent('Docs', {
headline: headline.value,
})
const editLink = computed(() => {
if (!appConfig.github) {
return
}
return [
appConfig.github.url,
'edit',
appConfig.github.branch,
appConfig.github.rootDir,
'content',
`${page.value?.stem}.${page.value?.extension}`,
].filter(Boolean).join('/')
})
</script>
<template>
<UPage v-if="page">
<UPageHeader
: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.links"
:key="index"
size="sm"
v-bind="link"
/>
<DocsPageHeaderLinks />
</template>
</UPageHeader>
<UPageBody>
<ContentRenderer
v-if="page"
:value="page"
/>
<USeparator>
<div
v-if="editLink"
class="flex items-center gap-2 text-sm text-muted"
>
<UButton
variant="link"
color="neutral"
:to="editLink"
target="_blank"
icon="i-lucide-pen"
:ui="{ leadingIcon: 'size-4' }"
>
Edit this page
</UButton>
or
<UButton
variant="link"
color="neutral"
:to="`${appConfig.github.url}/issues/new/choose`"
target="_blank"
icon="i-lucide-alert-circle"
:ui="{ leadingIcon: 'size-4' }"
>
Report an issue
</UButton>
</div>
</USeparator>
<UContentSurround :surround="surround" />
</UPageBody>
<template
v-if="page?.body?.toc?.links?.length"
#right
>
<UContentToc
highlight
:title="appConfig.toc?.title || 'Table of Contents'"
:links="page.body?.toc?.links"
>
<template #bottom>
<DocsAsideRightBottom />
</template>
</UContentToc>
</template>
</UPage>
</template>
-39
View File
@@ -1,39 +0,0 @@
<script setup lang="ts">
const { data: page } = await useAsyncData('index', () => queryCollection('landing').path('/').first())
if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
// Reconsider it once this is implemented: https://github.com/nuxt/content/issues/3419
const prose = page.value.meta.prose as boolean
const title = page.value.seo?.title || page.value.title
const description = page.value.seo?.description || page.value.description
useSeoMeta({
title,
description,
ogTitle: title,
ogDescription: description,
})
if (page.value?.seo?.ogImage) {
useSeoMeta({
ogImage: page.value.seo.ogImage,
twitterImage: page.value.seo.ogImage,
})
}
else {
defineOgImageComponent('Landing', {
title,
description,
})
}
</script>
<template>
<ContentRenderer
v-if="page"
:value="page"
:prose="prose || false"
/>
</template>
-39
View File
@@ -1,39 +0,0 @@
declare module 'nuxt/schema' {
interface AppConfig {
seo: {
titleTemplate: string
title: string
description: string
}
header: {
title: string
logo: {
light: string
dark: string
alt: string
}
}
socials: Record<string, string>
toc: {
title: string
bottom: {
title: string
links: {
icon: string
label: string
to: string
target: string
}[]
}
}
github: {
owner: string
name: string
url: string
branch: string
rootDir?: string
}
}
}
export {}
-110
View File
@@ -1,110 +0,0 @@
import { execSync } from 'node:child_process'
import { readGitConfig } from 'pkg-types'
import gitUrlParse from 'git-url-parse'
export interface GitInfo {
// Repository name
name: string
// Repository owner/organization
owner: string
// Repository URL
url: string
}
export function getGitBranch() {
const envName
= process.env.CF_PAGES_BRANCH
|| process.env.CI_COMMIT_BRANCH
|| process.env.VERCEL_GIT_COMMIT_REF
|| process.env.BRANCH
|| process.env.GITHUB_REF_NAME
if (envName && envName !== 'HEAD') {
return envName
}
try {
const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim()
if (branch && branch !== 'HEAD') {
return branch
}
}
catch {
// Ignore error
}
return 'main'
}
export async function getLocalGitInfo(rootDir: string): Promise<GitInfo | undefined> {
const remote = await getLocalGitRemote(rootDir)
if (!remote) {
return
}
// https://www.npmjs.com/package/git-url-parse#clipboard-example
const { name, owner, source } = gitUrlParse(remote)
const url = `https://${source}/${owner}/${name}`
return {
name,
owner,
url,
}
}
async function getLocalGitRemote(dir: string): Promise<string | undefined> {
try {
const parsed = await readGitConfig(dir)
if (!parsed) {
return
}
return parsed.remote?.['origin']?.url
}
catch {
// Ignore error
}
}
export function getGitEnv(): GitInfo {
// https://github.com/unjs/std-env/issues/59
const envInfo = {
// Provider
provider: process.env.VERCEL_GIT_PROVIDER // vercel
|| (process.env.GITHUB_SERVER_URL ? 'github' : undefined) // github
|| '',
// Owner
owner: process.env.VERCEL_GIT_REPO_OWNER // vercel
|| process.env.GITHUB_REPOSITORY_OWNER // github
|| process.env.CI_PROJECT_PATH?.split('/').shift() // gitlab
|| '',
// Name
name: process.env.VERCEL_GIT_REPO_SLUG
|| process.env.GITHUB_REPOSITORY?.split('/').pop() // github
|| process.env.CI_PROJECT_PATH?.split('/').splice(1).join('/') // gitlab
|| '',
// Url
url: process.env.REPOSITORY_URL || '', // netlify
}
if (!envInfo.url && envInfo.provider && envInfo.owner && envInfo.name) {
envInfo.url = `https://${envInfo.provider}.com/${envInfo.owner}/${envInfo.name}`
}
// If only url available (ex: Netlify)
if (!envInfo.name && !envInfo.owner && envInfo.url) {
try {
const { name, owner } = gitUrlParse(envInfo.url)
envInfo.name = name
envInfo.owner = owner
}
catch {
// Ignore error
}
}
return {
name: envInfo.name,
owner: envInfo.owner,
url: envInfo.url,
}
}
-29
View File
@@ -1,29 +0,0 @@
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
export function inferSiteURL() {
// https://github.com/unjs/std-env/issues/59
return (
process.env.NUXT_SITE_URL
|| (process.env.NEXT_PUBLIC_VERCEL_URL && `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`) // Vercel
|| process.env.URL // Netlify
|| process.env.CI_PAGES_URL // Gitlab Pages
|| process.env.CF_PAGES_URL // Cloudflare Pages
)
}
export async function getPackageJsonMetadata(dir: string) {
try {
const packageJson = await readFile(resolve(dir, 'package.json'), 'utf-8')
const parsed = JSON.parse(packageJson)
return {
name: parsed.name,
description: parsed.description,
}
}
catch {
return {
name: 'docs',
}
}
}
-12
View File
@@ -1,12 +0,0 @@
export const addPrerenderPath = (path: string) => {
const event = useRequestEvent()
if (event) {
event.node.res.setHeader(
'x-nitro-prerender',
[
event.node.res.getHeader('x-nitro-prerender'),
path,
].filter(Boolean).join(','),
)
}
}
-31
View File
@@ -1,31 +0,0 @@
import { defineContentConfig, defineCollection, z } from '@nuxt/content'
export default defineContentConfig({
collections: {
landing: defineCollection({
type: 'page',
source: {
// @ts-expect-error __DOCS_DIR__ is not defined
cwd: globalThis.__DOCS_DIR__,
include: 'index.md',
},
}),
docs: defineCollection({
type: 'page',
source: {
// @ts-expect-error __DOCS_DIR__ is not defined
cwd: globalThis.__DOCS_DIR__,
include: '**',
exclude: ['index.md'],
},
schema: z.object({
links: z.array(z.object({
label: z.string(),
icon: z.string(),
to: z.string(),
target: z.string().optional(),
})).optional(),
}),
}),
},
})
-54
View File
@@ -1,54 +0,0 @@
import { defineNuxtModule } from '@nuxt/kit'
import { defu } from 'defu'
import { inferSiteURL, getPackageJsonMetadata } from '../app/utils/meta'
import { getGitBranch, getGitEnv, getLocalGitInfo } from '../app/utils/git'
export default defineNuxtModule({
meta: {
name: 'default-configs',
},
async setup(_options, nuxt) {
const dir = nuxt.options.rootDir
const url = inferSiteURL()
const meta = await getPackageJsonMetadata(dir)
const gitInfo = await getLocalGitInfo(dir) || getGitEnv()
const siteName = nuxt.options?.site?.name || meta.name || gitInfo?.name || ''
nuxt.options.llms = defu(nuxt.options.llms, {
domain: url,
title: siteName,
description: meta.description || '',
full: {
title: siteName,
description: meta.description || '',
},
})
nuxt.options.site = defu(nuxt.options.site, {
url,
name: siteName,
debug: false,
})
nuxt.options.appConfig.header = defu(nuxt.options.appConfig.header, {
title: siteName,
})
nuxt.options.appConfig.seo = defu(nuxt.options.appConfig.seo, {
titleTemplate: `%s - ${siteName}`,
title: siteName,
description: meta.description || '',
})
nuxt.options.appConfig.github = defu(nuxt.options.appConfig.github, {
owner: gitInfo?.owner,
name: gitInfo?.name,
url: gitInfo?.url,
branch: getGitBranch(),
})
nuxt.options.appConfig.toc = defu(nuxt.options.appConfig.toc, {
title: 'On this page',
})
},
})
-49
View File
@@ -1,49 +0,0 @@
import { extendViteConfig } from '@nuxt/kit'
// Flag enabled when developing docs theme
const dev = !!process.env.NUXT_DOCS_DEV
export default defineNuxtConfig({
modules: [
'@nuxt/ui-pro',
'@nuxt/content',
'@nuxt/image',
'@nuxtjs/robots',
'nuxt-og-image',
'nuxt-llms',
() => {
// Update @nuxt/content optimizeDeps options
extendViteConfig((config) => {
config.optimizeDeps ||= {}
config.optimizeDeps.include ||= []
config.optimizeDeps.include.push('@nuxt/content > slugify')
config.optimizeDeps.include = config.optimizeDeps.include
.map(id => id.replace(/^@nuxt\/content > /, 'docus > @nuxt/content > '))
})
},
],
devtools: {
enabled: dev,
},
css: ['../app/assets/css/main.css'],
content: {
build: {
markdown: {
highlight: {
langs: ['bash', 'diff', 'json', 'js', 'ts', 'html', 'css', 'vue', 'shell', 'mdc', 'md', 'yaml'],
},
},
},
},
nitro: {
prerender: {
routes: ['/'],
crawlLinks: true,
failOnError: false,
autoSubfolderIndex: false,
},
},
icon: {
provider: 'iconify',
},
})
-218
View File
@@ -1,218 +0,0 @@
import { field, group } from '@nuxt/content/preview'
export default defineNuxtSchema({
appConfig: {
ui: group({
title: 'UI',
description: 'UI Customization.',
icon: 'i-lucide-palette',
fields: {
colors: group({
title: 'Colors',
description: 'Manage main colors of your application',
icon: 'i-lucide-palette',
fields: {
primary: field({
type: 'string',
title: 'Primary',
description: 'Primary color of your UI.',
icon: 'i-lucide-palette',
default: 'green',
required: ['red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose'],
}),
neutral: field({
type: 'string',
title: 'Neutral',
description: 'Neutral color of your UI.',
icon: 'i-lucide-palette',
default: 'slate',
required: ['slate', 'gray', 'zinc', 'neutral', 'stone'],
}),
},
}),
icons: group({
title: 'Icons',
description: 'Manage icons used in the application.',
icon: 'i-lucide-settings',
fields: {
search: field({
type: 'icon',
title: 'Search Bar',
description: 'Icon to display in the search bar.',
icon: 'i-lucide-search',
default: 'i-lucide-search',
}),
dark: field({
type: 'icon',
title: 'Dark mode',
description: 'Icon of color mode button for dark mode.',
icon: 'i-lucide-moon',
default: 'i-lucide-moon',
}),
light: field({
type: 'icon',
title: 'Light mode',
description: 'Icon of color mode button for light mode.',
icon: 'i-lucide-sun',
default: 'i-lucide-sun',
}),
external: field({
type: 'icon',
title: 'External Link',
description: 'Icon for external link.',
icon: 'i-lucide-external-link',
default: 'i-lucide-external-link',
}),
chevron: field({
type: 'icon',
title: 'Chevron',
description: 'Icon for chevron.',
icon: 'i-lucide-chevron-down',
default: 'i-lucide-chevron-down',
}),
hash: field({
type: 'icon',
title: 'Hash',
description: 'Icon for hash anchors.',
icon: 'i-lucide-hash',
default: 'i-lucide-hash',
}),
},
}),
},
}),
seo: group({
title: 'SEO',
description: 'SEO configuration.',
icon: 'i-lucide-search',
fields: {
title: field({
type: 'string',
title: 'Title',
description: 'Title to display in the header.',
icon: 'i-lucide-type',
default: '',
}),
description: field({
type: 'string',
title: 'Description',
description: 'Description to display in the header.',
icon: 'i-lucide-type',
default: '',
}),
},
}),
header: group({
title: 'Header',
description: 'Header configuration.',
icon: 'i-lucide-layout',
fields: {
title: field({
type: 'string',
title: 'Title',
description: 'Title to display in the header.',
icon: 'i-lucide-type',
default: '',
}),
logo: group({
title: 'Logo',
description: 'Header logo configuration.',
icon: 'i-lucide-image',
fields: {
light: field({
type: 'media',
title: 'Light Mode Logo',
description: 'Pick an image from your gallery.',
icon: 'i-lucide-sun',
default: '',
}),
dark: field({
type: 'media',
title: 'Dark Mode Logo',
description: 'Pick an image from your gallery.',
icon: 'i-lucide-moon',
default: '',
}),
alt: field({
type: 'string',
title: 'Alt',
description: 'Alt to display for accessibility.',
icon: 'i-lucide-text',
default: '',
}),
},
}),
},
}),
socials: field({
type: 'object',
title: 'Social Networks',
description: 'Social links configuration.',
icon: 'i-lucide-network',
default: {},
}),
toc: group({
title: 'Table of contents',
description: 'TOC configuration.',
icon: 'i-lucide-list',
fields: {
title: field({
type: 'string',
title: 'Title',
description: 'Title of the table of contents.',
icon: 'i-lucide-heading',
default: 'On this page',
}),
bottom: group({
title: 'Bottom',
description: 'Bottom section of the table of contents.',
icon: 'i-lucide-list',
fields: {
title: field({
type: 'string',
title: 'Title',
description: 'Title of the bottom section.',
icon: 'i-lucide-heading',
default: 'Community',
}),
links: field({
type: 'array',
title: 'Links',
description: 'Links to display in the bottom section.',
icon: 'i-lucide-link',
default: [],
}),
},
}),
},
}),
github: group({
title: 'GitHub',
description: 'GitHub configuration.',
icon: 'i-simple-icons-github',
fields: {
url: field({
type: 'string',
title: 'URL',
description: 'GitHub URL.',
icon: 'i-simple-icons-github',
default: '',
}),
branch: field({
type: 'string',
title: 'Branch',
description: 'GitHub branch.',
icon: 'i-lucide-git-branch',
default: 'main',
}),
rootDir: field({
type: 'string',
title: 'Root Directory',
description: 'Root directory of the GitHub repository.',
icon: 'i-lucide-folder',
default: '',
}),
},
}),
},
})
-25
View File
@@ -1,25 +0,0 @@
import { withLeadingSlash } from 'ufo'
import { stringify } from 'minimark/stringify'
import { queryCollection } from '@nuxt/content/nitro'
export default eventHandler(async (event) => {
const slug = getRouterParams(event)['slug.md']
if (!slug?.endsWith('.md')) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
const path = withLeadingSlash(slug.replace('.md', ''))
const page = await queryCollection(event, 'docs').path(path).first()
if (!page) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
// Add title and description to the top of the page if missing
if (page.body.value[0]?.[0] !== 'h1') {
page.body.value.unshift(['blockquote', {}, page.description])
page.body.value.unshift(['h1', {}, page.title])
}
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
})
+87
View File
@@ -0,0 +1,87 @@
@media (min-width: 1024px) {
.card-grid .layout {
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
}
}
.alert .shiki {
--shiki-dark: #00000000 !important;
--shiki-default: #00000000 !important;
--shiki-dark-bg: #00000000 !important;
--shiki-default-bg: #00000000 !important;
}
.dark .shiki {
background-color: #00000000 !important;
}
*html .dark .shiki span, html.dark .shiki span {
background-color: var(--prose-code-block-backgroundColor) !important;
}
/*html .dark .shiki span, html.dark .shiki span {
background-color: #00000000 !important;
}*/
.alert.success .prose-code, .alert.success .shiki span {
background-color: var(--elements-state-success-backgroundColor-secondary) !important;
border-color: #00361f !important;
}
.alert.info .prose-code, .alert.info .shiki span {
background-color: var(--elements-state-info-backgroundColor-secondary) !important;
border-color: #00304a !important;
}
.alert.warning .prose-code, .alert.warning .shiki span {
background-color: var(--elements-state-warning-backgroundColor-secondary) !important;
border-color: #382d00 !important;
}
.alert.danger .prose-code, .alert.danger .shiki span {
background-color: var(--elements-danger-info-backgroundColor-secondary) !important;
border-color: #00304a !important;
}
.section.right > :nth-child(2) {
display:none;
}
.container {
max-width: var(--elements-container-maxWidth);
}
.has-parent-icon .icon {
color: #ADA9A4;
}
.has-parent-icon.active .icon {
color: var(--color-primary-500) !important;
}
.card:hover{
color:#00304a;
}
p img {
border-radius:7px;
}
@media (min-width: 1024px) {
.card-grid {
padding-bottom: 80px !important;
}
}
.prose-code.highlight-sh code .line {
padding-inline-start: 0 !important;
}
.prose-code.highlight-sh code .line:before {
display:none !important;
}
.prose-code.highlight-bash code .line {
padding-inline-start: 0 !important;
}
.prose-code.highlight-bash code .line:before {
display:none !important;
}
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 561.54">
<defs>
<style>
.cls-1 {
fill: #55c3ec;
}
.cls-1, .cls-2 {
stroke-width: 0px;
}
.cls-2 {
fill: #fff;
}
</style>
</defs>
<path class="cls-2" d="M167.5,561.54c-34.5,0-64.27-8.56-89.31-25.68-25.05-17.12-44.34-41.65-57.88-73.59C6.77,430.32,0,392.12,0,347.66v-.38c0-44.71,6.77-82.98,20.32-114.8,13.54-31.81,32.83-56.21,57.88-73.21,25.04-16.99,54.68-25.49,88.93-25.49,19.67,0,37.69,3.2,54.05,9.58,16.35,6.39,30.72,15.52,43.12,27.41,12.39,11.88,22.29,26.13,29.71,42.74h2.3V0h111.92v553.11h-111.92v-70.15h-2.3c-7.41,16.36-17.25,30.41-29.51,42.16-12.27,11.76-26.58,20.76-42.93,27.02-16.36,6.26-34.37,9.39-54.05,9.39ZM205.07,468.78c18.14,0,34.11-4.92,47.91-14.76,13.8-9.83,24.47-23.82,32.01-41.97,7.53-18.14,11.31-39.61,11.31-64.39v-.38c0-24.53-3.83-45.86-11.5-64.01-7.67-18.14-18.34-32.13-32.01-41.97-13.67-9.83-29.58-14.76-47.72-14.76s-34.76,4.86-48.3,14.57c-13.55,9.71-24.02,23.57-31.43,41.59-7.41,18.02-11.12,39.55-11.12,64.59v.38c0,24.79,3.64,46.25,10.92,64.39,7.28,18.15,17.76,32.14,31.43,41.97,13.67,9.84,29.83,14.76,48.49,14.76Z"/>
<path class="cls-1" d="M520.82,390.59c-17.38,0-32.14-6-44.27-18.01-12.14-12.01-18.21-26.83-18.21-44.46s6.07-32.07,18.21-44.08c12.13-12.01,26.89-18.02,44.27-18.02s32.07,6.01,44.08,18.02c12.01,12.01,18.02,26.71,18.02,44.08s-6.01,32.45-18.02,44.46c-12.01,12.01-26.71,18.01-44.08,18.01Z"/>
<path class="cls-2" d="M783.28,561.54c-34.5,0-64.27-8.56-89.31-25.68-25.05-17.12-44.34-41.65-57.88-73.59-13.55-31.94-20.32-70.14-20.32-114.61v-.38c0-44.71,6.77-82.98,20.32-114.8,13.54-31.81,32.83-56.21,57.88-73.21,25.04-16.99,54.68-25.49,88.93-25.49,19.67,0,37.69,3.2,54.05,9.58,16.35,6.39,30.72,15.52,43.12,27.41,12.39,11.88,22.29,26.13,29.71,42.74h2.3V0h111.92v553.11h-111.92v-70.15h-2.3c-7.41,16.36-17.25,30.41-29.51,42.16-12.27,11.76-26.58,20.76-42.93,27.02-16.36,6.26-34.37,9.39-54.05,9.39ZM820.85,468.78c18.14,0,34.11-4.92,47.91-14.76,13.8-9.83,24.47-23.82,32.01-41.97,7.54-18.14,11.31-39.61,11.31-64.39v-.38c0-24.53-3.83-45.86-11.5-64.01-7.67-18.14-18.34-32.13-32.01-41.97-13.67-9.83-29.58-14.76-47.72-14.76s-34.76,4.86-48.3,14.57c-13.55,9.71-24.02,23.57-31.43,41.59-7.41,18.02-11.12,39.55-11.12,64.59v.38c0,24.79,3.64,46.25,10.92,64.39,7.28,18.15,17.76,32.14,31.43,41.97,13.67,9.84,29.83,14.76,48.49,14.76Z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

-95
View File
@@ -1,95 +0,0 @@
import { resolve } from 'node:path'
import { defineCommand, runMain } from 'citty'
import type { ArgsDef } from 'citty'
import { getNuxtConfig } from './setup'
import type { CLIOptions } from './types'
export function createCLI(opts: CLIOptions) {
const sharedArgs: ArgsDef = {
dir: {
type: 'positional',
description: 'Docs directory',
required: true,
default: '.',
},
}
const init = defineCommand({
meta: {
name: 'init',
description: 'Initialize a fresh Docus project',
},
args: { ...sharedArgs },
async setup({ args }) {
const dir = resolve(args.dir as string)
const { runCommand } = await import('nuxi')
await runCommand('init', [dir, '-t', 'gh:nuxtlabs/docus/.starter', dir])
},
})
const dev = defineCommand({
meta: {
name: 'dev',
description: 'Start docs in development mode',
},
args: { ...sharedArgs },
async setup({ args }) {
const dir = resolve(args.dir as string)
const nuxtConfig = await getNuxtConfig(dir, {
...opts.setup,
dev: true,
})
const { runCommand } = await import('nuxi')
await runCommand('dev', [dir, '--no-fork', '--port', process.env.PORT || '4000'], { overrides: nuxtConfig })
},
})
const prepare = defineCommand({
meta: {
name: 'prepare',
description: 'Prepare docs for development or production',
},
args: { ...sharedArgs },
async setup({ args }) {
const dir = resolve(args.dir as string)
const nuxtConfig = await getNuxtConfig(dir, opts.setup)
const { runCommand } = await import('nuxi')
await runCommand('prepare', [dir], { overrides: nuxtConfig })
},
})
const build = defineCommand({
meta: {
name: 'build',
description: 'Build docs for production',
},
args: { ...sharedArgs },
async setup({ args }) {
const dir = resolve(args.dir as string)
const nuxtConfig = await getNuxtConfig(dir, opts.setup)
const { runCommand } = await import('nuxi')
await runCommand('build', [dir], { overrides: nuxtConfig })
},
})
const main = defineCommand({
meta: {
name: opts.name,
description: opts.description,
},
subCommands: {
init,
dev,
prepare,
build,
},
})
return {
runMain: () => runMain(main),
}
}
-16
View File
@@ -1,16 +0,0 @@
#!/usr/bin/env node
import * as dotenv from 'dotenv'
import { createCLI } from './cli'
dotenv.config()
const cli = createCLI({
name: 'Docus',
description: 'Docus Docs CLI',
setup: {
defaults: {},
},
})
cli.runMain()
-38
View File
@@ -1,38 +0,0 @@
import { fileURLToPath } from 'node:url'
import { resolve } from 'node:path'
import type { NuxtConfig } from 'nuxt/config'
import type { DocsOptions } from './types'
declare global {
const __DOCS_DIR__: string
}
const appDir = fileURLToPath(new URL('../app', import.meta.url))
const pkgDir = fileURLToPath(new URL('..', import.meta.url))
export async function getNuxtConfig(dir: string, _opts: DocsOptions = {}) {
const fixLayers = (_, nuxt) => {
const hasDocsDir = nuxt.options._layers.some(layer => layer.cwd === dir)
if (!hasDocsDir) {
nuxt.options._layers.unshift({
cwd: dir,
config: {
rootDir: dir,
srcDir: dir,
},
})
}
}
// @ts-expect-error __DOCS_DIR__ is not defined
global.__DOCS_DIR__ = resolve(dir, 'content')
// Prepare loadNuxt overrides
return {
compatibilityDate: '2025-06-17',
extends: [appDir],
modulesDir: [resolve(pkgDir, 'node_modules'), resolve(appDir, 'node_modules')],
modules: [fixLayers],
} as NuxtConfig
}
-23
View File
@@ -1,23 +0,0 @@
export interface CLIOptions {
name: string
description: string
setup: DocsOptions
}
export interface DocsOptions {
dev?: boolean
defaults?: {
// Module name
name?: string
// Module description
description?: string
// Docs directory
dir?: string
// Website URL
url?: string
// GitHub repository
github?: string
// GitHub branch
branch?: string
}
}
+3
View File
@@ -0,0 +1,3 @@
<template>
<img width="120" src="/img/logo.svg"/>
</template>
+35
View File
@@ -0,0 +1,35 @@
---
title: Home
navigation: false
layout: page
main:
fluid: false
---
:ellipsis{right=0px width=75% blur=150px}
::block-hero
---
cta:
- Access the Docs
- /about/welcome
secondary:
- 🇫🇷 →
- https://docu.djeex.fr/fr/
---
#title
Welcome to docu[·]{style="color: #1ad6ff"}djeex
#description
Docs, more docs. Tips and experiments. Build your homelab and your own NAS.
#extra
![](/img/global/docudjeex-home.svg)
#support
::card{icon=cib:gitea style="color:#1ad6ff;"}
#title
__git.djeex.fr__
#description
[Check my nonsense projects](https://git.djeex.fr)
::
+44
View File
@@ -0,0 +1,44 @@
---
icon: lucide:home
title: Welcome
main:
fluid: false
---
:ellipsis{right=0px width=75% blur=150px}
# 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 12 + 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.
::card-grid
#title
Available or Upcoming Documentation
#root
:ellipsis{left=0px width=40rem top=10rem blur=140px}
#default
::card{icon=noto:microscope}
#title
Serveex
#description
[Step-by-step Homelab Deployment Guide](/serveex/introduction)
::
::card{icon=noto:computer-disk}
#title
Stockeex
#description
*(coming soon)* Build your own home NAS to store your data and media
::
::
+3
View File
@@ -0,0 +1,3 @@
icon: noto:star
navigation.title: About
navigation.redirect: /about/welcome
+79
View File
@@ -0,0 +1,79 @@
---
navigation: true
title: NAT & DHCP
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Router and NAT
::alert{type="info"}
🎯 __Goals:__
- Understand how port forwarding works
- Learn how to configure router NAT
- Learn how to issue DHCP leases (fixed IPs)
::
![picture](/img/global/nat.svg)
## 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.
::alert{type="warning"}
:::list{type="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.
+69
View File
@@ -0,0 +1,69 @@
---
navigation: true
title: DNS Zone
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Domain Names and DNS Zones
::alert{type="info"}
🎯 __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.
![Picture](/img/global/dns.svg)
## 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.
::alert{type="warning"}
:::list{type="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`.
::alert{type="info"}
:::list{type="info"}
- __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 wont need to update the subdomain record.
:::
::
Most registrars offer user-friendly interfaces to manage DNS records. Refer to your registrars documentation for specific instructions.
+228
View File
@@ -0,0 +1,228 @@
---
navigation: true
title: Samba
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# 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.
::alert{type="info"}
🎯 __Objectives:__
- Create a network folder on a remote machine
- Access the network folder from our server
::
![samba](/img/global/smb.svg)
## Sharing a Network Folder
---
::alert{type="info"}
:::list{type="info"}
- 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`.
::alert{type="success"}
__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
```
Youll 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
```
+2
View File
@@ -0,0 +1,2 @@
navigation.title: Networking
icon: lucide:network
+111
View File
@@ -0,0 +1,111 @@
---
navigation: true
title: RAID
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# 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)
::alert{type="warning"}
:::list{type="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>
</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>
</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>
</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 disks capacity, making it an expensive solution.
::alert{type="success"}
__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)
+76
View File
@@ -0,0 +1,76 @@
---
navigation: true
title: ZFS
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# ZFS
::alert{type="info"}
🎯 __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.
Heres what were 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
---
![](/img/global/zfs.svg)
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.
::alert{type="info"}
:::list{type="info"}
- 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.
+2
View File
@@ -0,0 +1,2 @@
navigation.title: Storage
icon: lucide:hard-drive
+169
View File
@@ -0,0 +1,169 @@
---
navigation: true
title: The Basics
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Server Basics
::alert{type="info"}
🎯 __Objectives:__
- Understand the fundamentals of server hardware
::
![hardware](/img/global/hardware.svg)
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>
::alert{type="warning"}
:::list{type="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 systems 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 doesnt 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.
::alert{type="success"}
__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.
::alert{type="warning"}
:::list{type="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 machines 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 systems 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.
+132
View File
@@ -0,0 +1,132 @@
---
navigation: true
title: Network
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Network
::alert{type="info"}
🎯 __Objectives:__
- Understand the basics of networking hardware
::
![hardware](/img/global/hardware-networking.svg)
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, youll 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 routers 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 youre 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.
::alert{type="success"}
__Tip:__
You can easily use a third-party router to manage your network if your ISPs 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.
::alert{type="warning"}
:::list{type="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.
Heres 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, youll 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 ISPs 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.
::alert{type="warning"}
:::list{type="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
::alert{type="warning"}
:::list{type="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.
:::
::
+2
View File
@@ -0,0 +1,2 @@
navigation.title: Hardware
icon: lucide:server
+3
View File
@@ -0,0 +1,3 @@
icon: noto:open-book
navigation.title: General
navigation.redirect: /general/networking/nat
+278
View File
@@ -0,0 +1,278 @@
---
icon: lucide:bookmark
navigation: true
title: Introduction
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
## A Home Lab by a Beginner, for Beginners
![](/img/serveex/serveex-server.svg)
**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.
::alert{type="info"}
**Prerequisites:**
:::list{type="primary"}
- 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)
:::
::
<p align="center">
<img src="/img/serveex/serveex.svg" align="center" width="700">
The goal is to be easily deployable and easy to migrate, so here is its structure:
::card-grid{grid-template-columns="repeat(2, minmax(0, 1fr));"}
#title
The Core of the Server
#root
:ellipsis{left=0px width=40rem top=10rem blur=140px}
#default
::card{icon=logos:debian}
#title
__Operating System__
#description
[Install and configure Debian 12](/serveex/core/installation)
::
::card{icon=logos:docker-icon}
#title
__Container Engine__
#description
[Install Docker](/serveex/core/docker)
::
::card{icon=carbon:container-registry style="color: rgb(41, 194, 243);" }
#title
__Docker GUI__
#description
[Install and deploy Dockge](/serveex/core/docker#installer-dockge-pour-gérer-et-déployer-les-conteneurs)
::
::card{icon=noto:globe-showing-americas}
#title
__Reverse Proxy__
#description
[Expose your services with SWAG](/serveex/core/swag)
::
::
::card-grid
#title
Security
#root
:ellipsis{left=0px width=40rem top=10rem blur=140px}
#default
::card{icon=simple-icons:wireguard style="color: #88171a;"}
#title
__VPN__
#description
[Install and deploy Wireguard](/serveex/security/wireguard)
::
::card{icon=noto:key}
#title
__SSO & MFA__
#description
[Install and deploy Authentik](/serveex/security/authentik)
::
::card{icon=logos:cloudflare-icon}
#title
__Zero Trust__
#description
[Install and deploy Cloudflared](/serveex/security/cloudflare)
::
::
::card-grid
#title
Monitoring
#root
:ellipsis{left=0px width=40rem top=10rem blur=140px}
#default
::card{icon=solar:pulse-linear style="color: rgb(99, 222, 144);"}
#title
__Service Status__
#description
[Install and deploy Uptime-Kuma](/serveex/monitoring/uptime-kuma)
::
::card{icon=lucide:logs style="color: #1AD6FF;"}
#title
__Log Management__
#description
[Install and deploy Dozzle](/serveex/monitoring/dozzle)
::
::card{icon=noto:rabbit style="color: #1AD6FF;"}
#title
__Connection Management__
#description
[Install and deploy Speedtest Tracker](/serveex/monitoring/speedtest-tracker)
::
::card{icon=lucide:chart-column-decreasing style="color:rgb(26, 255, 213);"}
#title
__Resource Status__
#description
[Install and deploy Beszel](/serveex/monitoring/beszel)
::
::card{icon=lucide:circle-power style="color:rgb(228, 117, 117);"}
#title
__Wake on Lan__
#description
[Install and deploy UpSnap](/serveex/monitoring/upsnap)
::
::
::card-grid
#title
Media
#root
:ellipsis{left=0px width=40rem top=10rem blur=140px}
#default
::card{icon=cbi:plex-alt style="color: rgb(229, 160, 13);"}
#title
__Media__
#description
[Install and deploy Plex](/serveex/media/plex)
::
::card{icon=cbi:qbittorrent style="color: rgb(#2f67ba);"}
#title
__Seedbox__
#description
[Install and deploy Qbittorrent](/serveex/media/qbittorrent)
::
::
::card-grid
#title
Cloud Drive & Photos
#root
:ellipsis{left=0px width=40rem top=10rem blur=140px}
#default
::card{icon=cib:nextcloud style="color: rgb(0, 104, 161);"}
#title
__Drive__
#description
[Install and deploy Nextcloud](/serveex/cloud/nextcloud)
::
::card{icon=simple-icons:immich style="color: #ed79b5;"}
#title
__Photos__
#description
[Install and deploy Immich](/serveex/cloud/immich)
::
::
::card-grid
#title
Files & Sharing
#root
:ellipsis{left=0px width=40rem top=10rem blur=140px}
#default
::card{icon=noto:open-file-folder }
#title
__File Explorer__
#description
[Install and deploy file-browser](/serveex/files/file-browser)
::
::card{icon=carbon:share style="color: #47428e;" }
#title
__Sharing__
#description
[Install and deploy Pingvin](/serveex/files/pingvin)
::
::
::card-grid
#title
Development Tools
#root
:ellipsis{left=0px width=40rem top=10rem blur=140px}
#default
::card{icon=logos:visual-studio-code}
#title
__Visual Studio Code__
#description
[Install and deploy code-server](/serveex/development/code-server)
::
::card{icon=simple-icons:gitea style="color: #9ee773;"}
#title
__Git Repository__
#description
[Install and deploy Gitea](/serveex/development/gitea)
::
::card{icon=noto:hammer-and-wrench }
#title
__Tools__
#description
[Install and deploy IT Tools](/serveex/development/it-tools)
::
::
::card-grid
#title
Useful Applications
#root
:ellipsis{left=0px width=40rem top=10rem blur=140px}
#default
::card{icon=cbi:adguard style="color: #67b279;"}
#title
__Ad-blocking DNS and Filters__
#description
[Install and deploy Adguard Home](/serveex/apps/adguard)
::
::card{icon=cbi:bitwarden style="color: rgb(25 128 255);" }
#title
__Password Manager__
#description
[Install and deploy Vaultwarden](/serveex/apps/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,75 @@
---
navigation: true
title: Debian 12
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Debian 12
::alert{type="info"}
🎯 __Goal:__ Install Debian 12 and the main dependencies to have a ready-to-use OS, accessible via SSH.
::
![picture](/img/serveex/server.svg)
## Installation
---
1. [BIOS Setup](https://www.debian.org/releases/stable/i386/ch03s06.fr.html#bios-setup)
2. [Download Debian Image](https://www.debian.org/download.fr.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 youll 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 ufw
```
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
```
::alert{type="info" icon="exclamation-circle"}
:::list{type="info"}
- Add ` --exclude @eaDir`{lang=shell} if the source is a Synology NAS
:::
::
+174
View File
@@ -0,0 +1,174 @@
---
navigation: true
title: Docker
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Docker
Docker, to install deployable services in seconds and manage them with just a few commands or clicks.
::alert{type="info"}
🎯 __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
::
![picture](/img/serveex/docker.svg)
## 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 12](https://docs.docker.com/engine/install/debian/)
::alert{type="info" icon="exclamation-circle"}
:::list{type="info"}
- 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 thats lighter and easier for beginners than using the CLI or Portainer.
![picture](/img/serveex/dockge.png)
### 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://github.com/containrrr/watchtower?tab=readme-ov-file), 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: containrrr/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!
+379
View File
@@ -0,0 +1,379 @@
---
navigation: true
title: SWAG
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# SWAG
::alert{type="info"}
🎯 __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. Its 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).
::alert{type="warning"}
:::list{type="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 dont 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:**
![Picture](/img/serveex/reverse-proxy.svg)
## Installation
---
::alert{type="info" icon="exclamation-circle"}
:::list{type="info"}
- 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
```
::alert{type="success"}
__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 | `your@email.com` |
| `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:
::alert{type="success"}
__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).
![picture](https://www.linuxserver.io/user/pages/03.blog/introducing-swag-dashboard/example.png)
## 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, well configure it to block a list of countries known to be the source of most malicious traffic. Well also configure a variable to allow internal server traffic, your boxs 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
---
::alert{type="info"}
📋 __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`.
::alert{type="warning"}
:::list{type="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, dont 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.
::alert{type="info"}
:::list{type="info"}
- By default, SWAG doesnt recognize the name "dockge". Youll need to add Dockges network to SWAGs `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
```
::alert{type="info"}
:::list{type="info"}
- 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
```
::alert{type="danger"}
:::list{type="danger"}
- __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 SWAGs `compose.yml`
::
You can also customize the subdomain by editing `server_name yoursubdomain.*;`{lang=nginx} and renaming the file to `yoursubdomain.subdomain.conf`.
+2
View File
@@ -0,0 +1,2 @@
navigation.title: Server core
icon: lucide:server-cog
+258
View File
@@ -0,0 +1,258 @@
---
navigation: true
title: Wireguard
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Wireguard
::alert{type="info"}
🎯 __Goals:__
- Install Wireguard
- Configure clients
- Access the secure network
::
## Introduction
---
Using a VPN allows remote access to a servers local resources without exposing them to the internet. Its 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, well create a **Virtual Private Network** (VPN), i.e., a secure tunnel that only connected machines can use. Theyll 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.
![picture](/img/serveex/vpn.svg)
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 wont 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
---
::alert{type="info"}
📋 __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.
::
::alert{type="warning"}
:::list{type="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 hosts 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
```
::alert{type="success"}
__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`
::alert{type="danger"}
:::list{type="danger"}
- If it fails, check firewall rules.
:::
::
## On the Client Server
---
::alert{type="info"}
:::list{type="info"}
- 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`:
::alert{type="success"}
__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:
::alert{type="success"}
__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
```
::alert{type="info" icon="exclamation-circle"}
:::list{type="info"}
- 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
::alert{type="warning"}
:::list{type="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:
![picture](/img/serveex/wireguard.svg)
+574
View File
@@ -0,0 +1,574 @@
---
navigation: true
title: Authentik
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Authentik
::alert{type="info"}
🎯 __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`, youll first land on an Authentik login page when accessing it. If you've already authenticated with another Authentik-protected service, you wont 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, well 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 whats public and whats protected.
![Picture](/img/serveex/auth-native.svg)
- The second method injects Authentik authentication through SWAG before reaching the target service.
![Picture](/img/serveex/auth-proxy.svg)
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 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
```
::alert{type="info"}
:::list{type="info"}
- 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:-2025.6.3}`{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:-2025.2.1}
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:-2025.2.1}
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/`
::alert{type="warning"}
:::list{type="warning"}
- __Warning:__ Its 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.
::alert{type="info"}
📋 __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:
::alert{type="success"}
✨ __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
Youll 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 Authentiks 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:
![Picture](/img/serveex/auth1.png)
- At the next step, choose "Forward authentication (single application)" and configure it as shown (flows are important):
![Picture](/img/serveex/auth2.png)
- Next, go to the _Outposts_ menu on the left and edit _authentik Embedded Outpost_:
![Picture](/img/serveex/auth3.png)
- 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`, youll be redirected to the Authentik login screen.
::alert{type="success"}
✨ __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.
::
::alert{type="info"}
:::list{type="info"}
- Repeat this process for each app you want to protect (unless it has native integration with Authentik).
:::
::
Your new architecture looks like this:
![Picture](/img/serveex/authentik.svg)
## 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.
::alert{type="info"}
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.
![auth-outpost](/img/serveex/auth-outpost.svg)
### 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 youve 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 havent 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
```
::alert{type="success"}
✨ __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.
::alert{type="info"}
:::list{type="info"}
- 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, lets 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,263 @@
---
navigation: true
title: Cloudflare Zero Trust
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Cloudflare Zero Trust
::alert{type="info"}
🎯 __Goals:__
- Understand the concept of Cloudflare Tunnels
- Configure your Cloudflare account
- Configure SWAG
- Manage multiple tunnels
::
![cloudfare_tunnels](/img/serveex/cloudflared.svg)
## 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. Its a broad concept, but well 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 servers 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 well explain how to integrate SWAG with Cloudflare tunnels.
::alert{type="warning"}
:::list{type="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 its 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, thats 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, youll see the `zone` and `account` IDs at the bottom right. Save both securely.
![id and account](/img/serveex/cf-id.png)
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.
![API token](/img/serveex/cf-token.png)
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 theres 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
---
::alert{type="info"}
:::list{type="info"}
- 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 Dockers 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`.
::alert{type="success"}
__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
```
::alert{type="success"}
__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 | `you@email.com` |
| `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).
::alert{type="success"}
__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`.
![dns](/img/serveex/cf-dns.png)
::
## 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:
![tunnels_id](/img/serveex/cf-tunnels-id.png)
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` |
+2
View File
@@ -0,0 +1,2 @@
navigation.title: Security
icon: lucide:shield
@@ -0,0 +1,202 @@
---
navigation: true
title: Uptime-Kuma
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Uptime-Kuma
::alert{type="info"}
🎯 __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.
![picture](https://user-images.githubusercontent.com/1336778/212262296-e6205815-ad62-488c-83ec-a5b0d0689f7c.jpg)
## 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:1
container_name: uptime-kuma
volumes:
- /docker/uptime-kuma/uptime-kuma-data:/app/data
ports:
- 3200:3001 # <Host Port>:<Container Port>
restart: always
```
::alert{type="success"}
__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`.
::alert{type="danger"}
:::list{type="danger"}
- __If it fails:__ check your firewall rules.
:::
::
## Expose with Swag
---
::alert{type="info"}
📋 __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).
::
::alert{type="warning"}
:::list{type="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, dont expose it with SWAG; use a VPN like [Wireguard](/serveex/security/wireguard) instead.
:::
::
In the Swag folders, create the `stats.subdomain.conf` file.
::alert{type="success"}
✨ __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.
::alert{type="info"}
:::list{type="info"}
- 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`.
::alert{type="success"}
✨ __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;`.
Dont 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`.
::alert{type="success"}
__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.
::
+179
View File
@@ -0,0 +1,179 @@
---
navigation: true
title: Dozzle
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Dozzle
::alert{type="info"}
🎯 __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.
![Dozzle](https://blog.unixhost.pro/wp-content/uploads/2023/03/image-5.png)
## 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
```
::alert{type="success"}
__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
---
::alert{type="warning"}
:::list{type="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, well expose Dozzle via Swag.
::alert{type="info"}
📋 __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), youve 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 Dozzles 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.
::alert{type="info"}
:::list{type="info"}
- 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.
::alert{type="success"}
✨ __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!
::alert{type="success"}
✨ 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}. Dont forget to [create an application and a provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
::
@@ -0,0 +1,196 @@
---
navigation: true
title: Speedtest Tracker
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Speedtest Tracker
::alert{type="info"}
🎯 **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.
![speedtest-tracker](/img/serveex/speedtest-tracker.avif)
## Installation
---
::alert{type="info"}
:::list{type="info"}
- 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
KEY=base64:zihejehkj8_nzhY/OjeieR= # your key
PUID=1000
GUID=1000
PORT=3225 # port to access the web UI
```
::alert{type="success"}
**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 `admin@exemple.com` and the password `password`. Dont forget to change your ID and password once logged in!
## Expose Speedtest Tracker
---
::alert{type="info"}
📋 **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 youre 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`.
::alert{type="warning"}
:::list{type="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.
::alert{type="info"}
:::list{type="info"}
- By default, SWAG doesnt know the name "speedtest-tracker". To allow access, you need to add Speedtest Trackers network to SWAGs `compose.yml`.
:::
::
Go to Dockge, and edit SWAGs compose to include Speedtest Trackers 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.
::alert{type="info"}
:::list{type="info"}
- This assumes the Speedtest Tracker network is named `speedtest-tracker_default`. You can verify the connection by visiting SWAGs 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).
::alert{type="success"}
✨ 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;`.
Dont forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
::
+250
View File
@@ -0,0 +1,250 @@
---
navigation: true
title: Beszel
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Beszel
::alert{type="info"}
🎯 __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.
![Beszel](/img/serveex/beszel.png)
## 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}
```
::alert{type="success"}
__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!
::alert{type="danger"}
:::list{type="danger"}
- __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:
![Beszel add system](/img/serveex/beszel-add.png)
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:
![Beszel system](/img/serveex/beszel-system.png)
### 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.
::alert{type="danger"}
:::list{type="danger"}
- __If it fails:__ check your firewall rules.
:::
::
## Expose Beszel with Swag
---
::alert{type="warning"}
:::list{type="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.
::alert{type="info"}
📋 __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 servers `443` port via [NAT rules](/general/networking/nat).
::
In Dockge, edit Swag's compose file and add Beszels 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.
::alert{type="info"}
:::list{type="info"}
- We assume the network name is `beszel_default`. You can check connectivity by visiting Swag's dashboard at `http://yourserverip:81`.
:::
::
In Swags config folders, create `beszel.subdomain.conf`.
::alert{type="success"}
__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.
Thats it—Beszel is now exposed!
::alert{type="success"}
✨ 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;`. Dont forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
::
+194
View File
@@ -0,0 +1,194 @@
---
navigation: true
title: UpSnap
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# UpSnap
::alert{type="info"}
🎯 __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.
![Beszel](/img/serveex/upsnap.webp)
## 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
```
::alert{type="success"}
__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 routers IP
```
Deploy the container and go to `http://yourserverip:8095`. Just follow the steps to create your account!
::alert{type="danger"}
:::list{type="danger"}
- __If it fails:__ check your firewall rules.
:::
::
## Exposing UpSnap with Swag
---
::alert{type="warning"}
:::list{type="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.
::alert{type="info"}
📋 __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.
::alert{type="info"}
:::list{type="info"}
- 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`.
::alert{type="success"}
✨ __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 thats it — youve exposed UpSnap!
::alert{type="success"}
✨ 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}. Dont forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
::
+2
View File
@@ -0,0 +1,2 @@
navigation.title: Monitoring
icon: lucide:chart-no-axes-column
+314
View File
@@ -0,0 +1,314 @@
---
navigation: true
title: Plex
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Plex
::alert{type="info"}
🎯 **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 its your content, hosted and streamed from your server.
![picture](/img/serveex/plex.png)
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/)
::alert{type="info"}
:::list{type="info"}
- Youll need to create a *Plex.tv* account. You dont 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
```
::alert{type="success"}
✨ 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`.
::alert{type="warning"}
:::list{type="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 (well use `1234`). It's best not to use the default port.
![picture](/img/serveex/plex-port.png)
- On your router, forward TCP port `1234` to port `32400` for your servers IP using [NAT rules](/general/networking/nat).
- Once done, return to Plex to verify that remote access is functional.
::alert{type="danger"}
:::list{type="danger"}
- **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 thats 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.
::alert{type="info"}
:::list{type="info"}
- 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 dont 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.
::alert{type="info"}
:::list{type="info"}
- 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 SWAGs compose file by adding Tautullis 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.
::alert{type="info"}
:::list{type="info"}
- Here we assume the Tautulli network name is `tautulli_default`. You can check the connection by visiting SWAGs dashboard at `http://yourserverip:81`.
:::
::
Copy and rename the file `tautulli.subdomain.conf.sample` to `tautulli.subdomain.conf`, then edit it:
::alert{type="success"}
**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;
}
}
```
::alert{type="success"}
✨ You can protect this app with Authentik by removing the `#` before `include /config/nginx/authentik-server.conf;` and `include /config/nginx/authentik-location.conf;`. Dont 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.
::alert{type="danger"}
:::list{type="danger"}
- **If it fails:** check your firewall rules.
:::
::
And you're done!
+323
View File
@@ -0,0 +1,323 @@
---
navigation: true
title: Qbittorrent
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Qbittorrent
::alert{type="info"}
🎯 __Goals:__
- Install and configure Qbittorrent
- Securely connect to the BitTorrent network using Gluetun and Proton VPN
::
![Picture](https://github.com/VueTorrent/VueTorrent/blob/master/public/screenshots/screenshot-desktop-dark-mode.jpeg?raw=true)
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.
Heres the system well set up:
![Picture](/img/serveex/qbit.svg)
## 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:latest
container_name: qbittorrent
restart: unless-stopped
network_mode: service:gluetun
mem_limit: 1g
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.40
container_name: gluetun
restart: unless-stopped
mem_limit: 1g
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
```
::alert{type="success"}
__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.
Weve 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/openvpn/portforwarded"]
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.
::alert{type="warning"}
:::list{type="warning"}
- **Startup logs will show a temporary password for `admin` user**
:::
::
Login at `http://server-ip:5695` (or the port you set).
::alert{type="danger"}
:::list{type="danger"}
- __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
---
::alert{type="warning"}
:::list{type="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, dont 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.
::alert{type="info"}
:::list{type="info"}
- 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 youre using Cloudflare Zero Trust.
:::
::
In Dockge, edit the SWAG compose file and add Gluetuns network:
```yaml
services:
swag:
container_name: # ...
# ...
networks:
# ...
- seedbox
networks:
# ...
seedbox:
name: seedbox_default
external: true
```
Click "Deploy" and wait for SWAG to fully initialize.
::alert{type="info"}
:::list{type="info"}
- 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`.
::alert{type="success"}
__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;
}
}
```
::alert{type="success"}
✨ You can secure this app with Authentik by uncommenting the `authentik-server.conf` and `authentik-location.conf` lines. Dont 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 thats it! You now have a ready-to-use media center.
![Picture](/img/serveex/seed.svg)
+511
View File
@@ -0,0 +1,511 @@
---
navigation: true
title: Automation
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Servarr
::alert{type="info"}
🎯 __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.
![arr](/img/serveex/arr.svg)
Well 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
```
::alert{type="warning"}
:::list{type="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
```
::alert{type="success"}
✨ 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`.
::alert{type="danger"}
:::list{type="danger"}
- __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`.
::alert{type="warning"}
:::list{type="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.
![profiles_radarr](/img/serveex/radarr1.png)
##### 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.
::alert{type="danger"}
:::list{type="danger"}
- __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*.
::alert{type="danger"}
:::list{type="danger"}
- __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`.
::alert{type="danger"}
:::list{type="danger"}
- __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.
![Bazarr](/img/serveex/bazarr2.png)
- Save using the button at the top of the screen.
#### Add Subtitle Providers
- In *Settings > Providers*, add your preferred providers, for example:
![Bazarr](/img/serveex/bazarr.png)
- 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 Sonarrs 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 hasnt been released yet, it will be downloaded automatically when available. This way, episodes of a series appear in Plex without any manual intervention.
![Overseerr](/img/serveex/overseerr.webp)
Go to `http://yourserverip:5055` and log in with your Plex account.
::alert{type="danger"}
:::list{type="danger"}
- __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:__ Radarrs 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:__ Sonarrs 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 thats 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.
::alert{type="info"}
:::list{type="info"}
- 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 youre 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 its 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 its an external network
```
Restart the stack by clicking “Deploy” and wait until SWAG is fully operational.
::alert{type="info"}
:::list{type="info"}
- 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`:
::alert{type="success"}
__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.
::alert{type="danger"}
:::list{type="danger"}
- __If it fails:__ check your firewall rules.
:::
::
And there you go, Overseerr is now publicly accessible!
+2
View File
@@ -0,0 +1,2 @@
navigation.title: Media & Seedbox
icon: lucide:list-video
+169
View File
@@ -0,0 +1,169 @@
---
navigation: true
title: Immich
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Immich
::alert{type="info"}
🎯 __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.
![Picture](/img/serveex/immich.png)
## 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).
::alert{type="warning"}
:::list{type="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.
::alert{type="info"}
:::list{type="info"}
- 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.
:::
::
::alert{type="success"}
__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.
::alert{type="info"}
📋 __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
```
::alert{type="info"}
:::list{type="info"}
- 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`.
::alert{type="success"}
:::list{type="success"}
- __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. Dont 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.
::alert{type="success"}
__Tip:__ You can protect this app with Authentik natively by [following these instructions](https://docs.goauthentik.io/integrations/services/immich/).
::
+198
View File
@@ -0,0 +1,198 @@
---
navigation: true
title: Nextcloud
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Nextcloud
::alert{type="info"}
🎯 __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. Its a great alternative to services like Google Drive, iCloud, or OneDrive.
![Picture](/img/serveex/nextcloud.png)
## Installation
---
::alert{type="info"}
:::list{type="info"}
- 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
```
::alert{type="info"}
:::list{type="info"}
- If youre 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.
::alert{type="danger"}
:::list{type="danger"}
- __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. Well use Swag to expose the app.
::alert{type="info"}
:::list{type="info"}
- 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 youre 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
```
::alert{type="info"}
:::list{type="info"}
- 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 Nextclouds files, edit the `config.php` file:
::alert{type="success"}
__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—dont change it!
1 => 'nextcloud.yourdomain.com', # Add your domain here
),
```
Press `Esc`, then save and exit by typing `:x` and hitting Enter.
In Swags 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.
Thats it—youve exposed Nextcloud! Dont forget to install [the desktop and mobile apps](https://nextcloud.com/install/).
::alert{type="success"}
__Tip:__ You can natively protect this app with Authentik by [following these instructions](https://docs.goauthentik.io/integrations/services/nextcloud/).
::
+2
View File
@@ -0,0 +1,2 @@
navigation.title: Cloud Drive & Photos
icon: lucide:cloud-upload
+163
View File
@@ -0,0 +1,163 @@
---
navigation: true
title: File Browser
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# File Browser
::alert{type="info"}
🎯 __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.
![File Browser](/img/serveex/filebrowser.png)
## 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
```
::alert{type="success"}
__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`. Thats it—your File Browser web UI is up and running!
::alert{type="danger"}
:::list{type="danger"}
- __If it doesnt work:__ check your firewall rules.
:::
::
## Exposing File Browser with Swag
---
::alert{type="warning"}
:::list{type="warning"}
- File Browser does not support multi-factor authentication. Exposing it publicly could put your systems at risk. Only do this if youre 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, well expose it through Swag.
::alert{type="info"}
:::list{type="info"}
- __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 Browsers 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
```
::alert{type="info"}
:::list{type="info"}
- 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`.
Thats it—File Browser is now exposed!
::alert{type="success"}
✨ __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}. Dont forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
::
+208
View File
@@ -0,0 +1,208 @@
---
navigation: true
title: Pingvin
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Pingvin
::alert{type="info"}
🎯 __Objectifs :__
- Installer Pingvin
- Exposer Pingvin
::
[Pingvin](https://github.com/stonith404/pingvin-share) est un outil permettant de partager rapidement des fichiers, à la manière de WeTransfer. Ses nombreuses options de partage (mot de passe, durée d'expiration, personnalisation du lien...) en font l'outil idéal pour partager rapidement des fichiers. Pingvin permet également de créer des _demandes de dépot_, c'est à dire un lien partageable à envoyer à quelqu'un de votre choix pour qu'il puisse téléverser ses fichiers afin que vous puissiez les récupérer.
![File Browser](/img/serveex/pingvin.png)
## Installation
---
Ouvrez Dockge, cliquez sur `compose`, appelez la stack `pingvin` puis copiez collez ceci :
```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 # chemin du dossier dans lequel vous souhaitez stocker les fichiers uploadés dans pingvin. A changer selon vos préférences.
depends_on:
clamav:
condition: service_healthy
networks:
- swag
clamav: #antivirus pour les fichiers
restart: unless-stopped
image: clamav/clamav
```
::alert{type="info"}
:::list{type="info"}
- Ici nous partons du principe que le nom du réseau de Swag est `swag_default`.
:::
::
::alert{type="success"}
__Astuce :__ ajoutez le label de watchtower dans chaque conteneur afin d'automatiser les mises à jour
```yaml
services:
filebrowser:
#...
labels:
- com.centurylinklabs.watchtower.enable=true
clamav:
#...
labels:
- com.centurylinklabs.watchtower.enable=true
::
Déployez le conteneur et rendez-vous sur `http://ipduserveur:3600`. Et voilà, votre instance File Browser en webui est disponible !
::alert{type="danger"}
:::list{type="danger"}
- __En cas d'échec :__ vérifiez les règles de votre pare-feu.
:::
::
## Exposer Immich avec Swag
---
Tout l'intérêt d'une telle solution, c'est de pouvoir y accéder à distance et sur tout vos appareils. Pour cela, nous allons exposer Pingvin via Swag.
::alert{type="info"}
📋 __Au préalable :__
<br/><br/>
Nous partons du principe que vous avez le sous-domaine `pingvin.mondomaine.fr` avec un `CNAME` qui pointe vers `mondomaine.fr` dans votre [zone DNS](/general/networking/dns). Et que bien sûr, [à moins que vous utilisiez Cloudflare Zero Trust](/serveex/security/cloudflare), le port `443` de votre box pointe bien sur le port `443` de votre serveur via [les règles NAT](/general/networking/nat).
::
Dans Dockge, rendez-vous dans la stack de SWAG et éditez le compose en ajoutant le réseau de pingvin :
```yaml
services:
swag:
container_name: # ...
# ...
networks: # Relie le conteneur au réseau custom
# ...
- pingvin # Nom du réseau déclaré dans la stack
networks: # Définit le réseau custom
# ...
pingvin: # Nom du réseau déclaré dans la stack
name: pingvin_default # Nom véritable du réseau externe
external: true # Précise que c'est un réseau à rechercher en externe
```
::alert{type="info"}
:::list{type="info"}
- Ici nous partons du principe que le nom du réseau de pingvin est `pingvin_default`. Vous pouvez vérifier que la connexion est opérationnelle en visitant le dashboard de SWAG en tapant http://ipduserveur:81.
:::
::
Relancez la stack en cliquant sur "déployer" et patientez le temps que SWAG soit complètement opérationnel.
Dans les dossiers de Swag, créez le fichier `pingvin.subdomain.conf`.
::alert{type="success"}
:::list{type="success"}
- __Astuce :__ vous pouvez utiliser [File Browser](/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/nginx/proxy-confs/pingvin.subdomain.conf
```
Entrez en modification avec la touche `i` et collez la configuration ci-dessous :
```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;
}
}
```
Appuyez sur `Echap puis sauvegardez et quittez en tapant `:x` puis en appuyant sur `Entrée`.
Et voilà, vous avez exposé Pingvin !
## Sécuriser Pingvin avec Authentik
Vous pouvez protéger cette app avec Authentik de façon native en suivant les instructions ci-dessous.
1. Dans votre espace admin authentik, créez un fournisseur OAuth2/OpenID.
2. Remplissez chaque section comme suit en remplaçant `mondomaine.fr` par votre domaine. Copiez quelque part le contenu des champs `ID du client` et `Secret du client`.
![Picture](/img/serveex/pingvin-auth1.png)
![Picture](/img/serveex/pingvin-auth2.png)
![Picture](/img/serveex/pingvin-auth3.png)
3. Enregistrez et créez une application `pingvin` comme suit.
![Picture](/img/serveex/pingvin-auth4.png)
4. Enregistrez et aller dans la liste de vos avant-postes. Ajoutez le fournisseur pingvin` à votre avant-poste.
5. Quittez authentik, et allez dans l'interface d'administration de Pingvin.
6. Dans la section _« Identifiant social »_ renseignez les champs suivant :
- `URI de découverte OpenID` avec `https://pingvin.mondomaine.fr/application/o/pingvin/.well-known/openid-configuration` (n'oubliez pas de remplacer `mondomaine.fr` par votre domaine)
- `Revendication du nom dutilisateur OpenID` avec `preferred_username`
- `ID du client OpenID` avec l'ID que vous avez copié en étape 2.
- `Secret du client OpenID` avec le token que vous avez copié en étape 2.
Et voilà, désormais lorsque vous vous connectez à Pingvin, un bouton "Open ID" sera disponible en dessous de la mire de connexion.
+2
View File
@@ -0,0 +1,2 @@
navigation.title: File & share
icon: lucide:folder-tree
@@ -0,0 +1,223 @@
---
navigation: true
title: Code-Server
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Code-Server
::alert{type="info"}
🎯 __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.
![code-server](https://github.com/coder/code-server/raw/main/docs/assets/screenshot-2.png)
## Installation
---
::alert{type="info"}
:::list{type="info"}
- For this setup, well 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
```
::alert{type="success"}
✨ 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
```
::alert{type="warning"}
:::list{type="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!
::alert{type="danger"}
:::list{type="danger"}
- __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, well expose code-server via Swag.
::alert{type="info"}
:::list{type="info"}
- __Preliminary:__ We assume youve 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 youve 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-servers 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 its an external network
```
::alert{type="info"}
:::list{type="info"}
- 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`.
::alert{type="success"}
✨ __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`.
Thats it — code-server is now exposed!
::alert{type="success"}
✨ __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;`. Dont forget to [create an application and provider in Authentik](/serveex/security/authentik#protecting-an-app-via-reverse-proxy).
::
+198
View File
@@ -0,0 +1,198 @@
---
navigation: true
title: Gitea
main:
fluid: false
---
:ellipsis{left=0px width=40rem top=10rem blur=140px}
# Gitea
::alert{type="info"}
🎯 __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.
![gitea](https://about.gitea.com/img/home-screenshot.png)
## 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!
::alert{type="danger"}
:::list{type="danger"}
- __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, well expose Gitea through Swag.
::alert{type="info"}
:::list{type="info"}
- __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 servers 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
```
::alert{type="info"}
:::list{type="info"}
- 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`.
::alert{type="success"}
__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 thats it! Gitea is now exposed to the web.
::alert{type="success"}
__Tip:__ You can natively protect this app with Authentik by [following these instructions](https://docs.goauthentik.io/integrations/services/gitea/).
::

Some files were not shown because too many files have changed in this diff Show More