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:
parent
fd48fed9d8
commit
1c3b7e53a1
16 changed files with 1554 additions and 0 deletions
19
.github/CODEOWNERS
vendored
Normal file
19
.github/CODEOWNERS
vendored
Normal 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
64
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal 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 -->
|
||||
63
.github/ISSUE_TEMPLATE/service-request.md
vendored
Normal file
63
.github/ISSUE_TEMPLATE/service-request.md
vendored
Normal 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
47
.github/labeler.yml
vendored
Normal 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
96
.github/pull_request_template.md
vendored
Normal 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
44
.github/workflows/auto-label.yml
vendored
Normal 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'
|
||||
159
.github/workflows/docker-compose-validation.yml
vendored
Normal file
159
.github/workflows/docker-compose-validation.yml
vendored
Normal 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
177
.github/workflows/documentation.yml
vendored
Normal 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
193
.github/workflows/security-checks.yml
vendored
Normal 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
135
.github/workflows/yaml-lint.yml
vendored
Normal 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
18
.markdown-link-check.json
Normal 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
13
.markdownlint.json
Normal 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
65
.pre-commit-config.yaml
Normal 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
53
.yamllint.yml
Normal 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
264
CONTRIBUTING.md
Normal 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
144
SECURITY.md
Normal 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)
|
||||
Loading…
Reference in a new issue