homelab/.planning/codebase/CONVENTIONS.md
Mikkel Georgsen a639a53b0b docs: add codebase map and domain research
Codebase: 7 documents (stack, architecture, structure, conventions, testing, integrations, concerns)
Research: 5 documents (stack, features, architecture, pitfalls, summary)
2026-02-04 13:50:03 +00:00

274 lines
9.7 KiB
Markdown

# Coding Conventions
**Analysis Date:** 2026-02-04
## Naming Patterns
**Files:**
- Python files: lowercase with underscores (e.g., `bot.py`, `credentials`)
- Bash scripts: lowercase with hyphens (e.g., `npm-api`, `uptime-kuma`)
- Helper scripts in `~/bin/`: all lowercase, no extension (e.g., `pve`, `pbs`, `dns`)
**Functions:**
- Python: snake_case (e.g., `cmd_status()`, `get_authorized_users()`, `run_command()`)
- Bash: snake_case with `cmd_` prefix for command handlers (e.g., `cmd_status()`, `cmd_tasks()`)
- Bash: auxiliary functions also use snake_case (e.g., `ssh_pbs()`, `get_token()`)
**Variables:**
- Python: snake_case for local/module vars (e.g., `authorized_users`, `output_lines`)
- Python: UPPERCASE for constants (e.g., `TOKEN`, `INBOX_FILE`, `AUTHORIZED_FILE`, `NODE`, `PBS_HOST`)
- Bash: UPPERCASE for environment variables and constants (e.g., `PBS_HOST`, `TOKEN`, `BASE`, `DEFAULT_ZONE`)
- Bash: lowercase for local variables (e.g., `hours`, `cutoff`, `status_icon`)
**Types/Classes:**
- Python: PascalCase for imported classes (e.g., `ProxmoxAPI`, `Update`, `Application`)
- Dictionary/config keys: lowercase with hyphens or underscores (e.g., `token_name`, `max-mem`)
## Code Style
**Formatting:**
- No automated formatter detected in codebase
- Python: PEP 8 conventions followed informally
- 4-space indentation
- Max line length ~90-100 characters (observed in practice)
- Blank lines: 2 lines before module-level functions, 1 line before methods
- Bash: 4-space indentation (observed)
**Linting:**
- No linting configuration detected (no .pylintrc, .flake8, .eslintrc)
- Code style is manually maintained
**Docstrings:**
- Python: Triple-quoted strings at module level describing purpose
- Example from `telegram/bot.py`:
```python
"""
Homelab Telegram Bot
Two-way interactive bot for homelab management and notifications.
"""
```
- Python: Function docstrings used for major functions
- Single-line format for simple functions
- Example: `"""Handle /start command - first contact with bot."""`
- Example: `"""Load authorized user IDs."""`
## Import Organization
**Order:**
1. Standard library imports (e.g., `sys`, `os`, `json`, `subprocess`)
2. Third-party imports (e.g., `ProxmoxAPI`, `telegram`, `pocketbase`)
3. Local imports (rarely used in this codebase)
**Path Aliases:**
- No aliases detected
- Absolute imports used throughout
**Credential Loading Pattern:**
All scripts that need credentials follow the same pattern:
```python
# Load credentials
creds_path = Path.home() / ".config" / <service> / "credentials"
creds = {}
with open(creds_path) as f:
for line in f:
if '=' in line:
key, value = line.strip().split('=', 1)
creds[key] = value
```
Or in Bash:
```bash
source ~/.config/dns/credentials
```
## Error Handling
**Patterns:**
- Python: Try-except with broad exception catching (bare `except:` used in `pve` script lines 70, 82, 95, 101)
- Not ideal but pragmatic for CLI tools that need to try multiple approaches
- Example from `pve`:
```python
try:
status = pve.nodes(NODE).lxc(vmid).status.current.get()
# ...
return
except:
pass
```
- Python: Explicit exception handling in telegram bot
- Catches `subprocess.TimeoutExpired` specifically in `run_command()` function
- Example from `telegram/bot.py`:
```python
try:
result = subprocess.run(...)
output = result.stdout or result.stderr or "No output"
if len(output) > 4000:
output = output[:4000] + "\n... (truncated)"
return output
except subprocess.TimeoutExpired:
return "Command timed out"
except Exception as e:
return f"Error: {e}"
```
- Bash: Set strict mode with `set -e` in some scripts (`dns` script line 12)
- Causes script to exit on first error
- Bash: No error handling in most scripts (`pbs`, `beszel`, `kuma`)
- Relies on exit codes implicitly
**Return Value Handling:**
- Python: Functions return data directly or None on failure
- Example from `pbs` helper: Returns JSON-parsed data or string output
- Example from `pve`: Returns nothing (prints output), but uses exceptions for flow control
- Python: Command runner returns error strings: `"Command timed out"`, `"Error: {e}"`
## Logging
**Framework:**
- Python: Standard `logging` module
- Configured in `telegram/bot.py` lines 18-22:
```python
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(__name__)
```
- Log level: INFO
- Format includes timestamp, logger name, level, message
**Patterns:**
- `logger.info()` for general informational messages
- Example: `logger.info("Starting Homelab Bot...")`
- Example: `logger.info(f"Inbox message from {user.first_name}: {message[:50]}...")`
- Example: `logger.info(f"Photo saved from {user.first_name}: {filepath}")`
- Bash: Uses `echo` for output, no structured logging
- Informational messages for user feedback
- Error messages sent to stdout (not stderr)
## Comments
**When to Comment:**
- Module-level docstrings at top of file (required for all scripts)
- Usage examples in module docstrings (e.g., `pve`, `pbs`, `kuma`)
- Inline comments for complex logic (e.g., in `pbs` script parsing hex timestamps)
- Comments on tricky regex patterns (e.g., `pbs` tasks parsing)
**Bash Comments:**
- Header comment with script name, purpose, and usage (lines 1-10)
- Inline comments before major sections (e.g., `# Datastore info`, `# Storage stats`)
- No comments in simple expressions
**Python Comments:**
- Header comment with purpose (module docstring)
- Sparse inline comments except for complex sections
- Example from `telegram/bot.py` line 71: `# Telegram has 4096 char limit per message`
- Example from `pve` line 70: `# Try as container first`
## Function Design
**Size:**
- Python: Functions are generally 10-50 lines
- Smaller functions for simple operations (e.g., `is_authorized()` is 2 lines)
- Larger functions for command handlers that do setup + API calls (e.g., `status()` is 40 lines)
- Bash: Functions are typically 20-80 lines
- Longer functions acceptable for self-contained operations like `cmd_status()` in `pbs`
**Parameters:**
- Python: Explicit parameters, typically 1-5 parameters per function
- Optional parameters with defaults (e.g., `timeout: int = 30`, `port=45876`)
- Type hints not used consistently (some functions have them, many don't)
- Bash: Parameters passed as positional arguments
- Some functions take zero parameters and rely on global variables
- Example: `ssh_pbs()` in `pbs` uses global `$PBS_HOST`
**Return Values:**
- Python: Functions return data (strings, dicts, lists) or None
- Command handlers often return nothing (implicitly None)
- Helper functions return computed values (e.g., `is_authorized()` returns bool)
- Bash: Functions print output directly, return exit codes
- No explicit return values beyond exit codes
- Output captured by caller with `$()`
## Module Design
**Exports:**
- Python: All functions are module-level, no explicit exports
- `if __name__ == "__main__":` pattern used in all scripts to guard main execution
- Example from `beszel` lines 101-152
- Bash: All functions are script-level, called via case statement
- Main dispatch logic at bottom of script
- Example from `dns` lines 29-106: `case "$1" in ... esac`
**Async/Await (Telegram Bot Only):**
- Python telegram bot uses `asyncio` and `async def` for all handlers
- All command handlers are async (e.g., `async def start()`)
- Use `await` for async operations (e.g., `await update.message.reply_text()`)
- Example from `telegram/bot.py` lines 81-94:
```python
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle /start command - first contact with bot."""
user = update.effective_user
chat_id = update.effective_chat.id
# ... async operations with await
```
**File Structure:**
- Single-file modules: Most helpers are single files
- `telegram/bot.py`: Main bot implementation with all handlers
- `/bin/` scripts: Each script is self-contained with helper functions + main dispatch
## Data Structures
**JSON/Config Files:**
- Credentials files: Simple `KEY=value` format (no JSON)
- PBS task logging: Uses hex-encoded UPID format, parsed with regex
- Telegram bot: Saves messages to text files with timestamp prefix
- JSON output: Parsed with `python3 -c "import sys, json; ..."` in Bash scripts
**Error Response Patterns:**
- API calls: Check for `.get('status') == 'ok'` or similar
- Command execution: Check `returncode == 0`, capture stdout/stderr
- API clients: Let exceptions bubble up, caught at command handler level
## Conditionals and Flow Control
**Python:**
- if/elif/else chains for command dispatch
- Simple truthiness checks: `if not user_id:`, `if not alerts:`
- Example from `telegram/bot.py` line 86-100: Authorization check pattern
**Bash:**
- case/esac for command dispatch (preferred)
- if [[ ]] with regex matching for parsing
- Example from `pbs` lines 122-143: Complex regex with BASH_REMATCH array
## Security Patterns
**Credential Management:**
- Credentials stored in `~/.config/<service>/credentials` with restricted permissions (not enforced in code)
- Telegram token loaded from file, not environment
- Credentials never logged or printed
**Input Validation:**
- Bash: Basic validation with isalnum() check in `ping_host()` function
- Example: `if not host.replace('.', '').replace('-', '').isalnum():`
- Bash: Whitelist command names from case statements
- No SQL injection risk (no databases used directly)
**Shell Injection:**
- Bash scripts use quoted variables appropriately
- Some inline Python in Bash uses string interpolation (potential risk)
- Example from `dns` lines 31-37: `curl ... | python3 -c "..."` with variable interpolation
---
*Convention analysis: 2026-02-04*