# 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" / / "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//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*