Skip to content

Commit 0ab21da

Browse files
committed
Add Docker setup, Aussie config, docs; remove redundant run guides
Made-with: Cursor
1 parent 639dea1 commit 0ab21da

10 files changed

Lines changed: 351 additions & 6 deletions

File tree

.dockerignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Dependencies and local state (never copy into image)
2+
.venv
3+
venv
4+
env
5+
__pycache__
6+
*.pyc
7+
.pytest_cache
8+
.mypy_cache
9+
.ruff_cache
10+
11+
# Git and IDE
12+
.git
13+
.gitignore
14+
.cursor
15+
*.swp
16+
*.swo
17+
18+
# Local data and secrets (mount at runtime instead)
19+
data/
20+
*.db
21+
.env
22+
.env.*
23+
!.env.example
24+
25+
# Docs and dev artifacts (optional: keep repo small)
26+
*.log
27+
bot.log
28+
bot_output.log
29+
30+
# Test and CI (not needed in production image)
31+
tests/
32+
.pre-commit-config.yaml
33+
htmlcov
34+
.coverage

Dockerfile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Gitcord - Offline-first Discord–GitHub automation engine
2+
# Production Dockerfile: minimal layers, non-root user, reproducible build.
3+
# Python 3.11 slim for smaller image and security updates.
4+
5+
FROM python:3.11-slim
6+
7+
# Prevent Python from writing bytecode and buffering stdout (cleaner logs in containers).
8+
ENV PYTHONDONTWRITEBYTECODE=1 \
9+
PYTHONUNBUFFERED=1
10+
11+
WORKDIR /app
12+
13+
# Install dependencies first (better layer caching: only re-run when deps change).
14+
# We copy only dependency manifests and source package, then install.
15+
COPY pyproject.toml README.md ./
16+
COPY src/ ./src/
17+
18+
RUN apt-get update \
19+
&& apt-get install -y --no-install-recommends gosu \
20+
&& rm -rf /var/lib/apt/lists/* \
21+
&& pip install --no-cache-dir -e . \
22+
&& useradd --create-home --shell /bin/bash appuser \
23+
&& chown -R appuser:appuser /app \
24+
&& mkdir -p /data && chown appuser:appuser /data
25+
26+
# Config and entrypoint (entrypoint runs as root to chown /data, then gosu to appuser).
27+
COPY config/ ./config/
28+
COPY docker-entrypoint.sh /docker-entrypoint.sh
29+
RUN chmod +x /docker-entrypoint.sh
30+
31+
# Default: run Discord bot. Override with run-once or other commands.
32+
# Example: docker compose run --rm bot --config /app/config/config.yaml run-once
33+
ENTRYPOINT ["/docker-entrypoint.sh"]
34+
CMD ["--config", "/app/config/config.yaml", "bot"]

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Gitcord is a local, offline‑first automation engine that reads GitHub activity
7878
1. [Main Repository](https://github.com/AOSSIE-Org/Gitcord-GithubDiscordBot)
7979
2. [Installation Guide](INSTALLATION.md) - Complete setup instructions
8080
3. [Technical Documentation](TECHNICAL_DOCUMENTATION.md) - Architecture and design
81+
4. [Docker Guide](docs/DOCKER.md) - Docker setup and mentor-friendly deployment
8182

8283
---
8384

@@ -122,13 +123,29 @@ Load config -> Ingest -> Score -> Plan -> Audit -> (Optional) Apply
122123

123124
Before installing Gitcord, you need:
124125

125-
-**Python 3.11+** installed
126126
-**GitHub Organization** access
127127
-**Discord Server** with admin permissions
128128
-**GitHub Personal Access Token** (fine-grained PAT) - [How to create](INSTALLATION.md#step-1-create-github-token-pat)
129129
-**Discord Bot Token** - [How to create](INSTALLATION.md#step-2-create-discord-bot)
130130

131-
### Quick Setup Overview
131+
### Quick Start with Docker (recommended for mentors)
132+
133+
If you have Docker installed, you can skip Python setup and run Gitcord in one go:
134+
135+
```bash
136+
git clone https://github.com/AOSSIE-Org/Gitcord-GithubDiscordBot.git
137+
cd Gitcord-GithubDiscordBot
138+
cp .env.example .env # Add your GITHUB_TOKEN and DISCORD_TOKEN
139+
cp config/docker-example.yaml config/config.yaml # Set github.org and discord.guild_id
140+
docker compose up -d
141+
```
142+
143+
The Discord bot stays running; SQLite data and reports persist in a Docker volume. To run a one-off sync (e.g. dry-run):
144+
`docker compose run --rm bot --config /app/config/config.yaml run-once`
145+
146+
See **[docs/DOCKER.md](docs/DOCKER.md)** for details, pitfalls, and audit-first workflow.
147+
148+
### Quick Setup Overview (local Python install)
132149

133150
**1. Create GitHub Token** ([Detailed Guide](INSTALLATION.md#step-1-create-github-token-pat))
134151

config/aussie.yaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Gitcord config for Aussie organization (AOSSIE-Org).
2+
# Set GITHUB_TOKEN and DISCORD_TOKEN in .env, or replace the token values below.
3+
# For Docker: set data_dir to "/data" and use: docker compose run --rm bot --config /app/config/aussie.yaml bot
4+
5+
runtime:
6+
mode: "dry-run"
7+
log_level: "INFO"
8+
data_dir: "./data/aussie"
9+
github_adapter: "ghdcbot.adapters.github.rest:GitHubRestAdapter"
10+
discord_adapter: "ghdcbot.adapters.discord.api:DiscordApiAdapter"
11+
storage_adapter: "ghdcbot.adapters.storage.sqlite:SqliteStorage"
12+
13+
github:
14+
org: "AOSSIE-Org"
15+
token: "${GITHUB_TOKEN}"
16+
api_base: "https://api.github.com"
17+
permissions:
18+
read: true
19+
write: false
20+
user_fallback: false
21+
22+
discord:
23+
guild_id: "1022871757289422898"
24+
token: "${DISCORD_TOKEN}"
25+
permissions:
26+
read: true
27+
write: false
28+
activity_channel_id: null
29+
pr_preview_channels: []
30+
31+
scoring:
32+
period_days: 30
33+
weights:
34+
issue_opened: 3
35+
pr_opened: 5
36+
pr_reviewed: 2
37+
comment: 1
38+
39+
role_mappings:
40+
- discord_role: "Contributor"
41+
min_score: 10
42+
- discord_role: "Maintainer"
43+
min_score: 40
44+
45+
assignments:
46+
review_roles:
47+
- "Maintainer"
48+
issue_assignees:
49+
- "Mentor"
50+
issue_request_eligible_roles: []
51+
52+
repo_contributor_roles: {}
53+
54+
identity_mappings: []

config/docker-example.yaml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Gitcord config for Docker: data_dir must be /data (mounted volume).
2+
# Copy to config/config.yaml and set github.org, discord.guild_id, and tokens in .env.
3+
4+
runtime:
5+
mode: "dry-run"
6+
log_level: "INFO"
7+
data_dir: "/data"
8+
github_adapter: "ghdcbot.adapters.github.rest:GitHubRestAdapter"
9+
discord_adapter: "ghdcbot.adapters.discord.api:DiscordApiAdapter"
10+
storage_adapter: "ghdcbot.adapters.storage.sqlite:SqliteStorage"
11+
12+
github:
13+
org: "example-org"
14+
token: "${GITHUB_TOKEN}"
15+
api_base: "https://api.github.com"
16+
permissions:
17+
read: true
18+
write: false
19+
user_fallback: false
20+
21+
discord:
22+
guild_id: "000000000000000000"
23+
token: "${DISCORD_TOKEN}"
24+
permissions:
25+
read: true
26+
write: false
27+
activity_channel_id: null
28+
pr_preview_channels: []
29+
30+
scoring:
31+
period_days: 30
32+
weights:
33+
issue_opened: 3
34+
pr_opened: 5
35+
pr_reviewed: 2
36+
comment: 1
37+
38+
role_mappings:
39+
- discord_role: "Contributor"
40+
min_score: 10
41+
- discord_role: "Maintainer"
42+
min_score: 40
43+
44+
assignments:
45+
review_roles:
46+
- "Maintainer"
47+
issue_assignees:
48+
- "Mentor"
49+
issue_request_eligible_roles: []
50+
51+
repo_contributor_roles: {}
52+
53+
identity_mappings: []

docker-compose.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Gitcord - Docker Compose for mentor-friendly deployment.
2+
# Usage: copy .env and config, then run: docker compose up -d
3+
# Data (SQLite, reports, identity links) persists in named volume gitcord_data.
4+
5+
services:
6+
bot:
7+
build: .
8+
image: gitcord:latest
9+
env_file: .env
10+
volumes:
11+
# Mount config so you can edit YAML without rebuilding (use data_dir: /data in config).
12+
- ./config:/app/config:ro
13+
# Persist SQLite state, reports, and audit logs between restarts.
14+
- gitcord_data:/data
15+
command: ["--config", "/app/config/config.yaml", "bot"]
16+
restart: unless-stopped
17+
18+
volumes:
19+
gitcord_data:

docker-entrypoint.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/sh
2+
# Ensure /data is writable by appuser (named volume may be root-owned on first run).
3+
chown -R appuser:appuser /data 2>/dev/null || true
4+
exec gosu appuser ghdcbot "$@"

docs/DOCKER.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Gitcord Docker Guide
2+
3+
Docker support is designed for **mentor-friendly deployment** and **reproducible runs** without changing Gitcord’s offline-first architecture. The bot and `run-once` both work; SQLite and reports persist across restarts.
4+
5+
---
6+
7+
## Why Docker?
8+
9+
- **No local Python/setup**: Mentors run `docker compose up` after adding `.env` and config.
10+
- **Same behavior as CLI**: Same code paths; only the runtime is containerized.
11+
- **Persistent state**: Named volume keeps SQLite, reports, and identity links across restarts.
12+
- **Audit-first unchanged**: Dry-run and reports work the same; config and mutation policy are unchanged.
13+
14+
---
15+
16+
## Quick Start (3 steps)
17+
18+
1. **Create `.env`** in the project root (copy from `.env.example`):
19+
20+
```env
21+
GITHUB_TOKEN=your_fine_grained_pat
22+
DISCORD_TOKEN=your_discord_bot_token
23+
```
24+
25+
2. **Create config** (use Docker-specific data dir):
26+
27+
```bash
28+
cp config/docker-example.yaml config/config.yaml
29+
```
30+
31+
Edit `config/config.yaml`: set `github.org`, `discord.guild_id`, and any other options. **Do not change `data_dir`**; it must stay `/data` so the mounted volume is used.
32+
33+
3. **Start the bot**:
34+
35+
```bash
36+
docker compose up -d
37+
```
38+
39+
The Discord bot runs in the background. Slash commands sync within ~30 seconds.
40+
41+
**Run a one-off sync (dry-run or active):**
42+
43+
```bash
44+
docker compose run --rm bot --config /app/config/config.yaml run-once
45+
```
46+
(Do not pass `ghdcbot` — the image entrypoint is already `ghdcbot`.)
47+
48+
---
49+
50+
## Recommended Folder Structure
51+
52+
```
53+
Gitcord-GithubDiscordBot/
54+
├── .env # Tokens (never commit; not in image)
55+
├── .env.example
56+
├── config/
57+
│ ├── config.yaml # Your active config (data_dir: /data for Docker)
58+
│ ├── docker-example.yaml # Template for Docker
59+
│ └── example.yaml # Template for local install
60+
├── docker-compose.yml
61+
├── Dockerfile
62+
├── pyproject.toml
63+
├── src/
64+
└── ...
65+
```
66+
67+
**Inside the container:**
68+
69+
- `/app` = app root (code, config mount at `/app/config`).
70+
- `/data` = persistent volume (SQLite `state.db`, `reports/`, `audit_events.jsonl`). Set `data_dir: "/data"` in config.
71+
72+
---
73+
74+
## Dockerfile Design (Why Each Part)
75+
76+
| Section | Purpose |
77+
|--------|--------|
78+
| `FROM python:3.11-slim` | Matches `requires-python = ">=3.11"`; slim reduces image size and attack surface. |
79+
| `PYTHONDONTWRITEBYTECODE=1` | Avoids writing `.pyc` in the image; cleaner and slightly faster. |
80+
| `PYTHONUNBUFFERED=1` | Logs show up immediately in `docker compose logs`. |
81+
| Copy `pyproject.toml` + `src/` then `pip install -e .` | Dependency layer is cached; only code/setup changes trigger reinstall. |
82+
| `useradd appuser` / `USER appuser` | Process does not run as root. |
83+
| `ENTRYPOINT ["ghdcbot"]` | All invocations use the same binary; override with `run-once`, `bot`, etc. |
84+
| `CMD ["--config", "/app/config/config.yaml", "bot"]` | Default is Discord bot; overridden by `docker-compose` `command` or `docker run ... run-once`. |
85+
86+
---
87+
88+
## docker-compose.yml Design
89+
90+
| Section | Purpose |
91+
|--------|--------|
92+
| `env_file: .env` | Loads `GITHUB_TOKEN` and `DISCORD_TOKEN`; config YAML uses `${GITHUB_TOKEN}` etc. |
93+
| `./config:/app/config:ro` | Host config dir mounted read-only; edit YAML on host without rebuilding. |
94+
| `gitcord_data:/data` | Named volume for SQLite and reports; survives `docker compose down`. |
95+
| `command: ["--config", "/app/config/config.yaml", "bot"]` | Ensures config path is correct and default is bot. |
96+
| `restart: unless-stopped` | Bot comes back after reboot or Docker restart. |
97+
98+
---
99+
100+
## Common Pitfalls and How to Avoid Them
101+
102+
| Pitfall | Cause | Fix |
103+
|--------|--------|-----|
104+
| **"Config file does not exist"** | No `config/config.yaml` or wrong path. | Copy `config/docker-example.yaml` to `config/config.yaml` and keep `data_dir: "/data"`. |
105+
| **"Missing required environment variable: GITHUB_TOKEN"** | `.env` missing or not loaded. | Create `.env` in project root (same dir as `docker-compose.yml`) with `GITHUB_TOKEN` and `DISCORD_TOKEN`. |
106+
| **State lost after restart** | `data_dir` pointed at a non-persistent path. | Use `data_dir: "/data"` and the provided `docker-compose` volume; do not override `/data` with a host path unless you intend to. |
107+
| **Bot doesn’t respond / "application did not respond"** | Same as non-Docker: slow storage or missing intents. | Ensure Server Members Intent is enabled; check logs with `docker compose logs -f`. |
108+
| **Permission errors on `/data`** | Container user cannot write. | Dockerfile already runs as `appuser`; the volume is writable by the container. If you use a host bind mount for `data`, ensure the host dir is writable (e.g. `chown` to the same UID as `appuser`). |
109+
| **Running both bot and run-once** | Need two invocations. | Bot: `docker compose up -d`. Run-once: `docker compose run --rm bot ghdcbot --config /app/config/config.yaml run-once`. |
110+
111+
---
112+
113+
## Audit-First Workflow in Docker
114+
115+
1. Keep `runtime.mode: "dry-run"` in config.
116+
2. Run once:
117+
`docker compose run --rm bot --config /app/config/config.yaml run-once`
118+
3. Inspect reports in the volume (e.g. copy out or run a temporary container that mounts the same volume and cats the file):
119+
Reports are under `/data/reports/` (e.g. `audit.md`, `audit.json`).
120+
4. When satisfied, set `runtime.mode: "active"` and `discord.permissions.write: true` in config, then run `run-once` again or let the bot apply changes on the next sync.
121+
122+
---
123+
124+
## Production and Maintainability Notes
125+
126+
- **Reproducibility**: Same image and config produce the same behavior; use tagged images if you need to pin versions.
127+
- **Secrets**: Never bake tokens into the image; use `.env` or a secrets manager and `env_file` / env.
128+
- **Updates**: Rebuild with `docker compose build --no-cache` after dependency or code changes; config and data are unchanged.
129+
- **Logs**: Use `docker compose logs -f bot` for live logs; log level is controlled by config `runtime.log_level`.

src/ghdcbot/engine/pr_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def determine_mentor_signal(
127127
merged = pr.get("merged", False)
128128

129129
if state != "open" or draft or merged:
130-
if merged:
130+
if merged or state == "merged":
131131
return "Merged"
132132
elif state == "closed":
133133
return "Closed"

0 commit comments

Comments
 (0)