feat: Add comprehensive GitHub Actions CI/CD pipeline

GitHub Actions Workflows:
- docker-compose-validation.yml: Validates all compose files
  - Syntax validation
  - Network configuration checks
  - Traefik label validation
  - Port exposure warnings
  - Domain consistency checks
  - File naming convention enforcement

- security-checks.yml: Security scanning and validation
  - Gitleaks secret detection
  - Environment file validation
  - Placeholder password checks
  - Container image vulnerability scanning with Trivy
  - Dependency review for pull requests
  - Security report generation

- yaml-lint.yml: YAML formatting and validation
  - yamllint with custom configuration
  - File extension consistency checks
  - YAML structure validation
  - Service naming convention checks
  - Docker Compose version validation

- documentation.yml: Documentation quality checks
  - Markdown linting
  - Link validation
  - README completeness verification
  - Service documentation checks
  - Domain URL validation

- auto-label.yml: Automated PR labeling
  - Category-based labeling (core/media/services)
  - File type detection
  - Size-based labeling
  - Security-related changes detection

Configuration Files:
- .yamllint.yml: YAML linting rules for Docker Compose
- .markdownlint.json: Markdown formatting rules
- .markdown-link-check.json: Link checking configuration
- .pre-commit-config.yaml: Pre-commit hooks setup
- .github/labeler.yml: Auto-labeler configuration
- .github/CODEOWNERS: Code ownership definitions

Templates:
- pull_request_template.md: Comprehensive PR checklist
- ISSUE_TEMPLATE/bug-report.md: Bug report template
- ISSUE_TEMPLATE/service-request.md: New service request template

Documentation:
- SECURITY.md: Security policy and best practices
- CONTRIBUTING.md: Contribution guidelines

Benefits:
- Automated validation of all compose files
- Security scanning on every PR
- Consistent code formatting
- Documentation quality assurance
- Automated issue/PR management
- Pre-commit hooks for local validation
- Comprehensive security policy
- Clear contribution guidelines
This commit is contained in:
Claude 2025-11-05 20:09:33 +00:00
parent fd48fed9d8
commit 1c3b7e53a1
No known key found for this signature in database
16 changed files with 1554 additions and 0 deletions

19
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1,19 @@
# CODEOWNERS file
# These owners will be the default owners for everything in the repo
# Global owners
* @efigueroa
# Core infrastructure requires careful review
/compose/core/ @efigueroa
# Security-sensitive files
**/.env @efigueroa
.github/workflows/security-checks.yml @efigueroa
# Documentation
/README.md @efigueroa
/docs/ @efigueroa
# CI/CD
/.github/ @efigueroa

64
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View file

@ -0,0 +1,64 @@
---
name: Bug Report
about: Report a bug or issue with a service
title: '[BUG] '
labels: 'bug'
assignees: ''
---
## Bug Description
<!-- A clear and concise description of what the bug is -->
## Service Affected
**Service Name:**
**Service Location:** (e.g., compose/media/frontend/jellyfin)
## Steps to Reproduce
1.
2.
3.
## Expected Behavior
<!-- What should happen -->
## Actual Behavior
<!-- What actually happens -->
## Logs
```
# Paste relevant logs here
# docker compose logs [service]
```
## Environment
- Docker version:
- Docker Compose version:
- Host OS:
## Configuration
**compose.yaml:**
```yaml
# Paste relevant parts of your compose file
```
**.env (sanitized):**
```bash
# Paste relevant environment variables (remove passwords!)
```
## Additional Context
<!-- Any other context about the problem -->
## Possible Solution
<!-- If you have ideas on how to fix this -->

View file

@ -0,0 +1,63 @@
---
name: Service Request
about: Request a new service to be added to the homelab
title: '[SERVICE] '
labels: 'enhancement, service-request'
assignees: ''
---
## Service Information
**Service Name:**
**Official Website/Docs:**
**Docker Image:**
**Category:** (core/media/services)
## Description
<!-- What does this service do? Why would it be useful? -->
## Configuration Requirements
**Required Environment Variables:**
```
VAR_NAME=description
```
**Required Volumes:**
```
/path/to/data:/container/path
```
**Required Ports:**
- Port: (if any need to be exposed)
**Dependencies:**
- Database: (postgres/mysql/redis/etc.)
- Other services:
## Integration Requirements
**Domain Preference:**
<!-- e.g., service.fig.systems -->
**SSO Protected:** Yes/No
**Access to /media folders:** (list which ones if needed)
- [ ] /media/movies
- [ ] /media/tv
- [ ] /media/music
- [ ] /media/books
- [ ] /media/photos
- [ ] Other:
## Additional Context
<!-- Any other relevant information -->
## Security Considerations
<!-- Any security concerns or special configurations needed? -->

47
.github/labeler.yml vendored Normal file
View file

@ -0,0 +1,47 @@
# Auto labeler configuration
'category: core':
- compose/core/**/*
'category: media':
- compose/media/**/*
'category: services':
- compose/services/**/*
'type: documentation':
- '**/*.md'
- docs/**/*
'type: configuration':
- '**/*.yaml'
- '**/*.yml'
- '**/*.env'
'type: ci/cd':
- .github/**/*
- .pre-commit-config.yaml
'security':
- '**/*.env'
- '**/secrets/**/*'
'traefik':
- compose/core/traefik/**/*
- any:
- changed-files:
- any-glob-to-any-file: 'compose/**/compose.yaml'
changed-lines:
- pattern: 'traefik\.'
'authentication':
- compose/core/lldap/**/*
- compose/core/tinyauth/**/*
'dependencies':
- '**/compose.yaml'
- any:
- changed-files:
- any-glob-to-any-file: 'compose/**/compose.yaml'
changed-lines:
- pattern: 'image:'

96
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,96 @@
## Description
<!-- Provide a brief description of what this PR does -->
## Type of Change
<!-- Mark the relevant option with an "x" -->
- [ ] New service addition
- [ ] Service configuration update
- [ ] Bug fix
- [ ] Documentation update
- [ ] Security fix
- [ ] Infrastructure change
## Changes Made
<!-- List the main changes in this PR -->
-
-
-
## Checklist
<!-- Mark completed items with an "x" -->
### General
- [ ] All compose files use `compose.yaml` (not `.yml`)
- [ ] Code follows Docker Compose best practices
- [ ] Changes tested locally
- [ ] Documentation updated (README.md)
### Services (if applicable)
- [ ] Service added to correct category (core/media/services)
- [ ] Proper network configuration (homelab + internal if needed)
- [ ] Volumes properly configured
- [ ] Environment variables use `.env` file or are documented
### Traefik & SSL (if applicable)
- [ ] Traefik labels configured correctly
- [ ] Uses `websecure` entrypoint
- [ ] Let's Encrypt cert resolver configured
- [ ] Both domains configured (`fig.systems` and `edfig.dev`)
- [ ] SSO middleware applied (if appropriate)
### Security
- [ ] No secrets committed in `.env` files
- [ ] Placeholder passwords use `changeme_*` format
- [ ] No sensitive data in compose files
- [ ] Container runs as non-root user (where possible)
### Documentation
- [ ] Service added to README.md service table
- [ ] Deployment instructions added/updated
- [ ] Configuration requirements documented
- [ ] Comments added to compose file explaining purpose
## Testing
<!-- Describe how you tested these changes -->
```bash
# Commands used to test:
# Expected behavior:
# Actual behavior:
```
## Screenshots (if applicable)
<!-- Add screenshots of the service running, configuration, etc. -->
## Related Issues
<!-- Link any related issues: Fixes #123, Closes #456 -->
## Additional Notes
<!-- Any additional context, breaking changes, migration notes, etc. -->
---
## For Reviewers
<!-- Automatically checked by CI/CD -->
- [ ] All CI checks pass
- [ ] Docker Compose validation passes
- [ ] YAML linting passes
- [ ] Security scans pass
- [ ] No security vulnerabilities introduced

44
.github/workflows/auto-label.yml vendored Normal file
View file

@ -0,0 +1,44 @@
name: Auto Label
on:
pull_request:
types: [opened, edited, synchronize]
jobs:
label-pr:
name: Auto Label PR
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Label based on changed files
uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/labeler.yml
size-label:
name: PR Size Label
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Label by size
uses: codelytv/pr-size-labeler@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
xs_label: 'size/xs'
xs_max_size: 10
s_label: 'size/s'
s_max_size: 100
m_label: 'size/m'
m_max_size: 500
l_label: 'size/l'
l_max_size: 1000
xl_label: 'size/xl'

View file

@ -0,0 +1,159 @@
name: Docker Compose Validation
on:
pull_request:
paths:
- 'compose/**/*.yaml'
- 'compose/**/*.yml'
- '.github/workflows/docker-compose-validation.yml'
push:
branches:
- main
paths:
- 'compose/**/*.yaml'
- 'compose/**/*.yml'
jobs:
validate-compose-files:
name: Validate Docker Compose Files
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Find all compose files
id: find-files
run: |
echo "Finding all compose.yaml files..."
find compose -name "compose.yaml" -type f > compose-files.txt
cat compose-files.txt
echo "Found $(wc -l < compose-files.txt) compose files"
- name: Validate compose file syntax
run: |
echo "Validating Docker Compose files..."
exit_code=0
while IFS= read -r file; do
echo "Validating: $file"
if docker compose -f "$file" config > /dev/null 2>&1; then
echo "✅ Valid: $file"
else
echo "❌ Invalid: $file"
docker compose -f "$file" config
exit_code=1
fi
done < compose-files.txt
exit $exit_code
- name: Check for old .yml files
run: |
old_files=$(find compose -name "compose.yml" -o -name "docker-compose.yml" 2>/dev/null || true)
if [ -n "$old_files" ]; then
echo "❌ Found deprecated .yml files (should be .yaml):"
echo "$old_files"
exit 1
else
echo "✅ All compose files use .yaml extension"
fi
- name: Validate network references
run: |
echo "Checking network references..."
exit_code=0
while IFS= read -r file; do
# Check if file uses 'homelab' network
if grep -q "networks:" "$file"; then
if grep -q "homelab" "$file"; then
# Ensure it's marked as external
if grep -A 5 "^networks:" "$file" | grep -A 2 "homelab:" | grep -q "external: true"; then
echo "✅ $file: homelab network properly marked as external"
else
echo "⚠️ $file: homelab network should be marked as external: true"
exit_code=1
fi
fi
fi
done < compose-files.txt
exit $exit_code
- name: Check for exposed ports
run: |
echo "Checking for unnecessary port exposures..."
files_with_ports=$(grep -l "ports:" compose/**/compose.yaml || true)
if [ -n "$files_with_ports" ]; then
echo "⚠️ Files with exposed ports (consider using Traefik only):"
echo "$files_with_ports"
echo ""
echo "This is a warning, not an error. Review if these ports need to be exposed."
else
echo "✅ No ports exposed (using Traefik for all routing)"
fi
- name: Validate Traefik labels
run: |
echo "Validating Traefik labels..."
exit_code=0
while IFS= read -r file; do
if grep -q "traefik.enable: true" "$file"; then
# Check for required labels
has_router=$(grep -q "traefik.http.routers\." "$file" && echo "yes" || echo "no")
has_entrypoint=$(grep -q "entrypoints: websecure" "$file" && echo "yes" || echo "no")
has_tls=$(grep -q "tls.certresolver: letsencrypt" "$file" && echo "yes" || echo "no")
has_rule=$(grep -q "\.rule: Host" "$file" && echo "yes" || echo "no")
if [ "$has_router" = "yes" ] && [ "$has_entrypoint" = "yes" ] && [ "$has_tls" = "yes" ] && [ "$has_rule" = "yes" ]; then
echo "✅ $file: Complete Traefik configuration"
else
echo "⚠️ $file: Incomplete Traefik configuration"
[ "$has_router" = "no" ] && echo " - Missing router definition"
[ "$has_entrypoint" = "no" ] && echo " - Missing websecure entrypoint"
[ "$has_tls" = "no" ] && echo " - Missing TLS/Let's Encrypt config"
[ "$has_rule" = "no" ] && echo " - Missing Host rule"
exit_code=1
fi
fi
done < compose-files.txt
exit $exit_code
- name: Check domain consistency
run: |
echo "Checking domain consistency..."
# Extract all domains from Traefik rules
domains=$(grep -h "rule: Host" compose/**/compose.yaml | grep -oP '`\K[^`]+' | sort -u)
echo "Configured domains:"
echo "$domains"
# Check that both fig.systems and edfig.dev are used
fig_systems_count=$(echo "$domains" | grep -c "fig.systems" || true)
edfig_dev_count=$(echo "$domains" | grep -c "edfig.dev" || true)
echo ""
echo "fig.systems domains: $fig_systems_count"
echo "edfig.dev domains: $edfig_dev_count"
# Check for services that don't have both domains
while IFS= read -r file; do
if grep -q "traefik.enable: true" "$file"; then
has_fig_systems=$(grep "rule: Host" "$file" | grep -c "fig.systems" || true)
has_edfig_dev=$(grep "rule: Host" "$file" | grep -c "edfig.dev" || true)
if [ "$has_fig_systems" -gt 0 ] && [ "$has_edfig_dev" -eq 0 ]; then
echo "⚠️ $file: Has fig.systems but missing edfig.dev"
elif [ "$has_edfig_dev" -gt 0 ] && [ "$has_fig_systems" -eq 0 ]; then
echo "⚠️ $file: Has edfig.dev but missing fig.systems"
fi
fi
done < compose-files.txt

177
.github/workflows/documentation.yml vendored Normal file
View file

@ -0,0 +1,177 @@
name: Documentation Checks
on:
pull_request:
paths:
- '**.md'
- 'compose/**/*.yaml'
- '.github/workflows/documentation.yml'
push:
branches:
- main
jobs:
markdown-lint:
name: Markdown Linting
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Lint Markdown files
uses: nosborn/github-action-markdown-cli@v3.3.0
with:
files: .
config_file: .markdownlint.json
ignore_files: node_modules/
continue-on-error: true # Don't fail CI on markdown issues
link-check:
name: Check Links in Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check links in README
uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
use-quiet-mode: 'yes'
config-file: '.markdown-link-check.json'
continue-on-error: true
readme-sync:
name: Validate README is up-to-date
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check README completeness
run: |
echo "Checking if README.md documents all services..."
# Extract service names from compose files
services=$(find compose -name "compose.yaml" -exec dirname {} \; | \
sed 's|compose/||' | \
sort -u)
echo "Services found in repository:"
echo "$services"
missing_services=""
# Check if each service is mentioned in README
while IFS= read -r service; do
service_name=$(basename "$service")
if ! grep -qi "$service_name" README.md; then
echo "⚠️ Service '$service_name' not found in README.md"
missing_services="${missing_services}${service}\n"
fi
done <<< "$services"
if [ -n "$missing_services" ]; then
echo ""
echo "⚠️ The following services are not documented in README.md:"
echo -e "$missing_services"
echo ""
echo "This is a warning. Consider updating the README."
else
echo "✅ All services are documented in README.md"
fi
- name: Check for required sections
run: |
echo "Checking for required README sections..."
required_sections=(
"Infrastructure"
"Directory Structure"
"Domains"
"Deployment"
"Security"
)
missing_sections=""
for section in "${required_sections[@]}"; do
if ! grep -qi "$section" README.md; then
echo "❌ Missing section: $section"
missing_sections="${missing_sections}${section}\n"
else
echo "✅ Found section: $section"
fi
done
if [ -n "$missing_sections" ]; then
echo ""
echo "❌ Missing required sections in README.md:"
echo -e "$missing_sections"
exit 1
fi
service-documentation:
name: Check Service Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check for service-level documentation
run: |
echo "Checking that compose files have documentation comments..."
compose_files=$(find compose -name "compose.yaml" -type f)
for file in $compose_files; do
# Check if file has a comment at the top
if head -n 1 "$file" | grep -q "^#"; then
echo "✅ $file has documentation"
else
echo "⚠️ $file missing documentation header"
fi
# Check for docs URLs
if grep -q "# Docs:" "$file"; then
echo " ✅ Has docs link"
fi
done
- name: Validate domain URLs in README
run: |
echo "Validating service URLs in README match compose files..."
# Extract URLs from README (simple check)
readme_domains=$(grep -oP '\b\w+\.fig\.systems\b' README.md | sort -u)
echo "Domains in README:"
echo "$readme_domains"
# Extract domains from compose files
compose_domains=$(grep -h "rule: Host" compose/**/compose.yaml | \
grep -oP '\b\w+\.fig\.systems\b' | \
sort -u)
echo ""
echo "Domains in compose files:"
echo "$compose_domains"
# Basic comparison
readme_count=$(echo "$readme_domains" | wc -l)
compose_count=$(echo "$compose_domains" | wc -l)
echo ""
echo "README domains: $readme_count"
echo "Compose domains: $compose_count"
if [ "$readme_count" -ne "$compose_count" ]; then
echo "⚠️ Domain count mismatch between README and compose files"
echo "This may indicate outdated documentation."
else
echo "✅ Domain counts match"
fi

193
.github/workflows/security-checks.yml vendored Normal file
View file

@ -0,0 +1,193 @@
name: Security Checks
on:
pull_request:
push:
branches:
- main
jobs:
secret-scanning:
name: Secret Detection
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_ENABLE_COMMENTS: false
env-file-validation:
name: Environment File Validation
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check for placeholder passwords
run: |
echo "Checking for placeholder passwords in .env files..."
exit_code=0
# Find all .env files
env_files=$(find compose -name ".env" -type f)
for env_file in $env_files; do
echo "Checking: $env_file"
# Check for common placeholder passwords
if grep -E "(changeme|password123|admin123|test123|example)" "$env_file" > /dev/null 2>&1; then
echo "⚠️ WARNING: $env_file contains placeholder passwords"
echo " This is expected in the repository template."
echo " Users should change these before deployment."
fi
# Check for actual leaked passwords (common patterns)
if grep -E "PASSWORD=.{20,}" "$env_file" | grep -v "changeme" > /dev/null 2>&1; then
echo "❌ ERROR: $env_file may contain a real password!"
exit_code=1
fi
done
exit $exit_code
- name: Validate environment file format
run: |
echo "Validating .env file format..."
exit_code=0
env_files=$(find compose -name ".env" -type f)
for env_file in $env_files; do
# Check for common .env issues
if grep -E "^\s+[A-Z_]+=.*" "$env_file" > /dev/null 2>&1; then
echo "❌ $env_file: Contains indented variables (should not be indented)"
exit_code=1
fi
if grep -E "^[A-Z_]+=\s+.*" "$env_file" > /dev/null 2>&1; then
echo "⚠️ $env_file: Contains space after = (may cause issues)"
fi
# Check for commented-out critical variables
if grep -E "^#\s*(DATABASE_URL|POSTGRES_PASSWORD|JWT_SECRET|SESSION_SECRET)=" "$env_file" > /dev/null 2>&1; then
echo "⚠️ $env_file: Critical variables are commented out"
fi
done
if [ $exit_code -eq 0 ]; then
echo "✅ All .env files are properly formatted"
fi
exit $exit_code
dockerfile-security:
name: Dockerfile Security Scan
runs-on: ubuntu-latest
if: false # Disabled since we use pre-built images, enable if you add custom Dockerfiles
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Hadolint
uses: hadolint/hadolint-action@v3.1.0
with:
dockerfile: "**/Dockerfile"
failure-threshold: warning
container-image-scan:
name: Container Image Vulnerability Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Extract images from compose files
id: extract-images
run: |
echo "Extracting container images..."
# Extract unique images from all compose files
images=$(grep -h "^\s*image:" compose/**/compose.yaml | \
awk '{print $2}' | \
grep -v "^$" | \
sort -u)
echo "Found images:"
echo "$images"
# Save to file for next step
echo "$images" > images.txt
# Count images
image_count=$(echo "$images" | wc -l)
echo "total_images=$image_count" >> $GITHUB_OUTPUT
- name: Scan critical images with Trivy
run: |
# Install Trivy
sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo gpg --dearmor -o /usr/share/keyrings/trivy.gpg
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy
# Scan a sample of critical images (to avoid long CI times)
critical_images=(
"traefik:v3.3"
"lldap/lldap:stable"
"ghcr.io/steveiliop56/tinyauth:latest"
"postgres:16-alpine"
"postgres:18"
"redis:alpine"
)
echo "Scanning critical images for HIGH and CRITICAL vulnerabilities..."
for image in "${critical_images[@]}"; do
# Check if image is used in our compose files
if grep -q "$image" images.txt 2>/dev/null; then
echo "Scanning: $image"
trivy image --severity HIGH,CRITICAL --exit-code 0 "$image" || true
fi
done
- name: Generate security report
if: always()
run: |
echo "# Security Scan Summary" > security-report.md
echo "" >> security-report.md
echo "## Images Scanned" >> security-report.md
echo "Total unique images: $(cat images.txt | wc -l)" >> security-report.md
echo "" >> security-report.md
echo "See job logs for detailed vulnerability information." >> security-report.md
- name: Upload security report
if: always()
uses: actions/upload-artifact@v4
with:
name: security-report
path: security-report.md
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Dependency Review
uses: actions/dependency-review-action@v4
with:
fail-on-severity: moderate

135
.github/workflows/yaml-lint.yml vendored Normal file
View file

@ -0,0 +1,135 @@
name: YAML Linting
on:
pull_request:
paths:
- '**.yaml'
- '**.yml'
- '.yamllint.yml'
push:
branches:
- main
paths:
- '**.yaml'
- '**.yml'
jobs:
yamllint:
name: YAML Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install yamllint
run: pip install yamllint
- name: Run yamllint
run: |
yamllint -f colored compose/
- name: Check YAML file extensions
run: |
echo "Checking for consistent YAML file extensions..."
# Find all YAML files
yaml_files=$(find . -name "*.yaml" -o -name "*.yml" | grep -v ".git" | grep -v "node_modules")
# Count by extension
yaml_count=$(find . -name "*.yaml" | grep -v ".git" | wc -l)
yml_count=$(find . -name "*.yml" | grep -v ".git" | wc -l)
echo "Files with .yaml extension: $yaml_count"
echo "Files with .yml extension: $yml_count"
# Check for any .yml files in compose directory (should be .yaml)
yml_in_compose=$(find compose -name "*.yml" 2>/dev/null | wc -l)
if [ $yml_in_compose -gt 0 ]; then
echo "❌ Found .yml files in compose directory (should be .yaml):"
find compose -name "*.yml"
exit 1
else
echo "✅ All compose files use .yaml extension"
fi
validate-yaml-structure:
name: Validate YAML Structure
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install yq
run: |
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
- name: Validate YAML syntax
run: |
echo "Validating YAML syntax for all files..."
exit_code=0
yaml_files=$(find compose -name "*.yaml" -type f)
for file in $yaml_files; do
if yq eval '.' "$file" > /dev/null 2>&1; then
echo "✅ Valid YAML: $file"
else
echo "❌ Invalid YAML: $file"
yq eval '.' "$file"
exit_code=1
fi
done
exit $exit_code
- name: Check for Docker Compose version
run: |
echo "Checking for version field in compose files..."
yaml_files=$(find compose -name "compose.yaml" -type f)
for file in $yaml_files; do
# Docker Compose v2+ doesn't require version field
# But check if it's present and warn if it's old
version=$(yq eval '.version // "none"' "$file")
if [ "$version" != "none" ]; then
echo "⚠️ $file: Contains version field (not needed in Compose v2+)"
if [ "$version" = "2" ] || [ "$version" = "2.0" ]; then
echo " Consider removing or updating to version 3+"
fi
fi
done
- name: Validate service names
run: |
echo "Checking service naming conventions..."
yaml_files=$(find compose -name "compose.yaml" -type f)
for file in $yaml_files; do
services=$(yq eval '.services | keys | .[]' "$file" 2>/dev/null)
for service in $services; do
# Check for invalid characters in service names
if echo "$service" | grep -qE '[^a-zA-Z0-9_-]'; then
echo "❌ $file: Service '$service' has invalid characters"
echo " Service names should only contain: a-z, A-Z, 0-9, _, -"
exit 1
fi
# Check for recommended naming (lowercase with hyphens)
if echo "$service" | grep -qE '[A-Z]'; then
echo "⚠️ $file: Service '$service' contains uppercase (consider lowercase with hyphens)"
fi
done
done

18
.markdown-link-check.json Normal file
View file

@ -0,0 +1,18 @@
{
"ignorePatterns": [
{
"pattern": "^http://localhost"
},
{
"pattern": "^https://.*\\.fig\\.systems"
},
{
"pattern": "^https://.*\\.edfig\\.dev"
}
],
"timeout": "20s",
"retryOn429": true,
"retryCount": 3,
"fallbackRetryDelay": "30s",
"aliveStatusCodes": [200, 206, 429]
}

13
.markdownlint.json Normal file
View file

@ -0,0 +1,13 @@
{
"default": true,
"MD013": {
"line_length": 200,
"code_blocks": false,
"tables": false
},
"MD033": false,
"MD041": false,
"MD024": {
"siblings_only": true
}
}

65
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,65 @@
# Pre-commit hooks for homelab repository
# Install: pip install pre-commit
# Setup: pre-commit install
# Run manually: pre-commit run --all-files
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
exclude: '.md$'
- id: end-of-file-fixer
- id: check-yaml
args: ['--allow-multiple-documents']
- id: check-added-large-files
args: ['--maxkb=1000']
- id: check-merge-conflict
- id: detect-private-key
- id: mixed-line-ending
- repo: https://github.com/adrienverge/yamllint
rev: v1.35.1
hooks:
- id: yamllint
args: ['-c', '.yamllint.yml']
files: \.(yaml|yml)$
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.2
hooks:
- id: gitleaks
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.39.0
hooks:
- id: markdownlint
args: ['--config', '.markdownlint.json']
- repo: local
hooks:
- id: check-compose-filenames
name: Check compose file naming
entry: bash -c 'find compose -name "compose.yml" -o -name "docker-compose.yml" | grep . && exit 1 || exit 0'
language: system
pass_filenames: false
always_run: true
- id: check-placeholder-passwords
name: Check for non-placeholder passwords
entry: bash -c 'git diff --cached --name-only | grep "\.env$" | xargs grep -E "PASSWORD=.{20,}" | grep -v changeme && exit 1 || exit 0'
language: system
pass_filenames: false
always_run: false
- id: validate-traefik-labels
name: Validate Traefik labels
entry: bash -c 'for file in $(git diff --cached --name-only | grep "compose.yaml$"); do if grep -q "traefik.enable: true" "$file"; then grep -q "entrypoints: websecure" "$file" || { echo "Missing websecure entrypoint in $file"; exit 1; }; fi; done'
language: system
pass_filenames: false
- id: check-env-files
name: Check .env files exist for services with env_file
entry: bash -c 'for file in $(git diff --cached --name-only | grep "compose.yaml$"); do if grep -q "env_file:" "$file"; then dir=$(dirname "$file"); if [ ! -f "$dir/.env" ]; then echo "Missing .env file for $file"; exit 1; fi; fi; done'
language: system
pass_filenames: false

53
.yamllint.yml Normal file
View file

@ -0,0 +1,53 @@
---
# yamllint configuration for Docker Compose files
extends: default
rules:
# Line length - Docker Compose files can have long lines (especially for commands)
line-length:
max: 200
level: warning
# Allow multiple spaces for alignment
colons:
max-spaces-after: 1
# Indentation - Docker Compose uses 2 spaces
indentation:
spaces: 2
indent-sequences: true
# Comments
comments:
min-spaces-from-content: 2
# Document start - not required for Docker Compose
document-start: disable
# Allow truthy values for Docker Compose (yes/no, true/false, on/off)
truthy:
allowed-values: ['true', 'false', 'yes', 'no', 'on', 'off']
check-keys: false
# Brackets
brackets:
min-spaces-inside: 0
max-spaces-inside: 0
# Allow empty values
empty-values:
forbid-in-block-mappings: false
forbid-in-flow-mappings: false
# Key ordering - not enforced
key-ordering: disable
# Allow duplicate keys (sometimes needed in Docker labels)
key-duplicates:
forbid-duplicated-merge-keys: true
ignore: |
.github/
node_modules/
venv/

264
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,264 @@
# Contributing Guide
Thank you for your interest in contributing to this homelab configuration! While this is primarily a personal repository, contributions are welcome.
## How to Contribute
### Reporting Issues
- Use the [bug report template](.github/ISSUE_TEMPLATE/bug-report.md) for bugs
- Use the [service request template](.github/ISSUE_TEMPLATE/service-request.md) for new services
- Search existing issues before creating a new one
- Provide as much detail as possible
### Submitting Changes
1. **Fork the repository**
2. **Create a feature branch**
```bash
git checkout -b feature/your-feature-name
```
3. **Make your changes** following the guidelines below
4. **Test your changes** locally
5. **Commit with clear messages**
```bash
git commit -m "feat: add new service"
```
6. **Push to your fork**
```bash
git push origin feature/your-feature-name
```
7. **Open a Pull Request** using the PR template
## Guidelines
### File Naming
- All Docker Compose files must be named `compose.yaml` (not `.yml`)
- Use lowercase with hyphens for service directories (e.g., `calibre-web`)
- Environment files must be named `.env`
### Docker Compose Best Practices
- Use version-pinned images when possible
- Include health checks for databases and critical services
- Use bind mounts for configuration, named volumes for data
- Set proper restart policies (`unless-stopped` or `always`)
- Include resource limits for production services
### Network Configuration
- All services must use the `homelab` network (marked as `external: true`)
- Services with multiple containers should use an internal network
- Example:
```yaml
networks:
homelab:
external: true
service_internal:
name: service_internal
driver: bridge
```
### Traefik Labels
All web services must include:
```yaml
labels:
traefik.enable: true
traefik.http.routers.service.rule: Host(`service.fig.systems`) || Host(`service.edfig.dev`)
traefik.http.routers.service.entrypoints: websecure
traefik.http.routers.service.tls.certresolver: letsencrypt
traefik.http.services.service.loadbalancer.server.port: 8080
# Optional SSO:
traefik.http.routers.service.middlewares: tinyauth
```
### Environment Variables
- Use `.env` files for configuration
- Never commit real passwords
- Use `changeme_*` prefix for placeholder passwords
- Document all required environment variables
- Include comments explaining non-obvious settings
### Documentation
- Add service to README.md service table
- Include deployment instructions
- Document any special configuration
- Add comments to compose files explaining purpose
- Include links to official documentation
### Security
- Never commit secrets
- Scan compose files for vulnerabilities
- Use official or well-maintained images
- Enable SSO when appropriate
- Document security considerations
## Code Style
### YAML Style
- 2-space indentation
- No trailing whitespace
- Use `true/false` instead of `yes/no`
- Quote strings with special characters
- Follow yamllint rules in `.yamllint.yml`
### Commit Messages
Follow [Conventional Commits](https://www.conventionalcommits.org/):
- `feat:` New feature
- `fix:` Bug fix
- `docs:` Documentation changes
- `refactor:` Code refactoring
- `security:` Security improvements
- `chore:` Maintenance tasks
Examples:
```
feat: add jellyfin media server
fix: correct traefik routing for sonarr
docs: update README with new services
security: update postgres to latest version
```
## Testing
Before submitting a PR:
1. **Validate compose files**
```bash
docker compose -f compose/path/to/compose.yaml config
```
2. **Check YAML syntax**
```bash
yamllint compose/
```
3. **Test locally**
```bash
docker compose up -d
docker compose logs
```
4. **Check for secrets**
```bash
git diff --cached | grep -i "password\|secret\|token"
```
5. **Run pre-commit hooks** (optional)
```bash
pre-commit install
pre-commit run --all-files
```
## Pull Request Process
1. Fill out the PR template completely
2. Ensure all CI checks pass
3. Request review if needed
4. Address review feedback
5. Squash commits if requested
6. Wait for approval and merge
## CI/CD Checks
Your PR will be automatically checked for:
- Docker Compose validation
- YAML linting
- Security scanning
- Secret detection
- Documentation completeness
- Traefik configuration
- Network setup
- File naming conventions
Fix any failures before requesting review.
## Adding a New Service
1. Choose the correct category:
- `compose/core/` - Infrastructure (Traefik, auth, etc.)
- `compose/media/` - Media-related services
- `compose/services/` - Utility services
2. Create service directory:
```bash
mkdir -p compose/category/service-name
```
3. Create `compose.yaml`:
- Include documentation header
- Add Traefik labels
- Configure networks
- Set up volumes
- Add health checks if applicable
4. Create `.env` if needed:
- Use placeholder passwords
- Document all variables
- Include comments
5. Update README.md:
- Add to service table
- Include URL
- Document deployment
6. Test deployment:
```bash
cd compose/category/service-name
docker compose up -d
docker compose logs -f
```
7. Create PR with detailed description
## Project Structure
```
homelab/
├── .github/
│ ├── workflows/ # CI/CD workflows
│ ├── ISSUE_TEMPLATE/ # Issue templates
│ └── pull_request_template.md
├── compose/
│ ├── core/ # Infrastructure services
│ ├── media/ # Media services
│ └── services/ # Utility services
├── README.md # Main documentation
├── CONTRIBUTING.md # This file
├── SECURITY.md # Security policy
└── .yamllint.yml # YAML linting config
```
## Getting Help
- Check existing issues and PRs
- Review the README.md
- Examine similar services for examples
- Ask in PR comments
## License
By contributing, you agree that your contributions will be licensed under the same terms as the repository.
## Code of Conduct
- Be respectful and professional
- Focus on constructive feedback
- Help others learn and improve
- Keep discussions relevant
## Questions?
Open an issue with the question label or comment on an existing PR/issue.
Thank you for contributing! 🎉

144
SECURITY.md Normal file
View file

@ -0,0 +1,144 @@
# Security Policy
## Supported Versions
This is a personal homelab configuration repository. The latest commit on `main` is always the supported version.
| Branch | Supported |
| ------ | ------------------ |
| main | :white_check_mark: |
| other | :x: |
## Security Considerations
### Secrets Management
**DO NOT commit secrets to this repository!**
- All passwords in `.env` files should use placeholder values (e.g., `changeme_*`)
- Real passwords should only be set in your local deployment
- Use environment variables or Docker secrets for sensitive data
- Never commit files containing real credentials
### Container Security
- All container images are scanned for vulnerabilities via GitHub Actions
- HIGH and CRITICAL vulnerabilities are reported in security scans
- Keep images up to date by pulling latest versions regularly
- Review security scan results before deploying
### Network Security
- All services are behind Traefik reverse proxy
- SSL/TLS is enforced via Let's Encrypt
- Internal services use isolated Docker networks
- SSO is enabled on most services via Tinyauth
### Authentication
- LLDAP provides centralized user management
- Tinyauth handles SSO authentication
- Services with built-in authentication are documented in README
- Change all default passwords before deployment
## Reporting a Vulnerability
If you discover a security vulnerability in this configuration:
1. **DO NOT** open a public issue
2. Contact the repository owner directly via GitHub private message
3. Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
### What to Report
- Exposed secrets or credentials
- Insecure configurations
- Vulnerable container images (not already detected by CI)
- Authentication bypasses
- Network security issues
### What NOT to Report
- Issues with third-party services (report to their maintainers)
- Theoretical vulnerabilities without proof of concept
- Social engineering attempts
## Security Best Practices
### Before Deployment
1. **Change all passwords** in `.env` files
2. **Review** all service configurations
3. **Update** container images to latest versions
4. **Configure** firewall to only allow ports 80/443
5. **Enable** automatic security updates on host OS
### After Deployment
1. **Monitor** logs regularly for suspicious activity
2. **Update** services monthly (at minimum)
3. **Backup** data regularly
4. **Review** access logs
5. **Test** disaster recovery procedures
### Network Hardening
- Use a firewall (ufw, iptables, etc.)
- Only expose ports 80 and 443 to the internet
- Consider using a VPN for administrative access
- Enable fail2ban or similar intrusion prevention
- Use strong DNS providers with DNSSEC
### Container Hardening
- Run containers as non-root when possible
- Use read-only filesystems where applicable
- Limit container resources (CPU, memory)
- Enable security options (no-new-privileges, etc.)
- Regularly scan for vulnerabilities
## Automated Security Scanning
This repository includes automated security scanning:
- **Gitleaks**: Detects secrets in commits
- **Trivy**: Scans container images for vulnerabilities
- **YAML Linting**: Ensures proper configuration
- **Dependency Review**: Checks for vulnerable dependencies
Review GitHub Actions results before merging PRs.
## Compliance
This is a personal homelab configuration and does not claim compliance with any specific security standards. However, it follows general security best practices:
- Principle of least privilege
- Defense in depth
- Secure by default
- Regular updates and patching
## External Dependencies
Security of this setup depends on:
- Docker and Docker Compose security
- Container image maintainers
- Traefik security
- LLDAP security
- Host OS security
Always keep these dependencies up to date.
## Disclaimer
This configuration is provided "as is" without warranty. Use at your own risk. The maintainer is not responsible for any security incidents resulting from the use of this configuration.
## Additional Resources
- [Docker Security Best Practices](https://docs.docker.com/engine/security/)
- [Traefik Security Documentation](https://doc.traefik.io/traefik/https/overview/)
- [OWASP Container Security](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html)