feat: add documentation infrastructure and service implementation guide
## Documentation Infrastructure & Service Implementation Guide This PR introduces documentation tooling and contributor resources to help maintain and expand the SelfHostBlocks project: ### Service Implementation Guide - Add comprehensive guide for implementing new SHB services - Covers contracts, testing, documentation, and integration patterns - Provides step-by-step instructions and best practices for contributors ### Redirect Management Tool - Add automated update-redirects tool to maintain documentation links - Extracts heading IDs from markdown files across docs/, modules/, demo/ - Maps source files to target HTML pages using project routing logic - Generates missing redirects in format expected by nixos-render-docs
This commit is contained in:
parent
0f22b8c2a3
commit
16fcec4d40
5 changed files with 1172 additions and 148 deletions
208
docs/generate-redirects-nixos-render-docs.py
Normal file
208
docs/generate-redirects-nixos-render-docs.py
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate redirects.json by scanning actual HTML files produced by nixos-render-docs.
|
||||||
|
|
||||||
|
This script implements a runtime patching mechanism to automatically generate a
|
||||||
|
complete redirects.json file by scanning generated HTML files for real anchor
|
||||||
|
locations, eliminating manual maintenance and ensuring accuracy.
|
||||||
|
|
||||||
|
ARCHITECTURE OVERVIEW:
|
||||||
|
The script works by monkey-patching nixos-render-docs at runtime to:
|
||||||
|
1. Disable redirect validation during HTML generation
|
||||||
|
2. Generate HTML documentation normally
|
||||||
|
3. Scan all generated HTML files to extract anchor IDs and their file locations
|
||||||
|
4. Apply filtering logic to exclude system-generated anchors
|
||||||
|
5. Generate and write redirects.json with accurate mappings
|
||||||
|
|
||||||
|
KEY COMPONENTS:
|
||||||
|
- Runtime patching: Modifies nixos-render-docs behavior without source changes
|
||||||
|
- HTML scanning: Extracts anchor IDs using regex pattern matching
|
||||||
|
- Filtering: Excludes NixOS options (opt-*) and extra options (selfhostblock*)
|
||||||
|
- Output generation: Creates both debug information and production redirects.json
|
||||||
|
|
||||||
|
IMPORTANT NOTES:
|
||||||
|
- Uses atexit handler to ensure output is generated even if process is interrupted
|
||||||
|
- Patches are applied on module import, making this a side-effect import
|
||||||
|
- Error handling preserves original validation behavior in case of failure
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import atexit
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Global storage for anchor-to-file mappings discovered during HTML scanning
|
||||||
|
# Structure: {anchor_id: html_filename}
|
||||||
|
file_target_mapping = {}
|
||||||
|
|
||||||
|
def scan_html_files(output_dir, html_files):
|
||||||
|
"""
|
||||||
|
Scan HTML files to extract anchor IDs and build anchor-to-file mappings.
|
||||||
|
|
||||||
|
Discovers all HTML files in output_dir and extracts id attributes to populate
|
||||||
|
the global file_target_mapping. Filters out NixOS system options during scanning.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output_dir: Directory containing generated HTML files
|
||||||
|
html_files: Unused parameter (always discovers files from filesystem)
|
||||||
|
"""
|
||||||
|
# Always discover HTML files from the output directory
|
||||||
|
if not os.path.exists(output_dir):
|
||||||
|
print(f"DEBUG: Output directory {output_dir} does not exist", file=sys.stderr)
|
||||||
|
return
|
||||||
|
|
||||||
|
html_files = [f for f in os.listdir(output_dir) if f.endswith('.html')]
|
||||||
|
print(f"DEBUG: Discovered {len(html_files)} HTML files in {output_dir}", file=sys.stderr)
|
||||||
|
|
||||||
|
# Process each HTML file to extract anchor IDs
|
||||||
|
for html_file in html_files:
|
||||||
|
html_path = os.path.join(output_dir, html_file)
|
||||||
|
try:
|
||||||
|
with open(html_path, 'r', encoding='utf-8') as f:
|
||||||
|
html_content = f.read()
|
||||||
|
|
||||||
|
# Extract all id attributes using regex pattern matching
|
||||||
|
# Matches: id="anchor-name" and captures anchor-name
|
||||||
|
anchor_matches = re.findall(r'id="([^"]+)"', html_content)
|
||||||
|
|
||||||
|
# Filter and record anchor mappings
|
||||||
|
non_opt_count = 0
|
||||||
|
for anchor_id in anchor_matches:
|
||||||
|
# Skip NixOS system option anchors (opt-* prefix)
|
||||||
|
if not anchor_id.startswith('opt-'):
|
||||||
|
file_target_mapping[anchor_id] = html_file
|
||||||
|
non_opt_count += 1
|
||||||
|
|
||||||
|
if non_opt_count > 0:
|
||||||
|
print(f"Found {non_opt_count} anchors in {html_file}", file=sys.stderr)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Log errors but continue processing other files
|
||||||
|
print(f"Failed to scan {html_path}: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
def output_collected_refs():
|
||||||
|
"""
|
||||||
|
Generate and write the final redirects.json file from collected anchor mappings.
|
||||||
|
|
||||||
|
This function is registered as an atexit handler to ensure output is generated
|
||||||
|
even if the process is interrupted. It processes the global file_target_mapping
|
||||||
|
to create the final redirects file with appropriate filtering.
|
||||||
|
|
||||||
|
Output files:
|
||||||
|
- out/redirects.json: Production redirects mapping
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Generate redirects from discovered HTML anchor mappings
|
||||||
|
if file_target_mapping:
|
||||||
|
print(f"Creating redirects from {len(file_target_mapping)} HTML mappings", file=sys.stderr)
|
||||||
|
redirects = {}
|
||||||
|
filtered_count = 0
|
||||||
|
|
||||||
|
# Apply filtering logic to exclude system-generated anchors
|
||||||
|
for anchor_id, html_file in file_target_mapping.items():
|
||||||
|
# Filter out:
|
||||||
|
# - opt-*: NixOS system options
|
||||||
|
# - selfhostblock*: Extra options from this project
|
||||||
|
if not anchor_id.startswith('opt-') and not anchor_id.startswith('selfhostblock'):
|
||||||
|
redirects[anchor_id] = [f"{html_file}#{anchor_id}"]
|
||||||
|
else:
|
||||||
|
filtered_count += 1
|
||||||
|
|
||||||
|
print(f"Generated {len(redirects)} redirects (filtered out {filtered_count} system options)", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
# Fallback case - should not occur during normal operation
|
||||||
|
print("Warning: No HTML mappings available", file=sys.stderr)
|
||||||
|
redirects = {}
|
||||||
|
|
||||||
|
# Ensure output directory exists
|
||||||
|
os.makedirs('out', exist_ok=True)
|
||||||
|
|
||||||
|
# Write production redirects file
|
||||||
|
try:
|
||||||
|
redirects_file = 'out/redirects.json'
|
||||||
|
|
||||||
|
with open(redirects_file, 'w') as f:
|
||||||
|
json.dump(redirects, f, indent=2, sort_keys=True)
|
||||||
|
|
||||||
|
print(f"Generated redirects.json with {len(redirects)} redirects", file=sys.stderr)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to write redirects.json: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
# Register output generation to run on process exit
|
||||||
|
atexit.register(output_collected_refs)
|
||||||
|
|
||||||
|
def apply_patches():
|
||||||
|
"""
|
||||||
|
Apply runtime monkey patches to nixos-render-docs modules.
|
||||||
|
|
||||||
|
This function modifies the behavior of nixos-render-docs by:
|
||||||
|
1. Hooking into the HTML generation CLI command
|
||||||
|
2. Temporarily disabling redirect validation during HTML generation
|
||||||
|
3. Scanning generated HTML files to extract anchor mappings
|
||||||
|
4. Restoring original validation behavior
|
||||||
|
|
||||||
|
The patching approach allows us to extract anchor information without
|
||||||
|
modifying the nixos-render-docs source code directly.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ImportError: If nixos-render-docs modules cannot be imported
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Import required nixos-render-docs modules
|
||||||
|
import nixos_render_docs.html as html_module
|
||||||
|
import nixos_render_docs.redirects as redirects_module
|
||||||
|
import nixos_render_docs.manual as manual_module
|
||||||
|
|
||||||
|
# Store reference to original HTML CLI function
|
||||||
|
original_run_cli_html = manual_module._run_cli_html
|
||||||
|
|
||||||
|
def patched_run_cli_html(args):
|
||||||
|
"""
|
||||||
|
Patched version of _run_cli_html that disables validation and scans output.
|
||||||
|
|
||||||
|
This wrapper function:
|
||||||
|
1. Temporarily disables redirect validation to prevent errors
|
||||||
|
2. Runs normal HTML generation
|
||||||
|
3. Scans generated HTML files for anchor mappings
|
||||||
|
4. Restores original validation behavior
|
||||||
|
"""
|
||||||
|
print("Generating HTML documentation...", file=sys.stderr)
|
||||||
|
|
||||||
|
# Temporarily disable redirect validation
|
||||||
|
original_validate = redirects_module.Redirects.validate
|
||||||
|
redirects_module.Redirects.validate = lambda self, targets: None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run original HTML generation
|
||||||
|
result = original_run_cli_html(args)
|
||||||
|
|
||||||
|
# Determine output directory from CLI arguments
|
||||||
|
if hasattr(args, 'outfile') and args.outfile:
|
||||||
|
output_dir = os.path.dirname(args.outfile)
|
||||||
|
else:
|
||||||
|
output_dir = '.'
|
||||||
|
|
||||||
|
# Scan generated HTML files for anchor mappings
|
||||||
|
scan_html_files(output_dir, None)
|
||||||
|
print(f"Scanned {len(file_target_mapping)} anchor mappings", file=sys.stderr)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Always restore original validation function
|
||||||
|
redirects_module.Redirects.validate = original_validate
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Replace the original function with our patched version
|
||||||
|
manual_module._run_cli_html = patched_run_cli_html
|
||||||
|
|
||||||
|
print("Applied patches to nixos-render-docs", file=sys.stderr)
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"Failed to apply patches: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
# Apply patches immediately when this module is imported
|
||||||
|
# This ensures the patches are active before nixos-render-docs CLI runs
|
||||||
|
apply_patches()
|
||||||
|
|
@ -32,6 +32,10 @@ demos.md
|
||||||
contributing.md
|
contributing.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```{=include=} chapters html:into-file=//service-implementation-guide.html
|
||||||
|
service-implementation-guide.md
|
||||||
|
```
|
||||||
|
|
||||||
```{=include=} appendix html:into-file=//options.html
|
```{=include=} appendix html:into-file=//options.html
|
||||||
options.md
|
options.md
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,49 @@
|
||||||
{
|
{
|
||||||
|
"adding-new-service-documentation": [
|
||||||
|
"service-implementation-guide.html#adding-new-service-documentation"
|
||||||
|
],
|
||||||
"all-options": [
|
"all-options": [
|
||||||
"options.html#all-options"
|
"options.html#all-options"
|
||||||
],
|
],
|
||||||
|
"analyze-existing-services": [
|
||||||
|
"service-implementation-guide.html#analyze-existing-services"
|
||||||
|
],
|
||||||
|
"api-health-check": [
|
||||||
|
"service-implementation-guide.html#api-health-check"
|
||||||
|
],
|
||||||
|
"authentication-integration": [
|
||||||
|
"service-implementation-guide.html#authentication-integration"
|
||||||
|
],
|
||||||
|
"authentication-integration-pitfalls": [
|
||||||
|
"service-implementation-guide.html#authentication-integration-pitfalls"
|
||||||
|
],
|
||||||
|
"automated-redirect-generation": [
|
||||||
|
"service-implementation-guide.html#automated-redirect-generation"
|
||||||
|
],
|
||||||
|
"best-practices-summary": [
|
||||||
|
"service-implementation-guide.html#best-practices-summary"
|
||||||
|
],
|
||||||
|
"block-ssl": [
|
||||||
|
"blocks-ssl.html#block-ssl"
|
||||||
|
],
|
||||||
|
"block-ssl-debug": [
|
||||||
|
"blocks-ssl.html#block-ssl-debug"
|
||||||
|
],
|
||||||
|
"block-ssl-impl-lets-encrypt": [
|
||||||
|
"blocks-ssl.html#block-ssl-impl-lets-encrypt"
|
||||||
|
],
|
||||||
|
"block-ssl-impl-self-signed": [
|
||||||
|
"blocks-ssl.html#block-ssl-impl-self-signed"
|
||||||
|
],
|
||||||
|
"block-ssl-options": [
|
||||||
|
"blocks-ssl.html#block-ssl-options"
|
||||||
|
],
|
||||||
|
"block-ssl-tests": [
|
||||||
|
"blocks-ssl.html#block-ssl-tests"
|
||||||
|
],
|
||||||
|
"block-ssl-usage": [
|
||||||
|
"blocks-ssl.html#block-ssl-usage"
|
||||||
|
],
|
||||||
"blocks": [
|
"blocks": [
|
||||||
"blocks.html#blocks"
|
"blocks.html#blocks"
|
||||||
],
|
],
|
||||||
|
|
@ -14,20 +56,14 @@
|
||||||
"blocks-monitoring-configuration": [
|
"blocks-monitoring-configuration": [
|
||||||
"blocks-monitoring.html#blocks-monitoring-configuration"
|
"blocks-monitoring.html#blocks-monitoring-configuration"
|
||||||
],
|
],
|
||||||
"blocks-monitoring-nextcloud-dashboard": [
|
|
||||||
"blocks-monitoring.html#blocks-monitoring-nextcloud-dashboard"
|
|
||||||
],
|
|
||||||
"blocks-monitoring-deluge-dashboard": [
|
"blocks-monitoring-deluge-dashboard": [
|
||||||
"blocks-monitoring.html#blocks-monitoring-deluge-dashboard"
|
"blocks-monitoring.html#blocks-monitoring-deluge-dashboard"
|
||||||
],
|
],
|
||||||
"blocks-monitoring-error-dashboard": [
|
"blocks-monitoring-error-dashboard": [
|
||||||
"blocks-monitoring.html#blocks-monitoring-error-dashboard"
|
"blocks-monitoring.html#blocks-monitoring-error-dashboard"
|
||||||
],
|
],
|
||||||
"blocks-monitoring-performance-dashboard": [
|
"blocks-monitoring-nextcloud-dashboard": [
|
||||||
"blocks-monitoring.html#blocks-monitoring-performance-dashboard"
|
"blocks-monitoring.html#blocks-monitoring-nextcloud-dashboard"
|
||||||
],
|
|
||||||
"blocks-monitoring-provisioning": [
|
|
||||||
"blocks-monitoring.html#blocks-monitoring-provisioning"
|
|
||||||
],
|
],
|
||||||
"blocks-monitoring-options": [
|
"blocks-monitoring-options": [
|
||||||
"blocks-monitoring.html#blocks-monitoring-options"
|
"blocks-monitoring.html#blocks-monitoring-options"
|
||||||
|
|
@ -149,6 +185,12 @@
|
||||||
"blocks-monitoring-options-shb.monitoring.subdomain": [
|
"blocks-monitoring-options-shb.monitoring.subdomain": [
|
||||||
"blocks-monitoring.html#blocks-monitoring-options-shb.monitoring.subdomain"
|
"blocks-monitoring.html#blocks-monitoring-options-shb.monitoring.subdomain"
|
||||||
],
|
],
|
||||||
|
"blocks-monitoring-performance-dashboard": [
|
||||||
|
"blocks-monitoring.html#blocks-monitoring-performance-dashboard"
|
||||||
|
],
|
||||||
|
"blocks-monitoring-provisioning": [
|
||||||
|
"blocks-monitoring.html#blocks-monitoring-provisioning"
|
||||||
|
],
|
||||||
"blocks-postgresql": [
|
"blocks-postgresql": [
|
||||||
"blocks-postgresql.html#blocks-postgresql"
|
"blocks-postgresql.html#blocks-postgresql"
|
||||||
],
|
],
|
||||||
|
|
@ -473,32 +515,11 @@
|
||||||
"blocks-sops-usage": [
|
"blocks-sops-usage": [
|
||||||
"blocks-sops.html#blocks-sops-usage"
|
"blocks-sops.html#blocks-sops-usage"
|
||||||
],
|
],
|
||||||
"blocks-sops-usage-requester": [
|
|
||||||
"blocks-sops.html#blocks-sops-usage-requester"
|
|
||||||
],
|
|
||||||
"blocks-sops-usage-manual": [
|
"blocks-sops-usage-manual": [
|
||||||
"blocks-sops.html#blocks-sops-usage-manual"
|
"blocks-sops.html#blocks-sops-usage-manual"
|
||||||
],
|
],
|
||||||
"block-ssl": [
|
"blocks-sops-usage-requester": [
|
||||||
"blocks-ssl.html#block-ssl"
|
"blocks-sops.html#blocks-sops-usage-requester"
|
||||||
],
|
|
||||||
"block-ssl-debug": [
|
|
||||||
"blocks-ssl.html#block-ssl-debug"
|
|
||||||
],
|
|
||||||
"block-ssl-impl-lets-encrypt": [
|
|
||||||
"blocks-ssl.html#block-ssl-impl-lets-encrypt"
|
|
||||||
],
|
|
||||||
"block-ssl-impl-self-signed": [
|
|
||||||
"blocks-ssl.html#block-ssl-impl-self-signed"
|
|
||||||
],
|
|
||||||
"block-ssl-options": [
|
|
||||||
"blocks-ssl.html#block-ssl-options"
|
|
||||||
],
|
|
||||||
"block-ssl-tests": [
|
|
||||||
"blocks-ssl.html#block-ssl-tests"
|
|
||||||
],
|
|
||||||
"block-ssl-usage": [
|
|
||||||
"blocks-ssl.html#block-ssl-usage"
|
|
||||||
],
|
],
|
||||||
"blocks-ssl-options-shb.certs.cas.selfsigned": [
|
"blocks-ssl-options-shb.certs.cas.selfsigned": [
|
||||||
"blocks-ssl.html#blocks-ssl-options-shb.certs.cas.selfsigned"
|
"blocks-ssl.html#blocks-ssl-options-shb.certs.cas.selfsigned"
|
||||||
|
|
@ -617,6 +638,27 @@
|
||||||
"blocks-ssl-options-shb.certs.systemdService": [
|
"blocks-ssl-options-shb.certs.systemdService": [
|
||||||
"blocks-ssl.html#blocks-ssl-options-shb.certs.systemdService"
|
"blocks-ssl.html#blocks-ssl-options-shb.certs.systemdService"
|
||||||
],
|
],
|
||||||
|
"build-time-validation": [
|
||||||
|
"service-implementation-guide.html#build-time-validation"
|
||||||
|
],
|
||||||
|
"check-nixos-integration": [
|
||||||
|
"service-implementation-guide.html#check-nixos-integration"
|
||||||
|
],
|
||||||
|
"common-pitfalls-and-solutions": [
|
||||||
|
"service-implementation-guide.html#common-pitfalls-and-solutions"
|
||||||
|
],
|
||||||
|
"complete-shb-service": [
|
||||||
|
"service-implementation-guide.html#complete-shb-service"
|
||||||
|
],
|
||||||
|
"complete-workflow": [
|
||||||
|
"service-implementation-guide.html#complete-workflow"
|
||||||
|
],
|
||||||
|
"configuration-issues": [
|
||||||
|
"service-implementation-guide.html#configuration-issues"
|
||||||
|
],
|
||||||
|
"configuration-management": [
|
||||||
|
"service-implementation-guide.html#configuration-management"
|
||||||
|
],
|
||||||
"contract-backup": [
|
"contract-backup": [
|
||||||
"contracts-backup.html#contract-backup"
|
"contracts-backup.html#contract-backup"
|
||||||
],
|
],
|
||||||
|
|
@ -686,27 +728,6 @@
|
||||||
"contracts": [
|
"contracts": [
|
||||||
"contracts.html#contracts"
|
"contracts.html#contracts"
|
||||||
],
|
],
|
||||||
"contracts-nixpkgs": [
|
|
||||||
"contracts.html#contracts-nixpkgs"
|
|
||||||
],
|
|
||||||
"contracts-provided": [
|
|
||||||
"contracts.html#contracts-provided"
|
|
||||||
],
|
|
||||||
"contracts-concept": [
|
|
||||||
"contracts.html#contracts-concept"
|
|
||||||
],
|
|
||||||
"contracts-schema": [
|
|
||||||
"contracts.html#contracts-schema"
|
|
||||||
],
|
|
||||||
"contracts-test": [
|
|
||||||
"contracts.html#contracts-test"
|
|
||||||
],
|
|
||||||
"contracts-videos": [
|
|
||||||
"contracts.html#contracts-videos"
|
|
||||||
],
|
|
||||||
"contracts-why": [
|
|
||||||
"contracts.html#contracts-why"
|
|
||||||
],
|
|
||||||
"contracts-backup-options-shb.contracts.backup": [
|
"contracts-backup-options-shb.contracts.backup": [
|
||||||
"contracts-backup.html#contracts-backup-options-shb.contracts.backup"
|
"contracts-backup.html#contracts-backup-options-shb.contracts.backup"
|
||||||
],
|
],
|
||||||
|
|
@ -743,6 +764,9 @@
|
||||||
"contracts-backup-options-shb.contracts.backup.settings": [
|
"contracts-backup-options-shb.contracts.backup.settings": [
|
||||||
"contracts-backup.html#contracts-backup-options-shb.contracts.backup.settings"
|
"contracts-backup.html#contracts-backup-options-shb.contracts.backup.settings"
|
||||||
],
|
],
|
||||||
|
"contracts-concept": [
|
||||||
|
"contracts.html#contracts-concept"
|
||||||
|
],
|
||||||
"contracts-databasebackup-options-shb.contracts.databasebackup": [
|
"contracts-databasebackup-options-shb.contracts.databasebackup": [
|
||||||
"contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup"
|
"contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup"
|
||||||
],
|
],
|
||||||
|
|
@ -773,6 +797,15 @@
|
||||||
"contracts-databasebackup-options-shb.contracts.databasebackup.settings": [
|
"contracts-databasebackup-options-shb.contracts.databasebackup.settings": [
|
||||||
"contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup.settings"
|
"contracts-databasebackup.html#contracts-databasebackup-options-shb.contracts.databasebackup.settings"
|
||||||
],
|
],
|
||||||
|
"contracts-nixpkgs": [
|
||||||
|
"contracts.html#contracts-nixpkgs"
|
||||||
|
],
|
||||||
|
"contracts-provided": [
|
||||||
|
"contracts.html#contracts-provided"
|
||||||
|
],
|
||||||
|
"contracts-schema": [
|
||||||
|
"contracts.html#contracts-schema"
|
||||||
|
],
|
||||||
"contracts-secret-options-shb.contracts.secret": [
|
"contracts-secret-options-shb.contracts.secret": [
|
||||||
"contracts-secret.html#contracts-secret-options-shb.contracts.secret"
|
"contracts-secret.html#contracts-secret-options-shb.contracts.secret"
|
||||||
],
|
],
|
||||||
|
|
@ -815,6 +848,15 @@
|
||||||
"contracts-ssl-options-shb.contracts.ssl.systemdService": [
|
"contracts-ssl-options-shb.contracts.ssl.systemdService": [
|
||||||
"contracts-ssl.html#contracts-ssl-options-shb.contracts.ssl.systemdService"
|
"contracts-ssl.html#contracts-ssl-options-shb.contracts.ssl.systemdService"
|
||||||
],
|
],
|
||||||
|
"contracts-test": [
|
||||||
|
"contracts.html#contracts-test"
|
||||||
|
],
|
||||||
|
"contracts-videos": [
|
||||||
|
"contracts.html#contracts-videos"
|
||||||
|
],
|
||||||
|
"contracts-why": [
|
||||||
|
"contracts.html#contracts-why"
|
||||||
|
],
|
||||||
"contributing": [
|
"contributing": [
|
||||||
"contributing.html#contributing"
|
"contributing.html#contributing"
|
||||||
],
|
],
|
||||||
|
|
@ -857,6 +899,15 @@
|
||||||
"contributing-upstream": [
|
"contributing-upstream": [
|
||||||
"contributing.html#contributing-upstream"
|
"contributing.html#contributing-upstream"
|
||||||
],
|
],
|
||||||
|
"create-comprehensive-tests": [
|
||||||
|
"service-implementation-guide.html#create-comprehensive-tests"
|
||||||
|
],
|
||||||
|
"create-service-documentation": [
|
||||||
|
"service-implementation-guide.html#create-service-documentation"
|
||||||
|
],
|
||||||
|
"create-service-module": [
|
||||||
|
"service-implementation-guide.html#create-service-module"
|
||||||
|
],
|
||||||
"demo-homeassistant": [
|
"demo-homeassistant": [
|
||||||
"demo-homeassistant.html#demo-homeassistant"
|
"demo-homeassistant.html#demo-homeassistant"
|
||||||
],
|
],
|
||||||
|
|
@ -866,6 +917,9 @@
|
||||||
"demo-homeassistant-deploy-basic": [
|
"demo-homeassistant-deploy-basic": [
|
||||||
"demo-homeassistant.html#demo-homeassistant-deploy-basic"
|
"demo-homeassistant.html#demo-homeassistant-deploy-basic"
|
||||||
],
|
],
|
||||||
|
"demo-homeassistant-deploy-colmena": [
|
||||||
|
"demo-homeassistant.html#demo-homeassistant-deploy-colmena"
|
||||||
|
],
|
||||||
"demo-homeassistant-deploy-ldap": [
|
"demo-homeassistant-deploy-ldap": [
|
||||||
"demo-homeassistant.html#demo-homeassistant-deploy-ldap"
|
"demo-homeassistant.html#demo-homeassistant-deploy-ldap"
|
||||||
],
|
],
|
||||||
|
|
@ -896,9 +950,6 @@
|
||||||
"demo-homeassistant-virtual-machine": [
|
"demo-homeassistant-virtual-machine": [
|
||||||
"demo-homeassistant.html#demo-homeassistant-virtual-machine"
|
"demo-homeassistant.html#demo-homeassistant-virtual-machine"
|
||||||
],
|
],
|
||||||
"demo-homeassistant-deploy-colmena": [
|
|
||||||
"demo-homeassistant.html#demo-homeassistant-deploy-colmena"
|
|
||||||
],
|
|
||||||
"demo-nextcloud": [
|
"demo-nextcloud": [
|
||||||
"demo-nextcloud.html#demo-nextcloud"
|
"demo-nextcloud.html#demo-nextcloud"
|
||||||
],
|
],
|
||||||
|
|
@ -947,12 +998,69 @@
|
||||||
"demos": [
|
"demos": [
|
||||||
"demos.html#demos"
|
"demos.html#demos"
|
||||||
],
|
],
|
||||||
|
"external-exporter": [
|
||||||
|
"service-implementation-guide.html#external-exporter"
|
||||||
|
],
|
||||||
|
"handle-unfree-dependencies": [
|
||||||
|
"service-implementation-guide.html#handle-unfree-dependencies"
|
||||||
|
],
|
||||||
|
"how-redirects-work": [
|
||||||
|
"service-implementation-guide.html#how-redirects-work"
|
||||||
|
],
|
||||||
|
"implementation-considerations": [
|
||||||
|
"service-implementation-guide.html#implementation-considerations"
|
||||||
|
],
|
||||||
|
"implementation-steps": [
|
||||||
|
"service-implementation-guide.html#implementation-steps"
|
||||||
|
],
|
||||||
|
"iterative-development-approach": [
|
||||||
|
"service-implementation-guide.html#iterative-development-approach"
|
||||||
|
],
|
||||||
|
"local-testing": [
|
||||||
|
"service-implementation-guide.html#local-testing"
|
||||||
|
],
|
||||||
|
"monitoring-failures": [
|
||||||
|
"service-implementation-guide.html#monitoring-failures"
|
||||||
|
],
|
||||||
|
"monitoring-implementation": [
|
||||||
|
"service-implementation-guide.html#monitoring-implementation"
|
||||||
|
],
|
||||||
|
"native-prometheus-metrics": [
|
||||||
|
"service-implementation-guide.html#native-prometheus-metrics"
|
||||||
|
],
|
||||||
|
"nixpkgs-integration": [
|
||||||
|
"service-implementation-guide.html#nixpkgs-integration"
|
||||||
|
],
|
||||||
|
"pre-implementation-research": [
|
||||||
|
"service-implementation-guide.html#pre-implementation-research"
|
||||||
|
],
|
||||||
"preface": [
|
"preface": [
|
||||||
"index.html#preface"
|
"index.html#preface"
|
||||||
],
|
],
|
||||||
|
"quick-reference": [
|
||||||
|
"service-implementation-guide.html#quick-reference"
|
||||||
|
],
|
||||||
|
"redirect-management": [
|
||||||
|
"service-implementation-guide.html#redirect-management"
|
||||||
|
],
|
||||||
|
"redirect-patterns": [
|
||||||
|
"service-implementation-guide.html#redirect-patterns"
|
||||||
|
],
|
||||||
|
"required-test-variants": [
|
||||||
|
"service-implementation-guide.html#required-test-variants"
|
||||||
|
],
|
||||||
|
"resources": [
|
||||||
|
"service-implementation-guide.html#resources"
|
||||||
|
],
|
||||||
|
"security-best-practices": [
|
||||||
|
"service-implementation-guide.html#security-best-practices"
|
||||||
|
],
|
||||||
"self-host-blocks-manual": [
|
"self-host-blocks-manual": [
|
||||||
"index.html#self-host-blocks-manual"
|
"index.html#self-host-blocks-manual"
|
||||||
],
|
],
|
||||||
|
"service-implementation-guide": [
|
||||||
|
"service-implementation-guide.html#service-implementation-guide"
|
||||||
|
],
|
||||||
"services": [
|
"services": [
|
||||||
"services.html#services"
|
"services.html#services"
|
||||||
],
|
],
|
||||||
|
|
@ -968,39 +1076,6 @@
|
||||||
"services-forgejo-options": [
|
"services-forgejo-options": [
|
||||||
"services-forgejo.html#services-forgejo-options"
|
"services-forgejo.html#services-forgejo-options"
|
||||||
],
|
],
|
||||||
"services-forgejo-options-shb.forgejo.users": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.users"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.users._name_.email": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.email"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.users._name_.isAdmin": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.isAdmin"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.users._name_.password": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.users._name_.password.request": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.request"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.users._name_.password.request.group": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.request.group"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.users._name_.password.request.mode": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.request.mode"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.users._name_.password.request.owner": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.request.owner"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.users._name_.password.request.restartUnits": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.request.restartUnits"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.users._name_.password.result": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.result"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.users._name_.password.result.path": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.result.path"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.backup": [
|
"services-forgejo-options-shb.forgejo.backup": [
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.backup"
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.backup"
|
||||||
],
|
],
|
||||||
|
|
@ -1187,6 +1262,27 @@
|
||||||
"services-forgejo-options-shb.forgejo.sso.sharedSecret": [
|
"services-forgejo-options-shb.forgejo.sso.sharedSecret": [
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret"
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret"
|
||||||
],
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.sso.sharedSecret.request": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.sso.sharedSecret.request.group": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.group"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.sso.sharedSecret.request.mode": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.mode"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.sso.sharedSecret.request.owner": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.owner"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.sso.sharedSecret.request.restartUnits": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.restartUnits"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.sso.sharedSecret.result": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.result"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.sso.sharedSecret.result.path": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.result.path"
|
||||||
|
],
|
||||||
"services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia": [
|
"services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia": [
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia"
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia"
|
||||||
],
|
],
|
||||||
|
|
@ -1211,30 +1307,42 @@
|
||||||
"services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.result.path": [
|
"services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.result.path": [
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.result.path"
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecretForAuthelia.result.path"
|
||||||
],
|
],
|
||||||
"services-forgejo-options-shb.forgejo.sso.sharedSecret.request": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.sso.sharedSecret.request.group": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.group"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.sso.sharedSecret.request.mode": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.mode"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.sso.sharedSecret.request.owner": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.owner"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.sso.sharedSecret.request.restartUnits": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.request.restartUnits"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.sso.sharedSecret.result": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.result"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.sso.sharedSecret.result.path": [
|
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.sso.sharedSecret.result.path"
|
|
||||||
],
|
|
||||||
"services-forgejo-options-shb.forgejo.subdomain": [
|
"services-forgejo-options-shb.forgejo.subdomain": [
|
||||||
"services-forgejo.html#services-forgejo-options-shb.forgejo.subdomain"
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.subdomain"
|
||||||
],
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.users": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.users"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.users._name_.email": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.email"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.users._name_.isAdmin": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.isAdmin"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.users._name_.password": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.users._name_.password.request": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.request"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.users._name_.password.request.group": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.request.group"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.users._name_.password.request.mode": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.request.mode"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.users._name_.password.request.owner": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.request.owner"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.users._name_.password.request.restartUnits": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.request.restartUnits"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.users._name_.password.result": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.result"
|
||||||
|
],
|
||||||
|
"services-forgejo-options-shb.forgejo.users._name_.password.result.path": [
|
||||||
|
"services-forgejo.html#services-forgejo-options-shb.forgejo.users._name_.password.result.path"
|
||||||
|
],
|
||||||
"services-forgejo-usage": [
|
"services-forgejo-usage": [
|
||||||
"services-forgejo.html#services-forgejo-usage"
|
"services-forgejo.html#services-forgejo-usage"
|
||||||
],
|
],
|
||||||
|
|
@ -1244,12 +1352,12 @@
|
||||||
"services-forgejo-usage-configuration": [
|
"services-forgejo-usage-configuration": [
|
||||||
"services-forgejo.html#services-forgejo-usage-configuration"
|
"services-forgejo.html#services-forgejo-usage-configuration"
|
||||||
],
|
],
|
||||||
"services-forgejo-usage-https": [
|
|
||||||
"services-forgejo.html#services-forgejo-usage-https"
|
|
||||||
],
|
|
||||||
"services-forgejo-usage-extra-settings": [
|
"services-forgejo-usage-extra-settings": [
|
||||||
"services-forgejo.html#services-forgejo-usage-extra-settings"
|
"services-forgejo.html#services-forgejo-usage-extra-settings"
|
||||||
],
|
],
|
||||||
|
"services-forgejo-usage-https": [
|
||||||
|
"services-forgejo.html#services-forgejo-usage-https"
|
||||||
|
],
|
||||||
"services-forgejo-usage-ldap": [
|
"services-forgejo-usage-ldap": [
|
||||||
"services-forgejo.html#services-forgejo-usage-ldap"
|
"services-forgejo.html#services-forgejo-usage-ldap"
|
||||||
],
|
],
|
||||||
|
|
@ -1259,6 +1367,9 @@
|
||||||
"services-nextcloudserver": [
|
"services-nextcloudserver": [
|
||||||
"services-nextcloud.html#services-nextcloudserver"
|
"services-nextcloud.html#services-nextcloudserver"
|
||||||
],
|
],
|
||||||
|
"services-nextcloudserver-dashboard": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-dashboard"
|
||||||
|
],
|
||||||
"services-nextcloudserver-debug": [
|
"services-nextcloudserver-debug": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-debug"
|
"services-nextcloud.html#services-nextcloudserver-debug"
|
||||||
],
|
],
|
||||||
|
|
@ -1268,9 +1379,6 @@
|
||||||
"services-nextcloudserver-features": [
|
"services-nextcloudserver-features": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-features"
|
"services-nextcloud.html#services-nextcloudserver-features"
|
||||||
],
|
],
|
||||||
"services-nextcloudserver-dashboard": [
|
|
||||||
"services-nextcloud.html#services-nextcloudserver-dashboard"
|
|
||||||
],
|
|
||||||
"services-nextcloudserver-options": [
|
"services-nextcloudserver-options": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options"
|
"services-nextcloud.html#services-nextcloudserver-options"
|
||||||
],
|
],
|
||||||
|
|
@ -1373,20 +1481,14 @@
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.memories": [
|
"services-nextcloudserver-options-shb.nextcloud.apps.memories": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.memories"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.memories"
|
||||||
],
|
],
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.memories.vaapi": [
|
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.memories.vaapi"
|
|
||||||
],
|
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.memories.enable": [
|
"services-nextcloudserver-options-shb.nextcloud.apps.memories.enable": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.memories.enable"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.memories.enable"
|
||||||
],
|
],
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.memories.photosPath": [
|
"services-nextcloudserver-options-shb.nextcloud.apps.memories.photosPath": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.memories.photosPath"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.memories.photosPath"
|
||||||
],
|
],
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.recognize": [
|
"services-nextcloudserver-options-shb.nextcloud.apps.memories.vaapi": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.recognize"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.memories.vaapi"
|
||||||
],
|
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.recognize.enable": [
|
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.recognize.enable"
|
|
||||||
],
|
],
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice": [
|
"services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.onlyoffice"
|
||||||
|
|
@ -1430,6 +1532,12 @@
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator.recommendedSettings": [
|
"services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator.recommendedSettings": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator.recommendedSettings"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.previewgenerator.recommendedSettings"
|
||||||
],
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.apps.recognize": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.recognize"
|
||||||
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.apps.recognize.enable": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.recognize.enable"
|
||||||
|
],
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.sso": [
|
"services-nextcloudserver-options-shb.nextcloud.apps.sso": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso"
|
||||||
],
|
],
|
||||||
|
|
@ -1457,6 +1565,27 @@
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret": [
|
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret"
|
||||||
],
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request"
|
||||||
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.group": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.group"
|
||||||
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.mode": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.mode"
|
||||||
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.owner": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.owner"
|
||||||
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.restartUnits": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.restartUnits"
|
||||||
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result"
|
||||||
|
],
|
||||||
|
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result.path": [
|
||||||
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result.path"
|
||||||
|
],
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia": [
|
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia"
|
||||||
],
|
],
|
||||||
|
|
@ -1481,27 +1610,6 @@
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.result.path": [
|
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.result.path": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.result.path"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secretForAuthelia.result.path"
|
||||||
],
|
],
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request": [
|
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request"
|
|
||||||
],
|
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.group": [
|
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.group"
|
|
||||||
],
|
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.mode": [
|
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.mode"
|
|
||||||
],
|
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.owner": [
|
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.owner"
|
|
||||||
],
|
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.restartUnits": [
|
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.request.restartUnits"
|
|
||||||
],
|
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result": [
|
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result"
|
|
||||||
],
|
|
||||||
"services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result.path": [
|
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.apps.sso.secret.result.path"
|
|
||||||
],
|
|
||||||
"services-nextcloudserver-options-shb.nextcloud.autoDisableMaintenanceModeOnStart": [
|
"services-nextcloudserver-options-shb.nextcloud.autoDisableMaintenanceModeOnStart": [
|
||||||
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.autoDisableMaintenanceModeOnStart"
|
"services-nextcloud.html#services-nextcloudserver-options-shb.nextcloud.autoDisableMaintenanceModeOnStart"
|
||||||
],
|
],
|
||||||
|
|
@ -1835,6 +1943,21 @@
|
||||||
"services-vaultwarden-zfs": [
|
"services-vaultwarden-zfs": [
|
||||||
"services-vaultwarden.html#services-vaultwarden-zfs"
|
"services-vaultwarden.html#services-vaultwarden-zfs"
|
||||||
],
|
],
|
||||||
|
"test-failures": [
|
||||||
|
"service-implementation-guide.html#test-failures"
|
||||||
|
],
|
||||||
|
"testing-and-validation": [
|
||||||
|
"service-implementation-guide.html#testing-and-validation"
|
||||||
|
],
|
||||||
|
"understand-target-service": [
|
||||||
|
"service-implementation-guide.html#understand-target-service"
|
||||||
|
],
|
||||||
|
"update-flake-configuration": [
|
||||||
|
"service-implementation-guide.html#update-flake-configuration"
|
||||||
|
],
|
||||||
|
"update-redirects-automatically": [
|
||||||
|
"service-implementation-guide.html#update-redirects-automatically"
|
||||||
|
],
|
||||||
"usage": [
|
"usage": [
|
||||||
"usage.html#usage"
|
"usage.html#usage"
|
||||||
],
|
],
|
||||||
|
|
@ -1850,9 +1973,6 @@
|
||||||
"usage-example-nixosrebuild": [
|
"usage-example-nixosrebuild": [
|
||||||
"usage.html#usage-example-nixosrebuild"
|
"usage.html#usage-example-nixosrebuild"
|
||||||
],
|
],
|
||||||
"usage-lib": [
|
|
||||||
"usage.html#usage-lib"
|
|
||||||
],
|
|
||||||
"usage-flake": [
|
"usage-flake": [
|
||||||
"usage.html#usage-flake"
|
"usage.html#usage-flake"
|
||||||
],
|
],
|
||||||
|
|
@ -1868,7 +1988,10 @@
|
||||||
"usage-flake-tag": [
|
"usage-flake-tag": [
|
||||||
"usage.html#usage-flake-tag"
|
"usage.html#usage-flake-tag"
|
||||||
],
|
],
|
||||||
|
"usage-lib": [
|
||||||
|
"usage.html#usage-lib"
|
||||||
|
],
|
||||||
"usage-secrets": [
|
"usage-secrets": [
|
||||||
"usage.html#usage-secrets"
|
"usage.html#usage-secrets"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
611
docs/service-implementation-guide.md
Normal file
611
docs/service-implementation-guide.md
Normal file
|
|
@ -0,0 +1,611 @@
|
||||||
|
# SelfHostBlocks Service Implementation Guide {#service-implementation-guide}
|
||||||
|
|
||||||
|
This guide documents the complete process for implementing a new service in SelfHostBlocks, based on lessons learned from the nzbget implementation and analysis of existing service patterns.
|
||||||
|
|
||||||
|
**Note**: SelfHostBlocks aims to be "the smallest amount of code above what is available in nixpkgs" (see `docs/contributing.md`). Services should leverage existing nixpkgs options when possible and focus on providing contract integrations rather than reimplementing configuration.
|
||||||
|
|
||||||
|
## What Makes a "Complete" SHB Service {#complete-shb-service}
|
||||||
|
|
||||||
|
According to the project maintainer's criteria, a service is considered fully supported if it includes:
|
||||||
|
|
||||||
|
1. **SSL block integration** - HTTPS/TLS certificate management
|
||||||
|
2. **Backup block integration** - Automated backup of service data
|
||||||
|
3. **Monitoring integration** - Prometheus metrics and health checks
|
||||||
|
4. **LDAP (LLDAP) integration** - Directory-based authentication
|
||||||
|
5. **SSO (Authelia) integration** - Single sign-on authentication
|
||||||
|
6. **Comprehensive tests** - All integration variants tested
|
||||||
|
|
||||||
|
## Pre-Implementation Research {#pre-implementation-research}
|
||||||
|
|
||||||
|
### 1. Analyze Existing Services {#analyze-existing-services}
|
||||||
|
|
||||||
|
Before starting, study existing services to understand patterns:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Study service patterns
|
||||||
|
ls modules/services/ # List all services
|
||||||
|
cat modules/services/deluge.nix # Best practice example
|
||||||
|
cat modules/services/vaultwarden.nix # Another good example
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key patterns to identify:**
|
||||||
|
- Configuration structure and options
|
||||||
|
- How contracts are used (SSL, backup, monitoring, secrets)
|
||||||
|
- Authentication integration approaches
|
||||||
|
- Service-specific settings and defaults
|
||||||
|
|
||||||
|
### 2. Understand the Target Service {#understand-target-service}
|
||||||
|
|
||||||
|
Research the service you're implementing:
|
||||||
|
- **Configuration format** (YAML, INI, JSON, etc.)
|
||||||
|
- **Authentication methods** (built-in users, LDAP, OIDC/OAuth)
|
||||||
|
- **API endpoints** (for monitoring/health checks)
|
||||||
|
- **Data directories** (what needs backing up)
|
||||||
|
- **Network requirements** (ports, protocols)
|
||||||
|
- **Dependencies** (databases, external tools)
|
||||||
|
|
||||||
|
### 3. Check NixOS Integration {#check-nixos-integration}
|
||||||
|
|
||||||
|
Verify nixpkgs support:
|
||||||
|
```bash
|
||||||
|
# Check if NixOS service exists
|
||||||
|
nix eval --impure --expr '(import <nixpkgs/nixos> { configuration = {...}: {}; }).options.services' --apply 'builtins.attrNames' --json | jq -r '.[]' | grep -i servicename
|
||||||
|
# or search online: https://search.nixos.org/options?query=services.servicename
|
||||||
|
```
|
||||||
|
|
||||||
|
If no nixpkgs integration exists, you may need to:
|
||||||
|
- Package the service first
|
||||||
|
- Use containerized approach
|
||||||
|
- Request upstream nixpkgs integration
|
||||||
|
|
||||||
|
## Implementation Steps {#implementation-steps}
|
||||||
|
|
||||||
|
### 1. Create the Service Module {#create-service-module}
|
||||||
|
|
||||||
|
Location: `modules/services/servicename.nix`
|
||||||
|
|
||||||
|
**Basic structure:**
|
||||||
|
```nix
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.shb.servicename;
|
||||||
|
contracts = pkgs.callPackage ../contracts {};
|
||||||
|
shblib = pkgs.callPackage ../../lib {};
|
||||||
|
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||||
|
|
||||||
|
# Choose appropriate format based on service config
|
||||||
|
settingsFormat = pkgs.formats.yaml {}; # or .ini, .json, etc.
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.shb.servicename = {
|
||||||
|
# Core options (always required)
|
||||||
|
enable = lib.mkEnableOption "selfhostblocks.servicename";
|
||||||
|
subdomain = lib.mkOption { ... };
|
||||||
|
domain = lib.mkOption { ... };
|
||||||
|
|
||||||
|
# SSL integration (always include)
|
||||||
|
ssl = lib.mkOption {
|
||||||
|
description = "Path to SSL files";
|
||||||
|
type = lib.types.nullOr contracts.ssl.certs;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Service-specific options
|
||||||
|
port = lib.mkOption { ... };
|
||||||
|
dataDir = lib.mkOption { ... };
|
||||||
|
settings = lib.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
freeformType = settingsFormat.type;
|
||||||
|
options = {
|
||||||
|
# Define key options with descriptions
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Authentication options
|
||||||
|
authEndpoint = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
description = "OIDC endpoint for SSO";
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
ldap = lib.mkOption { ... }; # LDAP integration
|
||||||
|
users = lib.mkOption { ... }; # Local user management
|
||||||
|
|
||||||
|
# Integration options
|
||||||
|
backup = lib.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = contracts.backup.mkRequester {
|
||||||
|
user = "servicename";
|
||||||
|
sourceDirectories = [ cfg.dataDir ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
monitoring = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
# Service-specific monitoring options
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
# System options
|
||||||
|
extraServiceConfig = lib.mkOption { ... };
|
||||||
|
logLevel = lib.mkOption { ... };
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable (lib.mkMerge [
|
||||||
|
{
|
||||||
|
# Base service configuration
|
||||||
|
services.servicename = {
|
||||||
|
enable = true;
|
||||||
|
# Map SHB options to nixpkgs service options
|
||||||
|
};
|
||||||
|
|
||||||
|
# Nginx reverse proxy
|
||||||
|
shb.nginx.vhosts = [{
|
||||||
|
inherit (cfg) subdomain domain ssl;
|
||||||
|
upstream = "http://127.0.0.1:${toString cfg.port}";
|
||||||
|
|
||||||
|
# SSO integration
|
||||||
|
autheliaRules = lib.mkIf (cfg.authEndpoint != null) [
|
||||||
|
{
|
||||||
|
domain = fqdn;
|
||||||
|
policy = "bypass";
|
||||||
|
resources = [ "^/api" ]; # API endpoints
|
||||||
|
}
|
||||||
|
{
|
||||||
|
domain = fqdn;
|
||||||
|
policy = "two_factor";
|
||||||
|
resources = [ "^.*" ]; # Everything else
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}];
|
||||||
|
|
||||||
|
# User/group setup
|
||||||
|
users.users.servicename = {
|
||||||
|
extraGroups = [ "media" ]; # If needed for file access
|
||||||
|
};
|
||||||
|
|
||||||
|
# Directory permissions
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d ${cfg.dataDir} 0755 servicename servicename - -"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
# Monitoring configuration (conditional)
|
||||||
|
(lib.mkIf (cfg.monitoring != null) {
|
||||||
|
services.prometheus.scrapeConfigs = [{
|
||||||
|
job_name = "servicename";
|
||||||
|
static_configs = [{
|
||||||
|
targets = [ "127.0.0.1:${toString cfg.port}" ];
|
||||||
|
labels = {
|
||||||
|
hostname = config.networking.hostName;
|
||||||
|
domain = cfg.domain;
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
metrics_path = "/metrics"; # or appropriate endpoint
|
||||||
|
scrape_interval = "30s";
|
||||||
|
}];
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Key Implementation Considerations {#implementation-considerations}
|
||||||
|
|
||||||
|
#### Configuration Management {#configuration-management}
|
||||||
|
- **Use freeform settings** when possible: `freeformType = settingsFormat.type`
|
||||||
|
- **Provide sensible defaults** for common options
|
||||||
|
- **Use lib.mkDefault** for user-overridable settings
|
||||||
|
- **Use lib.mkForce** for security-critical settings
|
||||||
|
|
||||||
|
#### Authentication Integration {#authentication-integration}
|
||||||
|
- **SSO (Authelia)**: Use `autheliaRules` with appropriate bypass policies
|
||||||
|
- **LDAP**: Follow the patterns from existing services
|
||||||
|
- **Local users**: Use SHB secret contracts for password management
|
||||||
|
|
||||||
|
#### Security Best Practices {#security-best-practices}
|
||||||
|
- **Bind to localhost**: Services should listen on `127.0.0.1` only
|
||||||
|
- **Use nginx for TLS**: Don't configure TLS in the service itself
|
||||||
|
- **Proper file permissions**: Use systemd.tmpfiles.rules
|
||||||
|
- **Secret management**: Always use SHB secret contracts
|
||||||
|
|
||||||
|
### 3. Monitoring Implementation {#monitoring-implementation}
|
||||||
|
|
||||||
|
Choose the appropriate monitoring approach:
|
||||||
|
|
||||||
|
#### Option A: Native Prometheus Metrics {#native-prometheus-metrics}
|
||||||
|
If the service supports Prometheus natively:
|
||||||
|
```nix
|
||||||
|
services.prometheus.scrapeConfigs = [{
|
||||||
|
job_name = "servicename";
|
||||||
|
static_configs = [{ targets = [ "127.0.0.1:${toString cfg.port}" ]; }];
|
||||||
|
metrics_path = "/metrics";
|
||||||
|
}];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: API Health Check {#api-health-check}
|
||||||
|
If no native metrics, monitor API endpoints:
|
||||||
|
```nix
|
||||||
|
services.prometheus.scrapeConfigs = [{
|
||||||
|
job_name = "servicename";
|
||||||
|
static_configs = [{ targets = [ "127.0.0.1:${toString cfg.port}" ]; }];
|
||||||
|
metrics_path = "/api/status"; # or appropriate endpoint
|
||||||
|
}];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option C: External Exporter {#external-exporter}
|
||||||
|
For services requiring dedicated exporters (like Deluge):
|
||||||
|
```nix
|
||||||
|
services.prometheus.exporters.servicename = {
|
||||||
|
enable = true;
|
||||||
|
# exporter-specific configuration
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Create Comprehensive Tests {#create-comprehensive-tests}
|
||||||
|
|
||||||
|
Location: `test/services/servicename.nix`
|
||||||
|
|
||||||
|
**Test structure:**
|
||||||
|
```nix
|
||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
testLib = pkgs.callPackage ../common.nix {};
|
||||||
|
|
||||||
|
# Common test scripts
|
||||||
|
commonTestScript = testLib.mkScripts {
|
||||||
|
hasSSL = { node, ... }: !(isNull node.config.shb.servicename.ssl);
|
||||||
|
waitForServices = { ... }: [ "nginx.service" "servicename.service" ];
|
||||||
|
waitForPorts = { node, ... }: [ node.config.services.servicename.port ];
|
||||||
|
|
||||||
|
# Service-specific connectivity test
|
||||||
|
extraScript = { node, proto_fqdn, ... }: ''
|
||||||
|
with subtest("service connectivity"):
|
||||||
|
response = curl(client, "", "${proto_fqdn}/api/health")
|
||||||
|
# Add service-specific checks
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Monitoring test script
|
||||||
|
prometheusTestScript = { nodes, ... }: ''
|
||||||
|
server.wait_for_open_port(${toString nodes.server.config.services.servicename.port})
|
||||||
|
with subtest("prometheus monitoring"):
|
||||||
|
# Test the actual monitoring endpoint
|
||||||
|
response = server.succeed("curl -sSf http://localhost:${port}/metrics")
|
||||||
|
# Validate response format
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Base configuration
|
||||||
|
basic = { config, ... }: {
|
||||||
|
imports = [
|
||||||
|
testLib.baseModule
|
||||||
|
../../modules/services/servicename.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
shb.servicename = {
|
||||||
|
enable = true;
|
||||||
|
inherit (config.test) domain subdomain;
|
||||||
|
# Basic configuration
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
in {
|
||||||
|
# Test variants (all 6 required)
|
||||||
|
basic = pkgs.testers.runNixOSTest { ... };
|
||||||
|
backup = pkgs.testers.runNixOSTest { ... };
|
||||||
|
https = pkgs.testers.runNixOSTest { ... };
|
||||||
|
ldap = pkgs.testers.runNixOSTest { ... };
|
||||||
|
monitoring = pkgs.testers.runNixOSTest { ... };
|
||||||
|
sso = pkgs.testers.runNixOSTest { ... };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Required Test Variants {#required-test-variants}
|
||||||
|
|
||||||
|
1. **basic**: Core functionality without authentication
|
||||||
|
2. **backup**: Tests backup integration
|
||||||
|
3. **https**: Tests SSL/TLS integration
|
||||||
|
4. **ldap**: Tests LDAP authentication
|
||||||
|
5. **monitoring**: Tests Prometheus integration
|
||||||
|
6. **sso**: Tests Authelia SSO integration
|
||||||
|
|
||||||
|
### 5. Update Flake Configuration {#update-flake-configuration}
|
||||||
|
|
||||||
|
Add to `flake.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
allModules = [
|
||||||
|
# ... existing modules
|
||||||
|
modules/services/servicename.nix
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
```nix
|
||||||
|
checks = {
|
||||||
|
# ... existing checks
|
||||||
|
// (vm_test "servicename" ./test/services/servicename.nix)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Create Service Documentation {#create-service-documentation}
|
||||||
|
|
||||||
|
Create comprehensive documentation for the new service:
|
||||||
|
|
||||||
|
**Location**: `modules/services/servicename/docs/default.md`
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# ServiceName Service {\#services-servicename}
|
||||||
|
|
||||||
|
Brief description of what the service does.
|
||||||
|
|
||||||
|
## Features {\#services-servicename-features}
|
||||||
|
|
||||||
|
- Feature 1
|
||||||
|
- Feature 2
|
||||||
|
|
||||||
|
## Usage {\#services-servicename-usage}
|
||||||
|
|
||||||
|
### Basic Configuration {\#services-servicename-basic}
|
||||||
|
|
||||||
|
shb.servicename = {
|
||||||
|
enable = true;
|
||||||
|
domain = "example.com";
|
||||||
|
subdomain = "servicename";
|
||||||
|
};
|
||||||
|
|
||||||
|
### SSL Configuration {\#services-servicename-ssl}
|
||||||
|
|
||||||
|
shb.servicename.ssl.paths = {
|
||||||
|
cert = /path/to/cert;
|
||||||
|
key = /path/to/key;
|
||||||
|
};
|
||||||
|
|
||||||
|
## Options Reference {\#services-servicename-options}
|
||||||
|
|
||||||
|
{=include=} options
|
||||||
|
id-prefix: services-servicename-options-
|
||||||
|
list-id: selfhostblocks-servicename-options
|
||||||
|
source: @OPTIONS_JSON@
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: Use consistent heading ID patterns:
|
||||||
|
- Service overview: `{\#services-servicename}`
|
||||||
|
- Features: `{\#services-servicename-features}`
|
||||||
|
- Usage sections: `{\#services-servicename-basic}`, `{\#services-servicename-ssl}`, etc.
|
||||||
|
- Options: `{\#services-servicename-options}`
|
||||||
|
|
||||||
|
Note: Replace `servicename` with your actual service name (e.g., `nzbget`, `jellyfin`).
|
||||||
|
|
||||||
|
### 7. Update Redirects Automatically {#update-redirects-automatically}
|
||||||
|
|
||||||
|
After creating documentation, generate the required redirects:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Scan documentation and add missing redirects
|
||||||
|
nix run .#update-redirects
|
||||||
|
|
||||||
|
# Review the changes
|
||||||
|
git diff docs/redirects.json
|
||||||
|
|
||||||
|
# The tool will show what redirects were added
|
||||||
|
```
|
||||||
|
|
||||||
|
The automation will:
|
||||||
|
- Find all heading IDs in your documentation
|
||||||
|
- Generate appropriate redirect entries
|
||||||
|
- Add them to `docs/redirects.json`
|
||||||
|
- Follow established naming patterns
|
||||||
|
|
||||||
|
### 8. Handle Unfree Dependencies {#handle-unfree-dependencies}
|
||||||
|
|
||||||
|
If the service requires unfree packages:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# In flake.nix
|
||||||
|
config = {
|
||||||
|
allowUnfree = true;
|
||||||
|
permittedInsecurePackages = [
|
||||||
|
# List any required insecure packages
|
||||||
|
];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Update CI workflow if needed:
|
||||||
|
```yaml
|
||||||
|
# In .github/workflows/build.yaml
|
||||||
|
- name: Setup Nix
|
||||||
|
uses: cachix/install-nix-action@v31
|
||||||
|
with:
|
||||||
|
extra_nix_config: |
|
||||||
|
allow-unfree = true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing and Validation {#testing-and-validation}
|
||||||
|
|
||||||
|
### Local Testing {#local-testing}
|
||||||
|
```bash
|
||||||
|
# Test redirect automation
|
||||||
|
nix run .#update-redirects
|
||||||
|
|
||||||
|
# Test all service variants (replace ${system} with your system, e.g., x86_64-linux)
|
||||||
|
nix build .#checks.${system}.vm_servicename_basic
|
||||||
|
nix build .#checks.${system}.vm_servicename_backup
|
||||||
|
nix build .#checks.${system}.vm_servicename_https
|
||||||
|
nix build .#checks.${system}.vm_servicename_ldap
|
||||||
|
nix build .#checks.${system}.vm_servicename_monitoring
|
||||||
|
nix build .#checks.${system}.vm_servicename_sso
|
||||||
|
|
||||||
|
# Or run all tests (as recommended in docs/contributing.md)
|
||||||
|
nix flake check
|
||||||
|
|
||||||
|
# For interactive testing and debugging, see docs/contributing.md:
|
||||||
|
# nix run .#checks.${system}.vm_servicename_basic.driverInteractive
|
||||||
|
|
||||||
|
# Test documentation build (includes redirect validation)
|
||||||
|
nix build .#manualHtml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Iterative Development Approach {#iterative-development-approach}
|
||||||
|
|
||||||
|
1. **Start with basic functionality** - get core service working
|
||||||
|
2. **Add SSL integration** - enable HTTPS
|
||||||
|
3. **Add backup integration** - ensure data protection
|
||||||
|
4. **Add monitoring** - implement health checks
|
||||||
|
5. **Add authentication** - LDAP and SSO integration
|
||||||
|
6. **Create documentation** - write service documentation with heading IDs
|
||||||
|
7. **Update redirects** - run `nix run .#update-redirects` to generate redirects
|
||||||
|
8. **Comprehensive testing** - all 6 test variants
|
||||||
|
9. **Final validation** - ensure documentation builds correctly
|
||||||
|
|
||||||
|
## Common Pitfalls and Solutions {#common-pitfalls-and-solutions}
|
||||||
|
|
||||||
|
### Configuration Issues {#configuration-issues}
|
||||||
|
- **Problem**: Service doesn't start due to config validation
|
||||||
|
- **Solution**: Use `lib.mkDefault` for user settings, `lib.mkForce` for security settings
|
||||||
|
|
||||||
|
### Authentication Integration {#authentication-integration-pitfalls}
|
||||||
|
- **Problem**: SSO redirect loops or access denied
|
||||||
|
- **Solution**: Check `autheliaRules` bypass patterns for API endpoints
|
||||||
|
|
||||||
|
### Monitoring Failures {#monitoring-failures}
|
||||||
|
- **Problem**: Prometheus scraping fails with 404
|
||||||
|
- **Solution**: Verify the actual API endpoints the service provides
|
||||||
|
|
||||||
|
### Test Failures {#test-failures}
|
||||||
|
- **Problem**: VM tests timeout or fail connectivity
|
||||||
|
- **Solution**: Check `waitForServices` and `waitForPorts` configurations
|
||||||
|
|
||||||
|
### Nixpkgs Integration {#nixpkgs-integration}
|
||||||
|
- **Problem**: Service options don't match SHB needs
|
||||||
|
- **Solution**: Map SHB options to nixpkgs options, use `extraConfig` for overrides
|
||||||
|
|
||||||
|
## Best Practices Summary {#best-practices-summary}
|
||||||
|
|
||||||
|
1. **Follow existing patterns** - study deluge.nix and vaultwarden.nix
|
||||||
|
2. **Use freeform configuration** - maximum flexibility with typed key options
|
||||||
|
3. **Implement all contracts** - SSL, backup, monitoring, secrets
|
||||||
|
4. **Test comprehensively** - all 6 integration variants
|
||||||
|
5. **Security first** - localhost binding, proper permissions, secret management
|
||||||
|
6. **Document thoroughly** - clear descriptions for all options
|
||||||
|
7. **Iterative development** - build complexity gradually
|
||||||
|
8. **CI/CD validation** - ensure all tests pass before submission
|
||||||
|
|
||||||
|
## Redirect Management {#redirect-management}
|
||||||
|
|
||||||
|
SelfHostBlocks uses `nixos-render-docs` for documentation generation, which includes built-in redirect validation. The `docs/redirects.json` file maps documentation identifiers to their target URLs.
|
||||||
|
|
||||||
|
### Automated Redirect Generation {#automated-redirect-generation}
|
||||||
|
|
||||||
|
SelfHostBlocks includes an automated redirect management tool that leverages the official `nixos-render-docs` ecosystem:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate fresh redirects from HTML documentation
|
||||||
|
nix run .#update-redirects
|
||||||
|
```
|
||||||
|
|
||||||
|
This tool:
|
||||||
|
- **Generates HTML documentation** using `nixos-render-docs` with redirect collection enabled
|
||||||
|
- **Scans actual HTML files** for anchor IDs to ensure perfect accuracy
|
||||||
|
- **Creates fresh redirects** from scratch by mapping anchors to their real file locations
|
||||||
|
- **Filters system-generated anchors** (excludes `opt-*` and `selfhostblock*` entries)
|
||||||
|
- **Provides interactive confirmation** before updating `docs/redirects.json`
|
||||||
|
|
||||||
|
### How Redirects Work {#how-redirects-work}
|
||||||
|
|
||||||
|
1. **nixos-render-docs validation**: During documentation builds, `nixos-render-docs` automatically validates that all heading IDs have corresponding redirect entries
|
||||||
|
2. **Automated maintenance**: The `update-redirects` tool automatically maintains `redirects.json` by:
|
||||||
|
- Building HTML documentation with patched `nixos-render-docs`
|
||||||
|
- Scanning generated HTML files for actual anchor IDs and their file locations
|
||||||
|
- Creating accurate redirect mappings without guesswork or pattern matching
|
||||||
|
3. **Manual override**: You can still manually edit `docs/redirects.json` for special cases
|
||||||
|
|
||||||
|
### Redirect Patterns {#redirect-patterns}
|
||||||
|
|
||||||
|
The automation follows these patterns when mapping headings to redirect targets:
|
||||||
|
|
||||||
|
| Heading ID | Source File | Redirect Target |
|
||||||
|
|------------|-------------|-----------------|
|
||||||
|
| `services-nzbget-basic` | `modules/services/nzbget/docs/default.md` | `["services-nzbget.html#services-nzbget-basic"]` |
|
||||||
|
| `blocks-monitoring` | `modules/blocks/monitoring/docs/default.md` | `["blocks-monitoring.html#blocks-monitoring"]` |
|
||||||
|
| `demo-nextcloud` | `demo/nextcloud/README.md` | `["demo-nextcloud.html#demo-nextcloud"]` |
|
||||||
|
| `contracts` | `docs/contracts.md` | `["contracts.html#contracts"]` |
|
||||||
|
|
||||||
|
Note: Redirects always include the anchor link (`#heading-id`) to jump to the specific heading within the target page.
|
||||||
|
|
||||||
|
### Adding New Service Documentation {#adding-new-service-documentation}
|
||||||
|
|
||||||
|
When implementing a new service, the redirect workflow is now automated:
|
||||||
|
|
||||||
|
1. **Write documentation** with heading IDs:
|
||||||
|
```markdown
|
||||||
|
# NewService {\#services-newservice}
|
||||||
|
## Basic Configuration {\#services-newservice-basic}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Update redirects automatically**:
|
||||||
|
```bash
|
||||||
|
nix run .#update-redirects
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Review and commit** the changes:
|
||||||
|
```bash
|
||||||
|
git add docs/redirects.json modules/services/newservice/docs/default.md
|
||||||
|
git commit -m "Add newservice documentation"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build-time Validation {#build-time-validation}
|
||||||
|
|
||||||
|
The documentation build process will fail if:
|
||||||
|
- Any documentation heading ID lacks a corresponding redirect entry
|
||||||
|
- Redirect targets point to non-existent content
|
||||||
|
- There are formatting errors in the redirects file
|
||||||
|
|
||||||
|
This ensures documentation links remain functional when content is moved or reorganized.
|
||||||
|
|
||||||
|
## Resources {#resources}
|
||||||
|
|
||||||
|
- **Contributing guide**: `docs/contributing.md` for authoritative development workflows and testing procedures
|
||||||
|
- **Existing services**: `modules/services/` for patterns and implementation examples
|
||||||
|
- **Contracts documentation**: `modules/contracts/` for understanding integration interfaces
|
||||||
|
- **Test framework**: `test/common.nix` for testing utilities and patterns
|
||||||
|
- **NixOS options**: https://search.nixos.org/options for upstream service options
|
||||||
|
- **SHB documentation**: Generated docs showing existing service patterns
|
||||||
|
- **Redirect automation**: `nix run .#update-redirects` for automated redirect management
|
||||||
|
- **nixos-render-docs**: Built-in redirect validation and documentation generation
|
||||||
|
|
||||||
|
## Quick Reference {#quick-reference}
|
||||||
|
|
||||||
|
### Complete Workflow {#complete-workflow}
|
||||||
|
```bash
|
||||||
|
# 1. Implement service module
|
||||||
|
vim modules/services/SERVICENAME.nix
|
||||||
|
|
||||||
|
# 2. Create tests
|
||||||
|
vim test/services/SERVICENAME.nix
|
||||||
|
|
||||||
|
# 3. Update flake
|
||||||
|
vim flake.nix # Add to allModules and checks
|
||||||
|
|
||||||
|
# 4. Write documentation
|
||||||
|
vim modules/services/SERVICENAME/docs/default.md
|
||||||
|
|
||||||
|
# 5. Generate redirects
|
||||||
|
nix run .#update-redirects
|
||||||
|
|
||||||
|
# 6. Test everything
|
||||||
|
nix flake check # Run all tests (recommended)
|
||||||
|
# Or test specific variants:
|
||||||
|
# nix build .#checks.${system}.vm_SERVICENAME_basic
|
||||||
|
nix build .#manualHtml
|
||||||
|
|
||||||
|
# 7. Commit changes
|
||||||
|
git add .
|
||||||
|
git commit -m "Add SERVICENAME with full integration"
|
||||||
|
```
|
||||||
|
|
||||||
|
This guide provides a complete roadmap for implementing production-ready SelfHostBlocks services that meet the project's quality standards.
|
||||||
78
flake.nix
78
flake.nix
|
|
@ -104,6 +104,58 @@
|
||||||
release = builtins.readFile ./VERSION;
|
release = builtins.readFile ./VERSION;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Documentation redirect generation tool - scans HTML files for anchor mappings
|
||||||
|
packages.generateRedirects =
|
||||||
|
let
|
||||||
|
# Python patch to inject redirect collector
|
||||||
|
pythonPatch = pkgs.writeText "nixos-render-docs-patch.py" ''
|
||||||
|
# Load redirect collector patch
|
||||||
|
try:
|
||||||
|
import sys, os
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__) + '/..')
|
||||||
|
import missing_refs_collector
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Failed to load redirect collector: {e}", file=sys.stderr)
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Patched nixos-render-docs that collects redirects during HTML generation
|
||||||
|
nixos-render-docs-patched = pkgs.writeShellApplication {
|
||||||
|
name = "nixos-render-docs";
|
||||||
|
runtimeInputs = [ pkgs.nixos-render-docs ];
|
||||||
|
text = ''
|
||||||
|
TEMP_DIR=$(mktemp -d); trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||||
|
|
||||||
|
cp -r ${pkgs.nixos-render-docs}/${pkgs.python3.sitePackages}/nixos_render_docs "$TEMP_DIR/"
|
||||||
|
chmod -R +w "$TEMP_DIR"
|
||||||
|
cp ${./docs/generate-redirects-nixos-render-docs.py} "$TEMP_DIR/missing_refs_collector.py"
|
||||||
|
echo '{}' > "$TEMP_DIR/empty_redirects.json"
|
||||||
|
cat ${pythonPatch} >> "$TEMP_DIR/nixos_render_docs/__init__.py"
|
||||||
|
|
||||||
|
ARGS=()
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--redirects) ARGS+=("$1" "$TEMP_DIR/empty_redirects.json"); shift 2 ;;
|
||||||
|
*) ARGS+=("$1"); shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
export PYTHONPATH="$TEMP_DIR:''${PYTHONPATH:-}"
|
||||||
|
nixos-render-docs "''${ARGS[@]}"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
(pkgs.callPackage ./docs {
|
||||||
|
inherit nmdsrc;
|
||||||
|
allModules = allModules ++ contractDummyModules;
|
||||||
|
release = builtins.readFile ./VERSION;
|
||||||
|
nixos-render-docs = nixos-render-docs-patched;
|
||||||
|
}).overrideAttrs (old: {
|
||||||
|
installPhase = ''
|
||||||
|
${old.installPhase}
|
||||||
|
ln -sf share/doc/selfhostblocks/redirects.json $out/redirects.json
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
|
||||||
lib =
|
lib =
|
||||||
(pkgs.callPackage ./lib {})
|
(pkgs.callPackage ./lib {})
|
||||||
// {
|
// {
|
||||||
|
|
@ -193,6 +245,32 @@
|
||||||
--set PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS true
|
--set PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS true
|
||||||
'';
|
'';
|
||||||
}) {};
|
}) {};
|
||||||
|
|
||||||
|
# Run "nix run .#update-redirects" to regenerate docs/redirects.json
|
||||||
|
apps.update-redirects = {
|
||||||
|
type = "app";
|
||||||
|
program = "${pkgs.writeShellApplication {
|
||||||
|
name = "update-redirects";
|
||||||
|
runtimeInputs = [ pkgs.nix pkgs.jq ];
|
||||||
|
text = ''
|
||||||
|
echo "=== SelfHostBlocks Redirects Updater ==="
|
||||||
|
echo "Generating fresh ./docs/redirects.json..."
|
||||||
|
|
||||||
|
nix build .#generateRedirects || { echo "Error: Failed to generate redirects" >&2; exit 1; }
|
||||||
|
[[ -f result/redirects.json ]] || { echo "Error: Generated redirects file not found" >&2; exit 1; }
|
||||||
|
|
||||||
|
echo "Generated $(jq 'keys | length' result/redirects.json) redirects"
|
||||||
|
echo
|
||||||
|
read -p "Update docs/redirects.json? This will backup the current file [y/N] " -r response
|
||||||
|
[[ "$response" =~ ^[Yy] ]] || { echo "Aborted - no changes made"; exit 0; }
|
||||||
|
|
||||||
|
[[ -f docs/redirects.json ]] && cp docs/redirects.json docs/redirects.json.backup && echo "Created backup"
|
||||||
|
cp result/redirects.json docs/redirects.json
|
||||||
|
echo " Updated docs/redirects.json"
|
||||||
|
echo "To verify: nix build .#manualHtml"
|
||||||
|
'';
|
||||||
|
}}/bin/update-redirects";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
) // {
|
) // {
|
||||||
herculesCI.ciSystems = [ "x86_64-linux" ];
|
herculesCI.ciSystems = [ "x86_64-linux" ];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue