diff --git a/Taskfile.yaml b/Taskfile.yaml index f6123738..19b9d820 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -56,10 +56,17 @@ tasks: build: desc: Run all linting and build tasks cmds: - - task: docker:update-example-env - - task: docs-deploy - - task: pre-commit-update - task: lint + - task: docker:update-example-env + - task: docs:build + + prepare-to-push: + desc: Prepare branch for push - lint, build, rebase + aliases: [prep] + cmds: + - clear + - task: build + - task: git-rebase-fixup pre-commit-update: desc: Update pre-commit repositories diff --git a/docker/ai/autogenstudio.yaml b/docker/ai/autogenstudio.yaml index ca1d383b..20196e65 100644 --- a/docker/ai/autogenstudio.yaml +++ b/docker/ai/autogenstudio.yaml @@ -1,11 +1,12 @@ # AutoGen Studio is an AutoGen-powered AI app (user interface) to help you rapidly prototype AI agents, enhance them with skills, compose them into workflows and interact with them to accomplish tasks. # It is built on top of the AutoGen framework, which is a toolkit for building AI agents. # -# https://microsoft.github.io/autogen/blog/2023/12/01/AutoGenStudio/ -# https://github.com/microsoft/autogen/tree/main/samples/apps/autogen-studio -# https://pypi.org/project/autogenstudio/ -# https://microsoft.github.io/autogen/ -# https://www.reddit.com/r/AutoGenAI/ +# Links: +# - Home: https://microsoft.github.io/autogen/ +# - https://microsoft.github.io/autogen/blog/2023/12/01/AutoGenStudio/ +# - https://github.com/microsoft/autogen/tree/main/samples/apps/autogen-studio +# - https://pypi.org/project/autogenstudio/ +# - https://www.reddit.com/r/AutoGenAI/ --- name: autogenstudio services: diff --git a/docker/ai/litellm.yaml b/docker/ai/litellm.yaml index ab70dc60..c9794669 100644 --- a/docker/ai/litellm.yaml +++ b/docker/ai/litellm.yaml @@ -1,8 +1,10 @@ # Python SDK, Proxy Server (LLM Gateway) to call 100+ LLM APIs in OpenAI format - [Bedrock, Azure, OpenAI, VertexAI, Cohere, Anthropic, Sagemaker, HuggingFace, Replicate, Groq] # -# https://github.com/BerriAI/litellm -# https://docs.litellm.ai/docs/proxy/deploy -# https://docs.litellm.ai/docs/proxy/docker_quick_start +# Links: +# - Home: https://docs.litellm.ai/ +# - Source: https://github.com/BerriAI/litellm +# - Proxy Deploy: https://docs.litellm.ai/docs/proxy/deploy +# - Docker Quick Start: https://docs.litellm.ai/docs/proxy/docker_quick_start --- name: litellm services: diff --git a/docker/ai/ollama.yaml b/docker/ai/ollama.yaml index db0fe459..5837a8ef 100644 --- a/docker/ai/ollama.yaml +++ b/docker/ai/ollama.yaml @@ -1,6 +1,8 @@ -# Get up and running with Llama 3.2, Mistral, Gemma 2, and other large language models. +# Get up and running with Llama 3.2, Mistral, Gemma 2, and other large language models. # -# https://github.com/ollama/ollama +# Links: +# - Home: https://ollama.com/ +# - Source: https://github.com/ollama/ollama --- name: ollama services: diff --git a/docker/ai/open-webui-pipelines.yaml b/docker/ai/open-webui-pipelines.yaml index 6a5320cc..f16a6d95 100644 --- a/docker/ai/open-webui-pipelines.yaml +++ b/docker/ai/open-webui-pipelines.yaml @@ -3,8 +3,9 @@ # > Welcome to Pipelines, an Open WebUI initiative. Pipelines bring modular, customizable workflows to any UI client supporting OpenAI API specs – and much more! # > Easily extend functionalities, integrate unique logic, and create dynamic workflows with just a few lines of code. # -# https://docs.openwebui.com/pipelines/ -# https://github.com/open-webui/pipelines +# Links: +# - Home: https://docs.openwebui.com/pipelines/ +# - Source: https://github.com/open-webui/pipelines --- name: open-webui-pipelines services: diff --git a/docker/ai/open-webui.yaml b/docker/ai/open-webui.yaml index 3cccd76f..8fa4e43c 100644 --- a/docker/ai/open-webui.yaml +++ b/docker/ai/open-webui.yaml @@ -1,8 +1,9 @@ -# Open WebUI is an extensible, feature-rich, and user-friendly self-hosted WebUI for various LLM runners, supported LLM runners include Ollama and OpenAI-compatible APIs. +# Open WebUI is an extensible, feature-rich, and user-friendly self-hosted WebUI for various LLM runners, supported LLM runners include Ollama and OpenAI-compatible APIs. # -# https://openwebui.com/ -# https://github.com/open-webui/open-webui/ -# https://docs.openwebui.com/getting-started/ +# Links: +# - Home: https://openwebui.com/ +# - Source: https://github.com/open-webui/open-webui/ +# - https://docs.openwebui.com/getting-started/ # # Compose file based on: https://github.com/open-webui/open-webui/blob/main/docker-compose.yaml --- diff --git a/docker/ai/qdrant.yaml b/docker/ai/qdrant.yaml index fab7a587..d6a34a8a 100644 --- a/docker/ai/qdrant.yaml +++ b/docker/ai/qdrant.yaml @@ -4,8 +4,9 @@ # To follow the tutorial, remove the `dashboard/` from the requests' URLs. Use e.g. F12 Developer Tools -> Edit and Resend feature. # Issue: https://github.com/qdrant/qdrant-web-ui/issues/252 # -# https://qdrant.tech/ -# https://github.com/qdrant/qdrant +# Links: +# - Home: https://qdrant.tech/ +# - Source: https://github.com/qdrant/qdrant --- name: qdrant services: diff --git a/docker/ai/sillytavern.yaml b/docker/ai/sillytavern.yaml index 1611297a..a1f47bb8 100644 --- a/docker/ai/sillytavern.yaml +++ b/docker/ai/sillytavern.yaml @@ -1,11 +1,12 @@ # LLM Frontend for Power Users. # -# Source: https://github.com/SillyTavern/SillyTavern -# Image: https://github.com/SillyTavern/SillyTavern/pkgs/container/sillytavern -# Docker setup: https://docs.sillytavern.app/installation/docker/ -# Docker Compose: https://github.com/SillyTavern/SillyTavern/blob/release/docker/docker-compose.yml -# -# Character cards: https://chub.ai/ +# Links: +# - Home: https://docs.sillytavern.app/ +# - Source: https://github.com/SillyTavern/SillyTavern +# - Image: https://github.com/SillyTavern/SillyTavern/pkgs/container/sillytavern +# - Docker setup: https://docs.sillytavern.app/installation/docker/ +# - Docker Compose: https://github.com/SillyTavern/SillyTavern/blob/release/docker/docker-compose.yml +# - Character cards: https://chub.ai/ --- name: sillytavern services: diff --git a/docker/arr/bazarr.yaml b/docker/arr/bazarr.yaml index 89a339e0..16e5fc1a 100644 --- a/docker/arr/bazarr.yaml +++ b/docker/arr/bazarr.yaml @@ -1,8 +1,9 @@ # Bazarr is a companion application to Sonarr and Radarr. It can manage and download subtitles based on your requirements. # You define your preferences by TV show or movie and Bazarr takes care of everything for you. # -# 🏠 Home: https://www.bazarr.media/ -# πŸ“¦ Image: https://hub.docker.com/r/linuxserver/bazarr +# Links: +# - Home: https://www.bazarr.media/ +# - Image: https://hub.docker.com/r/linuxserver/bazarr --- name: bazarr services: diff --git a/docker/arr/flaresolverr.yaml b/docker/arr/flaresolverr.yaml index 410d0cb8..4ca85f54 100644 --- a/docker/arr/flaresolverr.yaml +++ b/docker/arr/flaresolverr.yaml @@ -1,7 +1,8 @@ # FlareSolverr is a proxy server to bypass Cloudflare protection # -# πŸ“¦ Image: https://hub.docker.com/r/flaresolverr/flaresolverr -# πŸ“œ Source: https://github.com/FlareSolverr/FlareSolverr +# Links: +# - Home: https://github.com/FlareSolverr/FlareSolverr +# - Image: https://hub.docker.com/r/flaresolverr/flaresolverr --- name: flaresolverr services: diff --git a/docker/arr/jellyseerr.yaml b/docker/arr/jellyseerr.yaml index e9cdc158..c731af6d 100644 --- a/docker/arr/jellyseerr.yaml +++ b/docker/arr/jellyseerr.yaml @@ -1,8 +1,9 @@ # Jellyseerr is a free and open source software application for managing requests for your media library. # It is a a fork of Overseerr built to bring support for Jellyfin & Emby media servers! # -# πŸ“œ Source: https://github.com/Fallenbagel/jellyseerr -# πŸ“¦ Image: https://hub.docker.com/r/fallenbagel/jellyseerr +# Links: +# - Home: https://github.com/Fallenbagel/jellyseerr +# - Image: https://hub.docker.com/r/fallenbagel/jellyseerr --- name: jellyseerr services: diff --git a/docker/arr/prowlarr.yaml b/docker/arr/prowlarr.yaml index bd147424..90016dae 100644 --- a/docker/arr/prowlarr.yaml +++ b/docker/arr/prowlarr.yaml @@ -2,8 +2,10 @@ # Prowlarr supports both Torrent Trackers and Usenet Indexers. It integrates seamlessly with Sonarr, Radarr, Lidarr, and Readarr offering complete management # of your indexers with no per app Indexer setup required (we do it all). # -# πŸ“¦ Image: https://hub.docker.com/r/linuxserver/prowlarr -# πŸ“œ Source: https://github.com/Prowlarr/Prowlarr +# Links: +# - Home: https://prowlarr.com/ +# - Image: https://hub.docker.com/r/linuxserver/prowlarr +# - Source: https://github.com/Prowlarr/Prowlarr --- name: prowlarr services: diff --git a/docker/arr/radarr.yaml b/docker/arr/radarr.yaml index b159928d..dafd8705 100644 --- a/docker/arr/radarr.yaml +++ b/docker/arr/radarr.yaml @@ -1,7 +1,9 @@ # Radarr - A fork of Sonarr to work with movies Γ  la Couchpotato. # -# πŸ“¦ Image: https://hub.docker.com/r/linuxserver/radarr -# πŸ“œ Source: https://github.com/Radarr/Radarr +# Links: +# - Home: https://radarr.video/ +# - Image: https://hub.docker.com/r/linuxserver/radarr +# - Source: https://github.com/Radarr/Radarr --- name: radarr services: diff --git a/docker/arr/readarr.yaml b/docker/arr/readarr.yaml index 014dc6a9..934e5834 100644 --- a/docker/arr/readarr.yaml +++ b/docker/arr/readarr.yaml @@ -2,8 +2,14 @@ # # Quick start guide: https://wiki.servarr.com/readarr/quick-start-guide # -# πŸ“œ Source: https://github.com/Readarr/Readarr -# πŸ“¦ Image: https://docs.linuxserver.io/images/docker-readarr/ +# Links: +# - Home: https://readarr.com/ +# - Source: https://github.com/Readarr/Readarr +# - Image: https://docs.linuxserver.io/images/docker-readarr/ +# +# TODO: Replace this service +# > **Announcement: Retirement of Readarr** +# > We would like to announce that the Readarr project has been retired. This difficult decision was made due to a combination of factors: the project's metadata has become unusable, we no longer have the time to remake or repair it, and the community effort to transition to using Open Library as the source has stalled without much progress. --- name: readarr services: diff --git a/docker/arr/sonarr.yaml b/docker/arr/sonarr.yaml index 55bb9372..6ee33892 100644 --- a/docker/arr/sonarr.yaml +++ b/docker/arr/sonarr.yaml @@ -1,8 +1,9 @@ # Sonarr (formerly NZBdrone) is a PVR for usenet and bittorrent users. It can monitor multiple RSS feeds for new episodes of your favorite shows and will grab sort and rename them. # It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available. # -# 🏠 Home: https://sonarr.tv/ -# πŸ“¦ Image: https://hub.docker.com/r/linuxserver/sonarr +# Links: +# - Home: https://sonarr.tv/ +# - Image: https://hub.docker.com/r/linuxserver/sonarr --- name: sonarr services: diff --git a/docker/automation/esphome-device-builder.yaml b/docker/automation/esphome-device-builder.yaml index 71f0d601..8ce267e2 100644 --- a/docker/automation/esphome-device-builder.yaml +++ b/docker/automation/esphome-device-builder.yaml @@ -1,10 +1,11 @@ # ESPHome Device Builder is a tool for creating custom firmware for ESP8266/ESP32 microcontrollers # through simple YAML configuration files and managing them through Home Automation systems like Home Assistant. # -# 🏠 Home: https://esphome.io -# πŸ“¦ Source: https://github.com/esphome/esphome -# πŸ“œ Docs: https://esphome.io/guides/getting_started_command_line/#bonus-esphome-device-builder -# Docker: https://hub.docker.com/r/esphome/esphome +# Links: +# - Home: https://esphome.io +# - Source: https://github.com/esphome/esphome +# - Docs: https://esphome.io/guides/getting_started_command_line/#bonus-esphome-device-builder +# - Image: https://hub.docker.com/r/esphome/esphome # # TODO: Consider implementing non-privileged mode with specific device permissions for improved security # TODO: Set up udev rules for automatic USB device discovery diff --git a/docker/automation/homeassistant.yaml b/docker/automation/homeassistant.yaml index 7d95b96d..80a28ba7 100644 --- a/docker/automation/homeassistant.yaml +++ b/docker/automation/homeassistant.yaml @@ -9,8 +9,10 @@ # - 172.18.0.0/24 # ``` # -# 🏠 Home: https://www.home-assistant.io/ -# πŸ“¦ Image: https://hub.docker.com/r/linuxserver/homeassistant +# Links: +# - https://www.home-assistant.io/ +# - https://hub.docker.com/r/linuxserver/homeassistant +# - https://www.home-assistant.io/integrations/http/ --- name: homeassistant services: diff --git a/docker/automation/mosquitto.yaml b/docker/automation/mosquitto.yaml index 1a45d4b9..d866d712 100644 --- a/docker/automation/mosquitto.yaml +++ b/docker/automation/mosquitto.yaml @@ -2,10 +2,11 @@ # It is lightweight and suitable for devices from single board computers to full servers, providing publish/subscribe # messaging capabilities with support for TLS, WebSockets, and various authentication methods. # -# 🏠 Home: https://mosquitto.org/ -# πŸ“¦ Source: https://github.com/eclipse/mosquitto -# πŸ“œ Docs: https://hub.docker.com/_/eclipse-mosquitto -# Configure Authentication: https://mosquitto.org/documentation/authentication-methods/ +# Links: +# - Home: https://mosquitto.org/ +# - Source: https://github.com/eclipse/mosquitto +# - Image: https://hub.docker.com/_/eclipse-mosquitto +# - Configure Authentication: https://mosquitto.org/documentation/authentication-methods/ # # TODO: Consider enabling TLS/SSL certificates for secure connections on port 8883 # TODO: Implement authentication with password file and ACL for access control diff --git a/docker/automation/n8n.yaml b/docker/automation/n8n.yaml index e75a85df..f08a24e4 100644 --- a/docker/automation/n8n.yaml +++ b/docker/automation/n8n.yaml @@ -1,8 +1,9 @@ # The world's most popular workflow automation platform for technical teams # -# 🏠 Home: https://n8n.io/ -# Install: https://docs.n8n.io/hosting/installation/docker/ -# Compose based on: https://github.com/n8n-io/n8n-hosting/blob/main/docker-compose/withPostgres/docker-compose.yml +# Links: +# - Home: https://n8n.io/ +# - Instal: https://docs.n8n.io/hosting/installation/docker/ +# - Compose based on: https://github.com/n8n-io/n8n-hosting/blob/main/docker-compose/withPostgres/docker-compose.yml --- name: n8n services: diff --git a/docker/backup/kopia-b2.yaml b/docker/backup/kopia-b2.yaml index 626de41b..4b81699b 100644 --- a/docker/backup/kopia-b2.yaml +++ b/docker/backup/kopia-b2.yaml @@ -3,9 +3,10 @@ # # This instance's repository is configured for **Backbaze B2** storage # -# 🏠 Home: https://kopia.io/ -# πŸ“œ Source: https://github.com/kopia/kopia -# πŸ™ Compose: https://github.com/kopia/kopia/tree/master/tools/docker +# Links: +# - Home: https://kopia.io/ +# - Source: https://github.com/kopia/kopia +# - Compose: https://github.com/kopia/kopia/tree/master/tools/docker --- name: kopia-b2 services: diff --git a/docker/backup/kopia-nas.yaml b/docker/backup/kopia-nas.yaml index 39fd9a60..ea141543 100644 --- a/docker/backup/kopia-nas.yaml +++ b/docker/backup/kopia-nas.yaml @@ -3,9 +3,10 @@ # # This instance's repository is configured for local **NAS** storage # -# 🏠 Home: https://kopia.io/ -# πŸ“œ Source: https://github.com/kopia/kopia -# πŸ™ Compose: https://github.com/kopia/kopia/tree/master/tools/docker +# Links: +# - Home: https://kopia.io/ +# - Source: https://github.com/kopia/kopia +# - Compose: https://github.com/kopia/kopia/tree/master/tools/docker --- name: kopia-nas services: diff --git a/docker/dashboard/homepage.yaml b/docker/dashboard/homepage.yaml index 0d71a7f9..f770d8fd 100644 --- a/docker/dashboard/homepage.yaml +++ b/docker/dashboard/homepage.yaml @@ -1,11 +1,11 @@ # A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations. # -# 🏠 Home: https://gethomepage.dev/ -# πŸ“œ Source: https://github.com/gethomepage/homepage/ -# Docker integration: https://gethomepage.dev/installation/docker/ -# -# Dashboard Icons: https://github.com/homarr-labs/dashboard-icons/ -# Search for Dashboard Icons: https://andrzej-zuralovic.github.io/dashboard-icons-searcher/ +# Links: +# - Home: https://gethomepage.dev/ +# - Source: https://github.com/gethomepage/homepage/ +# - Docker integration: https://gethomepage.dev/installation/docker/ +# - Dashboard Icons: https://github.com/homarr-labs/dashboard-icons/ +# - Search for Dashboard Icons: https://andrzej-zuralovic.github.io/dashboard-icons-searcher/ --- name: homepage services: diff --git a/docker/dev/code-server.yaml b/docker/dev/code-server.yaml index 02811a85..0e281d44 100644 --- a/docker/dev/code-server.yaml +++ b/docker/dev/code-server.yaml @@ -1,8 +1,10 @@ # Code-server is VS Code running on a remote server, accessible through the browser. # -# πŸ“¦ Image: https://hub.docker.com/r/linuxserver/code-server -# πŸ“œ Source: https://github.com/coder/code-server -# FAQ: https://github.com/coder/code-server/blob/main/docs/FAQ.md +# Links: +# - Home: https://coder.com/ +# - Image: https://hub.docker.com/r/linuxserver/code-server +# - Source: https://github.com/coder/code-server +# - FAQ: https://github.com/coder/code-server/blob/main/docs/FAQ.md # # Alternative: OpenVSCode Server # - https://github.com/gitpod-io/openvscode-server diff --git a/docker/dev/gitea.yaml b/docker/dev/gitea.yaml index dd00f24c..7b9ee4d6 100644 --- a/docker/dev/gitea.yaml +++ b/docker/dev/gitea.yaml @@ -9,9 +9,10 @@ # ``` # Do not lose/change your SECRET_KEY after the installation, otherwise the encrypted data can not be decrypted anymore. # -# 🏠 Home: https://about.gitea.com/ -# πŸ“¦ Source: https://github.com/go-gitea/gitea -# πŸ“œ Docs: https://docs.gitea.com/installation/install-with-docker +# Links: +# - Home: https://about.gitea.com/ +# - Source: https://github.com/go-gitea/gitea +# - Docs: https://docs.gitea.com/installation/install-with-docker # # TODO: Generate SECRET_KEY and INTERNAL_TOKEN for enhanced security # TODO: Consider switching to PostgreSQL for improved performance with large repositories diff --git a/docker/dev/gitlab.yaml b/docker/dev/gitlab.yaml index c6c69ee5..31c18cb4 100644 --- a/docker/dev/gitlab.yaml +++ b/docker/dev/gitlab.yaml @@ -5,9 +5,10 @@ # # Register the runner (`gitlab-runner` service): https://docs.gitlab.com/runner/register/index.html#docker # -# 🏠 Home: https://about.gitlab.com/install/ce-or-ee/ -# πŸ“¦ Image: https://hub.docker.com/r/gitlab/gitlab-ce/ -# Docker install instructions: https://docs.gitlab.com/ee/install/docker.html +# Links: +# - Home: https://about.gitlab.com/install/ce-or-ee/ +# - Image: https://hub.docker.com/r/gitlab/gitlab-ce/ +# - Docker install instructions: https://docs.gitlab.com/ee/install/docker.html --- name: gitlab services: diff --git a/docker/dev/jupyter-notebook.yaml b/docker/dev/jupyter-notebook.yaml index 89a889ed..975fba2f 100644 --- a/docker/dev/jupyter-notebook.yaml +++ b/docker/dev/jupyter-notebook.yaml @@ -1,11 +1,13 @@ -# Minimal Jupyter Notebook Python Stack from https://github.com/jupyter/docker-stacks +# Minimal Jupyter Notebook Python Stack # # Show login token: `jupyter server list` (run inside the container) # # TODO setup permanent password: https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html#automatic-password-setup # -# 🏠 Home: https://jupyter-docker-stacks.readthedocs.io/en/latest/index.html -# πŸ“¦ Image: https://quay.io/repository/jupyter/minimal-notebook +# Links: +# - Home: https://jupyter-docker-stacks.readthedocs.io/en/latest/index.html +# - Source: https://github.com/jupyter/docker-stacks +# - Image: https://quay.io/repository/jupyter/minimal-notebook --- name: jupyter-notebook services: diff --git a/docker/dev/supabase.yaml b/docker/dev/supabase.yaml index dce3eff9..22e1a8d2 100644 --- a/docker/dev/supabase.yaml +++ b/docker/dev/supabase.yaml @@ -3,10 +3,11 @@ # (REST, GraphQL, and real-time subscriptions), serverless functions, file storage, and AI & vector # toolkit for embeddings and semantic search. # -# 🏠 Home: https://supabase.com -# πŸ“¦ Source: https://github.com/supabase/supabase -# πŸ“œ Docs: https://supabase.com/docs/guides/self-hosting/docker -# πŸ“œ Docker Setup: https://github.com/supabase/supabase/tree/master/docker +# Links: +# - Home: https://supabase.com +# - Source: https://github.com/supabase/supabase +# - Docs: https://supabase.com/docs/guides/self-hosting/docker +# - Docker Setup: https://github.com/supabase/supabase/tree/master/docker # # Note: The self-hosted version supports only one project - https://github.com/orgs/supabase/discussions/4907 # diff --git a/docker/fileshare/qbittorrent.yaml b/docker/fileshare/qbittorrent.yaml index 0b9db02a..73d2e15d 100644 --- a/docker/fileshare/qbittorrent.yaml +++ b/docker/fileshare/qbittorrent.yaml @@ -7,8 +7,9 @@ # - Configure categories (Settings menu) to specify default download locations # - Setup forwarding of port 6881 (TCP, UDP) in the router, check open port: https://www.yougetsignal.com/tools/open-ports/ # -# 🏠 Home: https://www.qbittorrent.org/ -# πŸ“¦ Image: https://hub.docker.com/r/linuxserver/qbittorrent +# Links: +# - Home: https://www.qbittorrent.org/ +# - Image: https://hub.docker.com/r/linuxserver/qbittorrent --- name: qbittorrent services: diff --git a/docker/guidelines.md b/docker/guidelines.md index f9149622..ef5598dd 100644 --- a/docker/guidelines.md +++ b/docker/guidelines.md @@ -224,9 +224,10 @@ When creating a new service, use this template: ```yaml # Brief description of the service in 1 to 3 sentences # -# 🏠 Home: https://service-homepage.com/ -# πŸ“¦ Source: https://github.com/vendor/service -# πŸ“œ Docs: https://docs.service.com/ +# Links: +# - Home: https://service-homepage.com/ +# - Source: https://github.com/vendor/service +# - Docs: https://docs.service.com/ # # TODO: List additional configuration tasks, possible enhancements --- diff --git a/docker/infra/adguardhome-sync.yaml b/docker/infra/adguardhome-sync.yaml index 9b510d53..4f609708 100644 --- a/docker/infra/adguardhome-sync.yaml +++ b/docker/infra/adguardhome-sync.yaml @@ -7,7 +7,8 @@ # > The same goes for Android, macOS, and iOS, where all servers are questioned at once, and the fastest to answer is considered correct. # See more: https://www.xda-developers.com/two-pi-holes-spare-resources-recommend/ # -# Source: https://github.com/bakito/adguardhome-sync +# Links: +# - Source: https://github.com/bakito/adguardhome-sync --- name: adguardhome-sync services: diff --git a/docker/infra/adguardhome.yaml b/docker/infra/adguardhome.yaml index d227ca5e..d0ef8b9f 100644 --- a/docker/infra/adguardhome.yaml +++ b/docker/infra/adguardhome.yaml @@ -8,8 +8,9 @@ # 2. Setup adguardhome-sync to sync the settings # 3. Specify both DNS servers in the router's DHCP settings # -# 🏠 Home: https://adguard.com/en/adguard-home/overview.html -# πŸ“¦ Image: https://hub.docker.com/r/adguard/adguardhome +# Links: +# - Home: https://adguard.com/en/adguard-home/overview.html +# - Image: https://hub.docker.com/r/adguard/adguardhome --- name: adguardhome services: diff --git a/docker/infra/ddclient.yaml b/docker/infra/ddclient.yaml index 8eec772b..9f78798b 100644 --- a/docker/infra/ddclient.yaml +++ b/docker/infra/ddclient.yaml @@ -1,9 +1,10 @@ # ddclient is a Perl client used to update dynamic DNS entries for accounts on Dynamic DNS Network Service Provider. It was originally written by Paul Burry and is now mostly by wimpunk. # It has the capability to update more than just dyndns and it can fetch your WAN-ipaddress in a few different ways. # -# https://ddclient.net/ -# https://github.com/ddclient/ddclient -# https://docs.linuxserver.io/images/docker-ddclient/ +# Links: +# - Home: https://ddclient.net/ +# - Source: https://github.com/ddclient/ddclient +# - Image: https://docs.linuxserver.io/images/docker-ddclient/ --- name: ddclient services: diff --git a/docker/infra/portainer-agent.yaml b/docker/infra/portainer-agent.yaml index 89f61afe..0daf5cdd 100644 --- a/docker/infra/portainer-agent.yaml +++ b/docker/infra/portainer-agent.yaml @@ -3,8 +3,9 @@ # # Portainer uses the Portainer Agent container to communicate with the Portainer Server instance and provide access to the node's resources. # -# 🏠 Home: https://www.portainer.io/ -# πŸ“œ Source: https://github.com/portainer/portainer +# Links: +# - Home: https://www.portainer.io/ +# - Source: https://github.com/portainer/portainer --- name: portainer-agent services: diff --git a/docker/infra/portainer.yaml b/docker/infra/portainer.yaml index 3715641a..797df180 100644 --- a/docker/infra/portainer.yaml +++ b/docker/infra/portainer.yaml @@ -3,8 +3,9 @@ # # In order to generate an API key, please follow the steps outlined here: https://docs.portainer.io/api/access # -# 🏠 Home: https://www.portainer.io/ -# πŸ“œ Source: https://github.com/portainer/portainer-ce +# Links: +# - Home: https://www.portainer.io/ +# - Source: https://github.com/portainer/portainer-ce --- name: portainer services: diff --git a/docker/infra/unifi-controller.yaml b/docker/infra/unifi-controller.yaml index e5bfa661..81d11262 100644 --- a/docker/infra/unifi-controller.yaml +++ b/docker/infra/unifi-controller.yaml @@ -10,8 +10,9 @@ # - Create the user on Settings -> Admins # - Set the `${UNIFI_LOCAL_VIEWONLY_USERNAME}` and `${UNIFI_LOCAL_VIEWONLY_PASSWORD}` variables # -# 🏠 Home: https://ui.com/download/releases/network-server -# πŸ“¦ Image: https://hub.docker.com/r/linuxserver/unifi-controller +# Links: +# - Home: https://ui.com/download/releases/network-server +# - Image: https://hub.docker.com/r/linuxserver/unifi-controller --- name: unifi-controller services: diff --git a/docker/media/audio/navidrome.yaml b/docker/media/audio/navidrome.yaml index 776140a8..88a661f9 100644 --- a/docker/media/audio/navidrome.yaml +++ b/docker/media/audio/navidrome.yaml @@ -3,10 +3,11 @@ # # In case of "unable to open database file: no such file or directory" error, chown the `data` directory to match `${PUID}:${PGID}`. # -# 🏠 Home: https://www.navidrome.org/ -# πŸ“¦ Image: https://hub.docker.com/r/deluan/navidrome -# Installing with Docker: https://www.navidrome.org/docs/installation/docker/ -# Configuration Options: https://www.navidrome.org/docs/usage/configuration-options/ +# Links: +# - Home: https://www.navidrome.org/ +# - Image: https://hub.docker.com/r/deluan/navidrome +# - Installing with Docker: https://www.navidrome.org/docs/installation/docker/ +# - Configuration Options: https://www.navidrome.org/docs/usage/configuration-options/ # # Recommended desktop player: # Sonixd - A full-featured Subsonic/Jellyfin compatible desktop music player diff --git a/docker/media/ebook/calibre-web.yaml b/docker/media/ebook/calibre-web.yaml index 72a54698..998087c0 100644 --- a/docker/media/ebook/calibre-web.yaml +++ b/docker/media/ebook/calibre-web.yaml @@ -3,8 +3,9 @@ # # Default login: `admin` / `admin123` # -# πŸ“œ Source: https://github.com/janeczku/calibre-web -# πŸ“¦ Image: https://docs.linuxserver.io/images/docker-calibre-web/ +# Links: +# - Source: https://github.com/janeczku/calibre-web +# - Image: https://docs.linuxserver.io/images/docker-calibre-web/ --- name: calibre-web services: diff --git a/docker/media/ebook/calibre.yaml b/docker/media/ebook/calibre.yaml index cac2d4fd..e6370d8c 100644 --- a/docker/media/ebook/calibre.yaml +++ b/docker/media/ebook/calibre.yaml @@ -2,6 +2,10 @@ # It'll allow you to do nearly everything and it takes things a step beyond normal e-book software. # It's also completely free and open source and great for both casual users and computer experts. # +# Links: +# - Home: https://calibre-ebook.com/ +# - Image: https://docs.linuxserver.io/images/docker-calibre/ +# # Application setup: # - Create the library at `/books` # - For Readarr integration: Enable Content Server at Preferences -> Sharing over the net, enable "Require username and password to access the Content server" @@ -9,9 +13,6 @@ # # Book database (STORAGE_CALIBRE_LIBRARY) is not recommended to mounted via SMB due to locking issue ("DB appears locked"). # A possible fix: https://www.mobileread.com/forums/showpost.php?s=2a2bb1273085bedaedd817ada675c7df&p=3460465&postcount=9 -# -# πŸ“œ Source: https://calibre-ebook.com/ -# πŸ“¦ Image: https://docs.linuxserver.io/images/docker-calibre/ --- name: calibre services: diff --git a/docker/media/ebook/kiwix-serve.yaml b/docker/media/ebook/kiwix-serve.yaml index edec8a04..fe2d59a0 100644 --- a/docker/media/ebook/kiwix-serve.yaml +++ b/docker/media/ebook/kiwix-serve.yaml @@ -1,4 +1,4 @@ -# > [Kiwix](https://kiwix.org/) is a non-profit organization and a free and open-source software project dedicated to providing offline access to free educational content. +# > Kiwix is a non-profit organization and a free and open-source software project dedicated to providing offline access to free educational content. # > We can make highly compressed copies of entire websites that each fit into a single (.zim) file. Zim files are small enough that they can be stored on users’ mobile phones, computers or small, inexpensive Hotspot. # > Kiwix then acts like a regular browser, except that it reads these local copies. People with no or limited internet access can enjoy the same browsing experience as anyone else. # @@ -9,9 +9,11 @@ # 4. Restart the container # More information on the `.zim` files: https://download.kiwix.org/zim/README # -# Container: https://github.com/kiwix/kiwix-tools/pkgs/container/kiwix-serve -# Applications for multiple platforms: https://kiwix.org/en/applications/ -# Android APK (supports sideloaded `.zim` files): https://download.kiwix.org/release/kiwix-android/ +# Links: +# - Home: https://kiwix.org/ +# - Container: https://github.com/kiwix/kiwix-tools/pkgs/container/kiwix-serve +# - Applications for multiple platforms: https://kiwix.org/en/applications/ +# - Android APK (supports sideloaded `.zim` files): https://download.kiwix.org/release/kiwix-android/ --- name: kiwix-serve services: diff --git a/docker/media/video/jellyfin-vue.yaml b/docker/media/video/jellyfin-vue.yaml index 9a547510..cd517f81 100644 --- a/docker/media/video/jellyfin-vue.yaml +++ b/docker/media/video/jellyfin-vue.yaml @@ -1,7 +1,8 @@ # This is an experimental web client for Jellyfin based on Vue.js. # -# πŸ“œ Source: https://github.com/jellyfin/jellyfin-vue -# πŸ“¦ Image: https://hub.docker.com/r/jellyfin/jellyfin-vue +# Links: +# - Source: https://github.com/jellyfin/jellyfin-vue +# - Image: https://hub.docker.com/r/jellyfin/jellyfin-vue --- name: jellyfin-vue services: diff --git a/docker/media/video/jellyfin.yaml b/docker/media/video/jellyfin.yaml index eb39b204..34b6ab95 100644 --- a/docker/media/video/jellyfin.yaml +++ b/docker/media/video/jellyfin.yaml @@ -1,10 +1,11 @@ # Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. It is an alternative to the proprietary Emby and Plex, to provide media from a dedicated server to end-user devices via multiple apps. # Jellyfin is descended from Emby's 3.5.2 release and ported to the .NET Core framework to enable full cross-platform support. There are no strings attached, no premium licenses or features, and no hidden agendas: just a team who want to build something better and work together to achieve it. # -# 🏠 Home: https://jellyfin.org/ -# πŸ“¦ Image: https://hub.docker.com/r/linuxserver/jellyfin +# Links: +# - Home: https://jellyfin.org/ +# - Image: https://hub.docker.com/r/linuxserver/jellyfin # -# Desktop client: https://github.com/jellyfin/jellyfin-media-player +# Desktop client: https://github.com/jellyfin/jellyfin-media-player --- name: jellyfin services: diff --git a/docker/media/video/metube.yaml b/docker/media/video/metube.yaml index 2d5ac46b..5eb17dcb 100644 --- a/docker/media/video/metube.yaml +++ b/docker/media/video/metube.yaml @@ -1,11 +1,10 @@ # Web GUI for youtube-dl (using the yt-dlp fork) with playlist support. Allows you to download videos from YouTube and dozens of other sites # -# πŸ“œ Source: https://github.com/alexta69/metube -# Supported sites: https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md -# -# Browser plugins: -# - Chrome/Chromium based browser: https://chromewebstore.google.com/detail/metube-downloader/fbmkmdnlhacefjljljlbhkodfmfkijdh -# - Firefox: https://addons.mozilla.org/en-US/firefox/addon/metube-downloader/ +# Links: +# - Source: https://github.com/alexta69/metube +# - Supported sites: https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md +# - Browser plugin for Chrome/Chromium based browsers: https://chromewebstore.google.com/detail/metube-downloader/fbmkmdnlhacefjljljlbhkodfmfkijdh +# - Browser plugin for Firefox: https://addons.mozilla.org/en-US/firefox/addon/metube-downloader/ --- name: metube services: diff --git a/docker/monitoring/grafana.yaml b/docker/monitoring/grafana.yaml index 53f8b823..ac44c151 100644 --- a/docker/monitoring/grafana.yaml +++ b/docker/monitoring/grafana.yaml @@ -5,10 +5,11 @@ # # Default admin user credentials: `admin` / `admin` # -# 🏠 Home: https://grafana.com/grafana/ -# πŸ“¦ Image: https://hub.docker.com/r/grafana/grafana-oss -# Getting Started: https://grafana.com/docs/grafana/latest/getting-started/getting-started/ -# New in v12: https://grafana.com/docs/grafana/latest/whatsnew/whats-new-in-v12-0/ +# Links: +# - Home: https://grafana.com/grafana/ +# - Image: https://hub.docker.com/r/grafana/grafana-oss +# - Getting Started: https://grafana.com/docs/grafana/latest/fundamentals/getting-started/ +# - New in v12: https://grafana.com/docs/grafana/latest/whatsnew/whats-new-in-v12-0/ # # Recommended dashboards to import: # - https://grafana.com/grafana/dashboards/1860-node-exporter-full/ diff --git a/docker/monitoring/node-exporter.yaml b/docker/monitoring/node-exporter.yaml index c231c6ba..7c3b6d20 100644 --- a/docker/monitoring/node-exporter.yaml +++ b/docker/monitoring/node-exporter.yaml @@ -1,7 +1,9 @@ # Prometheus exporter for machine metrics, written in Go with pluggable metric collectors. # -# πŸ“œ Source: https://github.com/prometheus/node_exporter -# πŸ“¦ Image: https://hub.docker.com/r/prom/node-exporter/ +# Links: +# - Home: https://prometheus.io/docs/guides/node-exporter/ +# - Source: https://github.com/prometheus/node_exporter +# - Image: https://hub.docker.com/r/prom/node-exporter/ --- name: node_exporter services: diff --git a/docker/monitoring/prometheus.yaml b/docker/monitoring/prometheus.yaml index e1930dd3..166cc9c1 100644 --- a/docker/monitoring/prometheus.yaml +++ b/docker/monitoring/prometheus.yaml @@ -1,8 +1,9 @@ # Prometheus is a systems and service monitoring system. It collects metrics from configured targets at given intervals, evaluates rule expressions, displays the results, and can trigger alerts if some condition is observed to be true. # -# 🏠 Home: https://prometheus.io/ -# πŸ“¦ Image: https://hub.docker.com/r/prom/prometheus -# Introduction: https://prometheus.io/docs/introduction/overview/ +# Links: +# - Home: https://prometheus.io/ +# - Image: https://hub.docker.com/r/prom/prometheus +# - Introduction: https://prometheus.io/docs/introduction/overview/ # # Recommended collectors: # - https://hub.docker.com/r/prom/node-exporter/ diff --git a/docker/monitoring/scrutiny-collector.yaml b/docker/monitoring/scrutiny-collector.yaml index 0b581bba..100d8483 100644 --- a/docker/monitoring/scrutiny-collector.yaml +++ b/docker/monitoring/scrutiny-collector.yaml @@ -1,9 +1,10 @@ # Hard Drive S.M.A.R.T Monitoring, Historical Trends & Real World Failure Thresholds # -# πŸ“œ Source: https://github.com/AnalogJ/scrutiny +# Links: +# - Source: https://github.com/AnalogJ/scrutiny # -# In addition to the Omnibus image (available under the latest tag) you can deploy in Hub/Spoke mode -# πŸ™ Compose: https://github.com/AnalogJ/scrutiny/blob/master/docker/example.hubspoke.docker-compose.yml +# In addition to the Omnibus image (available under the latest tag) you can deploy in Hub/Spoke mode. +# Compose: https://github.com/AnalogJ/scrutiny/blob/master/docker/example.hubspoke.docker-compose.yml --- name: scrutiny-collector services: diff --git a/docker/monitoring/scrutiny.yaml b/docker/monitoring/scrutiny.yaml index 121ece50..0cea3a56 100644 --- a/docker/monitoring/scrutiny.yaml +++ b/docker/monitoring/scrutiny.yaml @@ -1,6 +1,7 @@ # Hard Drive S.M.A.R.T Monitoring, Historical Trends & Real World Failure Thresholds # -# πŸ“œ Source: https://github.com/AnalogJ/scrutiny +# Links: +# - Source: https://github.com/AnalogJ/scrutiny # # In addition to the Omnibus image (available under the latest tag) you can deploy in Hub/Spoke mode. # Details: https://github.com/AnalogJ/scrutiny/blob/master/docker/example.hubspoke.docker-compose.yml diff --git a/docker/monitoring/uptime-kuma.yaml b/docker/monitoring/uptime-kuma.yaml index be4ff2a0..fb5ec130 100644 --- a/docker/monitoring/uptime-kuma.yaml +++ b/docker/monitoring/uptime-kuma.yaml @@ -3,9 +3,10 @@ # TODO Possible improvement: # AutoKuma is a utility that automates the creation of Uptime Kuma monitors based on Docker container labels - https://github.com/BigBoot/AutoKuma # -# 🏠 Home: https://uptime.kuma.pet/ -# πŸ“œ Source: https://github.com/louislam/uptime-kuma -# πŸ“¦ Image: https://hub.docker.com/r/louislam/uptime-kuma +# Links: +# - Home: https://uptime.kuma.pet/ +# - Source: https://github.com/louislam/uptime-kuma +# - Image: https://hub.docker.com/r/louislam/uptime-kuma --- name: uptime-kuma services: diff --git a/docker/security/authelia.yaml b/docker/security/authelia.yaml index 5fe87e06..cc6dcc82 100644 --- a/docker/security/authelia.yaml +++ b/docker/security/authelia.yaml @@ -1,10 +1,9 @@ # Authelia is an open-source authentication and authorization server providing two-factor authentication and single sign-on (SSO) for your applications via a web portal. # -# 🏠 Home: https://www.authelia.com/ -# -# Introduction: -# - https://docs.ibracorp.io/authelia/ -# - https://docs.technotim.live/posts/authelia-traefik/ +# Links: +# - Home: https://www.authelia.com/ +# - Introduction: https://docs.ibracorp.io/authelia/ +# - Introduction: https://docs.technotim.live/posts/authelia-traefik/ --- name: authelia services: diff --git a/docker/security/cloudflared.yaml b/docker/security/cloudflared.yaml index 3dc3c5ba..a5e4fb07 100644 --- a/docker/security/cloudflared.yaml +++ b/docker/security/cloudflared.yaml @@ -1,8 +1,9 @@ # Client for Cloudflare Tunnel, a daemon that exposes private services through the Cloudflare edge. # -# 🏠 Home: https://www.cloudflare.com/products/tunnel/ -# πŸ“¦ Image: https://hub.docker.com/r/cloudflare/cloudflared -# Introduction: https://www.crosstalksolutions.com/cloudflare-tunnel-easy-setup/ +# Links: +# - Home: https://www.cloudflare.com/products/tunnel/ +# - Image: https://hub.docker.com/r/cloudflare/cloudflared +# - Setup Guide: https://www.crosstalksolutions.com/cloudflare-tunnel-easy-setup/ --- name: cloudflared services: diff --git a/docker/security/crowdsec.yaml b/docker/security/crowdsec.yaml index 0c171127..f08fd6d9 100644 --- a/docker/security/crowdsec.yaml +++ b/docker/security/crowdsec.yaml @@ -1,9 +1,10 @@ # Crowdsec - An open-source, lightweight agent to detect and respond to bad behaviours. # -# 🏠 Home: https://www.crowdsec.net/ -# πŸ“¦ Image: https://hub.docker.com/r/crowdsecurity/crowdsec -# πŸ“œ Source: https://github.com/crowdsecurity/example-docker-compose -# Tutorial: https://docs.ibracorp.io/crowdsec/ +# Links: +# - Home: https://www.crowdsec.net/ +# - Image: https://hub.docker.com/r/crowdsecurity/crowdsec +# - Source: https://github.com/crowdsecurity/example-docker-compose +# - Tutorial: https://docs.ibracorp.io/crowdsec/ # # Register to dashboard, subscribe to blocklists: https://app.crowdsec.net/ # Configure iptables bouncer with Ansible: `ansible/roles/debian_base/tasks/45-crowdsec.yaml` diff --git a/docker/security/endlessh.yaml b/docker/security/endlessh.yaml index 6aa71f64..93def28e 100644 --- a/docker/security/endlessh.yaml +++ b/docker/security/endlessh.yaml @@ -3,9 +3,10 @@ # # endlessh-go is a golang implementation of endlessh exporting Prometheus metrics, visualized by a Grafana dashboard. # -# πŸ“œ Source: https://github.com/shizunge/endlessh-go/ -# C implementation: https://github.com/skeeto/endlessh/ -# Developer's blogpost: https://nullprogram.com/blog/2019/03/22/ +# Links: +# - Source: https://github.com/shizunge/endlessh-go/ +# - C implementation: https://github.com/skeeto/endlessh/ +# - Developer's blogpost: https://nullprogram.com/blog/2019/03/22/ # # Setup Grafana dashboard: # - https://github.com/shizunge/endlessh-go/?tab=readme-ov-file#dashboard diff --git a/docker/security/traefik.yaml b/docker/security/traefik.yaml index 4caf3d75..18f602da 100644 --- a/docker/security/traefik.yaml +++ b/docker/security/traefik.yaml @@ -1,9 +1,10 @@ # The Cloud Native Application Proxy # -# 🏠 Home: https://traefik.io/ -# πŸ“œ Source: https://github.com/traefik/traefik -# Comparison with nginx: https://blog.lrvt.de/nginx-proxy-manager-versus-traefik/ -# Customizing error pages in Traefik: https://www.imandrea.me/blog/traefik-custom-404/ +# Links: +# - Home: https://traefik.io/ +# - Source: https://github.com/traefik/traefik +# - Comparison with nginx: https://blog.lrvt.de/nginx-proxy-manager-versus-traefik/ +# - Customizing error pages in Traefik: https://www.imandrea.me/blog/traefik-custom-404/ --- name: traefik services: diff --git a/docker/security/wg-easy.yaml b/docker/security/wg-easy.yaml index eddcaf84..1ba91294 100644 --- a/docker/security/wg-easy.yaml +++ b/docker/security/wg-easy.yaml @@ -5,9 +5,10 @@ # # Android - configure application exclusion, e.g. Android Auto: Open the Wireguard app, edit the profile, click "All Applications", select the application you want to exclude. # -# 🏠 Home: https://www.wireguard.com/ -# πŸ“œ Source: https://github.com/wg-easy/wg-easy -# Migration Guide: [Migrate from v14 to v15](https://wg-easy.github.io/wg-easy/latest/advanced/migrate/from-14-to-15/) +# Links: +# - Home: https://www.wireguard.com/ +# - Source: https://github.com/wg-easy/wg-easy +# - Migration Guide: [Migrate from v14 to v15](https://wg-easy.github.io/wg-easy/latest/advanced/migrate/from-14-to-15/) --- name: wg-easy services: diff --git a/docker/storage/bees.yaml b/docker/storage/bees.yaml index c1f9d4b9..90161062 100644 --- a/docker/storage/bees.yaml +++ b/docker/storage/bees.yaml @@ -1,7 +1,8 @@ # Best-Effort Extent-Same, a btrfs dedupe agent # -# Source: https://github.com/Zygo/bees/ -# Docker image based on: https://github.com/theCalcaholic/bees-docker +# Links: +# - Source: https://github.com/Zygo/bees +# - Docker image based on: https://github.com/theCalcaholic/bees-docker --- name: bees services: diff --git a/docker/storage/couchdb.yaml b/docker/storage/couchdb.yaml index eb360c8e..30d206d0 100644 --- a/docker/storage/couchdb.yaml +++ b/docker/storage/couchdb.yaml @@ -2,7 +2,9 @@ # # Used by [Obsidian LiveSync](https://github.com/vrtmrz/obsidian-livesync) # -# https://github.com/apache/couchdb +# Links: +# - Home: https://couchdb.apache.org +# - Source: https://github.com/apache/couchdb --- name: couchdb services: diff --git a/docker/storage/filebrowser.yaml b/docker/storage/filebrowser.yaml index 270fd146..2ae3199c 100644 --- a/docker/storage/filebrowser.yaml +++ b/docker/storage/filebrowser.yaml @@ -2,8 +2,9 @@ # # Default credentials: `admin` / `admin` # -# 🏠 Home: https://filebrowser.org/ -# πŸ“œ Source: https://github.com/filebrowser/filebrowser +# Links: +# - Home: https://filebrowser.org +# - Source: https://github.com/filebrowser/filebrowser --- name: filebrowser services: diff --git a/docker/storage/minio.yaml b/docker/storage/minio.yaml index 52bd97f5..b56f8b41 100644 --- a/docker/storage/minio.yaml +++ b/docker/storage/minio.yaml @@ -10,7 +10,9 @@ # Similarly, if your TLS certificates do not have the IP SAN for the MinIO server host, the MinIO Console may fail to validate the connection to the server. Use the MINIO_SERVER_URL environment variable and specify the proxy-accessible hostname of the MinIO server to allow the Console to use the MinIO server API using the TLS certificate. # For example: `export MINIO_SERVER_URL="https://minio.example.net"` # -# Compose: https://github.com/minio/minio/blob/master/docs/orchestration/docker-compose/docker-compose.yaml +# Links: +# - Home: https://min.io +# - Compose: https://github.com/minio/minio/blob/master/docs/orchestration/docker-compose/docker-compose.yaml --- name: minio services: diff --git a/docker/storage/mssql-server.yaml b/docker/storage/mssql-server.yaml index 982d5408..2cac1290 100644 --- a/docker/storage/mssql-server.yaml +++ b/docker/storage/mssql-server.yaml @@ -1,8 +1,9 @@ # Microsoft SQL Server 2025 - AI-ready enterprise relational database with best-in-class performance and security # -# 🏠 Home: https://www.microsoft.com/en-us/sql-server -# πŸ“œ Docs: https://learn.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker -# 🐳 Image: https://hub.docker.com/_/microsoft-mssql-server +# Links: +# - Home: https://www.microsoft.com/en-us/sql-server +# - Docs: https://learn.microsoft.com/en-us/sql/linux/quickstart-install-connect-docker +# - Image: https://hub.docker.com/_/microsoft-mssql-server # # TODO: Implement health checks to monitor SQL Server availability # TODO: Set resource limits (CPU and memory) appropriate for workload diff --git a/docker/storage/samba.yaml b/docker/storage/samba.yaml index 0dea11a6..1193d342 100644 --- a/docker/storage/samba.yaml +++ b/docker/storage/samba.yaml @@ -20,8 +20,9 @@ # SAMBA_VOLUME_CONFIG_timemachine: "[TimeMachine]; path=/shares/timemachine/%U; valid users = alice, bob, foo; guest ok = no; read only = no; browseable = yes; fruit:time machine = yes; fruit:time machine max size = 500G" # ``` # -# πŸ“œ Source: https://github.com/ServerContainers/samba -# πŸ“¦ Image: https://github.com/servercontainers/samba/pkgs/container/samba +# Links: +# - Source: https://github.com/ServerContainers/samba +# - Image: https://github.com/servercontainers/samba/pkgs/container/samba --- name: samba services: diff --git a/docker/storage/syncthing.yaml b/docker/storage/syncthing.yaml index 7ee17e4f..5684f65b 100644 --- a/docker/storage/syncthing.yaml +++ b/docker/storage/syncthing.yaml @@ -1,8 +1,9 @@ # Syncthing replaces proprietary sync and cloud services with something open, trustworthy and decentralized. # Your data is your data alone and you deserve to choose where it is stored, if it is shared with some third party and how it's transmitted over the Internet. # -# 🏠 Home: https://syncthing.net/ -# πŸ“¦ Image: https://hub.docker.com/r/linuxserver/syncthing +# Links: +# - Home: https://syncthing.net +# - Image: https://hub.docker.com/r/linuxserver/syncthing --- name: syncthing services: diff --git a/docker/tools/cyberchef.yaml b/docker/tools/cyberchef.yaml index d59f497a..c74c5772 100644 --- a/docker/tools/cyberchef.yaml +++ b/docker/tools/cyberchef.yaml @@ -1,7 +1,9 @@ # CyberChef is the Cyber Swiss Army Knife web app for encryption, encoding, compression and data analysis. # -# πŸ“œ Source: https://github.com/mpepping/docker-cyberchef/ -# πŸ“¦ Image: https://hub.docker.com/r/mpepping/cyberchef/ +# Links: +# - Home: https://gchq.github.io/CyberChef +# - Source: https://github.com/mpepping/docker-cyberchef/ +# - Image: https://hub.docker.com/r/mpepping/cyberchef/ --- name: cyberchef services: diff --git a/docker/tools/guacamole.yaml b/docker/tools/guacamole.yaml index 6f6461be..e9f7d469 100644 --- a/docker/tools/guacamole.yaml +++ b/docker/tools/guacamole.yaml @@ -3,8 +3,9 @@ # # The default username is `guacadmin` with password `guacadmin`. # -# 🏠 Home: https://guacamole.apache.org/ -# πŸ“¦ Image: https://hub.docker.com/r/flcontainers/guacamole +# Links: +# - Home: https://guacamole.apache.org/ +# - Image: https://hub.docker.com/r/flcontainers/guacamole --- name: guacamole services: diff --git a/docker/tools/kasm.yaml b/docker/tools/kasm.yaml index cf0667a4..90f15617 100644 --- a/docker/tools/kasm.yaml +++ b/docker/tools/kasm.yaml @@ -20,8 +20,9 @@ # Kasm Workspaces can be configured to connect to fixed remote endpoints that are using either the Remote Desktop Protocol, VNC or SSH. # Details: https://kasmweb.com/docs/latest/how_to/fixed_infrastructure.html # -# https://www.kasmweb.com/ -# https://docs.linuxserver.io/images/docker-kasm/ +# Links: +# - Home: https://www.kasmweb.com/ +# - Image: https://docs.linuxserver.io/images/docker-kasm/ --- name: kasm services: diff --git a/docker/tools/obsidian.yaml b/docker/tools/obsidian.yaml index bb6d4119..e0373f80 100644 --- a/docker/tools/obsidian.yaml +++ b/docker/tools/obsidian.yaml @@ -2,9 +2,10 @@ # Obsidian is a free and flexible app for your private thoughts that stores notes locally # with extensive plugin support and open file formats. # -# 🏠 Home: https://obsidian.md -# πŸ“¦ Source: https://github.com/obsidianmd -# πŸ“œ Docs: https://docs.linuxserver.io/images/docker-obsidian/ +# Links: +# - Home: https://obsidian.md +# - Source: https://github.com/obsidianmd +# - Docs: https://docs.linuxserver.io/images/docker-obsidian/ # # TODO: Set up regular backups of the /config/vaults volume containing vault data # TODO: Consider GPU acceleration configuration if needed for advanced use cases diff --git a/docker/tools/openspeedtest.yaml b/docker/tools/openspeedtest.yaml index 0ae9b0ef..a7e71eee 100644 --- a/docker/tools/openspeedtest.yaml +++ b/docker/tools/openspeedtest.yaml @@ -1,6 +1,8 @@ # SpeedTest by OpenSpeedTestβ„’ is a Free and Open-Source HTML5 Network Performance Estimation Tool # -# πŸ“¦ Image: https://hub.docker.com/r/openspeedtest/latest +# Links: +# - Home: https://openspeedtest.com +# - Image: https://hub.docker.com/r/openspeedtest/latest --- name: openspeedtest services: diff --git a/docker/tools/scanservjs.yaml b/docker/tools/scanservjs.yaml index b320ac56..cd18c38f 100644 --- a/docker/tools/scanservjs.yaml +++ b/docker/tools/scanservjs.yaml @@ -1,8 +1,8 @@ # ScanServJS - A responsive web UI for SANE document scanners # -# 🏠 Home: https://github.com/sbs20/scanservjs -# πŸ“¦ Source: https://github.com/sbs20/scanservjs -# πŸ“œ Docs: https://github.com/sbs20/scanservjs/blob/master/README.md +# Links: +# - Source: https://github.com/sbs20/scanservjs +# - Docs: https://github.com/sbs20/scanservjs/blob/master/README.md --- name: scanservjs services: diff --git a/docker/tools/searxng.yaml b/docker/tools/searxng.yaml index 6950a380..6b49e854 100644 --- a/docker/tools/searxng.yaml +++ b/docker/tools/searxng.yaml @@ -1,8 +1,10 @@ # SearXNG is a free internet metasearch engine which aggregates results from various search services and databases. # Users are neither tracked nor profiled. # -# Source: https://github.com/searxng/searxng -# Compose: https://github.com/searxng/searxng-docker +# Links: +# - Home: https://docs.searxng.org +# - Source: https://github.com/searxng/searxng +# - Compose: https://github.com/searxng/searxng-docker --- name: searxng services: diff --git a/docker/tools/stirling-pdf.yaml b/docker/tools/stirling-pdf.yaml index 9616dde3..0543c537 100644 --- a/docker/tools/stirling-pdf.yaml +++ b/docker/tools/stirling-pdf.yaml @@ -1,6 +1,8 @@ # No. 1 Locally hosted web application that allows you to perform various operations on PDF files # -# https://github.com/Stirling-Tools/Stirling-PDF +# Links: +# - Home: https://stirlingtools.com +# - Source: https://github.com/Stirling-Tools/Stirling-PDF --- name: stirling-pdf services: diff --git a/docker/tools/vaultwarden.yaml b/docker/tools/vaultwarden.yaml index bbce73a7..c1f9bb6e 100644 --- a/docker/tools/vaultwarden.yaml +++ b/docker/tools/vaultwarden.yaml @@ -1,9 +1,10 @@ # Unofficial Bitwarden compatible server written in Rust # -# πŸ“œ Source: https://github.com/dani-garcia/vaultwarden -# Wiki: https://github.com/dani-garcia/vaultwarden/wiki -# Keepass import: https://github.com/dani-garcia/vaultwarden/wiki/Importing-data-from-Keepass-or-KeepassX -# Bitwarden: https://bitwarden.com/, https://bitwarden.com/browser-start/ +# Links: +# - Source: https://github.com/dani-garcia/vaultwarden +# - Wiki: https://github.com/dani-garcia/vaultwarden/wiki +# - Keepass import: https://github.com/dani-garcia/vaultwarden/wiki/Importing-data-from-Keepass-or-KeepassX +# - Bitwarden: https://bitwarden.com/, https://bitwarden.com/browser-start/ --- name: vaultwarden services: diff --git a/docs/web/.gitignore b/docs/web/.gitignore index 926ce95e..8214b3b2 100644 --- a/docs/web/.gitignore +++ b/docs/web/.gitignore @@ -1,5 +1,5 @@ # Output generated by `hugo` public/ -# Generated by update-docs.py, based the content in /docker/ and additional locations +# Generated by update_docs.py, based the content in /docker/ and additional locations src/content/ diff --git a/docs/web/Dockerfile b/docs/web/Dockerfile index 5a2974ee..94589649 100644 --- a/docs/web/Dockerfile +++ b/docs/web/Dockerfile @@ -3,12 +3,12 @@ ##################################################################### FROM python:3.14-alpine3.22 AS collect -WORKDIR /tmp +WORKDIR /repo COPY docs/web/requirements.txt requirements.txt RUN pip install --no-cache-dir -r requirements.txt # Note: Add parameter for verbose output: --verbose -RUN --mount=type=bind,ro,source=.,target=/repo /repo/docs/web/update-docs.py --repository-path /repo --output-content-path /src/content +RUN --mount=type=bind,ro,source=.,target=/repo python -m docs.web.update_docs --repository-path /repo --output-content-path /src/content ##################################################################### # Build Stage # diff --git a/docs/web/README.md b/docs/web/README.md index add8b54b..3f9471e5 100644 --- a/docs/web/README.md +++ b/docs/web/README.md @@ -5,7 +5,7 @@ A website is built with the [Hugo](https://gohugo.io/) static site generator, us ## Development -**Build process:** The Markdown and Docker Compose files are collected and converted by `update-docs.py`, then `hugo` build is executed in a Docker container, producing a container image with `nginx` serving the static website. +**Build process:** The Markdown and Docker Compose files are collected and converted by `update_docs.py`, then `hugo` build is executed in a Docker container, producing a container image with `nginx` serving the static website. Run `task docs:deploy` to build and locally deploy (using `docker/tools/homelab-docs.yaml`) the site. diff --git a/docs/web/__init__.py b/docs/web/__init__.py new file mode 100644 index 00000000..37f4c45c --- /dev/null +++ b/docs/web/__init__.py @@ -0,0 +1,3 @@ +""" +Documentation website package. +""" diff --git a/docs/web/compose_processor.py b/docs/web/compose_processor.py new file mode 100644 index 00000000..80dc29c7 --- /dev/null +++ b/docs/web/compose_processor.py @@ -0,0 +1,112 @@ +"""Module for processing Docker Compose files and extracting metadata.""" + +import logging + +import yaml + + +class ComposeFileProcessor: + """Class for processing Docker Compose files and extracting metadata.""" + + def __init__(self, logger=None): + """Initialize the ComposeFileProcessor. + + Args: + logger: Logger instance for logging messages. If None, creates a new logger. + """ + self.logger = logger or logging.getLogger(__name__) + + def get_compose_metadata(self, file_path): + """Extract metadata from a docker-compose file. + + Args: + file_path: Path to the docker-compose file + + Returns: + dict: Dictionary containing name, description, icon and icon URL from homepage labels + """ + try: + with open(file_path) as stream: + compose_dict = yaml.safe_load(stream) + if compose_dict is None: + return {} + + # Handle case where YAML is a list or not a dict + if not isinstance(compose_dict, dict): + self.logger.warning(f"Skipping {file_path}: root element is not a dictionary") + return {} + + services = compose_dict.get("services", {}) + + # Handle case where services is not a dict (invalid compose format) + if not isinstance(services, dict): + self.logger.warning(f"Skipping {file_path}: services element is not a dictionary") + return {} + + for service in services.values(): + labels = service.get("labels", {}) + homepage_name = labels.get("homepage.name", "") + homepage_description = labels.get("homepage.description", "") + homepage_icon = labels.get("homepage.icon", "") + icon_url = f"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/{homepage_icon}" if homepage_icon else "" + + if homepage_icon or homepage_description or homepage_name: + if not homepage_name: + homepage_name = file_path.stem.capitalize() + + return { + "name": homepage_name, + "description": homepage_description, + "icon": homepage_icon, + "icon_url": icon_url, + } + + # No matching labels found in any service + return {} # noqa: TRY300 + except yaml.YAMLError: + self.logger.exception("YAML parsing error") + return {} + + def extract_compose_file_data(self, source_file_path): + """Extract all information from a docker-compose file. + + Args: + source_file_path: Path to the docker-compose file + + Returns: + dict with keys: + - metadata: dict containing name, description, icon + - head_lines: list of comment lines before "---" + - yaml_lines: list of YAML content lines after "---" + """ + # Get metadata from compose file + metadata = self.get_compose_metadata(source_file_path) + + # Return no data if metadata is empty (not a Docker Compose file) + if not metadata: + return {} + + # Read all lines from the compose file + with open(source_file_path) as compose_file: + lines = compose_file.readlines() + + # Parse lines to extract head section and YAML content + head_lines = [] + yaml_lines = [] + yaml_started = False + + for line in lines: + if yaml_started: + yaml_lines.append(line) + elif line.startswith("# "): + head_lines.append(line[2:]) + elif line.startswith("#"): + head_lines.append(line[1:]) + elif line.strip() == "---": + yaml_started = True + + return { + "metadata": metadata, + "head_lines": head_lines, + "yaml_lines": yaml_lines, + } diff --git a/docs/web/docker_scanner.py b/docs/web/docker_scanner.py new file mode 100644 index 00000000..fde2a9df --- /dev/null +++ b/docs/web/docker_scanner.py @@ -0,0 +1,96 @@ +"""Module for scanning Docker directories and collecting compose file data.""" + +import logging +import os +from pathlib import Path + +from .compose_processor import ComposeFileProcessor + + +class DockerComposeScanner: + """Class for scanning Docker directories and collecting compose file data.""" + + def __init__(self, repository_path, logger=None): + """Initialize the DockerComposeScanner. + + Args: + repository_path: Path to the repository root + logger: Logger instance for logging messages. If None, creates a new logger. + """ + self.repository_path = Path(repository_path) + self.logger = logger or logging.getLogger(__name__) + self.compose_processor = ComposeFileProcessor(self.logger) + + def scan_docker_directory(self, docker_path="docker"): + """Scan docker directory and collect data from all compose files. + + Only processes compose files in directories that contain a README.md file. + This prevents processing configuration yaml files in subdirectories. + + Args: + docker_path: Relative path to docker directory from repository root + + Returns: + list: List of dictionaries, each containing: + - file_path: Path to compose file relative to docker directory + - category: Category path (e.g., "security", "media/video") + - metadata: dict with name, description, icon, icon_url + - head_lines: list of comment lines before "---" + - yaml_lines: list of YAML content lines after "---" + - has_readme: bool indicating if directory contains README.md + """ + source_dir = self.repository_path / docker_path + services = [] + + # Walk through the source directory recursively + for root, _, files in os.walk(source_dir): + root_path = Path(root) + has_readme = "README.md" in files + + # Only process compose files in directories that contain README.md + if not has_readme: + continue + + # Process all compose files in this directory + for file in files: + if file.endswith((".yaml", ".yml")): + source_file_path = root_path / file + relative_path = source_file_path.relative_to(source_dir) + + # Extract category from path (e.g., "security" or "media/video") + category = str(relative_path.parent) if relative_path.parent != Path(".") else "" + + # Extract all data from the compose file + data = self.compose_processor.extract_compose_file_data(source_file_path) + + if data: + services.append({ + "file_path": str(relative_path), + "category": category, + "metadata": data["metadata"], + "head_lines": data["head_lines"], + "yaml_lines": data["yaml_lines"], + "has_readme": has_readme, + }) + + return services + + def get_services_by_category(self, docker_path="docker"): + """Scan docker directory and organize services by category. + + Args: + docker_path: Relative path to docker directory from repository root + + Returns: + dict: Dictionary mapping category names to lists of service data + """ + services = self.scan_docker_directory(docker_path) + by_category = {} + + for service in services: + category = service["category"] or "root" + if category not in by_category: + by_category[category] = [] + by_category[category].append(service) + + return by_category diff --git a/docs/web/export_services.py b/docs/web/export_services.py new file mode 100755 index 00000000..cea78832 --- /dev/null +++ b/docs/web/export_services.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 + +"""Script to export all Docker Compose services metadata and documentation to YAML.""" + +import argparse +import logging +import math +import sys +from pathlib import Path + +import yaml + +from .docker_scanner import DockerComposeScanner +from .git_utils import get_git_root + + +def str_presenter(dumper, data): + if '\n' in data: + return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') + return dumper.represent_scalar('tag:yaml.org,2002:str', data) + + +yaml.add_representer(str, str_presenter) + + +def export_services(repository_path, output_file, docker_path="docker", verbose=False): + """Export all Docker Compose services to a YAML file. + + Args: + repository_path: Path to the repository root + output_file: Path to the output YAML file + docker_path: Relative path to docker directory (default: "docker") + verbose: Enable verbose logging + """ + # Set up logging + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + if verbose: + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + formatter = logging.Formatter(' %(levelname)s: %(message)s') + ch.setFormatter(formatter) + logger.addHandler(ch) + logger.setLevel(logging.DEBUG) + + # Scan all services + logger.info(f"Scanning Docker Compose files in {docker_path}") + scanner = DockerComposeScanner(repository_path, logger) + services = scanner.scan_docker_directory(docker_path) + + # Build output data structure + output_data = {"services": []} + + for service in services: + # Join head_lines into a single documentation string + documentation = "".join(service["head_lines"]).strip() + + service_data = { + "name": service["metadata"]["name"], + "description": service["metadata"]["description"], + "file_path": service["file_path"], + "category": service["category"], + } + + # Only include documentation if it's not empty + if documentation: + service_data["documentation"] = documentation + + output_data["services"].append(service_data) + + # Write to YAML file + logger.info(f"Writing {len(services)} services to {output_file}") + with open(output_file, "w") as f: + yaml.dump(output_data, f, width=math.inf, default_flow_style=False, sort_keys=False, allow_unicode=True, Dumper=yaml.Dumper) + + logger.info("Export complete") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Export Docker Compose services metadata and documentation to YAML." + ) + parser.add_argument("--verbose", action="store_true", help="Enable verbose output") + parser.add_argument( + "--repository-path", + type=str, + help="Specify the path to the repository root" + ) + parser.add_argument( + "--output-file", + type=str, + help="Specify the output YAML file path" + ) + parser.add_argument( + "--docker-path", + type=str, + default="docker", + help="Relative path to docker directory (default: docker)" + ) + args = parser.parse_args() + + # Get repository path + repository_path = Path(args.repository_path) if args.repository_path else Path(get_git_root()) + + # Default output file if not specified + output_file = Path(args.output_file) if args.output_file else repository_path / "services.yaml" + + # Export services + try: + export_services(repository_path, output_file, args.docker_path, args.verbose) + except Exception: + logging.exception("Error exporting services") + sys.exit(1) diff --git a/docs/web/git_utils.py b/docs/web/git_utils.py new file mode 100644 index 00000000..ab32f114 --- /dev/null +++ b/docs/web/git_utils.py @@ -0,0 +1,45 @@ +import shutil +import subprocess + + +class GitNotOnPathError(RuntimeError): + """Raised when Git executable is not found on PATH.""" + def __init__(self): + super().__init__("Git not found on PATH") + +class GitExecutableNotFoundError(RuntimeError): + """Raised when Git executable cannot be found.""" + def __init__(self): + super().__init__("Git executable not found. Please install Git and ensure it is on your PATH.") + +class NotInGitRepositoryError(RuntimeError): + """Raised when not running inside a Git repository.""" + def __init__(self): + super().__init__("Unable to locate git repository. Are you running this inside a Git repo?") + +def get_git_root() -> str: + """Get the git repository root directory. + + Returns: + str: The absolute path to the git repository root directory. + + Raises: + GitNotOnPathError: If git executable is not found on PATH. + GitExecutableNotFoundError: If git executable cannot be found. + NotInGitRepositoryError: If not running inside a Git repository. + """ + git_cmd = shutil.which("git") + if git_cmd is None: + raise GitNotOnPathError() from None + try: + result = subprocess.run( # noqa: S603 + [git_cmd, "rev-parse", "--show-toplevel"], + stdout=subprocess.PIPE, + check=True, + text=True, + ) + except FileNotFoundError: + raise GitExecutableNotFoundError() from None + except subprocess.CalledProcessError: + raise NotInGitRepositoryError() from None + return result.stdout.strip() diff --git a/docs/web/link_processor.py b/docs/web/link_processor.py new file mode 100644 index 00000000..65cb1e32 --- /dev/null +++ b/docs/web/link_processor.py @@ -0,0 +1,153 @@ +"""Module for processing and validating markdown links.""" + +import os +import re +from pathlib import Path + + +class LinkProcessor: + """Class for processing and validating markdown links.""" + + def __init__(self, logger, markdown_locations, repository_path, output_content_path): + """Initialize the LinkProcessor. + + Args: + logger: Logger instance for logging operations + markdown_locations: List of (source, target, weight) tuples for markdown file locations + repository_path: Path to the repository root + output_content_path: Path to the output directory for generated content + """ + self.logger = logger + self.markdown_locations = markdown_locations + self.repository_path = repository_path + self.output_content_path = output_content_path + + def extract_relative_links(self, content): + """Extract all relative markdown links from content. + + Returns: + list of tuples (link_text, link_url). + """ + # Match markdown links [text](url) but only when url doesn't start with http/https/# + link_pattern = re.compile(r'\[([^\]]+)\]\(([^\)]+)\)') + links = [] + + for match in link_pattern.finditer(content): + link_text, link_url = match.groups() + # Filter only relative links (not http, https, or anchor links) + if not (link_url.startswith("http://") or + link_url.startswith("https://") or + link_url.startswith("#") or + link_url.startswith("mailto:")): + links.append((link_text, link_url)) + + return links + + def is_valid_link(self, source_file_path, link_url): + """Check if a relative link in a markdown file points to an existing file.""" + # Get the directory of the source file + source_dir = source_file_path.parent + + # Remove any anchor part of the link (#section-id) + link_path = link_url.split("#")[0] + + # Handle empty links (usually anchors in the same file) + if not link_path: + return True + + # Resolve the path relative to the source file + target_path = (source_dir / link_path).resolve() + + return target_path.exists() + + def update_relative_link(self, link_url, source_path, target_path): + """Update a relative link to account for directory structure changes. + + Args: + link_url: The original relative link URL + source_path: Original file path + target_path: New file path + + Returns: + Updated link URL + """ + # Remove any anchor part and store it + if "#" in link_url: + link_path, anchor = link_url.split("#", 1) + anchor = "#" + anchor + else: + link_path = link_url + anchor = "" + + # If it's an empty link (just an anchor), return as is + if not link_path: + return link_url + + # Get the original link target absolute path + source_dir = source_path.parent + original_target = (source_dir / link_path).resolve() + + # Find the target path in markdown_locations + for src, tgt, _ in self.markdown_locations: + src_path = self.repository_path / src + + # If the original target is inside this source directory + if src.endswith("/") and original_target.is_relative_to(src_path): + # Calculate the relative path within the source directory + rel_path = original_target.relative_to(src_path) + + # Construct the new target path + new_target = Path(tgt) / rel_path + + # Calculate relative path from target_path's parent to new_target + new_relative_link = os.path.relpath( + self.output_content_path / new_target, + target_path.parent + ) + + self.logger.debug(f"Updated link from {link_url} to {new_relative_link}{anchor}") + return new_relative_link + anchor + + # If the original target matches a specific file + elif not src.endswith("/") and original_target == src_path: + new_target = tgt + + # Calculate relative path from target_path's parent to new_target + new_relative_link = os.path.relpath( + self.output_content_path / new_target, + target_path.parent + ) + + self.logger.debug(f"Updated link from {link_url} to {new_relative_link}{anchor}") + return new_relative_link + anchor + + # If we couldn't find a match in markdown_locations, the link might be broken + # or pointing to a file not included in the documentation + self.logger.warning(f"Could not update link: {link_url} - target file not found in markdown locations") + return link_url + + def process_markdown_content(self, content, source_file_path, target_file_path): + """Process markdown content to check and fix relative links.""" + links = self.extract_relative_links(content) + + if not links: + return content + + result = content + for link_text, link_url in links: + # Check if the link is valid + if not self.is_valid_link(source_file_path, link_url): + self.logger.warning(f"Broken link in {source_file_path}: [{link_text}]({link_url})") + else: + self.logger.debug(f"Found valid link in {source_file_path}: [{link_text}]({link_url})") + + # Update the link to account for directory structure changes + updated_link = self.update_relative_link(link_url, source_file_path, target_file_path) + + if updated_link != link_url: + # Replace the link in the content + original_link_pattern = re.escape(f"[{link_text}]({link_url})") + new_link = f"[{link_text}]({updated_link})" + result = re.sub(original_link_pattern, new_link, result) + + return result diff --git a/docs/web/src/go.mod b/docs/web/src/go.mod index 4a3c0895..7cb1fafe 100644 --- a/docs/web/src/go.mod +++ b/docs/web/src/go.mod @@ -3,5 +3,5 @@ module homelab-docs go 1.18 require ( - github.com/McShelby/hugo-theme-relearn v0.0.0-20231220191733-974798afca08 // indirect + github.com/McShelby/hugo-theme-relearn v0.0.0-20251127072033-57a5af815447 ) diff --git a/docs/web/src/go.sum b/docs/web/src/go.sum index c68c64a8..b7b05e7b 100644 --- a/docs/web/src/go.sum +++ b/docs/web/src/go.sum @@ -1,2 +1,2 @@ -github.com/McShelby/hugo-theme-relearn v0.0.0-20231220191733-974798afca08 h1:/IwtRTh902YVZs2tDZ9WzO8cQne5Z0SDqfvu90Gr/AY= -github.com/McShelby/hugo-theme-relearn v0.0.0-20231220191733-974798afca08/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM= +github.com/McShelby/hugo-theme-relearn v0.0.0-20251127072033-57a5af815447 h1:ujlHf8LfF9VdFLcdUJEKnAfLjEo5wPZmJmPxBROCo3o= +github.com/McShelby/hugo-theme-relearn v0.0.0-20251127072033-57a5af815447/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM= diff --git a/docs/web/src/layouts/partials/heading.html b/docs/web/src/layouts/partials/heading.html index 675f773e..cfc64c9f 100644 --- a/docs/web/src/layouts/partials/heading.html +++ b/docs/web/src/layouts/partials/heading.html @@ -1,8 +1,9 @@ +

{{- if .Params.Icon }} -{{ .Title }} +{{ .Title }} {{- end }} - -

{{ .Title }}

+{{ .Title }} + {{- if .Description }}

{{ .Description }}

diff --git a/docs/web/update-docs.py b/docs/web/update-docs.py deleted file mode 100755 index 59d99a35..00000000 --- a/docs/web/update-docs.py +++ /dev/null @@ -1,407 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import logging -import os -import re -import shutil -import subprocess -import sys -from pathlib import Path - -import yaml - - -class DocsProcessor: - """Class for processing documentation files and managing links.""" - - def __init__(self, repository_path, output_content_path, verbose=False): - """Initialize the DocsProcessor. - - Args: - repository_path: Path to the repository root - output_content_path: Path to the output directory for generated content - verbose: Whether to enable verbose logging - """ - self.repository_path = repository_path - self.output_content_path = output_content_path - - # Set up logging - self.logger = logging.getLogger(__name__) - self.logger.setLevel(logging.INFO) - - if verbose: - # Create console handler with a higher log level - ch = logging.StreamHandler() - ch.setLevel(logging.DEBUG) - formatter = logging.Formatter(' %(levelname)s: %(message)s') - ch.setFormatter(formatter) - self.logger.addHandler(ch) - self.logger.setLevel(logging.DEBUG) - - # Load markdown locations - self.markdown_locations = self.load_config() - - def load_config(self): - """Load markdown locations from YAML file.""" - yaml_path = self.repository_path / "docs" / "web" / "update-docs-config.yaml" - try: - with open(yaml_path) as yaml_file: - data = yaml.safe_load(yaml_file) - return data.get("locations", []) - except (FileNotFoundError, yaml.YAMLError): - self.logger.exception("Error loading update-docs-config.yaml") - sys.exit(1) - - def log_copy(self, source_file_path, target_file_path): - """Log file copying operation.""" - self.logger.debug(f"{source_file_path} ==> {target_file_path}") - - def extract_relative_links(self, content): - """Extract all relative markdown links from content. - - Returns: - list of tuples (link_text, link_url). - """ - # Match markdown links [text](url) but only when url doesn't start with http/https/# - link_pattern = re.compile(r'\[([^\]]+)\]\(([^\)]+)\)') - links = [] - - for match in link_pattern.finditer(content): - link_text, link_url = match.groups() - # Filter only relative links (not http, https, or anchor links) - if not (link_url.startswith("http://") or - link_url.startswith("https://") or - link_url.startswith("#") or - link_url.startswith("mailto:")): - links.append((link_text, link_url)) - - return links - - def is_valid_link(self, source_file_path, link_url): - """Check if a relative link in a markdown file points to an existing file.""" - # Get the directory of the source file - source_dir = source_file_path.parent - - # Remove any anchor part of the link (#section-id) - link_path = link_url.split("#")[0] - - # Handle empty links (usually anchors in the same file) - if not link_path: - return True - - # Resolve the path relative to the source file - target_path = (source_dir / link_path).resolve() - - return target_path.exists() - - def update_relative_link(self, link_url, source_path, target_path): - """Update a relative link to account for directory structure changes. - - Args: - link_url: The original relative link URL - source_path: Original file path - target_path: New file path - - Returns: - Updated link URL - """ - # Remove any anchor part and store it - if "#" in link_url: - link_path, anchor = link_url.split("#", 1) - anchor = "#" + anchor - else: - link_path = link_url - anchor = "" - - # If it's an empty link (just an anchor), return as is - if not link_path: - return link_url - - # Get the original link target absolute path - source_dir = source_path.parent - original_target = (source_dir / link_path).resolve() - - # Find the target path in markdown_locations - for src, tgt, _ in self.markdown_locations: - src_path = self.repository_path / src - - # If the original target is inside this source directory - if src.endswith("/") and str(original_target).startswith(str(src_path)): - # Calculate the relative path within the source directory - rel_path = original_target.relative_to(src_path) - - # Construct the new target path - new_target = Path(tgt) / rel_path - - # Calculate relative path from target_path's parent to new_target - new_relative_link = os.path.relpath( - self.output_content_path / new_target, - target_path.parent - ) - - self.logger.debug(f"Updated link from {link_url} to {new_relative_link}{anchor}") - return new_relative_link + anchor - - # If the original target matches a specific file - elif not src.endswith("/") and original_target == src_path: - new_target = tgt - - # Calculate relative path from target_path's parent to new_target - new_relative_link = os.path.relpath( - self.output_content_path / new_target, - target_path.parent - ) - - self.logger.debug(f"Updated link from {link_url} to {new_relative_link}{anchor}") - return new_relative_link + anchor - - # If we couldn't find a match in markdown_locations, the link might be broken - # or pointing to a file not included in the documentation - self.logger.warning(f"Could not update link: {link_url} - target file not found in markdown locations") - return link_url - - def process_markdown_content(self, content, source_file_path, target_file_path): - """Process markdown content to check and fix relative links.""" - links = self.extract_relative_links(content) - - if not links: - return content - - result = content - for link_text, link_url in links: - # Check if the link is valid - if not self.is_valid_link(source_file_path, link_url): - self.logger.warning(f"Broken link in {source_file_path}: [{link_text}]({link_url})") - else: - self.logger.debug(f"Found valid link in {source_file_path}: [{link_text}]({link_url})") - - # Update the link to account for directory structure changes - updated_link = self.update_relative_link(link_url, source_file_path, target_file_path) - - if updated_link != link_url: - # Replace the link in the content - original_link_pattern = re.escape(f"[{link_text}]({link_url})") - new_link = f"[{link_text}]({updated_link})" - result = re.sub(original_link_pattern, new_link, result) - - return result - - def create_directory(self, directory): - """Create directory and any parent directories if they don't exist.""" - directory.mkdir(parents=True, exist_ok=True) - - def delete_directory_content(self, content_path): - """Delete all content in a directory and recreate the directory.""" - if content_path.exists() and content_path.is_dir(): - shutil.rmtree(content_path) - self.create_directory(content_path) - - def copy_markdown_file(self, source_file_path, target_file_path, weight=0): - """Copy and process a markdown file, adding frontmatter and fixing links.""" - with open(source_file_path) as readme_file: - content = readme_file.read() - lines = content.splitlines(True) # Keep line endings - - title_found = False - processed_lines = [] - - # First line starting with "# " will be the title - for line in lines: - if not title_found and line.startswith("# "): - title = line[2:].replace("", "").strip() - processed_lines.append("---\n") - processed_lines.append(f"title: \"{title}\"\n") - if weight != 0: - processed_lines.append(f"weight: {weight}\n") - processed_lines.append("---\n") - title_found = True - else: - processed_lines.append(line) - - # Process the content to check and fix links - processed_content = ''.join(processed_lines) - processed_content = self.process_markdown_content(processed_content, source_file_path, target_file_path) - - with open(target_file_path, "w") as readme_file: - readme_file.write(processed_content) - - def process_location(self, source_path, target_name, weight=0): - """Process a location specified in markdown_locations.""" - if source_path.endswith("/"): - self.process_directory(source_path, target_name, weight) - else: - self.process_markdown_file(source_path, target_name, weight) - - def process_directory(self, source_path, target_name, weight=0): - """Process all markdown files in a directory.""" - source_dir = self.repository_path / source_path - for file in os.listdir(source_dir): - if file.endswith(".md"): - target_filename = "_index.md" if file == "README.md" else file - self.process_markdown_file(source_path + "/" + file, target_name + "/" + target_filename, weight) - - def process_markdown_file(self, source_path, target_name, weight=0): - """Process a single markdown file.""" - source_file_path = self.repository_path / source_path - target_file_path = self.output_content_path / target_name - - self.create_directory(target_file_path.parent) - self.log_copy(source_file_path, target_file_path) - self.copy_markdown_file(source_file_path, target_file_path, weight) - - def process_docker_stack_index(self, source_dir, target_dir, root, file): - """Process a docker stack README.md file.""" - source_file_path = Path(root) / file - relative_path = source_file_path.relative_to(source_dir).with_name("_index.md") - target_file_path = Path(target_dir) / relative_path - - self.create_directory(target_file_path.parent) - self.log_copy(source_file_path, target_file_path) - self.copy_markdown_file(source_file_path, target_file_path) - - def get_icon_url(self, icon): - """Get the URL for a dashboard icon.""" - return f"https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/{icon}" if icon else "" - - def get_compose_metadata(self, file_path): - """Extract metadata from a docker-compose file.""" - try: - with open(file_path) as stream: - compose_dict = yaml.safe_load(stream) - if compose_dict is None: - return {} - - services = compose_dict.get("services", {}) - - for service in services.values(): - labels = service.get("labels", {}) - homepage_name = labels.get("homepage.name", "") - homepage_description = labels.get("homepage.description", "") - homepage_icon = labels.get("homepage.icon", "") - - if homepage_icon or homepage_description or homepage_name: - return { - "name": homepage_name, - "description": homepage_description, - "icon": homepage_icon, - } - - # No matching labels found in any service - return {} # noqa: TRY300 - except yaml.YAMLError: - self.logger.exception("YAML parsing error") - return {} - - def process_docker_compose_file(self, source_dir, target_dir, root, file): - """Process a docker-compose file.""" - # Skip non-compose files (like .env files) - if not file.endswith((".yml", ".yaml")): - return - - source_file_path = Path(root) / file - relative_path = source_file_path.relative_to(source_dir) - target_file_path = Path(target_dir) / relative_path.with_suffix(".md") - - self.create_directory(target_file_path.parent) - self.log_copy(source_file_path, target_file_path) - - metadata = self.get_compose_metadata(source_file_path) - metadata.setdefault("name", source_file_path.stem.capitalize()) - - with open(source_file_path) as compose_file: - lines = compose_file.readlines() - - yaml_started = False - processed_lines = ["---\n", f"title: \"{metadata['name']}\"\n"] - if 'description' in metadata: - processed_lines.append(f"description: \"{metadata['description']}\"\n") - if 'icon' in metadata: - processed_lines.append("params:\n") - processed_lines.append(f" icon: \"{self.get_icon_url(metadata['icon'])}\"\n") - processed_lines.append("---\n") - - for line in lines: - if yaml_started: - processed_lines.append(line) - elif line.startswith("# "): - processed_lines.append(line[2:]) - elif line.startswith("#"): - processed_lines.append(line[1:]) - elif line.strip() == "---": - yaml_started = True - processed_lines.append("```yaml\n") - - if yaml_started: - processed_lines.append("```\n") - with open(target_file_path, "w") as doc_file: - doc_file.writelines(processed_lines) - - def process_docker_directory(self, docker_path, docker_target_path): - """Process docker directory containing docker-compose files.""" - source_dir = self.repository_path / docker_path - target_dir = self.output_content_path / docker_target_path - - self.create_directory(Path(target_dir)) - - # Walk through the source directory recursively - for root, _, files in os.walk(source_dir): - if "README.md" in files: - self.process_docker_stack_index(source_dir, target_dir, root, "README.md") - for file in files: - if file.endswith((".yaml", ".yml")): - self.process_docker_compose_file(source_dir, target_dir, root, file) - - def process(self): - """Main method to process all files.""" - # Remove the content directory (which only contains generated content) - self.delete_directory_content(self.output_content_path) - - self.logger.info("Processing Docker Compose stacks") - self.process_docker_directory("docker", "docker") - - for source, target, weight in self.markdown_locations: - self.logger.info(f"Processing {source} ==> {target}") - self.process_location(source, target, weight) - - -def get_git_root() -> str: - """Get the git repository root directory. - - Returns: - str: The absolute path to the git repository root directory. - - Raises: - RuntimeError: If git executable is not found or not in a git repository. - """ - git_cmd = shutil.which("git") - if git_cmd is None: - raise RuntimeError("Git not found on PATH") from None - try: - result = subprocess.run( # noqa: S603 - [git_cmd, "rev-parse", "--show-toplevel"], - stdout=subprocess.PIPE, - check=True, - text=True, - ) - except FileNotFoundError: - raise RuntimeError("Git executable not found. Please install Git and ensure it is on your PATH.") from None - except subprocess.CalledProcessError: - raise RuntimeError("Unable to locate git repository. Are you running this inside a Git repo?") from None - return result.stdout.strip() - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Process documentation files.") - parser.add_argument("--verbose", action="store_true", help="Enable verbose output") - parser.add_argument("--repository-path", type=str, help="Specify the path to the repository root") - parser.add_argument("--output-content-path", type=str, help="Specify the path of the generated content") - args = parser.parse_args() - - # Get repository and output paths - repository_path = Path(args.repository_path) if args.repository_path else Path(get_git_root()) - output_content_path = Path(args.output_content_path) if args.output_content_path else repository_path / "docs" / "web" / "src" / "content" - - # Create and run the docs processor - processor = DocsProcessor(repository_path, output_content_path, args.verbose) - processor.process() diff --git a/docs/web/update_docs.py b/docs/web/update_docs.py new file mode 100755 index 00000000..8b80f91f --- /dev/null +++ b/docs/web/update_docs.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 + +import argparse +import logging +import os +import shutil +import sys +from pathlib import Path + +import yaml + +from .docker_scanner import DockerComposeScanner +from .git_utils import get_git_root +from .link_processor import LinkProcessor + + +class DocsProcessor: + """Class for processing documentation files and managing links.""" + + def __init__(self, repository_path, output_content_path, verbose=False): + """Initialize the DocsProcessor. + + Args: + repository_path: Path to the repository root + output_content_path: Path to the output directory for generated content + verbose: Whether to enable verbose logging + """ + self.repository_path = repository_path + self.output_content_path = output_content_path + + # Set up logging + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.INFO) + + if verbose: + # Create console handler with a higher log level + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + formatter = logging.Formatter(' %(levelname)s: %(message)s') + ch.setFormatter(formatter) + self.logger.addHandler(ch) + self.logger.setLevel(logging.DEBUG) + + # Load markdown locations + self.markdown_locations = self.load_config() + + # Initialize link processor + self.link_processor = LinkProcessor( + self.logger, + self.markdown_locations, + self.repository_path, + self.output_content_path + ) + + # Initialize docker scanner + self.docker_scanner = DockerComposeScanner(self.repository_path, self.logger) + + def load_config(self): + """Load markdown locations from YAML file.""" + yaml_path = self.repository_path / "docs" / "web" / "update-docs-config.yaml" + try: + with open(yaml_path) as yaml_file: + data = yaml.safe_load(yaml_file) + return data.get("locations", []) + except (FileNotFoundError, yaml.YAMLError): + self.logger.exception("Error loading update-docs-config.yaml") + sys.exit(1) + + def log_copy(self, source_file_path, target_file_path): + """Log file copying operation.""" + self.logger.debug(f"{source_file_path} ==> {target_file_path}") + + def create_directory(self, directory): + """Create directory and any parent directories if they don't exist.""" + directory.mkdir(parents=True, exist_ok=True) + + def delete_directory_content(self, content_path): + """Delete all content in a directory and recreate the directory.""" + if content_path.exists() and content_path.is_dir(): + shutil.rmtree(content_path) + self.create_directory(content_path) + + def copy_markdown_file(self, source_file_path, target_file_path, weight=0): + """Copy and process a markdown file, adding frontmatter and fixing links.""" + with open(source_file_path) as readme_file: + content = readme_file.read() + lines = content.splitlines(True) # Keep line endings + + title_found = False + processed_lines = [] + + # First line starting with "# " will be the title + for line in lines: + if not title_found and line.startswith("# "): + title = line[2:].replace("", "").strip() + processed_lines.append("---\n") + processed_lines.append(f"title: \"{title}\"\n") + if weight != 0: + processed_lines.append(f"weight: {weight}\n") + processed_lines.append("---\n") + title_found = True + else: + processed_lines.append(line) + + # Process the content to check and fix links + processed_content = ''.join(processed_lines) + processed_content = self.link_processor.process_markdown_content(processed_content, source_file_path, target_file_path) + + with open(target_file_path, "w") as readme_file: + readme_file.write(processed_content) + + def process_location(self, source_path, target_name, weight=0): + """Process a location specified in markdown_locations.""" + if source_path.endswith("/"): + self.process_directory(source_path, target_name, weight) + else: + self.process_markdown_file(source_path, target_name, weight) + + def process_directory(self, source_path, target_name, weight=0): + """Process all markdown files in a directory.""" + source_dir = self.repository_path / source_path + for file in os.listdir(source_dir): + if file.endswith(".md"): + target_filename = "_index.md" if file == "README.md" else file + self.process_markdown_file(source_path + "/" + file, target_name + "/" + target_filename, weight) + + def process_markdown_file(self, source_path, target_name, weight=0): + """Process a single markdown file.""" + source_file_path = self.repository_path / source_path + target_file_path = self.output_content_path / target_name + + self.create_directory(target_file_path.parent) + self.log_copy(source_file_path, target_file_path) + self.copy_markdown_file(source_file_path, target_file_path, weight) + + def process_docker_directory(self, docker_path, docker_target_path): + """Process docker directory containing docker-compose files.""" + source_dir = self.repository_path / docker_path + target_dir = self.output_content_path / docker_target_path + + self.create_directory(Path(target_dir)) + + # Scan all compose files using the scanner + services = self.docker_scanner.scan_docker_directory(docker_path) + + # Group services by directory to handle READMEs + directories_processed = set() + + for service in services: + service_category = service["category"] + category_path = Path(service_category) if service_category else Path(".") + + # Process README.md for this directory if it exists and we haven't processed it yet + if service["has_readme"] and service_category not in directories_processed: + source_readme = source_dir / category_path / "README.md" + target_readme = target_dir / category_path / "_index.md" + + self.create_directory(target_readme.parent) + self.log_copy(source_readme, target_readme) + self.copy_markdown_file(source_readme, target_readme) + + directories_processed.add(service_category) + + # Process the compose file and write markdown + source_file_path = source_dir / service["file_path"] + target_file_path = target_dir / Path(service["file_path"]).with_suffix(".md") + + self.create_directory(target_file_path.parent) + self.log_copy(source_file_path, target_file_path) + + metadata = service["metadata"] + head_lines = service["head_lines"] + yaml_lines = service["yaml_lines"] + + # Build markdown content with frontmatter + processed_lines = ["---\n", f"title: \"{metadata['name']}\"\n"] + if 'description' in metadata: + processed_lines.append(f"description: \"{metadata['description']}\"\n") + if 'icon' in metadata: + processed_lines.append("params:\n") + processed_lines.append(f" icon: \"{metadata['icon_url']}\"\n") + processed_lines.append("---\n") + + # Add head section (comments from compose file) + processed_lines.extend(head_lines) + + # Add YAML content in code block if present + if yaml_lines: + processed_lines.append("```yaml\n") + processed_lines.extend(yaml_lines) + processed_lines.append("```\n") + + # Write the processed markdown file + with open(target_file_path, "w") as doc_file: + doc_file.writelines(processed_lines) + + def process(self): + """Main method to process all files.""" + # Remove the content directory (which only contains generated content) + self.delete_directory_content(self.output_content_path) + + self.logger.info("Processing Docker Compose stacks") + self.process_docker_directory("docker", "docker") + + for source, target, weight in self.markdown_locations: + self.logger.info(f"Processing {source} ==> {target}") + self.process_location(source, target, weight) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Process documentation files.") + parser.add_argument("--verbose", action="store_true", help="Enable verbose output") + parser.add_argument("--repository-path", type=str, help="Specify the path to the repository root") + parser.add_argument("--output-content-path", type=str, help="Specify the path of the generated content") + args = parser.parse_args() + + # Get repository and output paths + repository_path = Path(args.repository_path) if args.repository_path else Path(get_git_root()) + output_content_path = Path(args.output_content_path) if args.output_content_path else repository_path / "docs" / "web" / "src" / "content" + + # Create and run the docs processor + processor = DocsProcessor(repository_path, output_content_path, args.verbose) + processor.process()