Skip to content

Commit f90c383

Browse files
committed
feat: add Docker support and improved systemd configuration
Features: - Complete Docker containerization with s6-overlay init - PUID/PGID support for proper file permissions - Multi-architecture builds (amd64/arm64) - GitHub Actions CI/CD pipeline for ghcr.io publishing - OpenContainer labels for proper metadata Systemd improvements: - Template-based service for multiple subreddits - Per-subreddit config files in /etc/redditmodlog/ - Centralized logging to /var/log/redditmodlog/ - Automatic log rotation (30 days, 100MB max) - Security hardening with read-only filesystem - Resource limits (256MB RAM, 25% CPU) Infrastructure: - Installation script for easy deployment - Logrotate configuration included - Enhanced .gitignore for sensitive files - Updated README with Docker and systemd documentation This provides production-ready deployment options for both Docker and systemd environments.
1 parent 8cfe481 commit f90c383

File tree

6 files changed

+470
-18
lines changed

6 files changed

+470
-18
lines changed

.dockerignore

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Git files
2+
.git
3+
.gitignore
4+
.github
5+
6+
# Documentation
7+
README.md
8+
CLAUDE.md
9+
LICENSE
10+
CHANGELOG.md
11+
*.md
12+
13+
# Development files
14+
*.pyc
15+
__pycache__
16+
.pytest_cache
17+
.coverage
18+
htmlcov
19+
.env
20+
.env.*
21+
22+
# IDE files
23+
.vscode
24+
.idea
25+
*.swp
26+
*.swo
27+
*.swn
28+
.DS_Store
29+
30+
# Local data and logs
31+
data/
32+
logs/
33+
*.db
34+
*.log
35+
36+
# Config files (except template)
37+
config.json
38+
39+
# Test files
40+
test_*.py
41+
tests/
42+
debug_*.py
43+
44+
# Build artifacts
45+
build/
46+
dist/
47+
*.egg-info/
48+
49+
# Systemd files
50+
systemd/
51+
*.service
52+
53+
# Docker files (not needed in build context)
54+
docker-compose.yml
55+
docker-compose.*.yml
56+
Dockerfile.*

.github/workflows/docker-build.yml

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
name: Docker Build and Publish
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- develop
8+
- 'feature/docker-*'
9+
tags:
10+
- 'v*'
11+
pull_request:
12+
branches:
13+
- main
14+
workflow_dispatch:
15+
inputs:
16+
push:
17+
description: 'Push images to registry'
18+
required: false
19+
default: 'false'
20+
type: choice
21+
options:
22+
- 'true'
23+
- 'false'
24+
25+
env:
26+
REGISTRY: ghcr.io
27+
IMAGE_NAME: ${{ github.repository }}
28+
29+
jobs:
30+
build:
31+
runs-on: ubuntu-latest
32+
permissions:
33+
contents: read
34+
packages: write
35+
id-token: write
36+
37+
steps:
38+
- name: Checkout repository
39+
uses: actions/checkout@v4
40+
41+
- name: Set up Docker Buildx
42+
uses: docker/setup-buildx-action@v3
43+
44+
- name: Log in to GitHub Container Registry
45+
if: github.event_name != 'pull_request'
46+
uses: docker/login-action@v3
47+
with:
48+
registry: ${{ env.REGISTRY }}
49+
username: ${{ github.actor }}
50+
password: ${{ secrets.GITHUB_TOKEN }}
51+
52+
- name: Extract metadata
53+
id: meta
54+
uses: docker/metadata-action@v5
55+
with:
56+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
57+
tags: |
58+
type=ref,event=branch
59+
type=ref,event=pr
60+
type=semver,pattern={{version}}
61+
type=semver,pattern={{major}}.{{minor}}
62+
type=semver,pattern={{major}}
63+
type=raw,value=latest,enable={{is_default_branch}}
64+
type=sha,prefix={{branch}}-,enable=true,format=short
65+
labels: |
66+
org.opencontainers.image.title=Reddit ModLog Wiki Publisher
67+
org.opencontainers.image.description=Automated Reddit moderation log publisher to wiki pages
68+
org.opencontainers.image.vendor=bakerboy448
69+
org.opencontainers.image.licenses=GPL-3.0
70+
71+
- name: Build and push Docker image
72+
uses: docker/build-push-action@v5
73+
with:
74+
context: .
75+
platforms: linux/amd64,linux/arm64
76+
push: ${{ github.event_name != 'pull_request' || github.event.inputs.push == 'true' }}
77+
tags: ${{ steps.meta.outputs.tags }}
78+
labels: ${{ steps.meta.outputs.labels }}
79+
cache-from: type=gha
80+
cache-to: type=gha,mode=max
81+
build-args: |
82+
BUILD_DATE=${{ github.event.head_commit.timestamp }}
83+
VCS_REF=${{ github.sha }}
84+
VERSION=${{ steps.meta.outputs.version }}
85+
86+
- name: Generate SBOM
87+
if: github.event_name != 'pull_request'
88+
uses: anchore/sbom-action@v0
89+
with:
90+
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
91+
format: spdx-json
92+
output-file: sbom.spdx.json
93+
94+
- name: Upload SBOM
95+
if: github.event_name != 'pull_request'
96+
uses: actions/upload-artifact@v3
97+
with:
98+
name: sbom
99+
path: sbom.spdx.json
100+
101+
security-scan:
102+
needs: build
103+
runs-on: ubuntu-latest
104+
if: github.event_name != 'pull_request'
105+
permissions:
106+
contents: read
107+
security-events: write
108+
109+
steps:
110+
- name: Run Trivy vulnerability scanner
111+
uses: aquasecurity/trivy-action@master
112+
with:
113+
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
114+
format: 'sarif'
115+
output: 'trivy-results.sarif'
116+
117+
- name: Upload Trivy results to GitHub Security tab
118+
uses: github/codeql-action/upload-sarif@v2
119+
if: always()
120+
with:
121+
sarif_file: 'trivy-results.sarif'

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
config.json
33
*.config.json
44
config.*.json
5+
/etc/redditmodlog/*.json
6+
!config_template.json
7+
!*.json.example
58

69
# Database files
710
*.db
@@ -38,7 +41,13 @@ venv/
3841
ENV/
3942
env/
4043
.venv
44+
45+
# Environment files with credentials
4146
.env
47+
.env.*
48+
*.env
49+
!example.env
50+
!*.env.example
4251

4352
# IDE
4453
.vscode/

Dockerfile

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Build stage
2+
FROM python:3.11-slim as builder
3+
4+
# Install build dependencies
5+
RUN apt-get update && apt-get install -y --no-install-recommends \
6+
gcc \
7+
&& rm -rf /var/lib/apt/lists/*
8+
9+
# Create virtual environment
10+
RUN python -m venv /opt/venv
11+
ENV PATH="/opt/venv/bin:$PATH"
12+
13+
# Install Python dependencies
14+
COPY requirements.txt /tmp/
15+
RUN pip install --no-cache-dir -r /tmp/requirements.txt
16+
17+
# Runtime stage
18+
FROM python:3.11-slim
19+
20+
# OCI Labels
21+
LABEL org.opencontainers.image.title="Reddit ModLog Wiki Publisher" \
22+
org.opencontainers.image.description="Automated Reddit moderation log publisher to wiki pages" \
23+
org.opencontainers.image.authors="bakerboy448" \
24+
org.opencontainers.image.source="https://github.com/bakerboy448/RedditModLog" \
25+
org.opencontainers.image.documentation="https://github.com/bakerboy448/RedditModLog/blob/main/README.md" \
26+
org.opencontainers.image.licenses="GPL-3.0" \
27+
org.opencontainers.image.vendor="bakerboy448" \
28+
org.opencontainers.image.base.name="python:3.11-slim"
29+
30+
# Install runtime dependencies and s6-overlay for user management
31+
RUN apt-get update && apt-get install -y --no-install-recommends \
32+
ca-certificates \
33+
curl \
34+
&& rm -rf /var/lib/apt/lists/*
35+
36+
# Install s6-overlay for proper init and user management
37+
ARG S6_OVERLAY_VERSION=3.1.6.2
38+
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp
39+
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz /tmp
40+
RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz && \
41+
tar -C / -Jxpf /tmp/s6-overlay-x86_64.tar.xz && \
42+
rm /tmp/s6-overlay-*.tar.xz
43+
44+
# Create default user and group
45+
RUN groupadd -g 1000 modlogbot && \
46+
useradd -u 1000 -g modlogbot -d /app -s /bin/bash modlogbot
47+
48+
# Copy virtual environment from builder
49+
COPY --from=builder /opt/venv /opt/venv
50+
51+
# Set environment variables
52+
ENV PATH="/opt/venv/bin:$PATH" \
53+
PYTHONUNBUFFERED=1 \
54+
PYTHONDONTWRITEBYTECODE=1 \
55+
PUID=1000 \
56+
PGID=1000 \
57+
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
58+
59+
# Create application directories
60+
RUN mkdir -p /app /app/data /app/logs /etc/s6-overlay/s6-rc.d/modlog-bot /etc/s6-overlay/s6-rc.d/init-modlogbot
61+
62+
# Create s6 init script for user/group management
63+
RUN echo '#!/command/with-contenv bash\n\
64+
set -e\n\
65+
\n\
66+
PUID=${PUID:-1000}\n\
67+
PGID=${PGID:-1000}\n\
68+
\n\
69+
echo "Setting UID:GID to ${PUID}:${PGID}"\n\
70+
\n\
71+
# Update user and group IDs\n\
72+
groupmod -o -g "$PGID" modlogbot\n\
73+
usermod -o -u "$PUID" modlogbot\n\
74+
\n\
75+
# Fix ownership\n\
76+
echo "Fixing ownership of /app"\n\
77+
chown -R modlogbot:modlogbot /app\n\
78+
\n\
79+
# Ensure data directory has correct permissions\n\
80+
if [ ! -f /app/data/modlog.db ]; then\n\
81+
echo "Initializing database directory"\n\
82+
touch /app/data/modlog.db\n\
83+
chown modlogbot:modlogbot /app/data/modlog.db\n\
84+
fi' > /etc/s6-overlay/scripts/init-modlogbot-run && \
85+
chmod +x /etc/s6-overlay/scripts/init-modlogbot-run
86+
87+
# Create s6 service run script
88+
RUN echo '#!/command/with-contenv bash\n\
89+
cd /app\n\
90+
exec s6-setuidgid modlogbot python modlog_wiki_publisher.py --continuous' > /etc/s6-overlay/scripts/modlog-bot-run && \
91+
chmod +x /etc/s6-overlay/scripts/modlog-bot-run
92+
93+
# Setup s6 service definitions
94+
RUN echo 'oneshot' > /etc/s6-overlay/s6-rc.d/init-modlogbot/type && \
95+
echo '/etc/s6-overlay/scripts/init-modlogbot-run' > /etc/s6-overlay/s6-rc.d/init-modlogbot/up && \
96+
echo 'longrun' > /etc/s6-overlay/s6-rc.d/modlog-bot/type && \
97+
echo '/etc/s6-overlay/scripts/modlog-bot-run' > /etc/s6-overlay/s6-rc.d/modlog-bot/run && \
98+
echo 'init-modlogbot' > /etc/s6-overlay/s6-rc.d/modlog-bot/dependencies && \
99+
touch /etc/s6-overlay/s6-rc.d/user/contents.d/init-modlogbot && \
100+
touch /etc/s6-overlay/s6-rc.d/user/contents.d/modlog-bot
101+
102+
# Set working directory
103+
WORKDIR /app
104+
105+
# Copy application files
106+
COPY --chown=modlogbot:modlogbot modlog_wiki_publisher.py /app/
107+
COPY --chown=modlogbot:modlogbot config_template.json /app/
108+
109+
# Health check
110+
HEALTHCHECK --interval=5m --timeout=10s --start-period=30s --retries=3 \
111+
CMD python -c "import os, sys; sys.exit(0 if os.path.exists('/app/data/modlog.db') else 1)"
112+
113+
# Use s6-overlay as entrypoint
114+
ENTRYPOINT ["/init"]

0 commit comments

Comments
 (0)