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