diff --git a/.gitignore b/.gitignore index 699f948..705a1ad 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ CLAUDE.md /jabali-panel_*.deb /jabali-deps_*.deb .git-credentials + +# Local repository configuration (do not commit) +config.toml diff --git a/VERSION b/VERSION index a6b5aaf..3fa7c6a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=0.9-rc65 +VERSION=0.9-rc66 diff --git a/app/Console/Commands/Jabali/ImportProcessCommand.php b/app/Console/Commands/Jabali/ImportProcessCommand.php index 4cd77f0..06ad7c3 100644 --- a/app/Console/Commands/Jabali/ImportProcessCommand.php +++ b/app/Console/Commands/Jabali/ImportProcessCommand.php @@ -213,7 +213,7 @@ class ImportProcessCommand extends Command Domain::create([ 'domain' => $account->main_domain, 'user_id' => $user->id, - 'document_root' => "/home/{$user->username}/domains/{$account->main_domain}/public", + 'document_root' => "/home/{$user->username}/domains/{$account->main_domain}/public_html", 'is_active' => true, ]); $account->addLog("Created main domain: {$account->main_domain}"); @@ -235,7 +235,7 @@ class ImportProcessCommand extends Command Domain::create([ 'domain' => $domain, 'user_id' => $user->id, - 'document_root' => "/home/{$user->username}/domains/{$domain}/public", + 'document_root' => "/home/{$user->username}/domains/{$domain}/public_html", 'is_active' => true, ]); $account->addLog("Created addon domain: {$domain}"); @@ -290,7 +290,7 @@ class ImportProcessCommand extends Command // Copy public_html to the domain $publicHtml = "$homeDir/public_html"; if (is_dir($publicHtml) && $account->main_domain) { - $destDir = "/home/{$user->username}/domains/{$account->main_domain}/public"; + $destDir = "/home/{$user->username}/domains/{$account->main_domain}/public_html"; if (is_dir($destDir)) { exec('cp -r '.escapeshellarg($publicHtml).'/* '.escapeshellarg($destDir).'/ 2>&1'); exec('chown -R '.escapeshellarg($user->username).':'.escapeshellarg($user->username).' '.escapeshellarg($destDir).' 2>&1'); @@ -316,7 +316,7 @@ class ImportProcessCommand extends Command $publicHtml = "$domainDir/public_html"; if (is_dir($publicHtml)) { - $destDir = "/home/{$user->username}/domains/{$domain}/public"; + $destDir = "/home/{$user->username}/domains/{$domain}/public_html"; if (is_dir($destDir)) { exec('cp -r '.escapeshellarg($publicHtml).'/* '.escapeshellarg($destDir).'/ 2>&1'); exec('chown -R '.escapeshellarg($user->username).':'.escapeshellarg($user->username).' '.escapeshellarg($destDir).' 2>&1'); diff --git a/app/Models/User.php b/app/Models/User.php index 1bd7f8b..ac1e1ab 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -164,28 +164,25 @@ class User extends Authenticatable implements FilamentUser */ public function getDiskUsageBytes(): int { - // Try to get usage from quota system first (more accurate) + // Disk usage must be obtained via the agent (root) to avoid permission-based undercounting. try { - $agent = new \App\Services\Agent\AgentClient; - $result = $agent->quotaGet($this->username, '/'); + $agent = new \App\Services\Agent\AgentClient( + (string) config('jabali.agent.socket', '/var/run/jabali/agent.sock'), + (int) config('jabali.agent.timeout', 120), + ); + + $mount = $this->home_directory ?: ("/home/{$this->username}"); + $result = $agent->quotaGet($this->username, $mount); if (($result['success'] ?? false) && isset($result['used_mb'])) { return (int) ($result['used_mb'] * 1024 * 1024); } - } catch (\Exception $e) { - // Fall back to du command + } catch (\Throwable $e) { + \Log::warning('Disk usage read failed via agent: '.$e->getMessage(), [ + 'username' => $this->username, + ]); } - - // Fallback: try du command (may not work if www-data can't read home dir) - $homeDir = $this->home_directory; - - if (! is_dir($homeDir)) { - return 0; - } - - $output = shell_exec('du -sb '.escapeshellarg($homeDir).' 2>/dev/null | cut -f1'); - - return (int) trim($output ?: '0'); + return 0; } /** diff --git a/bin/jabali-agent b/bin/jabali-agent index ce50436..5906615 100755 --- a/bin/jabali-agent +++ b/bin/jabali-agent @@ -20681,43 +20681,63 @@ function quotaGet(array $params): array { $username = $params['username'] ?? ''; $mountPoint = $params['mount'] ?? '/home'; - + if (empty($username)) { return ['success' => false, 'error' => 'Username is required']; } - + + // Root-level disk usage probe for /home/. + $getDuUsageMb = static function (string $user): float { + $homeDir = "/home/{$user}"; + if (!is_dir($homeDir)) { + return 0.0; + } + + $duOutput = trim(shell_exec("du -sk " . escapeshellarg($homeDir) . " 2>/dev/null | cut -f1") ?? ''); + if ($duOutput === '' || preg_match('/^\d+$/', $duOutput) !== 1) { + return 0.0; + } + + return round(((int) $duOutput) / 1024, 2); + }; + + // Quota counters can be stale on some filesystems; prefer the larger value. + $normalizeUsage = static function (float $usedMb, float $softMb, float $hardMb, float $duMb): array { + $finalUsedMb = $duMb > $usedMb ? $duMb : $usedMb; + + return [ + 'used_mb' => $finalUsedMb, + 'soft_mb' => $softMb, + 'hard_mb' => $hardMb, + 'usage_percent' => $hardMb > 0 ? round(($finalUsedMb / $hardMb) * 100, 1) : 0, + 'quota_source' => $duMb > $usedMb ? 'du_override' : 'quota', + ]; + }; + // Find the actual mount point $findMount = trim(shell_exec("df --output=target " . escapeshellarg($mountPoint) . " 2>/dev/null | tail -1") ?? ''); if (empty($findMount)) { $findMount = '/'; } - + // Get quota info $cmd = sprintf('quota -u %s -w 2>&1', escapeshellarg($username)); exec($cmd, $output, $exitCode); - + $outputStr = implode("\n", $output); - + // Check if user has no quota if (strpos($outputStr, 'none') !== false || $exitCode !== 0) { // Try repquota for more detailed info - $cmd2 = sprintf('repquota -u %s 2>/dev/null | grep "^%s"', - escapeshellarg($findMount), + $cmd2 = sprintf('repquota -u %s 2>/dev/null | grep "^%s"', + escapeshellarg($findMount), escapeshellarg($username) ); $repOutput = trim(shell_exec($cmd2) ?? ''); - - if (empty($repOutput)) { - // Quota not enabled - use du to calculate actual disk usage - $homeDir = "/home/{$username}"; - $usedMb = 0; - if (is_dir($homeDir)) { - // Use du to get actual disk usage in KB - $duOutput = trim(shell_exec("du -sk " . escapeshellarg($homeDir) . " 2>/dev/null | cut -f1") ?? '0'); - $usedKb = (int)$duOutput; - $usedMb = round($usedKb / 1024, 2); - } + if (empty($repOutput)) { + // Quota not enabled - use du to calculate actual disk usage. + $usedMb = $getDuUsageMb($username); return [ 'success' => true, @@ -20727,36 +20747,39 @@ function quotaGet(array $params): array 'soft_mb' => 0, 'hard_mb' => 0, 'usage_percent' => 0, - 'quota_source' => 'du' // Indicate that we used du fallback + 'quota_source' => 'du', ]; } - + // Parse repquota output // Format: username -- used soft hard grace used soft hard grace $parts = preg_split('/\s+/', $repOutput); if (count($parts) >= 5) { - $usedKb = (int)$parts[2]; - $softKb = (int)$parts[3]; - $hardKb = (int)$parts[4]; - + $usedMb = round(((int) $parts[2]) / 1024, 2); + $softMb = round(((int) $parts[3]) / 1024, 2); + $hardMb = round(((int) $parts[4]) / 1024, 2); + $duMb = $getDuUsageMb($username); + $usage = $normalizeUsage($usedMb, $softMb, $hardMb, $duMb); + return [ 'success' => true, 'username' => $username, - 'has_quota' => $softKb > 0 || $hardKb > 0, - 'used_mb' => round($usedKb / 1024, 2), - 'soft_mb' => round($softKb / 1024, 2), - 'hard_mb' => round($hardKb / 1024, 2), - 'usage_percent' => $hardKb > 0 ? round(($usedKb / $hardKb) * 100, 1) : 0 + 'has_quota' => $softMb > 0 || $hardMb > 0, + 'used_mb' => $usage['used_mb'], + 'soft_mb' => $usage['soft_mb'], + 'hard_mb' => $usage['hard_mb'], + 'usage_percent' => $usage['usage_percent'], + 'quota_source' => $usage['quota_source'], ]; } } - + // Parse standard quota output // Look for lines with filesystem info $usedKb = 0; $softKb = 0; $hardKb = 0; - + foreach ($output as $line) { if (preg_match('/^\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)/', $line, $matches)) { $usedKb = (int)$matches[2]; @@ -20765,15 +20788,22 @@ function quotaGet(array $params): array break; } } - + + $usedMb = round($usedKb / 1024, 2); + $softMb = round($softKb / 1024, 2); + $hardMb = round($hardKb / 1024, 2); + $duMb = $getDuUsageMb($username); + $usage = $normalizeUsage($usedMb, $softMb, $hardMb, $duMb); + return [ 'success' => true, 'username' => $username, - 'has_quota' => $softKb > 0 || $hardKb > 0, - 'used_mb' => round($usedKb / 1024, 2), - 'soft_mb' => round($softKb / 1024, 2), - 'hard_mb' => round($hardKb / 1024, 2), - 'usage_percent' => $hardKb > 0 ? round(($usedKb / $hardKb) * 100, 1) : 0 + 'has_quota' => $softMb > 0 || $hardMb > 0, + 'used_mb' => $usage['used_mb'], + 'soft_mb' => $usage['soft_mb'], + 'hard_mb' => $usage['hard_mb'], + 'usage_percent' => $usage['usage_percent'], + 'quota_source' => $usage['quota_source'], ]; } diff --git a/config.toml.example b/config.toml.example new file mode 100644 index 0000000..04dd6e6 --- /dev/null +++ b/config.toml.example @@ -0,0 +1,26 @@ +# Jabali Panel repository config +# +# Used by `scripts/deploy.sh` (CLI flags still override these settings). +# Keep secrets out of this file. Prefer SSH keys and server-side git remotes. + +[deploy] +# Test server (where GitHub deploy key is configured) +host = "192.168.100.50" +user = "root" +path = "/var/www/jabali" +www_user = "www-data" + +# Optional: keep npm cache outside the repo (saves time on repeated builds) +# npm_cache_dir = "/var/www/.npm" + +# Optional: override the branch that gets pushed from the deploy server +push_branch = "main" + +# Optional: push to explicit URLs (instead of relying on named remotes) +# These pushes run FROM the test server. +gitea_url = "ssh://git@192.168.100.100:2222/shukivaknin/jabali-panel.git" +github_url = "git@github.com:shukiv/jabali-panel.git" + +# If you prefer named remotes on the deploy server instead of URLs: +# gitea_remote = "gitea" +# github_remote = "origin" diff --git a/docs/architecture/mcp-and-filament-blueprint.md b/docs/architecture/mcp-and-filament-blueprint.md new file mode 100644 index 0000000..b3bdb05 --- /dev/null +++ b/docs/architecture/mcp-and-filament-blueprint.md @@ -0,0 +1,150 @@ +# MCP and Filament Blueprint (Jabali Panel) + +Last updated: 2026-02-12 + +This document is an internal developer blueprint for working on Jabali Panel with: + +- MCP tooling (Model Context Protocol) for fast, version-correct introspection and docs. +- Filament (Admin + User panels) conventions and project-specific UI rules. + +## Goals + +- Keep changes consistent with the existing architecture and UI. +- Prefer version-specific documentation and project-aware inspection. +- Avoid UI regressions by following Filament-native patterns. +- Keep privileged operations isolated behind the agent. + +## MCP Tooling Blueprint + +Jabali is set up to be worked on with MCP tools. Use them to reduce guesswork and prevent version drift. + +### 1) Laravel Boost (Most Important) + +Laravel Boost MCP gives application-aware tools (routes, config, DB schema, logs, and version-specific docs). + +Use it when: +- You need to confirm route names/paths and middleware. +- You need to confirm the active config (not just what you expect in `.env`). +- You need the DB schema or sample records to understand existing behavior. +- You need version-specific docs for Laravel/Livewire/Filament/Tailwind. + +Preferred workflow: +- `application-info` to confirm versions and installed packages. +- `list-routes` to find the correct URL, route names, and panel prefixes. +- `get-config` for runtime config values. +- `database-schema` and `database-query` (read-only) to verify tables and relationships. +- `read-log-entries` / `last-error` to confirm the active failure. +- `search-docs` before implementing anything that depends on framework behavior. + +Project rule of thumb: +- Before making a structural change in the panel, list relevant routes and key config values first. + +### 2) Jabali Docs MCP Server + +The repository includes `mcp-docs-server/` which exposes project docs as MCP resources/tools. + +What it is useful for: +- Quick search across `README.md`, `AGENT.md`, and changelog content. +- Pulling a specific section by title. + +This is not a runtime dependency of the panel. It is a developer tooling layer. + +### 3) Frontend and Quality MCPs + +Use these to audit and reduce UI/HTML/CSS regressions: + +- `css-mcp`: + - Analyze CSS quality/complexity. + - Check browser compatibility for specific CSS features. + - Pull MDN docs for CSS properties/selectors when implementing UI. +- `stylelint`: + - Lint CSS where applicable (note: Filament pages should not use custom CSS files). +- `webdev-tools`: + - Prettier formatting for snippets. + - `php -l` lint for PHP syntax. + - HTML validation for standalone HTML. + +Security rule: +- Do not send secrets (tokens, passwords, private keys) into any tool query. + +## Filament Blueprint (How Jabali Panels Are Built) + +Jabali has two Filament panels: + +- Admin panel: server-wide operations. +- User panel ("Jabali" panel): tenant/user operations. + +High-level structure: +- `app/Filament/Admin/*` for admin. +- `app/Filament/Jabali/*` for user. + +### Pages vs Resources + +Default decision: +- Use a Filament Resource when the UI is primarily CRUD around an Eloquent model. +- Use a Filament Page when the UI is a dashboard, a multi-step wizard, or merges multiple concerns into a single screen. + +### Project UI Rules (Strict) + +These rules exist to keep the UI consistent and maintainable: + +- Use Filament native components for layout and UI. +- Avoid raw HTML layout in Filament pages. +- Avoid custom CSS for Filament pages. +- Use Filament tables for list data. + +Practical mapping: +- Layout: `Filament\Schemas\Components\Section`, `Grid`, `Tabs`, `Group`. +- Actions: `Filament\Actions\Action`. +- List data: `HasTable` / `InteractsWithTable` or `EmbeddedTable`. + +### Tabs + Tables Gotcha + +There is a known class of issues when a table is nested incorrectly inside schema Tabs. + +Rule of thumb: +- Prefer `EmbeddedTable::make()` in schema layouts. +- Avoid mounting tables inside `View::make()` within `Tabs::make()` unless you know the action mounting behavior is preserved. + +### Translations and RTL + +Jabali uses JSON-based translations. + +Rules: +- Use the English string as the translation key: `__('Create Domain')`. +- Do not introduce dotted translation keys like `__('domain.create')`. +- Ensure UI reads correctly in RTL locales (Arabic/Hebrew). + +### Privileged Operations (Agent Boundary) + +The Laravel app is the control plane. Privileged system operations are executed by the root-level agent. + +Key points: +- The agent is `bin/jabali-agent`. +- The panel should call privileged operations through the Agent client service (not by shelling out directly). +- Keep all path and input validation strict before an agent call. + +## Filament Blueprint Planning (Feature Specs) + +When writing an implementation plan for a Filament feature, use Filament Blueprint planning docs as a checklist. + +Reference: +- `vendor/filament/blueprint/resources/markdown/planning/overview.md` + +At minimum, a plan should specify: +- Data model changes (tables, columns, indexes, relationships). +- Panel placement (Admin vs User) and navigation. +- Page/Resource decisions. +- Authorization model (policies/guards). +- Background jobs (for long-running operations). +- Audit logging events. +- Tests (Feature tests for endpoints and Livewire/Filament behaviors). + +## Development Checklist (Per Feature) + +- Confirm the correct panel and route prefix. +- List routes and verify config assumptions (Boost tools). +- Follow Filament-native components (no custom HTML/CSS in Filament pages). +- Use tables for list data. +- Keep agent boundary intact for privileged operations. +- Add or update tests and run targeted test commands. diff --git a/docs/docs-index.md b/docs/docs-index.md index e8cfd26..e540359 100644 --- a/docs/docs-index.md +++ b/docs/docs-index.md @@ -14,6 +14,7 @@ Last updated: 2026-02-10 - /var/www/jabali/docs/installation.md - Debian package install path, Filament notifications patch, and deploy script usage. - /var/www/jabali/docs/architecture/control-panel-blueprint.md - High-level blueprint for a hosting panel. - /var/www/jabali/docs/architecture/directadmin-migration-blueprint.md - Blueprint for migrating DirectAdmin accounts into Jabali. +- /var/www/jabali/docs/architecture/mcp-and-filament-blueprint.md - Developer blueprint for MCP tooling and Filament panel conventions. - /var/www/jabali/docs/archive-notes.md - Archived files and restore notes. - /var/www/jabali/docs/screenshots/README.md - Screenshot generation instructions. - /var/www/jabali/docs/docs-summary.md - Project documentation summary (generated). diff --git a/docs/installation.md b/docs/installation.md index 73054c2..ab5cb4e 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -117,7 +117,14 @@ project to `root@192.168.100.50:/var/www/jabali`, commits on that server, bumps `VERSION`, updates the `install.sh` fallback, and pushes to Git remotes from that server. Then it runs composer/npm, migrations, and cache rebuilds. -Defaults (override via flags or env vars): +Defaults (override via flags, env vars, or config.toml): + +Config file: +- `config.toml` (ignored by git) is read automatically if present. +- Start from `config.toml.example`. +- Set `CONFIG_FILE` to use an alternate TOML file path. +- Supported keys are in `[deploy]` (for example: `host`, `user`, `path`, `www_user`, `push_branch`, `gitea_url`, `github_url`). + - Host: `192.168.100.50` - User: `root` - Path: `/var/www/jabali` diff --git a/install.sh b/install.sh index 4a5e968..553206f 100755 --- a/install.sh +++ b/install.sh @@ -16,7 +16,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [[ -f "$SCRIPT_DIR/VERSION" ]]; then JABALI_VERSION="$(sed -n 's/^VERSION=//p' "$SCRIPT_DIR/VERSION")" fi -JABALI_VERSION="${JABALI_VERSION:-0.9-rc65}" +JABALI_VERSION="${JABALI_VERSION:-0.9-rc66}" # Colors RED='\033[0;31m' @@ -414,6 +414,7 @@ install_packages() { wget zip unzip + cron htop net-tools dnsutils @@ -3020,6 +3021,18 @@ setup_scheduler_cron() { mkdir -p "$JABALI_DIR/storage/logs" chown -R www-data:www-data "$JABALI_DIR/storage/logs" + # Ensure crontab command is available + if ! command -v crontab >/dev/null 2>&1; then + warn "crontab command not found, installing cron package..." + apt-get update -qq + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq cron || true + fi + + if ! command -v crontab >/dev/null 2>&1; then + warn "Unable to configure scheduler: crontab command is still missing" + return + fi + # Ensure cron service is enabled and running if command -v systemctl >/dev/null 2>&1; then systemctl enable cron >/dev/null 2>&1 || true @@ -3030,8 +3043,8 @@ setup_scheduler_cron() { CRON_LINE="* * * * * cd $JABALI_DIR && php artisan schedule:run >> /dev/null 2>&1" # Add to www-data's crontab (not root) to avoid permission issues with log files - if ! sudo -u www-data crontab -l 2>/dev/null | grep -q "artisan schedule:run"; then - (sudo -u www-data crontab -l 2>/dev/null; echo "$CRON_LINE") | sudo -u www-data crontab - + if ! crontab -u www-data -l 2>/dev/null | grep -q "artisan schedule:run"; then + (crontab -u www-data -l 2>/dev/null; echo "$CRON_LINE") | crontab -u www-data - log "Laravel scheduler cron job added" else log "Laravel scheduler cron job already exists" @@ -3617,7 +3630,9 @@ uninstall() { rm -f /etc/logrotate.d/jabali-users # Remove www-data cron jobs (Laravel scheduler) - crontab -u www-data -r 2>/dev/null || true + if command -v crontab >/dev/null 2>&1; then + crontab -u www-data -r 2>/dev/null || true + fi log "Configuration files cleaned" diff --git a/scripts/deploy.sh b/scripts/deploy.sh index ee8d734..32b437e 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -3,16 +3,120 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -DEPLOY_HOST="${DEPLOY_HOST:-192.168.100.50}" -DEPLOY_USER="${DEPLOY_USER:-root}" -DEPLOY_PATH="${DEPLOY_PATH:-/var/www/jabali}" -WWW_USER="${WWW_USER:-www-data}" -NPM_CACHE_DIR="${NPM_CACHE_DIR:-}" -GITEA_REMOTE="${GITEA_REMOTE:-gitea}" -GITEA_URL="${GITEA_URL:-}" -GITHUB_REMOTE="${GITHUB_REMOTE:-origin}" -GITHUB_URL="${GITHUB_URL:-}" -PUSH_BRANCH="${PUSH_BRANCH:-}" + +CONFIG_FILE="${CONFIG_FILE:-$ROOT_DIR/config.toml}" + +# Capture env overrides before we assign defaults so config.toml can sit between +# defaults and environment: CLI > env > config > defaults. +ENV_DEPLOY_HOST="${DEPLOY_HOST-}" +ENV_DEPLOY_USER="${DEPLOY_USER-}" +ENV_DEPLOY_PATH="${DEPLOY_PATH-}" +ENV_WWW_USER="${WWW_USER-}" +ENV_NPM_CACHE_DIR="${NPM_CACHE_DIR-}" +ENV_GITEA_REMOTE="${GITEA_REMOTE-}" +ENV_GITEA_URL="${GITEA_URL-}" +ENV_GITHUB_REMOTE="${GITHUB_REMOTE-}" +ENV_GITHUB_URL="${GITHUB_URL-}" +ENV_PUSH_BRANCH="${PUSH_BRANCH-}" + +DEPLOY_HOST="192.168.100.50" +DEPLOY_USER="root" +DEPLOY_PATH="/var/www/jabali" +WWW_USER="www-data" +NPM_CACHE_DIR="" +GITEA_REMOTE="gitea" +GITEA_URL="" +GITHUB_REMOTE="origin" +GITHUB_URL="" +PUSH_BRANCH="" + +trim_ws() { + local s="${1:-}" + s="$(echo "$s" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')" + printf '%s' "$s" +} + +toml_unquote() { + local v + v="$(trim_ws "${1:-}")" + + # Only accept simple double-quoted strings, booleans, or integers. + if [[ ${#v} -ge 2 && "${v:0:1}" == '"' && "${v: -1}" == '"' ]]; then + printf '%s' "${v:1:${#v}-2}" + return 0 + fi + + if [[ "$v" =~ ^(true|false)$ ]]; then + printf '%s' "$v" + return 0 + fi + + if [[ "$v" =~ ^-?[0-9]+$ ]]; then + printf '%s' "$v" + return 0 + fi + + return 1 +} + +load_config_toml() { + local file section line key raw value + file="$1" + section="" + + [[ -f "$file" ]] || return 0 + + while IFS= read -r line || [[ -n "$line" ]]; do + # Strip comments and whitespace. + line="${line%%#*}" + line="$(trim_ws "$line")" + [[ -z "$line" ]] && continue + + if [[ "$line" =~ ^\[([A-Za-z0-9_.-]+)\]$ ]]; then + section="${BASH_REMATCH[1]}" + continue + fi + + [[ "$section" == "deploy" ]] || continue + + if [[ "$line" =~ ^([A-Za-z0-9_]+)[[:space:]]*=[[:space:]]*(.+)$ ]]; then + key="${BASH_REMATCH[1]}" + raw="${BASH_REMATCH[2]}" + value="" + + if ! value="$(toml_unquote "$raw")"; then + continue + fi + + case "$key" in + host) DEPLOY_HOST="$value" ;; + user) DEPLOY_USER="$value" ;; + path) DEPLOY_PATH="$value" ;; + www_user) WWW_USER="$value" ;; + npm_cache_dir) NPM_CACHE_DIR="$value" ;; + gitea_remote) GITEA_REMOTE="$value" ;; + gitea_url) GITEA_URL="$value" ;; + github_remote) GITHUB_REMOTE="$value" ;; + github_url) GITHUB_URL="$value" ;; + push_branch) PUSH_BRANCH="$value" ;; + esac + fi + done < "$file" +} + +load_config_toml "$CONFIG_FILE" + +# Apply environment overrides on top of config. +if [[ -n "${ENV_DEPLOY_HOST:-}" ]]; then DEPLOY_HOST="$ENV_DEPLOY_HOST"; fi +if [[ -n "${ENV_DEPLOY_USER:-}" ]]; then DEPLOY_USER="$ENV_DEPLOY_USER"; fi +if [[ -n "${ENV_DEPLOY_PATH:-}" ]]; then DEPLOY_PATH="$ENV_DEPLOY_PATH"; fi +if [[ -n "${ENV_WWW_USER:-}" ]]; then WWW_USER="$ENV_WWW_USER"; fi +if [[ -n "${ENV_NPM_CACHE_DIR:-}" ]]; then NPM_CACHE_DIR="$ENV_NPM_CACHE_DIR"; fi +if [[ -n "${ENV_GITEA_REMOTE:-}" ]]; then GITEA_REMOTE="$ENV_GITEA_REMOTE"; fi +if [[ -n "${ENV_GITEA_URL:-}" ]]; then GITEA_URL="$ENV_GITEA_URL"; fi +if [[ -n "${ENV_GITHUB_REMOTE:-}" ]]; then GITHUB_REMOTE="$ENV_GITHUB_REMOTE"; fi +if [[ -n "${ENV_GITHUB_URL:-}" ]]; then GITHUB_URL="$ENV_GITHUB_URL"; fi +if [[ -n "${ENV_PUSH_BRANCH:-}" ]]; then PUSH_BRANCH="$ENV_PUSH_BRANCH"; fi SKIP_SYNC=0 SKIP_COMPOSER=0 @@ -57,7 +161,8 @@ Options: -h, --help Show this help Environment overrides: - DEPLOY_HOST, DEPLOY_USER, DEPLOY_PATH, WWW_USER, NPM_CACHE_DIR, GITEA_REMOTE, GITEA_URL, GITHUB_REMOTE, GITHUB_URL, PUSH_BRANCH + CONFIG_FILE points to a TOML file (default: ./config.toml). The script reads [deploy] keys. + CONFIG_FILE, DEPLOY_HOST, DEPLOY_USER, DEPLOY_PATH, WWW_USER, NPM_CACHE_DIR, GITEA_REMOTE, GITEA_URL, GITHUB_REMOTE, GITHUB_URL, PUSH_BRANCH EOF }