Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 63 additions & 27 deletions .agent/scripts/cron-dispatch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,36 @@
#
# Called by crontab entries managed by cron-helper.sh
# Requires OpenCode server running (opencode serve)
#
# Security:
# - Uses HTTPS by default for remote hosts (non-localhost)
# - Supports basic auth via OPENCODE_SERVER_PASSWORD
# - SSL verification enabled by default (disable with OPENCODE_INSECURE=1)

set -euo pipefail

# Configuration
readonly CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/aidevops"
readonly CONFIG_FILE="$CONFIG_DIR/cron-jobs.json"
# WORKSPACE_DIR reserved for future use (e.g., temp files during execution)
readonly OPENCODE_PORT="${OPENCODE_PORT:-4096}"
readonly OPENCODE_HOST="${OPENCODE_HOST:-127.0.0.1}"
readonly OPENCODE_INSECURE="${OPENCODE_INSECURE:-}"
readonly MAIL_HELPER="$HOME/.aidevops/agents/scripts/mail-helper.sh"

#######################################
# Determine protocol based on host
# Localhost uses HTTP, remote uses HTTPS
#######################################
get_protocol() {
local host="$1"
# Use HTTP only for localhost/127.0.0.1, HTTPS for everything else
if [[ "$host" == "localhost" || "$host" == "127.0.0.1" || "$host" == "::1" ]]; then
echo "http"
else
echo "https"
fi
}

# Timestamp for logging
log_timestamp() {
date -u +"%Y-%m-%dT%H:%M:%SZ"
Expand All @@ -34,27 +53,41 @@ log_success() {
}

#######################################
# Get auth header for OpenCode server
# Build curl arguments array for secure requests
# Populates CURL_ARGS array with auth and SSL options
#######################################
get_auth_header() {
build_curl_args() {
CURL_ARGS=(-sf)

# Add authentication if configured
if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then
local user="${OPENCODE_SERVER_USERNAME:-admin}"
echo "-u ${user}:${OPENCODE_SERVER_PASSWORD}"
else
echo ""
CURL_ARGS+=(-u "${user}:${OPENCODE_SERVER_PASSWORD}")
fi

# Add SSL options for HTTPS
local protocol
protocol=$(get_protocol "$OPENCODE_HOST")
if [[ "$protocol" == "https" ]]; then
if [[ -n "$OPENCODE_INSECURE" ]]; then
# Allow insecure connections (self-signed certs) - use with caution
CURL_ARGS+=(-k)
log_info "WARNING: SSL verification disabled (OPENCODE_INSECURE=1)"
fi
fi
}
Comment on lines +59 to 78

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To avoid redundant calls to get_protocol in this function and in each function that calls it (check_server, create_session, etc.), build_curl_args should accept the protocol as an argument. This improves efficiency and clarifies the function's dependencies.

You will need to update all call sites to pass the protocol, for example: build_curl_args "$protocol".

Suggested change
build_curl_args() {
CURL_ARGS=(-sf)
# Add authentication if configured
if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then
local user="${OPENCODE_SERVER_USERNAME:-admin}"
echo "-u ${user}:${OPENCODE_SERVER_PASSWORD}"
else
echo ""
CURL_ARGS+=(-u "${user}:${OPENCODE_SERVER_PASSWORD}")
fi
# Add SSL options for HTTPS
local protocol
protocol=$(get_protocol "$OPENCODE_HOST")
if [[ "$protocol" == "https" ]]; then
if [[ -n "$OPENCODE_INSECURE" ]]; then
# Allow insecure connections (self-signed certs) - use with caution
CURL_ARGS+=(-k)
log_info "WARNING: SSL verification disabled (OPENCODE_INSECURE=1)"
fi
fi
}
build_curl_args() {
local protocol="$1"
CURL_ARGS=(-sf)
# Add authentication if configured
if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then
local user="${OPENCODE_SERVER_USERNAME:-admin}"
CURL_ARGS+=(-u "${user}:${OPENCODE_SERVER_PASSWORD}")
fi
# Add SSL options for HTTPS
if [[ "$protocol" == "https" ]]; then
if [[ -n "$OPENCODE_INSECURE" ]]; then
# Allow insecure connections (self-signed certs) - use with caution
CURL_ARGS+=(-k)
log_info "WARNING: SSL verification disabled (OPENCODE_INSECURE=1)"
fi
fi
}


#######################################
# Check server health
#######################################
check_server() {
local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health"
local auth_header
auth_header=$(get_auth_header)
local protocol
protocol=$(get_protocol "$OPENCODE_HOST")
local url="${protocol}://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health"

# shellcheck disable=SC2086
if curl -sf $auth_header "$url" &>/dev/null; then
build_curl_args

if curl "${CURL_ARGS[@]}" "$url" &>/dev/null; then
return 0
else
return 1
Expand Down Expand Up @@ -93,12 +126,13 @@ update_job_status() {
#######################################
create_session() {
local title="$1"
local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/session"
local auth_header
auth_header=$(get_auth_header)
local protocol
protocol=$(get_protocol "$OPENCODE_HOST")
local url="${protocol}://${OPENCODE_HOST}:${OPENCODE_PORT}/session"

build_curl_args

# shellcheck disable=SC2086
curl -sf $auth_header -X POST "$url" \
curl "${CURL_ARGS[@]}" -X POST "$url" \
-H "Content-Type: application/json" \
-d "{\"title\": \"$title\"}" | jq -r '.id'
}
Expand All @@ -110,10 +144,10 @@ send_prompt() {
local session_id="$1"
local task="$2"
local model="$3"
local timeout="$4"
local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/session/${session_id}/message"
local auth_header
auth_header=$(get_auth_header)
local cmd_timeout="$4"
local protocol
protocol=$(get_protocol "$OPENCODE_HOST")
local url="${protocol}://${OPENCODE_HOST}:${OPENCODE_PORT}/session/${session_id}/message"

# Parse model into provider and model ID
local provider_id model_id
Expand All @@ -134,9 +168,10 @@ send_prompt() {
parts: [{type: "text", text: $task}]
}')

build_curl_args

# Send with timeout
# shellcheck disable=SC2086
timeout "$timeout" curl -sf $auth_header -X POST "$url" \
timeout "$cmd_timeout" curl "${CURL_ARGS[@]}" -X POST "$url" \
-H "Content-Type: application/json" \
-d "$body"
}
Expand All @@ -146,12 +181,13 @@ send_prompt() {
#######################################
delete_session() {
local session_id="$1"
local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/session/${session_id}"
local auth_header
auth_header=$(get_auth_header)
local protocol
protocol=$(get_protocol "$OPENCODE_HOST")
local url="${protocol}://${OPENCODE_HOST}:${OPENCODE_PORT}/session/${session_id}"

build_curl_args

# shellcheck disable=SC2086
curl -sf $auth_header -X DELETE "$url" &>/dev/null || true
curl "${CURL_ARGS[@]}" -X DELETE "$url" &>/dev/null || true
}

#######################################
Expand Down
54 changes: 47 additions & 7 deletions .agent/scripts/cron-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
#
# Configuration: ~/.config/aidevops/cron-jobs.json
# Logs: ~/.aidevops/.agent-workspace/cron/
#
# Security:
# - Uses HTTPS by default for remote hosts (non-localhost)
# - Supports basic auth via OPENCODE_SERVER_PASSWORD
# - SSL verification enabled by default (disable with OPENCODE_INSECURE=1)

set -euo pipefail

Expand All @@ -25,6 +30,7 @@ readonly CRON_LOG_DIR="$WORKSPACE_DIR/cron"
readonly SCRIPTS_DIR="$HOME/.aidevops/agents/scripts"
readonly OPENCODE_PORT="${OPENCODE_PORT:-4096}"
readonly OPENCODE_HOST="${OPENCODE_HOST:-127.0.0.1}"
readonly OPENCODE_INSECURE="${OPENCODE_INSECURE:-}"
readonly DEFAULT_TIMEOUT=600
readonly DEFAULT_MODEL="anthropic/claude-sonnet-4-20250514"

Expand Down Expand Up @@ -84,19 +90,53 @@ check_jq() {
}

#######################################
# Check OpenCode server health
# Determine protocol based on host
# Localhost uses HTTP, remote uses HTTPS
#######################################
check_server() {
local url="http://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health"
local auth_header=""
get_protocol() {
local host="$1"
# Use HTTP only for localhost/127.0.0.1, HTTPS for everything else
if [[ "$host" == "localhost" || "$host" == "127.0.0.1" || "$host" == "::1" ]]; then
echo "http"
else
echo "https"
fi
}

#######################################
# Build curl arguments array for secure requests
#######################################
build_curl_args() {
CURL_ARGS=(-sf)

# Add authentication if configured
if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then
local user="${OPENCODE_SERVER_USERNAME:-admin}"
auth_header="-u ${user}:${OPENCODE_SERVER_PASSWORD}"
CURL_ARGS+=(-u "${user}:${OPENCODE_SERVER_PASSWORD}")
fi

# shellcheck disable=SC2086
if curl -sf $auth_header "$url" &>/dev/null; then
# Add SSL options for HTTPS
local protocol
protocol=$(get_protocol "$OPENCODE_HOST")
if [[ "$protocol" == "https" ]]; then
if [[ -n "$OPENCODE_INSECURE" ]]; then
# Allow insecure connections (self-signed certs) - use with caution
CURL_ARGS+=(-k)
fi
fi
}
Comment on lines +109 to +127

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This function can be improved in two ways:

  1. To avoid redundant calls to get_protocol, it should accept the protocol as an argument. This improves efficiency by determining the protocol only once in check_server.
  2. A warning should be printed to stderr when OPENCODE_INSECURE is used, to alert the user about the security risk. This is already done in cron-dispatch.sh.

You will need to update check_server to pass the protocol to this function: build_curl_args "$protocol".

Suggested change
build_curl_args() {
CURL_ARGS=(-sf)
# Add authentication if configured
if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then
local user="${OPENCODE_SERVER_USERNAME:-admin}"
auth_header="-u ${user}:${OPENCODE_SERVER_PASSWORD}"
CURL_ARGS+=(-u "${user}:${OPENCODE_SERVER_PASSWORD}")
fi
# shellcheck disable=SC2086
if curl -sf $auth_header "$url" &>/dev/null; then
# Add SSL options for HTTPS
local protocol
protocol=$(get_protocol "$OPENCODE_HOST")
if [[ "$protocol" == "https" ]]; then
if [[ -n "$OPENCODE_INSECURE" ]]; then
# Allow insecure connections (self-signed certs) - use with caution
CURL_ARGS+=(-k)
fi
fi
}
build_curl_args() {
local protocol="$1"
CURL_ARGS=(-sf)
# Add authentication if configured
if [[ -n "${OPENCODE_SERVER_PASSWORD:-}" ]]; then
local user="${OPENCODE_SERVER_USERNAME:-admin}"
CURL_ARGS+=(-u "${user}:${OPENCODE_SERVER_PASSWORD}")
fi
# Add SSL options for HTTPS
if [[ "$protocol" == "https" ]]; then
if [[ -n "$OPENCODE_INSECURE" ]]; then
# Allow insecure connections (self-signed certs) - use with caution
CURL_ARGS+=(-k)
echo "WARNING: SSL verification disabled (OPENCODE_INSECURE=1)" >&2
fi
fi
}


#######################################
# Check OpenCode server health
#######################################
check_server() {
local protocol
protocol=$(get_protocol "$OPENCODE_HOST")
local url="${protocol}://${OPENCODE_HOST}:${OPENCODE_PORT}/global/health"

build_curl_args

if curl "${CURL_ARGS[@]}" "$url" &>/dev/null; then
return 0
else
return 1
Expand Down
38 changes: 33 additions & 5 deletions .agent/tools/automation/cron-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,11 +381,39 @@ ls -la ~/.aidevops/.agent-workspace/cron/

## Security Considerations

1. **Server authentication**: Always use `OPENCODE_SERVER_PASSWORD` for network-exposed servers
2. **Task validation**: Jobs only execute pre-defined tasks from `cron-jobs.json`
3. **Timeout limits**: All jobs have configurable timeouts to prevent runaway sessions
4. **Log rotation**: Old logs are automatically pruned (configurable retention)
5. **Credential isolation**: Tasks inherit environment from cron, not from config files
1. **HTTPS by default**: Remote hosts (non-localhost) automatically use HTTPS
2. **Server authentication**: Always use `OPENCODE_SERVER_PASSWORD` for network-exposed servers
3. **SSL verification**: Enabled by default; use `OPENCODE_INSECURE=1` only for self-signed certs
4. **Task validation**: Jobs only execute pre-defined tasks from `cron-jobs.json`
5. **Timeout limits**: All jobs have configurable timeouts to prevent runaway sessions
6. **Log rotation**: Old logs are automatically pruned (configurable retention)
7. **Credential isolation**: Tasks inherit environment from cron, not from config files
Comment on lines 382 to +390

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Search for log rotation/pruning implementation
rg -n "logrotate|rotate|rotation|prune|retention" --type-list | head -5

Repository: marcusquinn/aidevops

Length of output: 453


🏁 Script executed:

# Get files matching cron-agent or related automation tools
fd "cron-agent" --type f

Repository: marcusquinn/aidevops

Length of output: 101


🏁 Script executed:

# Read the specific lines from the file to see actual content
cat -n ".agent/tools/automation/cron-agent.md" | sed -n '382,420p'

Repository: marcusquinn/aidevops

Length of output: 1753


🏁 Script executed:

# Search for log rotation/pruning patterns in shell scripts
rg "rotate|prune|retention|logrotate" -g "*.sh"

Repository: marcusquinn/aidevops

Length of output: 2168


🏁 Script executed:

# Check the cron-helper.sh script specifically
fd "cron-helper" --type f

Repository: marcusquinn/aidevops

Length of output: 93


🏁 Script executed:

# Look for any log-related operations
rg "log" -g "*.sh" --type sh -i | grep -E "(rotate|prune|cleanup|rotate|retention|rm.*log)" | head -20

Repository: marcusquinn/aidevops

Length of output: 2092


🏁 Script executed:

# Check the cron-helper.sh file for log rotation implementation
cat ".agent/scripts/cron-helper.sh" | wc -l

Repository: marcusquinn/aidevops

Length of output: 67


🏁 Script executed:

# Search specifically in cron-helper.sh for log-related operations
rg "log|rotate|prune" ".agent/scripts/cron-helper.sh"

Repository: marcusquinn/aidevops

Length of output: 4088


🏁 Script executed:

# Check system-cleanup.sh to see the rotate_logs implementation
cat -n ".agent/scripts/system-cleanup.sh" | grep -A 20 "rotate_logs()"

Repository: marcusquinn/aidevops

Length of output: 1183


Remove or correct the log rotation claim; add guideline violation flag for inline secrets.

The cron-helper.sh script does not implement automatic log pruning. While system-cleanup.sh contains a rotate_logs() function (.agent/scripts/system-cleanup.sh:78), the implementation explicitly notes it is incomplete: "A simpler robust approach for a single file is difficult without external tools." Cron jobs simply append to individual .log files with no automatic retention policy. Remove the log rotation bullet from line 389 or replace it with: "Log management: Jobs append logs to $CRON_LOG_DIR/${job_id}.log; configure external log rotation as needed."

Additionally, the inline bash block (lines 396–407) violates progressive disclosure guidelines and exposes OPENCODE_SERVER_PASSWORD in exported code. Reference .agent/scripts/cron-helper.sh for actual usage and note that credentials should be stored in ~/.config/aidevops/mcp-env.sh with 600 permissions rather than exported in documentation.

🤖 Prompt for AI Agents
In @.agent/tools/automation/cron-agent.md around lines 382 - 390, Update the
Security Considerations section to remove the incorrect "Log rotation" claim and
replace it with an accurate note about log management: state that jobs append to
$CRON_LOG_DIR/${job_id}.log and that external log rotation/retention must be
configured (reference cron-helper.sh and the rotate_logs() stub in
.agent/scripts/system-cleanup.sh as the source of truth). Also add a
guideline-violation flag and corrective note for the inline bash block that
exports OPENCODE_SERVER_PASSWORD: state that credentials must not be exposed
inline, reference .agent/scripts/cron-helper.sh for actual usage, and instruct
readers to store secrets in ~/.config/aidevops/mcp-env.sh with 600 permissions
instead of exporting them in docs.


### Remote Server Configuration

For connecting to a remote OpenCode server:

```bash
# Required: Set server host and authentication
export OPENCODE_HOST="opencode.example.com"
export OPENCODE_PORT="4096"
export OPENCODE_SERVER_PASSWORD="your-secure-password"

# Optional: For self-signed certificates (not recommended for production)
export OPENCODE_INSECURE=1

# Test connection
cron-helper.sh status
```

### Protocol Selection

| Host | Protocol | Notes |
|------|----------|-------|
| `localhost` | HTTP | Safe for local development |
| `127.0.0.1` | HTTP | Safe for local development |
| `::1` | HTTP | IPv6 localhost |
| Any other host | HTTPS | Encrypted connection required |

Comment on lines +392 to 417

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Replace inline remote-config snippets with authoritative references.
For agent docs, prefer file:line pointers (for example, .agent/scripts/cron-helper.sh:92-144 and .agent/scripts/cron-dispatch.sh:24-78) or a subagent link instead of inline export examples, and direct secret setup to ~/.config/aidevops/mcp-env.sh rather than in-doc exports.

As per coding guidelines: Apply progressive disclosure pattern by using pointers to subagents rather than including inline content in agent documentation. Include code examples only when authoritative; use file:line references to point to actual implementation instead of inline code snippets.
Based on learnings: Store API keys and tokens exclusively in ~/.config/aidevops/mcp-env.sh with file permissions set to 600 (owner read/write only).

🤖 Prompt for AI Agents
In @.agent/tools/automation/cron-agent.md around lines 392 - 417, Replace the
inline environment-export snippets in cron-agent.md with authoritative file:line
references or a subagent link pointing to the actual implementation in
.agent/scripts/cron-helper.sh (e.g., reference the status block) and
.agent/scripts/cron-dispatch.sh instead of embedding exports for OPENCODE_HOST,
OPENCODE_PORT, OPENCODE_SERVER_PASSWORD and OPENCODE_INSECURE; remove the in-doc
export examples and instead instruct readers to store secrets in
~/.config/aidevops/mcp-env.sh (set file permissions to 600) and show only the
file:line pointers (or subagent link) for the canonical examples.

## Related Documentation

Expand Down
Loading