add dx build system / CLI orchestrator
This commit is contained in:
commit
51a186ace6
74 changed files with 5829 additions and 0 deletions
321
README.md
Normal file
321
README.md
Normal file
|
|
@ -0,0 +1,321 @@
|
||||||
|
# 🛠️ DX
|
||||||
|
|
||||||
|
`dx` is a framework-agnostic bash CLI that orchestrates the full development lifecycle for PHP projects — Docker, Apache, Traefik, environment config, migrations, and tests — across multiple frameworks (Yii2, Laravel).
|
||||||
|
|
||||||
|
The `dx` script at the project root is the entrypoint. It loads `dxkit/bootstrap.sh` and dispatches to the command and module system inside `dxkit/`.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Bash (Linux/macOS native; Windows via Git Bash or WSL)
|
||||||
|
- Docker + Docker Compose
|
||||||
|
- `dx` must be executable: `chmod +x dx`
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dx setup # first time: build image + init environment
|
||||||
|
./dx up # start the stack
|
||||||
|
./dx shell # enter the app container
|
||||||
|
./dx console migrate # run database migrations
|
||||||
|
```
|
||||||
|
|
||||||
|
## Command reference
|
||||||
|
|
||||||
|
### Setup & initialization
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `dx build` | Build the Docker image and generate the compose stack |
|
||||||
|
| `dx init [env]` | Bring the project to a runnable state for an environment (default: `dev`) |
|
||||||
|
| `dx setup` | `build` + `init` in one step |
|
||||||
|
|
||||||
|
**`dx build` flags**
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|---|---|
|
||||||
|
| `--dev` / `--qly` / `--prd` | Target environment (default: `--dev`) |
|
||||||
|
| `--project=NAME` | Override the project name |
|
||||||
|
| `--port=PORT` | Override the app HTTP port |
|
||||||
|
| `--db=mysql\|mssql` | Database engine (default: `mysql`) |
|
||||||
|
| `--db-port=PORT` | Override the database port |
|
||||||
|
| `--sync-hosts` | Write the project domain to `/etc/hosts` after build |
|
||||||
|
| `--no-vhosts` | Skip Apache virtual host generation |
|
||||||
|
| `--no-interact` | Non-interactive mode (no prompts) |
|
||||||
|
| `--debug` | Enable shell trace output |
|
||||||
|
|
||||||
|
**`dx init` flags**
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|---|---|
|
||||||
|
| `--skip-vendors` | Skip `composer install` |
|
||||||
|
| `--skip-framework-init` | Skip framework init script |
|
||||||
|
| `--skip-framework-config` | Skip framework config generation |
|
||||||
|
| `--skip-proxy` | Skip starting the reverse proxy |
|
||||||
|
| `--debug` | Enable shell trace output |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Runtime
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `dx up` | Start the Docker stack |
|
||||||
|
| `dx down` | Stop the Docker stack |
|
||||||
|
| `dx logs` | Tail container logs |
|
||||||
|
| `dx shell` / `dx bash` | Open an interactive shell inside the app container |
|
||||||
|
| `dx exec <cmd> [args]` | Run a command inside the app container |
|
||||||
|
| `dx list` / `dx ls` | List stack containers |
|
||||||
|
| `dx test [args]` | Run PHPUnit tests inside the container |
|
||||||
|
|
||||||
|
**`dx up` flags**
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|---|---|
|
||||||
|
| `--sync-hosts` | Sync domain entries to `/etc/hosts` before starting |
|
||||||
|
| `--skip-proxy` | Do not ensure the reverse proxy is running |
|
||||||
|
| `--recreate` | Force container recreation |
|
||||||
|
| `--build` | Rebuild the image before starting |
|
||||||
|
| `--foreground` | Run in foreground (don't detach) |
|
||||||
|
| `--debug` | Enable shell trace output |
|
||||||
|
|
||||||
|
**`dx down` flags**
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|---|---|
|
||||||
|
| `--volumes` | Also remove Docker volumes |
|
||||||
|
| `--orphans` | Remove orphan containers |
|
||||||
|
| `--debug` | Enable shell trace output |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Framework console
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `dx console <cmd> [args]` | Run a framework console command |
|
||||||
|
| `dx yii <cmd>` | Alias for Yii2 (`php yii <cmd>`) |
|
||||||
|
| `dx artisan <cmd>` | Alias for Laravel (`php artisan <cmd>`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Migration shortcuts
|
||||||
|
|
||||||
|
These are resolved by the active framework driver.
|
||||||
|
|
||||||
|
**Yii2**
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `dx migrate` | Run all pending migrations |
|
||||||
|
| `dx migrate-create <name>` | Generate a new timestamped migration file in `console/migrations/` |
|
||||||
|
| `dx migrate-down [n]` | Revert `n` migrations (default: 1) |
|
||||||
|
| `dx migrate-new` | Show pending migrations |
|
||||||
|
| `dx migrate-history` | Show migration history |
|
||||||
|
|
||||||
|
**Laravel**
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `dx migrate` | Run all pending migrations |
|
||||||
|
| `dx migrate-create <name>` | Create a migration via `artisan make:migration` |
|
||||||
|
| `dx migrate-rollback [--step=N]` | Revert migrations |
|
||||||
|
| `dx migrate-status` | Show migration status |
|
||||||
|
| `dx migrate-fresh` | Drop all tables and re-run all migrations |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Apache
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `dx apache validate` | Validate vhost configuration |
|
||||||
|
| `dx apache reload` | Graceful reload (picks up config changes without downtime) |
|
||||||
|
| `dx apache restart` | Full restart |
|
||||||
|
| `dx apache vhosts` | List all loaded virtual hosts |
|
||||||
|
| `dx apache modules` | List all loaded Apache modules |
|
||||||
|
| `dx apache version` | Show Apache version |
|
||||||
|
| `dx apache logs [error\|access]` | Tail error or access logs |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Proxy (Traefik)
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `dx proxy start` | Start the Traefik reverse proxy |
|
||||||
|
| `dx proxy stop` | Stop the proxy |
|
||||||
|
| `dx proxy restart` | Restart the proxy |
|
||||||
|
| `dx proxy status` | Show proxy status |
|
||||||
|
| `dx proxy logs` | Tail proxy logs |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Network & hosts
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `dx network status` | Show resolved port assignments |
|
||||||
|
| `dx network resolve` | Re-run port resolution and show changes |
|
||||||
|
| `dx network hosts` | Alias for the `hosts` command |
|
||||||
|
| `dx hosts sync` | Write project domain entries to `/etc/hosts` (requires root/sudo) |
|
||||||
|
| `dx hosts remove` | Remove project entries from `/etc/hosts` |
|
||||||
|
| `dx hosts list` | Show current `/etc/hosts` entries for this project |
|
||||||
|
| `dx hosts preview` | Preview entries that would be written |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Workspace
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `dx workspace` / `dx ws` | Drop into a subshell where all `dx` commands and framework driver commands are available without the `dx` prefix |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Global flags
|
||||||
|
|
||||||
|
These flags are accepted by all commands:
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|---|---|
|
||||||
|
| `--framework=NAME` | Override auto-detected framework |
|
||||||
|
| `--debug` | Enable shell trace output (`set -x`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environments
|
||||||
|
|
||||||
|
Three built-in environments:
|
||||||
|
|
||||||
|
| Name | Use |
|
||||||
|
|---|---|
|
||||||
|
| `dev` | Development (default)
|
||||||
|
| `qly` | Quality |
|
||||||
|
| `prd` | Production |
|
||||||
|
|
||||||
|
Environment variables are layered in order:
|
||||||
|
|
||||||
|
1. `dxkit/env/globals.env` — shared defaults, source-controlled
|
||||||
|
2. `.project/artifacts/env/${ENVIRONMENT}.env` — local overrides, gitignored
|
||||||
|
3. `.project/artifacts/env/${ENVIRONMENT}.resolved.env` — generated resolved values, gitignored
|
||||||
|
|
||||||
|
Resolved port assignments are written to `.project/artifacts/ports` and loaded back on subsequent runs.
|
||||||
|
|
||||||
|
Port values in env files may use the `auto:PORT` syntax — dxkit will find the next available port starting from that number.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Framework support
|
||||||
|
|
||||||
|
Framework detection reads `composer.json` at project root.
|
||||||
|
|
||||||
|
| Framework | Detected by | Console binary | `dx console` alias |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Yii2 Advanced | `yiisoft/yii2` + advanced template structure | `yii` | `dx yii` |
|
||||||
|
| Yii2 Basic | `yiisoft/yii2` + basic template structure | `yii` | `dx yii` |
|
||||||
|
| Laravel | `laravel/framework` | `artisan` | `dx artisan` |
|
||||||
|
|
||||||
|
Use `--framework=NAME` to override detection (e.g. `--framework=yii2-advanced`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
For contributors and maintainers.
|
||||||
|
|
||||||
|
```
|
||||||
|
dx # project-level entrypoint; defines aliases, calls dx::dispatch()
|
||||||
|
dxkit/
|
||||||
|
├── bootstrap.sh # loads core.sh, exports PROJECT_ROOT, defines dx::load_modules()
|
||||||
|
├── core.sh # sources all core subsystems in order
|
||||||
|
├── core/ # low-level infrastructure
|
||||||
|
│ ├── platform.sh # OS detection, privilege elevation
|
||||||
|
│ ├── context.sh # execution context management
|
||||||
|
│ ├── utils.sh # general utilities
|
||||||
|
│ ├── string.sh # string helpers
|
||||||
|
│ ├── hook.sh # pre/post hook system
|
||||||
|
│ ├── module.sh # module loader
|
||||||
|
│ ├── command.sh # command registration framework
|
||||||
|
│ ├── loader.sh # dynamic file loader
|
||||||
|
│ ├── flag.sh # flag/argument parsing
|
||||||
|
│ └── runtime/runtime.sh # runtime abstraction entry
|
||||||
|
├── commands/ # one file per command, loaded dynamically by the dispatcher
|
||||||
|
│ ├── build.command.sh
|
||||||
|
│ ├── init.command.sh
|
||||||
|
│ ├── setup.command.sh
|
||||||
|
│ ├── console.command.sh
|
||||||
|
│ ├── apache.command.sh
|
||||||
|
│ ├── hosts.command.sh
|
||||||
|
│ ├── network.command.sh
|
||||||
|
│ ├── proxy.command.sh
|
||||||
|
│ ├── workspace.command.sh
|
||||||
|
│ ├── docker/ # low-level docker subcommands
|
||||||
|
│ └── runtime/ # runtime-abstracted subcommands (used by default)
|
||||||
|
├── modules/ # reusable function libraries sourced at startup
|
||||||
|
│ ├── app.module.sh # framework abstraction layer (scaffold, init, config, console)
|
||||||
|
│ ├── docker.module.sh # Docker and Docker Compose wrappers
|
||||||
|
│ ├── env.module.sh # environment variable loading and derivation
|
||||||
|
│ ├── log.module.sh # structured logging with icons and context prefixes
|
||||||
|
│ ├── fs.module.sh # cross-platform file write and sed utilities
|
||||||
|
│ ├── network.module.sh # port resolution and persistence
|
||||||
|
│ ├── apache.module.sh # Apache management functions
|
||||||
|
│ ├── proxy.module.sh # proxy abstraction (delegates to docker/proxy.module.sh)
|
||||||
|
│ ├── artifact.module.sh # runtime artifact directory management
|
||||||
|
│ ├── composer.module.sh # composer operations (install, update, require, auth)
|
||||||
|
│ ├── template.module.sh # envsubst-based template rendering
|
||||||
|
│ ├── yii.module.sh # Yii2 shared utilities (exec, env name mapping)
|
||||||
|
│ ├── phpstorm.module.sh # PhpStorm datasource config generation
|
||||||
|
│ ├── docker/
|
||||||
|
│ │ ├── db.module.sh # database port/path/env helpers (MySQL + MSSQL)
|
||||||
|
│ │ └── proxy.module.sh # Traefik container lifecycle and label generation
|
||||||
|
│ └── framework/
|
||||||
|
│ ├── yii2-advanced.module.sh
|
||||||
|
│ ├── yii2-basic.module.sh
|
||||||
|
│ └── laravel.module.sh
|
||||||
|
├── drivers/ # framework-specific command registration
|
||||||
|
│ ├── dispatcher.sh # resolves framework → driver, populates DRIVER_COMMANDS
|
||||||
|
│ ├── yii2/
|
||||||
|
│ │ ├── driver.sh # registers yii, migrate-* commands
|
||||||
|
│ │ └── migrate.sh # migration file generation helpers
|
||||||
|
│ └── laravel/
|
||||||
|
│ ├── driver.sh # registers artisan, migrate-* commands
|
||||||
|
│ └── migrate.sh # artisan make:migration wrappers
|
||||||
|
├── runtime/ # runtime abstraction (Docker vs. native execution)
|
||||||
|
│ ├── runtime.sh # selects active runtime
|
||||||
|
│ ├── docker.runtime.sh # implements runtime::exec via docker compose exec
|
||||||
|
│ └── native.runtime.sh # implements runtime::exec directly on host
|
||||||
|
├── templates/ # envsubst-rendered config templates
|
||||||
|
│ ├── docker/
|
||||||
|
│ │ ├── Dockerfile.template
|
||||||
|
│ │ └── docker-compose.yml.template
|
||||||
|
│ ├── apache/
|
||||||
|
│ │ ├── yii2-advanced/vhosts.conf.template
|
||||||
|
│ │ └── yii2-basic/vhosts.conf.template
|
||||||
|
│ ├── yii2-advanced/
|
||||||
|
│ │ ├── main-local.php.template
|
||||||
|
│ │ └── components/ # db.mysql, db.mssql, log php templates
|
||||||
|
│ └── yii2-basic/
|
||||||
|
│ ├── db.mysql.php.template
|
||||||
|
│ └── db.mssql.php.template
|
||||||
|
├── env/ # layered environment variable files
|
||||||
|
│ ├── globals.env
|
||||||
|
│ ├── dev.env
|
||||||
|
│ ├── qly.env
|
||||||
|
│ └── prd.env
|
||||||
|
└── docker/
|
||||||
|
└── entrypoint.sh # container entrypoint script
|
||||||
|
```
|
||||||
|
|
||||||
|
### How command dispatch works
|
||||||
|
|
||||||
|
1. `dx <command> [args]` calls `dx::dispatch()`
|
||||||
|
2. Aliases in `dx` are resolved first (e.g. `sh` → `runtime/shell`, `ls` → `runtime/list`)
|
||||||
|
3. Built-ins (`help`, `elevate`) are handled inline
|
||||||
|
4. Driver commands registered in `DRIVER_COMMANDS` (via `dxkit/drivers/dispatcher.sh`) are checked next
|
||||||
|
5. Remaining commands are matched to files in `dxkit/commands/` and loaded dynamically
|
||||||
|
|
||||||
|
### Adding a new command
|
||||||
|
|
||||||
|
1. Create `dxkit/commands/<name>.command.sh` with a function `command::<name>()`
|
||||||
|
2. Optionally register an alias in `dx` under the alias map
|
||||||
|
3. For framework-specific commands, register the command name in the driver file (`dxkit/drivers/<framework>/driver.sh`)
|
||||||
156
dx
Normal file
156
dx
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/dxkit/bootstrap.sh"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Alias map (commands)
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
declare -A CMD_ALIASES=(
|
||||||
|
[up]=runtime/up
|
||||||
|
[down]=runtime/down
|
||||||
|
[logs]=runtime/logs
|
||||||
|
[list]=runtime/list
|
||||||
|
[ls]=runtime/list
|
||||||
|
[list-stack]=runtime/list
|
||||||
|
[list-containers]=runtime/list
|
||||||
|
[exec]=runtime/exec
|
||||||
|
[shell]=runtime/shell
|
||||||
|
[sh]=runtime/shell
|
||||||
|
[bash]=runtime/shell
|
||||||
|
[test]=runtime/test
|
||||||
|
|
||||||
|
[net]=network
|
||||||
|
[ws]=workspace
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Modules
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
load_module env
|
||||||
|
load_module log
|
||||||
|
load_module fs
|
||||||
|
load_module docker
|
||||||
|
load_module network
|
||||||
|
load_module artifact
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Dispatch
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function dx::resolve_alias() {
|
||||||
|
local cmd="$1"
|
||||||
|
echo "${CMD_ALIASES[$cmd]:-$cmd}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function dx::dispatch() {
|
||||||
|
local raw_cmd="${1:-help}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
local cmd
|
||||||
|
cmd="$(dx::resolve_alias "$raw_cmd")"
|
||||||
|
|
||||||
|
# Special built-ins that are not file-based commands
|
||||||
|
case "$cmd" in
|
||||||
|
help) dx::help; return ;;
|
||||||
|
elevate) platform::elevate "$@"; return ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if driver::has_command "$cmd"; then
|
||||||
|
driver::run "$cmd" "$@"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Dynamic: load command file and call <cmd>::run
|
||||||
|
if load_command "$cmd"; then
|
||||||
|
if command::exists "${cmd}"; then
|
||||||
|
command::run "${cmd}" "$@"
|
||||||
|
else
|
||||||
|
log::error "Command file for '${cmd}' loaded but '${cmd}::run' is not defined"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log::error "Unknown command: '${raw_cmd}'"
|
||||||
|
echo "Run '$(basename "$0") help' to see the available commands." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Help
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function dx::help() {
|
||||||
|
local console_name
|
||||||
|
console_name="$(app::console_name)"
|
||||||
|
|
||||||
|
local framework
|
||||||
|
framework="$(app::framework)"
|
||||||
|
|
||||||
|
local console_cmd
|
||||||
|
console_cmd="${console_name} | console"
|
||||||
|
|
||||||
|
cat <<HELP
|
||||||
|
Usage: $(basename "$0") <command> [args...]
|
||||||
|
|
||||||
|
Active framework: ${framework}
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
up Start stack
|
||||||
|
build Build stack
|
||||||
|
down Stop stack
|
||||||
|
logs Tail logs
|
||||||
|
list | ls List stack containers
|
||||||
|
shell | bash Enter container shell
|
||||||
|
exec <cmd> Run command inside container
|
||||||
|
${console_cmd} Run framework console command
|
||||||
|
init [env] Initialize project for environment
|
||||||
|
setup Build and init in one step
|
||||||
|
test Run tests
|
||||||
|
apache <cmd> Manage Apache web server
|
||||||
|
hosts <cmd> Manage /etc/hosts entries
|
||||||
|
proxy <cmd> Manage reverse proxy
|
||||||
|
network | net <cmd> Network utilities
|
||||||
|
|
||||||
|
Global flags:
|
||||||
|
--framework=NAME Override active framework
|
||||||
|
--debug Enable shell trace
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$(basename "$0") up
|
||||||
|
$(basename "$0") build
|
||||||
|
$(basename "$0") ${console_name} migrate
|
||||||
|
$(basename "$0") init prd
|
||||||
|
$(basename "$0") setup
|
||||||
|
$(basename "$0") exec ls -la
|
||||||
|
$(basename "$0") shell
|
||||||
|
$(basename "$0") --framework=laravel build
|
||||||
|
HELP
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Main
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
local args=()
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--framework=*) export APP_FRAMEWORK="${arg#*=}" ;;
|
||||||
|
--debug) set -x ;;
|
||||||
|
*) args+=("$arg") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
load_module app # app::on_load loads framework/* and registers console alias
|
||||||
|
|
||||||
|
source "$(ctx::dxkit)/drivers/dispatcher.sh"
|
||||||
|
driver::load
|
||||||
|
|
||||||
|
env::load_env_files
|
||||||
|
dx::dispatch "${args[@]+"${args[@]}"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
34
dxkit/bootstrap.sh
Normal file
34
dxkit/bootstrap.sh
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Directories
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# Only set PROJECT_ROOT if not already exported by parent
|
||||||
|
if [[ -z "${PROJECT_ROOT:-}" ]]; then
|
||||||
|
readonly PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PROJECT_ROOT
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Load Core
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
source "$SCRIPT_DIR/core.sh"
|
||||||
|
platform::detect
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Module Loader
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function dx::load_modules() {
|
||||||
|
load_module env
|
||||||
|
load_module log
|
||||||
|
load_module fs
|
||||||
|
load_module docker
|
||||||
|
load_module network
|
||||||
|
load_module artifact
|
||||||
|
}
|
||||||
128
dxkit/commands/apache.command.sh
Normal file
128
dxkit/commands/apache.command.sh
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/apache.command.sh
|
||||||
|
#
|
||||||
|
# Manages the Apache web server.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx apache validate — validate vhost configuration
|
||||||
|
# dx apache reload — graceful reload (picks up vhost changes)
|
||||||
|
# dx apache restart — full restart
|
||||||
|
# dx apache vhosts — list all loaded virtual hosts
|
||||||
|
# dx apache modules — list all loaded Apache modules
|
||||||
|
# dx apache version — show Apache version
|
||||||
|
# dx apache logs <type> — tail error or access logs
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Lifecycle
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::apache::on_load() {
|
||||||
|
load_module apache
|
||||||
|
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Public entrypoint
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::apache::run() {
|
||||||
|
local subcmd="${1:-help}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
case "$subcmd" in
|
||||||
|
validate) cmd::apache::validate ;;
|
||||||
|
reload) cmd::apache::reload ;;
|
||||||
|
restart) cmd::apache::restart ;;
|
||||||
|
vhosts) cmd::apache::vhosts ;;
|
||||||
|
modules) cmd::apache::modules ;;
|
||||||
|
version) cmd::apache::version ;;
|
||||||
|
logs) cmd::apache::logs "$@" ;;
|
||||||
|
help) cmd::apache::help ;;
|
||||||
|
*)
|
||||||
|
log::error "Unknown subcommand: '${subcmd}'"
|
||||||
|
cmd::apache::help
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Help
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::apache::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx apache <subcommand>
|
||||||
|
|
||||||
|
Manages the Apache web server.
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
validate Validate vhost configuration
|
||||||
|
reload Graceful reload (picks up config changes)
|
||||||
|
restart Full restart
|
||||||
|
vhosts List all loaded virtual hosts
|
||||||
|
modules List all loaded Apache modules
|
||||||
|
version Show Apache version
|
||||||
|
logs [type] Tail logs (error | access, default: error)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx apache validate
|
||||||
|
dx apache reload
|
||||||
|
dx apache vhosts
|
||||||
|
dx apache logs access
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Subcommands
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::apache::validate() {
|
||||||
|
log::info "Validating Apache configuration..."
|
||||||
|
if apache::validate_config; then
|
||||||
|
log::success "Configuration is valid"
|
||||||
|
else
|
||||||
|
log::error "Configuration has errors"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::apache::reload() {
|
||||||
|
log::info "Reloading Apache..."
|
||||||
|
apache::reload
|
||||||
|
log::success "Apache reloaded"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::apache::restart() {
|
||||||
|
log::info "Restarting Apache..."
|
||||||
|
apache::restart
|
||||||
|
log::success "Apache restarted"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::apache::vhosts() {
|
||||||
|
log::info "Loaded virtual hosts:"
|
||||||
|
apache::list_vhosts
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::apache::modules() {
|
||||||
|
log::info "Loaded Apache modules:"
|
||||||
|
apache::list_modules
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::apache::version() {
|
||||||
|
apache::version
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::apache::logs() {
|
||||||
|
local type="${1:-error}"
|
||||||
|
|
||||||
|
case "$type" in
|
||||||
|
error) apache::logs::error ;;
|
||||||
|
access) apache::logs::access ;;
|
||||||
|
*)
|
||||||
|
log::error "Unknown log type: '${type}' (must be 'error' or 'access')"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
177
dxkit/commands/build.command.sh
Normal file
177
dxkit/commands/build.command.sh
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/build.command.sh
|
||||||
|
#
|
||||||
|
# Builds the Docker image and compose stack for a given environment.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx build [flags]
|
||||||
|
#
|
||||||
|
# Flags:
|
||||||
|
# --dev / --qly / --prd Target environment (default: dev)
|
||||||
|
# --project=NAME Override project name
|
||||||
|
# --port=PORT Override app port (e.g. 8082, auto:8080)
|
||||||
|
# --db=TYPE Override DB engine (mysql | mssql)
|
||||||
|
# --db-port=PORT Override DB port
|
||||||
|
# --sync-hosts Sync domain to /etc/hosts (requires root)
|
||||||
|
# --no-vhosts Skip Apache vhost generation
|
||||||
|
# --no-interact Non-interactive mode
|
||||||
|
# --debug Enable shell trace (set -x)
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Lifecycle
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::build::on_load() {
|
||||||
|
load_module template
|
||||||
|
load_module apache
|
||||||
|
|
||||||
|
flag::register \
|
||||||
|
--sync-hosts \
|
||||||
|
--no-vhosts \
|
||||||
|
--no-interact \
|
||||||
|
--scaffold \
|
||||||
|
--framework \
|
||||||
|
--debug
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Public entrypoint
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::build::run() {
|
||||||
|
local flags=()
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
flags+=("$arg")
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! flag::parse "${flags[@]+"${flags[@]}"}"; then
|
||||||
|
cmd::build::help
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# CLI overrides — applied after env files so flags win
|
||||||
|
[[ -n "${PROJECT_NAME_OVERRIDE:-}" ]] && export PROJECT_NAME="$PROJECT_NAME_OVERRIDE"
|
||||||
|
[[ -n "${APP_PORT_OVERRIDE:-}" ]] && export APP_PORT="$APP_PORT_OVERRIDE"
|
||||||
|
[[ -n "${DB_ENGINE_OVERRIDE:-}" ]] && export DB_ENGINE="$DB_ENGINE_OVERRIDE"
|
||||||
|
[[ -n "${DB_PORT_OVERRIDE:-}" ]] && export DB_PORT="$DB_PORT_OVERRIDE"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Project scaffolding
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
if ! app::exists || flag::enabled --scaffold; then
|
||||||
|
app::scaffold "$(ctx::root)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Runtime artifacts
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
artifact::init
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Network & Virtual Hosts
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
network::ports::prepare
|
||||||
|
|
||||||
|
if flag::enabled --sync-hosts && ! flag::enabled --no-interact; then
|
||||||
|
load_command hosts
|
||||||
|
hosts::sync
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! flag::enabled --no-vhosts; then
|
||||||
|
apache::generate_vhosts
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Docker image
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
runtime::run_if BUILD_IMAGE docker::image::generate || {
|
||||||
|
log::error "Failed to generate Docker image (${APP_IMAGE}:${ENVIRONMENT})"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime::run_if BUILD_IMAGE docker::image::build || {
|
||||||
|
log::error "Failed to build Docker image (${APP_IMAGE}:${ENVIRONMENT})"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Docker compose stack
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
runtime::run_if BUILD_STACK docker::compose::generate || {
|
||||||
|
log::error "Failed to generate compose stack (${ENVIRONMENT})"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime::run_if BUILD_STACK docker::compose::build || {
|
||||||
|
log::error "Failed to build compose stack (${ENVIRONMENT})"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Done
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
cmd::build::_show_networking
|
||||||
|
log::success "Build complete!"
|
||||||
|
|
||||||
|
flag::scaffold_defaults_file
|
||||||
|
cmd::build::_hint_next_steps
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Help
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::build::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx build [flags]
|
||||||
|
|
||||||
|
Generates and builds the Docker image and compose stack.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--dev / --qly / --prd Target environment (default: dev)
|
||||||
|
--project=NAME Override project name
|
||||||
|
--port=PORT Override app port
|
||||||
|
--db=TYPE Override DB engine (mysql | mssql)
|
||||||
|
--db-port=PORT Override DB port
|
||||||
|
--sync-hosts Sync domain to /etc/hosts
|
||||||
|
--no-vhosts Skip vhost generation
|
||||||
|
--no-interact Non-interactive mode
|
||||||
|
--debug Shell trace
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx build
|
||||||
|
dx build --qly
|
||||||
|
dx build --port=8082
|
||||||
|
dx build --sync-hosts
|
||||||
|
dx build --project=myapp --db=mssql
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Private
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::build::_show_networking() {
|
||||||
|
printf "\n🌐 Accessible on: http://%s\n\n" "$DOMAIN"
|
||||||
|
echo "🔌 Ports:"
|
||||||
|
echo " App: ${APP_PORT}"
|
||||||
|
echo " DB: $(docker::db::external_port)"
|
||||||
|
printf "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::build::_hint_next_steps() {
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
Next steps:
|
||||||
|
dx init Initialize environment
|
||||||
|
dx up Start the stack
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
56
dxkit/commands/console.command.sh
Normal file
56
dxkit/commands/console.command.sh
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/console.command.sh
|
||||||
|
#
|
||||||
|
# Framework-agnostic console command runner.
|
||||||
|
# Delegates to the active framework's console implementation.
|
||||||
|
#
|
||||||
|
# Aliased automatically to the framework's native name (yii, artisan etc.)
|
||||||
|
# via app::on_load — so both forms work:
|
||||||
|
#
|
||||||
|
# dx console migrate
|
||||||
|
# dx yii migrate (Yii2)
|
||||||
|
# dx artisan migrate (Laravel)
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Lifecycle
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::console::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Public entrypoint
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::console::run() {
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
cmd::console::help
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
app::console "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Help
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::console::help() {
|
||||||
|
local console_name
|
||||||
|
console_name="$(app::console_name)"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx console <command> [args...]
|
||||||
|
dx ${console_name} <command> [args...]
|
||||||
|
|
||||||
|
Runs a console command inside the app container via the active framework.
|
||||||
|
Active framework: $(app::framework)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx console migrate
|
||||||
|
dx console db:seed
|
||||||
|
dx ${console_name} migrate
|
||||||
|
dx ${console_name} cache/flush
|
||||||
|
EOF
|
||||||
|
}
|
||||||
25
dxkit/commands/docker/down.command.sh
Normal file
25
dxkit/commands/docker/down.command.sh
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/docker/down.command.sh
|
||||||
|
|
||||||
|
function cmd::docker::down::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::down::run() {
|
||||||
|
flag::parse "$@" || { cmd::docker::down::help; return 1; }
|
||||||
|
docker::compose::down
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::down::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx down [flags]
|
||||||
|
|
||||||
|
Stops the stack.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--debug Shell trace
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx down
|
||||||
|
EOF
|
||||||
|
}
|
||||||
28
dxkit/commands/docker/exec.command.sh
Normal file
28
dxkit/commands/docker/exec.command.sh
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/docker/exec.command.sh
|
||||||
|
|
||||||
|
function cmd::docker::exec::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::exec::run() {
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
cmd::docker::exec::help
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
runtime::exec "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::exec::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx exec <cmd> [args...]
|
||||||
|
|
||||||
|
Runs a command inside the app container.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx exec ls -la
|
||||||
|
dx exec php --version
|
||||||
|
dx exec composer install
|
||||||
|
EOF
|
||||||
|
}
|
||||||
28
dxkit/commands/docker/list.command.sh
Normal file
28
dxkit/commands/docker/list.command.sh
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/docker/list.command.sh
|
||||||
|
|
||||||
|
function cmd::docker::list::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::list::run() {
|
||||||
|
flag::parse "$@" || { cmd::docker::list::help; return 1; }
|
||||||
|
docker::list_containers
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::list::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx list [flags]
|
||||||
|
|
||||||
|
Lists stack containers.
|
||||||
|
|
||||||
|
Aliases: ls, list-stack, list-containers
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--debug Shell trace
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx list
|
||||||
|
dx ls
|
||||||
|
EOF
|
||||||
|
}
|
||||||
25
dxkit/commands/docker/logs.command.sh
Normal file
25
dxkit/commands/docker/logs.command.sh
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/docker/logs.command.sh
|
||||||
|
|
||||||
|
function cmd::docker::logs::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::logs::run() {
|
||||||
|
flag::parse "$@" || { cmd::docker::logs::help; return 1; }
|
||||||
|
docker::compose::logs
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::logs::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx logs [flags]
|
||||||
|
|
||||||
|
Tails stack logs.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--debug Shell trace
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx logs
|
||||||
|
EOF
|
||||||
|
}
|
||||||
24
dxkit/commands/docker/shell.command.sh
Normal file
24
dxkit/commands/docker/shell.command.sh
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/docker/shell.command.sh
|
||||||
|
|
||||||
|
function cmd::docker::shell::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::shell::run() {
|
||||||
|
runtime::shell
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::shell::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx shell
|
||||||
|
|
||||||
|
Opens an interactive shell inside the app container.
|
||||||
|
|
||||||
|
Aliases: bash
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx shell
|
||||||
|
dx bash
|
||||||
|
EOF
|
||||||
|
}
|
||||||
23
dxkit/commands/docker/test.command.sh
Normal file
23
dxkit/commands/docker/test.command.sh
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/docker/test.command.sh
|
||||||
|
|
||||||
|
function cmd::docker::test::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::test::run() {
|
||||||
|
runtime::exec php vendor/bin/phpunit "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::test::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx test [args...]
|
||||||
|
|
||||||
|
Runs PHPUnit tests inside the app container.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx test
|
||||||
|
dx test --filter MyTest
|
||||||
|
dx test tests/unit
|
||||||
|
EOF
|
||||||
|
}
|
||||||
42
dxkit/commands/docker/up.command.sh
Normal file
42
dxkit/commands/docker/up.command.sh
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/docker/up.command.sh
|
||||||
|
|
||||||
|
function cmd::docker::up::on_load() {
|
||||||
|
flag::register \
|
||||||
|
--sync-hosts \
|
||||||
|
--skip-proxy \
|
||||||
|
--debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::up::run() {
|
||||||
|
flag::parse "$@" || { cmd::docker::up::help; return 1; }
|
||||||
|
|
||||||
|
if flag::enabled --sync-hosts; then
|
||||||
|
load_command hosts
|
||||||
|
cmd::hosts::sync
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! flag::enabled --skip-proxy; then
|
||||||
|
proxy::ensure
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker::compose::up
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::docker::up::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx up [flags]
|
||||||
|
|
||||||
|
Starts the stack.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--sync-hosts Sync domain to /etc/hosts before starting
|
||||||
|
--skip-proxy Skip proxy startup
|
||||||
|
--debug Shell trace
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx up
|
||||||
|
dx up --sync-hosts
|
||||||
|
dx up --skip-proxy
|
||||||
|
EOF
|
||||||
|
}
|
||||||
203
dxkit/commands/hosts.command.sh
Normal file
203
dxkit/commands/hosts.command.sh
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/hosts.command.sh
|
||||||
|
#
|
||||||
|
# Manages /etc/hosts entries for the project.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx hosts sync — write project entries to /etc/hosts (requires root)
|
||||||
|
# dx hosts remove — remove project entries from /etc/hosts (requires root)
|
||||||
|
# dx hosts list — show current /etc/hosts entries for this project
|
||||||
|
# dx hosts preview — preview entries that would be written
|
||||||
|
#
|
||||||
|
# Also accessible via:
|
||||||
|
# dx network hosts <subcommand>
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Lifecycle
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::hosts::on_load() {
|
||||||
|
flag::register \
|
||||||
|
--debug
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Public entrypoint
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::hosts::run() {
|
||||||
|
local subcmd="${1:-help}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
case "$subcmd" in
|
||||||
|
sync) cmd::hosts::sync "$@" ;;
|
||||||
|
remove) cmd::hosts::remove "$@" ;;
|
||||||
|
list) cmd::hosts::list "$@" ;;
|
||||||
|
preview) cmd::hosts::preview "$@" ;;
|
||||||
|
help) cmd::hosts::help ;;
|
||||||
|
*)
|
||||||
|
log::error "Unknown subcommand: '${subcmd}'"
|
||||||
|
cmd::hosts::help
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Help
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::hosts::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx hosts <subcommand>
|
||||||
|
|
||||||
|
Manages /etc/hosts entries for ${PROJECT_NAME}.
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
sync Write project entries to /etc/hosts (requires root)
|
||||||
|
remove Remove project entries from /etc/hosts (requires root)
|
||||||
|
list Show current entries in /etc/hosts for this project
|
||||||
|
preview Preview entries that would be written
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx hosts sync
|
||||||
|
dx hosts remove
|
||||||
|
dx hosts list
|
||||||
|
dx hosts preview
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Sync
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::hosts::sync() {
|
||||||
|
local hosts_file
|
||||||
|
hosts_file="$(platform::hosts_file)"
|
||||||
|
|
||||||
|
platform::require_privileges || {
|
||||||
|
log::warn "$(platform::elevation_fail)"
|
||||||
|
log::error "Could not sync hosts — permission denied"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local block
|
||||||
|
block="$(cmd::hosts::_build_block)"
|
||||||
|
|
||||||
|
[[ -f "$hosts_file" ]] || touch "$hosts_file" || return 1
|
||||||
|
|
||||||
|
local tmp
|
||||||
|
tmp="$(fs::replace_block \
|
||||||
|
"$hosts_file" \
|
||||||
|
"$(cmd::hosts::_marker)" \
|
||||||
|
"$(cmd::hosts::_end_marker)" \
|
||||||
|
"$block"
|
||||||
|
)" || return 1
|
||||||
|
|
||||||
|
fs::write_file "$tmp" "$hosts_file"
|
||||||
|
|
||||||
|
log::success "Hosts synced for ${PROJECT_NAME}"
|
||||||
|
cmd::hosts::_show_entries "$block"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Remove
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::hosts::remove() {
|
||||||
|
local hosts_file
|
||||||
|
hosts_file="$(platform::hosts_file)"
|
||||||
|
|
||||||
|
platform::require_privileges || {
|
||||||
|
log::warn "$(platform::elevation_fail)"
|
||||||
|
log::error "Could not remove hosts — permission denied"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! grep -q "$(cmd::hosts::_marker)" "$hosts_file" 2>/dev/null; then
|
||||||
|
log::info "No entries found for ${PROJECT_NAME} in $(platform::hosts_file)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tmp
|
||||||
|
tmp="$(fs::replace_block \
|
||||||
|
"$hosts_file" \
|
||||||
|
"$(cmd::hosts::_marker)" \
|
||||||
|
"$(cmd::hosts::_end_marker)" \
|
||||||
|
""
|
||||||
|
)" || return 1
|
||||||
|
|
||||||
|
fs::write_file "$tmp" "$hosts_file"
|
||||||
|
|
||||||
|
log::success "Hosts entries removed for ${PROJECT_NAME}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# List
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::hosts::list() {
|
||||||
|
local hosts_file
|
||||||
|
hosts_file="$(platform::hosts_file)"
|
||||||
|
|
||||||
|
if ! grep -q "$(cmd::hosts::_marker)" "$hosts_file" 2>/dev/null; then
|
||||||
|
log::info "No entries found for ${PROJECT_NAME} in ${hosts_file}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log::info "Current entries for ${PROJECT_NAME} in ${hosts_file}:"
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
# Extract block between markers
|
||||||
|
awk "/$(cmd::hosts::_marker)/,/$(cmd::hosts::_end_marker)/" "$hosts_file" \
|
||||||
|
| grep -v "^#"
|
||||||
|
printf "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Preview
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::hosts::preview() {
|
||||||
|
log::info "Entries that would be written for ${PROJECT_NAME}:"
|
||||||
|
printf "\n"
|
||||||
|
cmd::hosts::_generate_entries
|
||||||
|
printf "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Private
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::hosts::_marker() { echo "# >>> ${PROJECT_NAME} >>>"; }
|
||||||
|
function cmd::hosts::_end_marker() { echo "# <<< ${PROJECT_NAME} <<<"; }
|
||||||
|
|
||||||
|
function cmd::hosts::_generate_entries() {
|
||||||
|
local ip="127.0.0.1"
|
||||||
|
|
||||||
|
local domains=(
|
||||||
|
"$DOMAIN"
|
||||||
|
"$BACKEND_DOMAIN"
|
||||||
|
)
|
||||||
|
|
||||||
|
for domain in "${domains[@]}"; do
|
||||||
|
echo "${ip} ${domain}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::hosts::_build_block() {
|
||||||
|
local entries
|
||||||
|
entries="$(cmd::hosts::_generate_entries | tr -d '\r')"
|
||||||
|
|
||||||
|
printf "%s\n%s\n%s" \
|
||||||
|
"$(cmd::hosts::_marker)" \
|
||||||
|
"$entries" \
|
||||||
|
"$(cmd::hosts::_end_marker)"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::hosts::_show_entries() {
|
||||||
|
local entries="$1"
|
||||||
|
printf "\n"
|
||||||
|
echo "$entries"
|
||||||
|
printf "\n"
|
||||||
|
}
|
||||||
150
dxkit/commands/init.command.sh
Normal file
150
dxkit/commands/init.command.sh
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/init.command.sh
|
||||||
|
#
|
||||||
|
# Initializes the project for a given environment.
|
||||||
|
# Brings the stack to a runnable state: containers up, vendors installed,
|
||||||
|
# proxy started, framework initialized and configured.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx init [env] [flags]
|
||||||
|
#
|
||||||
|
# Flags:
|
||||||
|
# --skip-vendors Skip composer install
|
||||||
|
# --skip-framework-init Skip framework init
|
||||||
|
# --skip-framework-config Skip framework config generation
|
||||||
|
# --debug Enable shell trace
|
||||||
|
#
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Public entrypoint
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::init::on_load() {
|
||||||
|
flag::register \
|
||||||
|
--skip-vendors \
|
||||||
|
--skip-framework-init \
|
||||||
|
--skip-framework-config \
|
||||||
|
--skip-proxy \
|
||||||
|
--debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::init::run() {
|
||||||
|
local env=""
|
||||||
|
local flags=()
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--*) flags+=("$arg") ;;
|
||||||
|
*) [[ -z "$env" ]] && env="$arg" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#flags[@]} -gt 0 ]]; then
|
||||||
|
if ! flag::parse "${flags[@]}"; then
|
||||||
|
cmd::init::help
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
env="${env:-${ENVIRONMENT:-dev}}"
|
||||||
|
|
||||||
|
local framework_env
|
||||||
|
framework_env="$(app::resolve_env "$env")"
|
||||||
|
|
||||||
|
log::env "Initializing environment: ${env} (${framework_env})..."
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Docker
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
docker::compose::up -d >/dev/null
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Vendors
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
if ! flag::enabled --skip-vendors; then
|
||||||
|
app::install_vendors
|
||||||
|
else
|
||||||
|
log::fs_warning "$(app::vendor_skip_message)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Proxy
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
if ! flag::enabled --skip-proxy; then
|
||||||
|
log::run_step network info "Starting proxy..." \
|
||||||
|
proxy::start
|
||||||
|
else
|
||||||
|
log::network_warning "Skipping Proxy initialization... (--skip-proxy)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Framework
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
if ! flag::enabled --skip-framework-init; then
|
||||||
|
app::init "$env"
|
||||||
|
else
|
||||||
|
log::env_warning "$(app::init_skip_message)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! flag::enabled --skip-framework-config; then
|
||||||
|
app::config
|
||||||
|
else
|
||||||
|
log::env_warning "$(app::config_skip_message)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Done
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
log::env_success "Environment '${env}' is ready!"
|
||||||
|
|
||||||
|
flag::scaffold_defaults_file
|
||||||
|
cmd::init::_hint_post_init
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Help
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::init::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx init [env] [flags]
|
||||||
|
|
||||||
|
Brings the project to a runnable state for the given environment.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
env dev | qly | prd (default: dev)
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--skip-vendors Skip vendor install
|
||||||
|
--skip-framework-init Skip framework init
|
||||||
|
--skip-framework-config Skip framework config generation
|
||||||
|
--debug Shell trace
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
app::post_init_hint
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx init
|
||||||
|
dx init qly
|
||||||
|
dx init --skip-vendors
|
||||||
|
dx init qly --skip-framework-init --skip-framework-config
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Private
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::init::_hint_post_init() {
|
||||||
|
echo ""
|
||||||
|
app::post_init_hint;
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
158
dxkit/commands/network.command.sh
Normal file
158
dxkit/commands/network.command.sh
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/network.command.sh
|
||||||
|
#
|
||||||
|
# Network diagnostics, port management, and host entries.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx network status — show port assignments and their status
|
||||||
|
# dx network resolve — re-run port resolution and show changes
|
||||||
|
# dx network hosts <subcmd> — manage /etc/hosts entries (delegates to hosts command)
|
||||||
|
#
|
||||||
|
# Hosts subcommands are also available directly:
|
||||||
|
# dx hosts sync
|
||||||
|
# dx hosts list
|
||||||
|
# dx hosts remove
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Lifecycle
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::network::on_load() {
|
||||||
|
flag::register \
|
||||||
|
--debug
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Public entrypoint
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::network::run() {
|
||||||
|
local subcmd="${1:-help}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
case "$subcmd" in
|
||||||
|
status) cmd::network::status::run "$@" ;;
|
||||||
|
resolve) cmd::network::resolve::run "$@" ;;
|
||||||
|
hosts)
|
||||||
|
load_command hosts
|
||||||
|
hosts::run "$@"
|
||||||
|
;;
|
||||||
|
help) cmd::network::help ;;
|
||||||
|
*)
|
||||||
|
log::error "Unknown subcommand: '${subcmd}'"
|
||||||
|
cmd::network::help
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Help
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::network::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx network <subcommand>
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
status Show current port assignments and their status
|
||||||
|
resolve Re-run port resolution and show what changed
|
||||||
|
hosts <subcmd> Manage /etc/hosts entries
|
||||||
|
|
||||||
|
Hosts subcommands:
|
||||||
|
hosts sync Sync project domain to /etc/hosts
|
||||||
|
hosts list Show current /etc/hosts entries for this project
|
||||||
|
hosts remove Remove project entries from /etc/hosts
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx network status
|
||||||
|
dx network resolve
|
||||||
|
dx network hosts sync
|
||||||
|
dx hosts sync (shorthand)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Status
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::network::status::run() {
|
||||||
|
log::info "Port status for ${PROJECT_NAME}..."
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
printf "%-20s %-10s %s\n" "Service" "Port" "Status"
|
||||||
|
printf "%-20s %-10s %s\n" "-------" "----" "------"
|
||||||
|
|
||||||
|
cmd::network::status::_report "App" "$APP_PORT"
|
||||||
|
cmd::network::status::_report "Database" "$(docker::db::external_port)"
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::network::status::_report() {
|
||||||
|
local label="$1"
|
||||||
|
local port="$2"
|
||||||
|
local status
|
||||||
|
|
||||||
|
if ! network::ports::in_use "$port"; then
|
||||||
|
status="✓ free"
|
||||||
|
else
|
||||||
|
local container
|
||||||
|
container="$(docker ps --format '{{.Names}}' | grep "^${PROJECT_NAME}" | head -1)"
|
||||||
|
|
||||||
|
if [[ -n "$container" ]]; then
|
||||||
|
status="✓ in use by ${PROJECT_NAME}"
|
||||||
|
else
|
||||||
|
status="⚠️ in use by another process — run: dx network resolve"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%-20s %-10s %s\n" "$label" "$port" "$status"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Resolve
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::network::resolve::run() {
|
||||||
|
log::info "Resolving ports for ${PROJECT_NAME}..."
|
||||||
|
|
||||||
|
local old_app_port="$APP_PORT"
|
||||||
|
local old_db_port
|
||||||
|
old_db_port="$(docker::db::external_port)"
|
||||||
|
|
||||||
|
network::ports::resolve_many APP_PORT DB_PORT
|
||||||
|
|
||||||
|
local new_db_port
|
||||||
|
new_db_port="$(docker::db::external_port)"
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
printf "%-20s %-15s %-15s %s\n" "Service" "Current" "Resolved" "Status"
|
||||||
|
printf "%-20s %-15s %-15s %s\n" "-------" "-------" "--------" "------"
|
||||||
|
|
||||||
|
cmd::network::resolve::_report "App" "$old_app_port" "$APP_PORT"
|
||||||
|
cmd::network::resolve::_report "Database" "$old_db_port" "$new_db_port"
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
if [[ "$old_app_port" != "$APP_PORT" || "$old_db_port" != "$new_db_port" ]]; then
|
||||||
|
log::warn "Ports changed — run 'dx build' to apply"
|
||||||
|
else
|
||||||
|
log::success "No changes — all ports are available"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::network::resolve::_report() {
|
||||||
|
local label="$1"
|
||||||
|
local old="$2"
|
||||||
|
local resolved="$3"
|
||||||
|
local status
|
||||||
|
|
||||||
|
if [[ "$old" == "$resolved" ]]; then
|
||||||
|
status="✓ unchanged"
|
||||||
|
else
|
||||||
|
status="⚠️ remapped: ${old} → ${resolved}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%-20s %-15s %-15s %s\n" "$label" "$old" "$resolved" "$status"
|
||||||
|
}
|
||||||
121
dxkit/commands/proxy.command.sh
Normal file
121
dxkit/commands/proxy.command.sh
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/proxy.command.sh
|
||||||
|
#
|
||||||
|
# Manages the reverse proxy service.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx proxy start — start the proxy
|
||||||
|
# dx proxy stop — stop the proxy
|
||||||
|
# dx proxy restart — restart the proxy
|
||||||
|
# dx proxy status — show proxy status
|
||||||
|
# dx proxy logs — tail proxy logs
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Lifecycle
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::proxy::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Public entrypoint
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::proxy::run() {
|
||||||
|
local subcmd="${1:-help}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
case "$subcmd" in
|
||||||
|
start) cmd::proxy::start "$@" ;;
|
||||||
|
stop) cmd::proxy::stop "$@" ;;
|
||||||
|
restart) cmd::proxy::restart "$@" ;;
|
||||||
|
status) cmd::proxy::status "$@" ;;
|
||||||
|
logs) cmd::proxy::logs "$@" ;;
|
||||||
|
help) cmd::proxy::help ;;
|
||||||
|
*)
|
||||||
|
log::error "Unknown subcommand: '${subcmd}'"
|
||||||
|
cmd::proxy::help
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Help
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::proxy::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx proxy <subcommand>
|
||||||
|
|
||||||
|
Manages the reverse proxy service (${PROXY_SERVICE}).
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
start Start the proxy
|
||||||
|
stop Stop the proxy
|
||||||
|
restart Restart the proxy
|
||||||
|
status Show proxy status
|
||||||
|
logs Tail proxy logs
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx proxy start
|
||||||
|
dx proxy stop
|
||||||
|
dx proxy restart
|
||||||
|
dx proxy status
|
||||||
|
dx proxy logs
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Subcommands
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function cmd::proxy::start() {
|
||||||
|
if proxy::running; then
|
||||||
|
log::info "Proxy (${PROXY_SERVICE}) is already running"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log::info "Starting proxy (${PROXY_SERVICE})..."
|
||||||
|
proxy::start
|
||||||
|
log::success "Proxy started"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::proxy::stop() {
|
||||||
|
if ! proxy::running; then
|
||||||
|
log::info "Proxy (${PROXY_SERVICE}) is not running"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log::info "Stopping proxy (${PROXY_SERVICE})..."
|
||||||
|
proxy::stop
|
||||||
|
log::success "Proxy stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::proxy::restart() {
|
||||||
|
log::info "Restarting proxy (${PROXY_SERVICE})..."
|
||||||
|
proxy::restart
|
||||||
|
log::success "Proxy restarted"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::proxy::status() {
|
||||||
|
printf "\n"
|
||||||
|
printf "%-20s %s\n" "Service:" "${PROXY_SERVICE}"
|
||||||
|
printf "%-20s %s\n" "Network:" "${PROXY_NETWORK}"
|
||||||
|
printf "%-20s %s\n" "Driver:" "$(proxy::driver)"
|
||||||
|
printf "%-20s " "Status:"
|
||||||
|
|
||||||
|
if proxy::running; then
|
||||||
|
echo "✓ running"
|
||||||
|
else
|
||||||
|
echo "⚠️ stopped"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::proxy::logs() {
|
||||||
|
log::info "Tailing proxy logs (${PROXY_SERVICE})..."
|
||||||
|
proxy::logs
|
||||||
|
}
|
||||||
32
dxkit/commands/runtime/down.command.sh
Normal file
32
dxkit/commands/runtime/down.command.sh
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/runtime/down.command.sh
|
||||||
|
|
||||||
|
function cmd::runtime::down::on_load() {
|
||||||
|
flag::register \
|
||||||
|
--volumes \
|
||||||
|
--orphans \
|
||||||
|
--debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::down::run() {
|
||||||
|
flag::parse "$@" || { cmd::runtime::down::help; return 1; }
|
||||||
|
runtime::down
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::down::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx down [flags]
|
||||||
|
|
||||||
|
Stops the stack.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--volumes Also remove volumes
|
||||||
|
--orphans Remove orphan containers
|
||||||
|
--debug Shell trace
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx down
|
||||||
|
dx down --volumes
|
||||||
|
dx down --orphans
|
||||||
|
EOF
|
||||||
|
}
|
||||||
28
dxkit/commands/runtime/exec.command.sh
Normal file
28
dxkit/commands/runtime/exec.command.sh
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/runtime/exec.command.sh
|
||||||
|
|
||||||
|
function cmd::runtime::exec::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::exec::run() {
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
cmd::runtime::exec::help
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
runtime::exec "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::exec::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx exec <cmd> [args...]
|
||||||
|
|
||||||
|
Runs a command inside the runtime.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx exec ls -la
|
||||||
|
dx exec php --version
|
||||||
|
dx exec composer install
|
||||||
|
EOF
|
||||||
|
}
|
||||||
28
dxkit/commands/runtime/list.command.sh
Normal file
28
dxkit/commands/runtime/list.command.sh
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/runtime/list.command.sh
|
||||||
|
|
||||||
|
function cmd::runtime::list::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::list::run() {
|
||||||
|
flag::parse "$@" || { cmd::runtime::list::help; return 1; }
|
||||||
|
runtime::list
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::list::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx list [flags]
|
||||||
|
|
||||||
|
Lists stack containers.
|
||||||
|
|
||||||
|
Aliases: ls, list-stack, list-containers
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--debug Shell trace
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx list
|
||||||
|
dx ls
|
||||||
|
EOF
|
||||||
|
}
|
||||||
29
dxkit/commands/runtime/logs.command.sh
Normal file
29
dxkit/commands/runtime/logs.command.sh
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/runtime/logs.command.sh
|
||||||
|
|
||||||
|
function cmd::runtime::logs::on_load() {
|
||||||
|
flag::register \
|
||||||
|
--follow \
|
||||||
|
--debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::logs::run() {
|
||||||
|
flag::parse "$@" || { cmd::runtime::logs::help; return 1; }
|
||||||
|
runtime::logs
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::logs::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx logs [flags]
|
||||||
|
|
||||||
|
Tails stack logs.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--follow Follow log output
|
||||||
|
--debug Shell trace
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx logs
|
||||||
|
dx logs --follow
|
||||||
|
EOF
|
||||||
|
}
|
||||||
24
dxkit/commands/runtime/shell.command.sh
Normal file
24
dxkit/commands/runtime/shell.command.sh
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/runtime/shell.command.sh
|
||||||
|
|
||||||
|
function cmd::runtime::shell::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::shell::run() {
|
||||||
|
runtime::shell
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::shell::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx shell
|
||||||
|
|
||||||
|
Opens an interactive shell inside the runtime.
|
||||||
|
|
||||||
|
Aliases: bash
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx shell
|
||||||
|
dx bash
|
||||||
|
EOF
|
||||||
|
}
|
||||||
23
dxkit/commands/runtime/test.command.sh
Normal file
23
dxkit/commands/runtime/test.command.sh
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/runtime/test.command.sh
|
||||||
|
|
||||||
|
function cmd::runtime::test::on_load() {
|
||||||
|
flag::register --debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::test::run() {
|
||||||
|
runtime::exec php vendor/bin/phpunit "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::test::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx test [args...]
|
||||||
|
|
||||||
|
Runs PHPUnit tests inside the runtime.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx test
|
||||||
|
dx test --filter MyTest
|
||||||
|
dx test tests/unit
|
||||||
|
EOF
|
||||||
|
}
|
||||||
50
dxkit/commands/runtime/up.command.sh
Normal file
50
dxkit/commands/runtime/up.command.sh
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# commands/runtime/up.command.sh
|
||||||
|
|
||||||
|
function cmd::runtime::up::on_load() {
|
||||||
|
flag::register \
|
||||||
|
--sync-hosts \
|
||||||
|
--skip-proxy \
|
||||||
|
--recreate \
|
||||||
|
--build \
|
||||||
|
--foreground \
|
||||||
|
--debug
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::up::run() {
|
||||||
|
flag::parse "$@" || { cmd::runtime::up::help; return 1; }
|
||||||
|
|
||||||
|
if flag::enabled --sync-hosts; then
|
||||||
|
load_command hosts
|
||||||
|
cmd::hosts::sync
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! flag::enabled --skip-proxy; then
|
||||||
|
proxy::ensure
|
||||||
|
fi
|
||||||
|
|
||||||
|
runtime::up
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::runtime::up::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx up [flags]
|
||||||
|
|
||||||
|
Starts the stack.
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--sync-hosts Sync domain to /etc/hosts before starting
|
||||||
|
--skip-proxy Skip proxy startup
|
||||||
|
--recreate Force-recreate containers (drops existing state)
|
||||||
|
--build Rebuild images before starting
|
||||||
|
--foreground Run in foreground (don't detach)
|
||||||
|
--debug Shell trace
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dx up
|
||||||
|
dx up --recreate
|
||||||
|
dx up --build --recreate
|
||||||
|
dx up --sync-hosts
|
||||||
|
dx up --foreground
|
||||||
|
EOF
|
||||||
|
}
|
||||||
27
dxkit/commands/setup.command.sh
Normal file
27
dxkit/commands/setup.command.sh
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
load_command build
|
||||||
|
load_command init
|
||||||
|
|
||||||
|
function cmd::setup::run() {
|
||||||
|
if ! flag::parse "$@"; then
|
||||||
|
cmd::setup::help
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
build::run
|
||||||
|
init::run "${ENVIRONMENT:-dev}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::setup::help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx setup [flags]
|
||||||
|
|
||||||
|
Runs build followed by init in a single step.
|
||||||
|
Accepts all flags from both commands.
|
||||||
|
|
||||||
|
$(cmd::build::help)
|
||||||
|
|
||||||
|
$(cmd::init::help)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
145
dxkit/commands/workspace.command.sh
Normal file
145
dxkit/commands/workspace.command.sh
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Workspace Command
|
||||||
|
# ============================================
|
||||||
|
#
|
||||||
|
# Drops into a subshell with all driver commands
|
||||||
|
# AND all static dx commands available as plain
|
||||||
|
# functions — no 'dx' prefix needed.
|
||||||
|
#
|
||||||
|
# The prompt changes to signal you are inside
|
||||||
|
# the dx workspace.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx workspace
|
||||||
|
# dx ws
|
||||||
|
|
||||||
|
function cmd::workspace::run() {
|
||||||
|
local framework
|
||||||
|
framework="$(app::framework)"
|
||||||
|
|
||||||
|
local driver
|
||||||
|
driver="$(driver::resolve)"
|
||||||
|
|
||||||
|
local driver_file
|
||||||
|
driver_file="$(ctx::dxkit)/drivers/${driver}/driver.sh"
|
||||||
|
|
||||||
|
if [[ ! -f "$driver_file" ]]; then
|
||||||
|
log::error "No driver found for framework '${framework}' (resolved: ${driver})"
|
||||||
|
log::error "Cannot start dx workspace without an active driver."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local rc_file
|
||||||
|
rc_file="$(mktemp /tmp/dx-workspace-rc.XXXXXX)"
|
||||||
|
|
||||||
|
cat > "$rc_file" <<RCEOF
|
||||||
|
# ============================================
|
||||||
|
# dx workspace rc — sourced for subshell only
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Carry over user's existing rc if present
|
||||||
|
if [[ -f "\$HOME/.bashrc" ]]; then
|
||||||
|
source "\$HOME/.bashrc" 2>/dev/null || true
|
||||||
|
elif [[ -f "\$HOME/.bash_profile" ]]; then
|
||||||
|
source "\$HOME/.bash_profile" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Load dxkit bootstrap — sources core, detects platform,
|
||||||
|
# applies MSYS2 guards, and defines dx::load_modules
|
||||||
|
source "$(ctx::dxkit)/bootstrap.sh"
|
||||||
|
|
||||||
|
# Set framework before loading app so app::on_load
|
||||||
|
# resolves the correct framework module
|
||||||
|
export APP_FRAMEWORK="${framework}"
|
||||||
|
|
||||||
|
# Load all standard modules (mirrors dx entrypoint)
|
||||||
|
dx::load_modules
|
||||||
|
load_module app
|
||||||
|
env::load_env_files
|
||||||
|
|
||||||
|
# Source the active driver so its functions are in scope
|
||||||
|
source "${driver_file}"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Driver command wrappers (run in-process)
|
||||||
|
# e.g. migrate-create → yii2::migrate::create
|
||||||
|
# ============================================
|
||||||
|
$(
|
||||||
|
for cmd in "${!DRIVER_COMMANDS[@]}"; do
|
||||||
|
echo "function ${cmd}() { ${DRIVER_COMMANDS[$cmd]} \"\$@\"; }"
|
||||||
|
done
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Static dx command wrappers (subprocess)
|
||||||
|
# Uses \$PROJECT_ROOT/dx to avoid path mangling
|
||||||
|
# on Windows/MSYS2 — PROJECT_ROOT is exported
|
||||||
|
# by bootstrap so it is always available here.
|
||||||
|
# ============================================
|
||||||
|
$(
|
||||||
|
while IFS= read -r -d '' cmd_file; do
|
||||||
|
cmd_name="$(basename "$cmd_file" .command.sh)"
|
||||||
|
[[ "$cmd_name" == "workspace" ]] && continue
|
||||||
|
echo "function ${cmd_name}() { \"\$PROJECT_ROOT/dx\" ${cmd_name} \"\$@\"; }"
|
||||||
|
done < <(find "$(ctx::dxkit)/commands" -name "*.command.sh" -print0)
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Prompt
|
||||||
|
# ============================================
|
||||||
|
export PS1="\[\033[0;36m\][dx:${driver}]\[\033[0m\] \w \$ "
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Welcome
|
||||||
|
# ============================================
|
||||||
|
echo ""
|
||||||
|
echo " dx workspace — ${framework}"
|
||||||
|
echo " Type 'exit' to return to your shell."
|
||||||
|
echo ""
|
||||||
|
echo " Framework commands:"
|
||||||
|
$(
|
||||||
|
for cmd in $(echo "${!DRIVER_COMMANDS[@]}" | tr ' ' '\n' | sort); do
|
||||||
|
echo " echo \" ${cmd}\""
|
||||||
|
done
|
||||||
|
)
|
||||||
|
echo ""
|
||||||
|
RCEOF
|
||||||
|
|
||||||
|
bash --rcfile "$rc_file" -i
|
||||||
|
rm -f "$rc_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd::workspace::help() {
|
||||||
|
local framework
|
||||||
|
framework="$(app::framework)"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
Usage: dx workspace
|
||||||
|
dx ws
|
||||||
|
|
||||||
|
Drops into a subshell with all framework and dx commands
|
||||||
|
available without the 'dx' prefix. Type 'exit' to return.
|
||||||
|
|
||||||
|
Active framework: ${framework}
|
||||||
|
|
||||||
|
Inside dx workspace, all of these work without 'dx':
|
||||||
|
|
||||||
|
Framework commands:
|
||||||
|
$(
|
||||||
|
for cmd in $(echo "${!DRIVER_COMMANDS[@]}" | tr ' ' '\n' | sort); do
|
||||||
|
printf " %-28s\n" "$cmd"
|
||||||
|
done
|
||||||
|
)
|
||||||
|
Static dx commands:
|
||||||
|
$(
|
||||||
|
while IFS= read -r -d '' cmd_file; do
|
||||||
|
cmd_name="$(basename "$cmd_file" .command.sh)"
|
||||||
|
[[ "$cmd_name" == "workspace" ]] && continue
|
||||||
|
printf " %-28s\n" "$cmd_name"
|
||||||
|
done < <(find "$(ctx::dxkit)/commands" -name "*.command.sh" -print0)
|
||||||
|
)
|
||||||
|
Shorthand: dx ws
|
||||||
|
EOF
|
||||||
|
}
|
||||||
12
dxkit/core.sh
Normal file
12
dxkit/core.sh
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
source "$SCRIPT_DIR/core/platform.sh"
|
||||||
|
source "$SCRIPT_DIR/core/context.sh"
|
||||||
|
source "$SCRIPT_DIR/core/utils.sh"
|
||||||
|
source "$SCRIPT_DIR/core/string.sh"
|
||||||
|
source "$SCRIPT_DIR/core/hook.sh"
|
||||||
|
source "$SCRIPT_DIR/core/module.sh"
|
||||||
|
source "$SCRIPT_DIR/core/command.sh"
|
||||||
|
source "$SCRIPT_DIR/core/loader.sh"
|
||||||
|
source "$SCRIPT_DIR/core/flag.sh"
|
||||||
|
source "$SCRIPT_DIR/runtime/runtime.sh"
|
||||||
29
dxkit/core/command.sh
Normal file
29
dxkit/core/command.sh
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
declare -A _LOADED_COMMANDS=() # Unused for now
|
||||||
|
readonly _COMMAND_NAMESPACE="cmd"
|
||||||
|
readonly _COMMAND_AUTO_LOAD_HOOK="on_load"
|
||||||
|
|
||||||
|
function command::namespace { echo "${_COMMAND_NAMESPACE}"; }
|
||||||
|
function command::fn { echo "$(command::namespace)::$(command::to_namespace "$1")::${2}"; }
|
||||||
|
function command::loaded() { [[ -n "${_LOADED_COMMANDS["$1"]:-}" ]]; }
|
||||||
|
function command::has_function() { declare -F "$(command::fn "$1" "$2")" >/dev/null 2>&1; }
|
||||||
|
function command::is_auto_load() { declare -F "$(command::fn "$1" on_load)" >/dev/null 2>&1; }
|
||||||
|
function command::to_namespace() { echo "${1//\//::}"; }
|
||||||
|
function command::exists() { command::has_function "$1" run; }
|
||||||
|
function command::run() {
|
||||||
|
local cmd="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
hook::run "$cmd" pre
|
||||||
|
|
||||||
|
core::call_function "$(command::fn "$cmd" run)" "$@"
|
||||||
|
|
||||||
|
local exit_code=$?
|
||||||
|
if [[ $exit_code -eq 0 ]]; then
|
||||||
|
hook::run "$cmd" post
|
||||||
|
fi
|
||||||
|
|
||||||
|
return $exit_code
|
||||||
|
}
|
||||||
|
|
||||||
72
dxkit/core/context.sh
Normal file
72
dxkit/core/context.sh
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
_CTX_ROOT="$PROJECT_ROOT"
|
||||||
|
_CTX_CONTAINER_ROOT="/app"
|
||||||
|
_CTX_DXKIT="${_CTX_ROOT}/dxkit"
|
||||||
|
_CTX_MODULES="${_CTX_DXKIT}/modules"
|
||||||
|
_CTX_COMMANDS="${_CTX_DXKIT}/commands"
|
||||||
|
_CTX_TEMPLATES="${_CTX_DXKIT}/templates"
|
||||||
|
_CTX_ENV="${_CTX_DXKIT}/env"
|
||||||
|
_CTX_PROJECT="${_CTX_ROOT}/.project"
|
||||||
|
_CTX_ARTIFACT="${_CTX_PROJECT}/artifacts"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Wrappers
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function ctx::require() {
|
||||||
|
local var="$1"
|
||||||
|
|
||||||
|
[[ -z "${!var}" ]] && {
|
||||||
|
echo "Missing required context variable: $var" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Static Context
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function ctx::root() { echo "$_CTX_ROOT"; }
|
||||||
|
function ctx::dxkit() { echo "$_CTX_DXKIT"; }
|
||||||
|
function ctx::modules() { echo "$_CTX_MODULES"; }
|
||||||
|
function ctx::commands() { echo "$_CTX_COMMANDS"; }
|
||||||
|
function ctx::templates() { echo "$_CTX_TEMPLATES"; }
|
||||||
|
function ctx::env() { echo "$_CTX_ENV"; }
|
||||||
|
function ctx::project() { echo "$_CTX_PROJECT"; }
|
||||||
|
function ctx::artifact() { echo "$_CTX_ARTIFACT"; }
|
||||||
|
function ctx::container::root() { echo $_CTX_CONTAINER_ROOT; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Derived Context
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function ctx::runtime() {
|
||||||
|
# ctx::require ENVIRONMENT || return 1
|
||||||
|
echo "$(ctx::artifact)/$ENVIRONMENT"
|
||||||
|
}
|
||||||
|
|
||||||
|
function ctx::docker() {
|
||||||
|
echo "$(ctx::artifact)/docker"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Path helpers
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function ctx::artifact::path() {
|
||||||
|
local IFS="/"
|
||||||
|
echo "$(ctx::artifact)/$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
function ctx::container::path() {
|
||||||
|
local base="$(ctx::container::root)"
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
echo "$base"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local IFS="/"
|
||||||
|
echo "$base/$*"
|
||||||
|
}
|
||||||
185
dxkit/core/flag.sh
Normal file
185
dxkit/core/flag.sh
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# dxkit/core/flags.sh
|
||||||
|
#
|
||||||
|
# Shared CLI flag parsing for build and init commands.
|
||||||
|
#
|
||||||
|
# Parses flags into exported RUN_* and other control vars.
|
||||||
|
# Both build::run and init::run call flag::parse before doing any work,
|
||||||
|
# so --skip-* flags work identically whether passed to dx build or dx init.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# flag::parse "$@"
|
||||||
|
# flag::parse --init --skip-vendors --port=8082
|
||||||
|
|
||||||
|
declare -A _REGISTERED_FLAGS=()
|
||||||
|
|
||||||
|
function flag::defaults() {
|
||||||
|
flag::set --framework yii2-advanced
|
||||||
|
flag::set --skip-vendors false
|
||||||
|
flag::set --skip-framework-init false
|
||||||
|
flag::set --skip-framework-config false
|
||||||
|
}
|
||||||
|
|
||||||
|
function flag::register() {
|
||||||
|
for flag in "$@"; do
|
||||||
|
_REGISTERED_FLAGS["$flag"]=1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function flag::registered() {
|
||||||
|
[[ -n "${_REGISTERED_FLAGS["$1"]:-}" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
function flag::set() {
|
||||||
|
local var
|
||||||
|
var="$(flag::to_var "$1")"
|
||||||
|
export "${var}"="${2:-true}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function flag::enable() { flag::set "$1" true; }
|
||||||
|
function flag::disable() { flag::set "$1" false; }
|
||||||
|
|
||||||
|
function flag::enabled() {
|
||||||
|
local var
|
||||||
|
var="$(flag::to_var "$1")"
|
||||||
|
[[ "${!var:-false}" == true ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
function flag::to_var() {
|
||||||
|
local flag="${1#--}"
|
||||||
|
flag="${flag//-/_}"
|
||||||
|
echo "${flag^^}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function flag::value() {
|
||||||
|
local flag="$1"
|
||||||
|
local default="${2:-}"
|
||||||
|
|
||||||
|
# Look for --flag=value in the raw args
|
||||||
|
local var
|
||||||
|
var="$(flag::to_var "$flag")"
|
||||||
|
|
||||||
|
# Check if set as a var (from flag::parse)
|
||||||
|
if [[ -n "${!var:-}" ]]; then
|
||||||
|
echo "${!var}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$default"
|
||||||
|
}
|
||||||
|
|
||||||
|
function flag::load_defaults() {
|
||||||
|
local defaults_file
|
||||||
|
defaults_file="$(ctx::artifact)/.dx-flags"
|
||||||
|
|
||||||
|
[[ -f "$defaults_file" ]] || return 0
|
||||||
|
|
||||||
|
# Read non-empty, non-comment lines as flags
|
||||||
|
local flags=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" || "$line" == "#"* ]] && continue
|
||||||
|
flags+=("$line")
|
||||||
|
done < "$defaults_file"
|
||||||
|
|
||||||
|
[[ ${#flags[@]} -eq 0 ]] && return 0
|
||||||
|
|
||||||
|
flag::parse "${flags[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function flag::scaffold_defaults_file() {
|
||||||
|
local file="$(ctx::artifact)/.dx-flags"
|
||||||
|
|
||||||
|
[[ -f "$file" ]] && return 0 # already exists, don't overwrite
|
||||||
|
|
||||||
|
cat > "$file" <<EOF
|
||||||
|
# .dx-flags — local developer flag defaults
|
||||||
|
# Uncomment to enable. This file is gitignored.
|
||||||
|
#
|
||||||
|
# --skip-vendors
|
||||||
|
# --skip-framework-init
|
||||||
|
# --skip-framework-config
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log::fs_write "Created .dx-flags — edit to set your local flag defaults"
|
||||||
|
}
|
||||||
|
|
||||||
|
function flag::parse() {
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--framework=*) export APP_FRAMEWORK="${arg#*=}" ;;
|
||||||
|
--dev) export ENVIRONMENT="dev" ;;
|
||||||
|
--qly) export ENVIRONMENT="qly" ;;
|
||||||
|
--prd) export ENVIRONMENT="prd" ;;
|
||||||
|
--project=*) export PROJECT_NAME="${arg#*=}" ;;
|
||||||
|
--port=*) export APP_PORT="${arg#*=}" ;;
|
||||||
|
--db=*) export DB_ENGINE="${arg#*=}" ;;
|
||||||
|
--db-port=*) export DB_PORT="${arg#*=}" ;;
|
||||||
|
--debug) set -x ;;
|
||||||
|
--help) return 1 ;;
|
||||||
|
--*)
|
||||||
|
if [[ "$arg" == *"="* ]]; then
|
||||||
|
# --flag=value form
|
||||||
|
local flag_name="${arg%%=*}"
|
||||||
|
local flag_value="${arg#*=}"
|
||||||
|
|
||||||
|
if [[ ${#_REGISTERED_FLAGS[@]} -gt 0 ]] && ! flag::registered "$flag_name"; then
|
||||||
|
log::error "Unknown flag: '${flag_name}'"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local var
|
||||||
|
var="$(flag::to_var "$flag_name")"
|
||||||
|
export "${var}"="$flag_value"
|
||||||
|
else
|
||||||
|
if [[ ${#_REGISTERED_FLAGS[@]} -gt 0 ]] && ! flag::registered "$arg"; then
|
||||||
|
log::error "Unknown flag: '${arg}'"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
flag::enable "$arg"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log::error "Unknown argument: '${arg}'"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write local overrides to env/local.env so subsequent dx commands
|
||||||
|
# (dx up, dx init, etc.) see the same values without re-passing flags.
|
||||||
|
#
|
||||||
|
# Only writes vars that differ from their defaults, keeping local.env minimal.
|
||||||
|
# Called by build::run after flag::parse.
|
||||||
|
function flag::persist_overrides() {
|
||||||
|
local local_env
|
||||||
|
local_env="$(ctx::artifact)/env/local.env"
|
||||||
|
|
||||||
|
# Vars that are meaningful to persist across commands
|
||||||
|
local persist_vars=(
|
||||||
|
ENVIRONMENT
|
||||||
|
PROJECT_NAME
|
||||||
|
APP_PORT
|
||||||
|
DB_ENGINE
|
||||||
|
DB_PORT
|
||||||
|
USE_VHOSTS
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build the new content
|
||||||
|
local content=""
|
||||||
|
for var in "${persist_vars[@]}"; do
|
||||||
|
[[ -n "${!var:-}" ]] && content+="export ${var}=\"${!var}\"\n"
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -z "$content" ]] && return 0
|
||||||
|
|
||||||
|
printf "%b" "$content" > "$local_env"
|
||||||
|
log::info "Persisted overrides to env/local.env"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Specify Defaults
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
flag::defaults
|
||||||
|
flag::load_defaults
|
||||||
135
dxkit/core/hook.sh
Normal file
135
dxkit/core/hook.sh
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# core/hook.sh
|
||||||
|
#
|
||||||
|
# Lifecycle hook runner.
|
||||||
|
#
|
||||||
|
# Hooks are shell scripts sourced in lexicographic order from:
|
||||||
|
# dxkit/hooks/<command>/<timing>/
|
||||||
|
#
|
||||||
|
# Timing:
|
||||||
|
# pre — runs before the command's main logic
|
||||||
|
# post — runs after the command's main logic
|
||||||
|
#
|
||||||
|
# Naming convention:
|
||||||
|
# NN-description.sh (e.g. 10-permissions.sh, 20-update-cron.sh)
|
||||||
|
# Numeric prefix controls execution order.
|
||||||
|
#
|
||||||
|
# Hooks are sourced (not executed) so they have full access to all
|
||||||
|
# loaded modules, functions, and environment variables.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# hook::run init pre
|
||||||
|
# hook::run init post
|
||||||
|
# hook::run build post
|
||||||
|
#
|
||||||
|
# Example structure:
|
||||||
|
# dxkit/hooks/
|
||||||
|
# init/
|
||||||
|
# pre/
|
||||||
|
# 10-something.sh
|
||||||
|
# post/
|
||||||
|
# 10-permissions.sh
|
||||||
|
# 20-update-cron.sh
|
||||||
|
# build/
|
||||||
|
# post/
|
||||||
|
# 10-something.sh
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Runner
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function hook::run() {
|
||||||
|
local command="$1"
|
||||||
|
local timing="$2"
|
||||||
|
|
||||||
|
local hook_dir
|
||||||
|
hook_dir="$(ctx::dxkit)/hooks/${command}/${timing}"
|
||||||
|
|
||||||
|
[[ -d "$hook_dir" ]] || return 0
|
||||||
|
|
||||||
|
local hooks
|
||||||
|
hooks="$(hook::_list "$hook_dir")"
|
||||||
|
|
||||||
|
[[ -z "$hooks" ]] && return 0
|
||||||
|
|
||||||
|
log::info "Running ${timing}-${command} hooks..."
|
||||||
|
|
||||||
|
local hook
|
||||||
|
while IFS= read -r hook; do
|
||||||
|
[[ -f "$hook" ]] || continue
|
||||||
|
hook::_run_one "$hook"
|
||||||
|
done <<< "$hooks"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Run a single hook
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function hook::_run_one() {
|
||||||
|
local hook="$1"
|
||||||
|
local name
|
||||||
|
name="$(basename "$hook")"
|
||||||
|
|
||||||
|
log::info " → ${name}"
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$hook" || {
|
||||||
|
log::error "Hook failed: ${name}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# List hooks in a directory sorted by name
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function hook::_list() {
|
||||||
|
local dir="$1"
|
||||||
|
|
||||||
|
find "$dir" -maxdepth 1 -name "*.sh" -type f | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Predicates
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function hook::has() {
|
||||||
|
local command="$1"
|
||||||
|
local timing="$2"
|
||||||
|
|
||||||
|
local hook_dir
|
||||||
|
hook_dir="$(ctx::dxkit)/hooks/${command}/${timing}"
|
||||||
|
|
||||||
|
[[ -d "$hook_dir" ]] && [[ -n "$(hook::_list "$hook_dir")" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Scaffold hook directory and example file
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function hook::scaffold() {
|
||||||
|
local command="$1"
|
||||||
|
local timing="${2:-post}"
|
||||||
|
|
||||||
|
local hook_dir
|
||||||
|
hook_dir="$(ctx::dxkit)/hooks/${command}/${timing}"
|
||||||
|
|
||||||
|
mkdir -p "$hook_dir"
|
||||||
|
|
||||||
|
local example="${hook_dir}/10-example.sh"
|
||||||
|
[[ -f "$example" ]] && return 0
|
||||||
|
|
||||||
|
cat > "$example" <<EOF
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ${timing}-${command} hook: 10-example.sh
|
||||||
|
#
|
||||||
|
# This hook runs ${timing} dx ${command}.
|
||||||
|
# All modules and environment variables are available.
|
||||||
|
#
|
||||||
|
# Remove or rename this file when adding real hooks.
|
||||||
|
|
||||||
|
# log::info "Running example ${timing}-${command} hook..."
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log::info "Scaffolded hook: ${example}"
|
||||||
|
}
|
||||||
122
dxkit/core/loader.sh
Normal file
122
dxkit/core/loader.sh
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function require_file() {
|
||||||
|
[[ -f "$1" ]]
|
||||||
|
}
|
||||||
|
function require_directory() {
|
||||||
|
[[ -d "$1" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_file() {
|
||||||
|
local mode="required"
|
||||||
|
local file
|
||||||
|
|
||||||
|
# Check if first argument is a mode
|
||||||
|
if [[ "$1" == "required" || "$1" == "optional" ]]; then
|
||||||
|
mode="$1"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
file="$1"
|
||||||
|
|
||||||
|
if [[ "$mode" == "required" && ! -f "$file" ]]; then
|
||||||
|
echo "❌ Missing file: $file" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Source if file exists
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
source "$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# load_module — Loads $(ctx::modules)/<name>.module.sh
|
||||||
|
# Always required.
|
||||||
|
# ============================================
|
||||||
|
function load_module() {
|
||||||
|
local name="$1"
|
||||||
|
|
||||||
|
# Wildcard: Load all submodules
|
||||||
|
if [[ "$name" == *"/*" ]]; then
|
||||||
|
local dir="${name%/*}"
|
||||||
|
local module_dir
|
||||||
|
module_dir="$(ctx::modules)/${dir}"
|
||||||
|
|
||||||
|
if [[ ! -d "$module_dir" ]]; then
|
||||||
|
log::error "Module directory not found: ${dir}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for file in "${module_dir}"/*.module.sh; do
|
||||||
|
[[ -f "$file" ]] || continue
|
||||||
|
local subname="${dir}/$(basename "${file%.module.sh}")"
|
||||||
|
load_module "$subname"
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Normal single module load
|
||||||
|
local file
|
||||||
|
file="$(ctx::modules)/${name}.module.sh"
|
||||||
|
|
||||||
|
module::loaded "$name" && return 0
|
||||||
|
|
||||||
|
if [[ ! -f "$file" ]]; then
|
||||||
|
log::error "Module not found: ${name}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source "$file"
|
||||||
|
_LOADED_MODULES["$name"]=1
|
||||||
|
|
||||||
|
core::call_if_exists "$(module::to_namespace "$name")::on_load"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# load_command — Loads $(ctx::commands)/<name>.command.sh
|
||||||
|
#
|
||||||
|
# Returns:
|
||||||
|
# 0 — file found and sourced
|
||||||
|
# 1 — file not found (caller decides how to handle)
|
||||||
|
#
|
||||||
|
# After sourcing, does NOT validate ::run here —
|
||||||
|
# that's the dispatcher's job, keeping this function
|
||||||
|
# a clean "did the file exist?" predicate.
|
||||||
|
# ============================================
|
||||||
|
function load_command() {
|
||||||
|
local name="$1"
|
||||||
|
local file
|
||||||
|
file="$(ctx::commands)/${name}.command.sh"
|
||||||
|
|
||||||
|
if [[ ! -f "$file" ]]; then
|
||||||
|
return 1 # No command file, not an error by itself
|
||||||
|
fi
|
||||||
|
|
||||||
|
source "$file"
|
||||||
|
_LOADED_COMMANDS["$name"]=1
|
||||||
|
|
||||||
|
core::call_if_exists "$(command::fn "$name" on_load)"
|
||||||
|
# core::call_if_exists "$(command::to_namespace "$name")::on_load"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# load_command_strict — Load + assert ::run exists
|
||||||
|
# ============================================
|
||||||
|
function load_command_strict() {
|
||||||
|
local name="$1"
|
||||||
|
|
||||||
|
if ! load_command "$name"; then
|
||||||
|
echo "❌ No command file found for: '${name}'" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! core::function_exists "${name}::run"; then
|
||||||
|
echo "❌ Command '${name}' loaded but '${name}::run' is not defined" >&2
|
||||||
|
echo " Expected function: ${name}::run()" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
9
dxkit/core/module.sh
Normal file
9
dxkit/core/module.sh
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
declare -A _LOADED_MODULES=()
|
||||||
|
readonly _MODULE_AUTO_LOAD_HOOK="on_load"
|
||||||
|
|
||||||
|
function module::loaded() { [[ -n "${_LOADED_MODULES["$1"]:-}" ]]; }
|
||||||
|
function module::has_function() { declare -F "${1}::${2}" >/dev/null 2>&1; }
|
||||||
|
function module::is_auto_load() { declare -F "${1}::on_load" >/dev/null 2>&1; }
|
||||||
|
function module::to_namespace() { echo "${1//\//::}"; }
|
||||||
100
dxkit/core/platform.sh
Normal file
100
dxkit/core/platform.sh
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Detection
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function platform::detect() {
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Linux*) PLATFORM="linux" ;;
|
||||||
|
Darwin*) PLATFORM="macos" ;;
|
||||||
|
CYGWIN*|MINGW*|MSYS*) PLATFORM="windows" ;;
|
||||||
|
*) PLATFORM="unknown" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
platform::_apply_platform_guards
|
||||||
|
}
|
||||||
|
|
||||||
|
function platform::is_macos() { [[ "$PLATFORM" == "macos" ]]; }
|
||||||
|
function platform::is_linux() { [[ "$PLATFORM" == "linux" ]]; }
|
||||||
|
function platform::is_windows() { [[ "$PLATFORM" == "windows" ]]; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function platform::_apply_platform_guards() {
|
||||||
|
if platform::is_windows; then
|
||||||
|
export MSYS_NO_PATHCONV=1
|
||||||
|
export MSYS2_ARG_CONV_EXCL="*"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function platform::elevation_fail() {
|
||||||
|
if platform::is_windows; then
|
||||||
|
echo "Action must be run as Administrator"
|
||||||
|
else
|
||||||
|
echo "Action must be run with sudo or as root"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function platform::hosts_file() {
|
||||||
|
local hostsPath='/etc/hosts'
|
||||||
|
|
||||||
|
if platform::is_windows; then
|
||||||
|
hostsPath='/c/Windows/System32/drivers/etc/hosts'
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo $hostsPath
|
||||||
|
}
|
||||||
|
|
||||||
|
function platform::sudo() {
|
||||||
|
if platform::is_windows; then
|
||||||
|
"$@"
|
||||||
|
else
|
||||||
|
[[ $EUID -ne 0 ]] && sudo "$1" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function platform::require_privileges() {
|
||||||
|
if platform::is_windows; then
|
||||||
|
if ! net session >/dev/null 2>&1; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
platform::sudo -n true 2>/dev/null || platform::sudo true || return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Docker
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function platform::docker_path() {
|
||||||
|
local path="$1"
|
||||||
|
|
||||||
|
if platform::is_windows; then
|
||||||
|
cygpath -m "$path"
|
||||||
|
else
|
||||||
|
echo "$path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Platform functions
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Detect GNU realpath (supports --relative-to)
|
||||||
|
function platform::has_gnu_realpath() {
|
||||||
|
realpath --help 2>&1 | grep -q -- '--relative-to'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect grealpath (Homebrew GNU coreutils)
|
||||||
|
function platform::has_grealpath() {
|
||||||
|
command -v grealpath >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect python3
|
||||||
|
function platform::has_python() {
|
||||||
|
command -v python3 >/dev/null 2>&1
|
||||||
|
}
|
||||||
63
dxkit/core/string.sh
Normal file
63
dxkit/core/string.sh
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function string::trim() {
|
||||||
|
local var="$*"
|
||||||
|
var="${var#"${var%%[![:space:]]*}"}"
|
||||||
|
var="${var%"${var##*[![:space:]]}"}"
|
||||||
|
printf "%s" "$var"
|
||||||
|
}
|
||||||
|
|
||||||
|
function string::lowercase() {
|
||||||
|
printf "%s" "$1" | tr '[:upper:]' '[:lower:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
function string::uppercase() {
|
||||||
|
printf "%s" "$1" | tr '[:lower:]' '[:upper:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
function string::replace() {
|
||||||
|
local str="$1" from="$2" to="$3"
|
||||||
|
echo "${str//$from/$to}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function string::strip_prefix() {
|
||||||
|
local str="$1" prefix="$2"
|
||||||
|
echo "${str#"$prefix"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function string::strip_suffix() {
|
||||||
|
local str="$1" suffix="$2"
|
||||||
|
echo "${str%"$suffix"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function string::slugify() {
|
||||||
|
echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd '[:alnum:]-'
|
||||||
|
}
|
||||||
|
|
||||||
|
function string::pad_right() {
|
||||||
|
local str="$1" width="$2"
|
||||||
|
printf "%-${width}s" "$str"
|
||||||
|
}
|
||||||
|
|
||||||
|
function string::pad_left() {
|
||||||
|
local str="$1" width="$2"
|
||||||
|
printf "%${width}s" "$str"
|
||||||
|
}
|
||||||
|
|
||||||
|
function string::repeat() {
|
||||||
|
local char="$1" count="$2"
|
||||||
|
printf "%${count}s" | tr " " "$char"
|
||||||
|
}
|
||||||
|
|
||||||
|
function string::split() {
|
||||||
|
local str="$1" delimiter="$2"
|
||||||
|
local -n result_ref="$3" # nameref — caller passes array name
|
||||||
|
|
||||||
|
IFS="$delimiter" read -ra result_ref <<< "$str"
|
||||||
|
}
|
||||||
|
|
||||||
|
function string::is_empty() { [[ -z "$1" ]]; }
|
||||||
|
function string::is_not_empty() { [[ -n "$1" ]]; }
|
||||||
|
function string::starts_with() { [[ "$1" == "$2"* ]]; }
|
||||||
|
function string::ends_with() { [[ "$1" == *"$2" ]]; }
|
||||||
|
function string::contains() { [[ "$1" == *"$2"* ]]; }
|
||||||
99
dxkit/core/utils.sh
Normal file
99
dxkit/core/utils.sh
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# System
|
||||||
|
# ===========================================
|
||||||
|
function system::uuid() {
|
||||||
|
od -x /dev/urandom | head -1 | awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}'
|
||||||
|
}
|
||||||
|
function system::hosts() {
|
||||||
|
cat "$(platform::hosts_file)"
|
||||||
|
}
|
||||||
|
function system::stderr() {
|
||||||
|
"$1" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Path
|
||||||
|
# ===========================================
|
||||||
|
function path::relative_to_root() {
|
||||||
|
local abs="$1"
|
||||||
|
local root="$(ctx::root)"
|
||||||
|
|
||||||
|
# remove root prefix
|
||||||
|
echo "${abs#$root/}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function path::from_root() {
|
||||||
|
local path="$1"
|
||||||
|
[[ "$path" == /* ]] && echo "$path" || echo "$(ctx::root)/${path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function path::relative_to() {
|
||||||
|
local from="$1"
|
||||||
|
local to="$2"
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# 1. GNU realpath (best case)
|
||||||
|
# ===========================================
|
||||||
|
if platform::has_gnu_realpath; then
|
||||||
|
realpath --relative-to="$from" "$to"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# 2. Homebrew GNU coreutils
|
||||||
|
# ===========================================
|
||||||
|
if platform::has_grealpath; then
|
||||||
|
grealpath --relative-to="$from" "$to"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# 3. Python fallback (POSIX-safe)
|
||||||
|
# ===========================================
|
||||||
|
if platform::has_python; then
|
||||||
|
python3 -c '
|
||||||
|
import os, sys
|
||||||
|
print(os.path.relpath(sys.argv[2], sys.argv[1]))
|
||||||
|
' "$from" "$to"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# 4. Hard failure
|
||||||
|
# ===========================================
|
||||||
|
echo "path::relative_to: no suitable backend found" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Core
|
||||||
|
# ===========================================
|
||||||
|
|
||||||
|
# Returns true if stdout is connected to a real TTY.
|
||||||
|
# Used to decide whether winpty or interactive flags are needed.
|
||||||
|
# False inside $(), pipes, or non-interactive subshells.
|
||||||
|
function core::has_tty() { [[ -t 1 ]]; } # stdout is a TTY
|
||||||
|
function core::is_interactive() { [[ $- == *i* ]]; } # shell is interactive mode
|
||||||
|
function core::function_exists() { declare -F "$1" >/dev/null 2>&1; }
|
||||||
|
function core::variable_exists() { declare -p "$1" &>/dev/null; }
|
||||||
|
function core::array_exists() { [[ "$(declare -p "$1" 2>/dev/null)" == "declare -a"* ]]; }
|
||||||
|
|
||||||
|
function core::call_function() {
|
||||||
|
local fn="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
"$fn" "$@";
|
||||||
|
}
|
||||||
|
|
||||||
|
function core::call_if_exists() {
|
||||||
|
local fn="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
if declare -F "$fn" >/dev/null 2>&1; then
|
||||||
|
"$fn" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
5
dxkit/docker/entrypoint.sh
Normal file
5
dxkit/docker/entrypoint.sh
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
service ssh start
|
||||||
|
/usr/sbin/apache2ctl -D FOREGROUND
|
||||||
98
dxkit/drivers/dispatcher.sh
Normal file
98
dxkit/drivers/dispatcher.sh
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Driver Dispatcher
|
||||||
|
# ============================================
|
||||||
|
#
|
||||||
|
# Resolves the active framework via app::framework()
|
||||||
|
# and loads the corresponding driver from dxkit/drivers/.
|
||||||
|
#
|
||||||
|
# Called in main() after load_module app, so app::framework()
|
||||||
|
# is guaranteed to be available.
|
||||||
|
#
|
||||||
|
# Each driver registers its commands into DRIVER_COMMANDS
|
||||||
|
# for dx::dispatch to pick up.
|
||||||
|
#
|
||||||
|
# Usage (in dx entrypoint, after load_module app):
|
||||||
|
# driver::load
|
||||||
|
|
||||||
|
declare -gA DRIVER_COMMANDS=()
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Resolution
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Maps APP_FRAMEWORK values to driver directory names.
|
||||||
|
# Multiple framework variants can share one driver (e.g. yii2-advanced
|
||||||
|
# and yii2-basic both use the yii2 driver).
|
||||||
|
# Unknown frameworks fall through as-is, so new drivers
|
||||||
|
# just need a matching directory — no changes here required.
|
||||||
|
function driver::resolve() {
|
||||||
|
local framework
|
||||||
|
framework="$(app::framework)"
|
||||||
|
|
||||||
|
case "$framework" in
|
||||||
|
yii2-advanced|yii2-basic) echo "yii2" ;;
|
||||||
|
laravel) echo "laravel" ;;
|
||||||
|
*) echo "$framework" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Loader
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function driver::load() {
|
||||||
|
local driver
|
||||||
|
driver="$(driver::resolve)"
|
||||||
|
|
||||||
|
local driver_file
|
||||||
|
driver_file="$(ctx::dxkit)/drivers/${driver}/driver.sh"
|
||||||
|
|
||||||
|
if [[ ! -f "$driver_file" ]]; then
|
||||||
|
log::warn "No driver found for framework '$(app::framework)' at ${driver_file}"
|
||||||
|
log::warn "Framework-specific commands (e.g. migrate-create) will not be available."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$driver_file"
|
||||||
|
|
||||||
|
log::debug "Loaded driver: ${driver} (framework: $(app::framework))"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Runtime Helpers
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Returns true if a command is registered by the active driver.
|
||||||
|
function driver::has_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
[[ -n "${DRIVER_COMMANDS[$cmd]:-}" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Runs a driver command.
|
||||||
|
function driver::run() {
|
||||||
|
local cmd="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
if ! driver::has_command "$cmd"; then
|
||||||
|
log::error "Driver command not found: '${cmd}'"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${DRIVER_COMMANDS[$cmd]}" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prints all registered driver commands (used by dx help and dx workspace).
|
||||||
|
function driver::list_commands() {
|
||||||
|
if [[ ${#DRIVER_COMMANDS[@]} -eq 0 ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Framework commands ($(app::framework)):"
|
||||||
|
for cmd in $(echo "${!DRIVER_COMMANDS[@]}" | tr ' ' '\n' | sort); do
|
||||||
|
printf " %-28s\n" "$cmd"
|
||||||
|
done
|
||||||
|
}
|
||||||
29
dxkit/drivers/laravel/driver.sh
Normal file
29
dxkit/drivers/laravel/driver.sh
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Laravel Driver
|
||||||
|
# ============================================
|
||||||
|
#
|
||||||
|
# Entry point for the Laravel driver.
|
||||||
|
# Sources all Laravel command modules and registers
|
||||||
|
# them into DRIVER_COMMANDS for dx::dispatch.
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Load Modules
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$(ctx::dxkit)/drivers/laravel/migrate.sh"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Register Commands
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
DRIVER_COMMANDS=(
|
||||||
|
[artisan]="laravel::exec"
|
||||||
|
[migrate]="laravel::migrate::run"
|
||||||
|
[migrate-create]="laravel::migrate::create"
|
||||||
|
[migrate-rollback]="laravel::migrate::rollback"
|
||||||
|
[migrate-status]="laravel::migrate::status"
|
||||||
|
[migrate-fresh]="laravel::migrate::fresh"
|
||||||
|
)
|
||||||
54
dxkit/drivers/laravel/migrate.sh
Normal file
54
dxkit/drivers/laravel/migrate.sh
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Laravel — Migration Commands
|
||||||
|
# ============================================
|
||||||
|
#
|
||||||
|
# Thin wrappers around artisan migrate commands,
|
||||||
|
# run inside the app container via artisan::exec.
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Commands
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Creates a new migration via artisan make:migration.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx migrate-create create_invoices_table
|
||||||
|
function laravel::migrate::create() {
|
||||||
|
local name="${1:?Usage: dx migrate-create <migration_name>}"
|
||||||
|
artisan::exec make:migration "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Runs all pending migrations.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx migrate
|
||||||
|
function laravel::migrate::run() {
|
||||||
|
artisan::exec migrate "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rolls back the last batch of migrations.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx migrate-rollback
|
||||||
|
# dx migrate-rollback --step=3
|
||||||
|
function laravel::migrate::rollback() {
|
||||||
|
artisan::exec migrate:rollback "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Shows the status of all migrations.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx migrate-status
|
||||||
|
function laravel::migrate::status() {
|
||||||
|
artisan::exec migrate:status "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Drops all tables and re-runs all migrations.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx migrate-fresh
|
||||||
|
function laravel::migrate::fresh() {
|
||||||
|
artisan::exec migrate:fresh "$@"
|
||||||
|
}
|
||||||
29
dxkit/drivers/yii2/driver.sh
Normal file
29
dxkit/drivers/yii2/driver.sh
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Yii2 Driver
|
||||||
|
# ============================================
|
||||||
|
#
|
||||||
|
# Entry point for the yii2-advanced driver.
|
||||||
|
# Sources all yii2 command modules and registers
|
||||||
|
# them into DRIVER_COMMANDS for dx::dispatch.
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Load Modules
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$(ctx::dxkit)/drivers/yii2/migrate.sh"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Register Commands
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
DRIVER_COMMANDS=(
|
||||||
|
[yii]="yii2::exec"
|
||||||
|
[migrate]="yii2::migrate::run"
|
||||||
|
[migrate-create]="yii2::migrate::create"
|
||||||
|
[migrate-down]="yii2::migrate::down"
|
||||||
|
[migrate-new]="yii2::migrate::new"
|
||||||
|
[migrate-history]="yii2::migrate::history"
|
||||||
|
)
|
||||||
138
dxkit/drivers/yii2/migrate.sh
Normal file
138
dxkit/drivers/yii2/migrate.sh
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Yii2 — Commands
|
||||||
|
# ============================================
|
||||||
|
#
|
||||||
|
# All functions run inside the app container
|
||||||
|
# via runtime::exec, so they work regardless
|
||||||
|
# of whether the stack is Docker or native.
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Helpers
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Generates a snake_case migration class name with timestamp.
|
||||||
|
# e.g. m260430_151441_create_invoice_table
|
||||||
|
function yii2::migrate::_class_name() {
|
||||||
|
local name="$1"
|
||||||
|
local timestamp
|
||||||
|
timestamp="$(date '+%y%m%d_%H%M%S')"
|
||||||
|
echo "m${timestamp}_${name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolves the migrations directory on the host.
|
||||||
|
function yii2::migrate::_dir() {
|
||||||
|
echo "$(ctx::root)/console/migrations"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Yii
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Runs any yii console command inside the container.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx yii <command> [args...]
|
||||||
|
# yii <command> [args...] (inside dx workspace)
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# dx yii migrate
|
||||||
|
# dx yii rbac/init
|
||||||
|
# yii cache/flush-all
|
||||||
|
function yii2::exec() {
|
||||||
|
runtime::exec php yii "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Migrations
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Creates a new migration file with the correct namespace,
|
||||||
|
# class name, and stub — bypassing Yii's generator entirely.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx migrate-create <name>
|
||||||
|
# e.g. dx migrate-create create_invoice_table
|
||||||
|
function yii2::migrate::create() {
|
||||||
|
local name="${1:?Usage: dx migrate-create <migration_name>}"
|
||||||
|
local namespace="console\\migrations"
|
||||||
|
local class
|
||||||
|
class="$(yii2::migrate::_class_name "$name")"
|
||||||
|
local dir
|
||||||
|
dir="$(yii2::migrate::_dir)"
|
||||||
|
local file="${dir}/${class}.php"
|
||||||
|
|
||||||
|
local title
|
||||||
|
title="$(echo "$name" | sed 's/_/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1')"
|
||||||
|
|
||||||
|
mkdir -p "$dir"
|
||||||
|
|
||||||
|
cat > "$file" <<PHP
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace ${namespace};
|
||||||
|
|
||||||
|
use console\models\Migration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ${title}
|
||||||
|
*/
|
||||||
|
class ${class} extends Migration
|
||||||
|
{
|
||||||
|
public \$tableName = '{{%TODO}}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function safeUp()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function safeDown()
|
||||||
|
{
|
||||||
|
\$this->dropTable(\$this->tableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PHP
|
||||||
|
|
||||||
|
log::success "Created: ${file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Runs all pending migrations.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx migrate
|
||||||
|
function yii2::migrate::run() {
|
||||||
|
yii2::exec migrate "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reverts the last N migrations (default: 1).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx migrate-down
|
||||||
|
# dx migrate-down 3
|
||||||
|
function yii2::migrate::down() {
|
||||||
|
local n="${1:-1}"
|
||||||
|
yii2::exec migrate/down "$n" "${@:2}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Shows new (pending) migrations.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx migrate-new
|
||||||
|
function yii2::migrate::new() {
|
||||||
|
yii2::exec migrate/new "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Shows migration history.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# dx migrate-history
|
||||||
|
function yii2::migrate::history() {
|
||||||
|
yii2::exec migrate/history "$@"
|
||||||
|
}
|
||||||
39
dxkit/env/globals.env
vendored
Normal file
39
dxkit/env/globals.env
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# App Settings
|
||||||
|
export BASE_APP_IMAGE="yiisoftware/yii2-php:8.3-apache"
|
||||||
|
export APP_IMAGE="${PROJECT_NAME}-app"
|
||||||
|
export APP_PORT=auto:8080
|
||||||
|
|
||||||
|
# Domains
|
||||||
|
export TLD="erlog"
|
||||||
|
export DOMAIN="${PROJECT_NAME}.${TLD}"
|
||||||
|
|
||||||
|
# Database Config
|
||||||
|
export DB_DRIVER="db"
|
||||||
|
export DB_IMAGE="mariadb:latest"
|
||||||
|
export DB_PORT=auto
|
||||||
|
export DB_HOST="${PROJECT_NAME}-${DB_DRIVER}"
|
||||||
|
export DB_NAME="${PROJECT_NAME}"
|
||||||
|
export DB_ENGINE="mysql"
|
||||||
|
|
||||||
|
# Database Auth
|
||||||
|
export DB_USER="root"
|
||||||
|
export DB_PASS="kxpto1l"
|
||||||
|
export DB_ROOT_PASSWORD="kxpto1l"
|
||||||
|
|
||||||
|
# Composer Auth
|
||||||
|
export GIT_DOMAIN="git.erlog.pt"
|
||||||
|
export GIT_AUTH_TOKEN_ID="gitlab+deploy-token-5"
|
||||||
|
export GIT_AUTH_TOKEN_PW="gldt-CMbMgwQ_EsxBydc13Ssv"
|
||||||
|
|
||||||
|
export COMPOSER_AUTH="{
|
||||||
|
\"http-basic\": {
|
||||||
|
\"${GIT_DOMAIN}\": {
|
||||||
|
\"username\": \"${GIT_AUTH_TOKEN_ID}\",
|
||||||
|
\"password\": \"${GIT_AUTH_TOKEN_PW}\"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
|
||||||
|
# Proxy
|
||||||
|
export PROXY_SERVICE="traefik"
|
||||||
|
export PROXY_NETWORK="proxy"
|
||||||
75
dxkit/modules/apache.module.sh
Normal file
75
dxkit/modules/apache.module.sh
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# modules/apache.module.sh
|
||||||
|
#
|
||||||
|
# Apache management module.
|
||||||
|
# Handles vhost generation, config validation, reloads, and module toggling.
|
||||||
|
#
|
||||||
|
# Runs commands through runtime::exec so it works identically with
|
||||||
|
# Docker and native runtimes.
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Vhost generation
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function apache::generate_vhosts() {
|
||||||
|
log::env_load "Generating Virtual Hosts (${ENVIRONMENT})..."
|
||||||
|
|
||||||
|
template::render \
|
||||||
|
"$(app::vhost_template_path)" \
|
||||||
|
"$(ctx::artifact::path apache vhosts vhosts.conf)" \
|
||||||
|
APP_PATH="$(ctx::container::root)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Config validation
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function apache::validate_config() {
|
||||||
|
runtime::exec apache2ctl configtest
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Lifecycle
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function apache::reload() {
|
||||||
|
runtime::exec apache2ctl graceful
|
||||||
|
}
|
||||||
|
|
||||||
|
function apache::restart() {
|
||||||
|
runtime::exec apache2ctl restart
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Inspection
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function apache::list_vhosts() {
|
||||||
|
runtime::exec apache2ctl -S
|
||||||
|
}
|
||||||
|
|
||||||
|
function apache::list_modules() {
|
||||||
|
runtime::exec apache2ctl -M
|
||||||
|
}
|
||||||
|
|
||||||
|
function apache::version() {
|
||||||
|
runtime::exec apache2 -v
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Logs
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function apache::logs::error() { runtime::exec tail -f /var/log/apache2/error.log; }
|
||||||
|
function apache::logs::access() { runtime::exec tail -f /var/log/apache2/access.log; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Module management
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function apache::module::enable() { runtime::exec a2enmod "$@"; }
|
||||||
|
function apache::module::disable() { runtime::exec a2dismod "$@"; }
|
||||||
|
|
||||||
|
function apache::module::is_enabled() {
|
||||||
|
runtime::exec a2query -m "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
44
dxkit/modules/app.module.sh
Normal file
44
dxkit/modules/app.module.sh
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# modules/app.module.sh
|
||||||
|
#
|
||||||
|
# Framework abstraction layer.
|
||||||
|
# Dispatches app lifecycle functions to the active framework module.
|
||||||
|
#
|
||||||
|
# Active framework is determined by --framework flag (default: yii2-advanced)
|
||||||
|
# Set in .dx-flags or passed as: dx build --framework=laravel
|
||||||
|
|
||||||
|
function app::on_load() {
|
||||||
|
load_module "framework/$(app::framework)"
|
||||||
|
load_module proxy
|
||||||
|
load_module composer
|
||||||
|
|
||||||
|
# Register framework console name as alias for console command
|
||||||
|
# yii2-advanced → yii, laravel → artisan
|
||||||
|
# CMD_ALIASES is declared in dx
|
||||||
|
CMD_ALIASES["$(app::console_name)"]=console
|
||||||
|
CMD_ALIASES[console]=console
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Framework identity
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function app::framework { echo "${APP_FRAMEWORK:-yii2-advanced}"; }
|
||||||
|
function app::console_name() { "framework::$(app::framework)::console_name"; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Interface
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function app::scaffold() { "framework::$(app::framework)::scaffold" "${1:-$(ctx::root)}"; }
|
||||||
|
function app::init() { "framework::$(app::framework)::init" "$@"; }
|
||||||
|
function app::config() { "framework::$(app::framework)::config" "$@"; }
|
||||||
|
function app::console() { "framework::$(app::framework)::console" "$@"; }
|
||||||
|
function app::resolve_env() { "framework::$(app::framework)::resolve_env" "$@"; }
|
||||||
|
function app::install_vendors() { "framework::$(app::framework)::install_vendors"; }
|
||||||
|
function app::vendor_skip_message() { "framework::$(app::framework)::vendor_skip_message"; }
|
||||||
|
function app::vhost_template_path() { "framework::$(app::framework)::vhost_template_path"; }
|
||||||
|
function app::exists() { [[ -f "$(ctx::root)/composer.json" ]]; }
|
||||||
|
function app::init_skip_message() { "framework::$(app::framework)::init_skip_message" "$@"; }
|
||||||
|
function app::config_skip_message() { "framework::$(app::framework)::config_skip_message" "$@"; }
|
||||||
|
function app::post_init_hint() { "framework::$(app::framework)::post_init_hint"; }
|
||||||
18
dxkit/modules/artifact.module.sh
Normal file
18
dxkit/modules/artifact.module.sh
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function artifact::dirs() {
|
||||||
|
local dirs=(
|
||||||
|
"$(ctx::artifact)"
|
||||||
|
"$(ctx::artifact::path env)"
|
||||||
|
"$(ctx::artifact::path apache)"
|
||||||
|
"$(ctx::artifact::path database)"
|
||||||
|
"$(ctx::artifact::path docker)"
|
||||||
|
)
|
||||||
|
printf "%s\n" "${dirs[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function artifact::init() {
|
||||||
|
while IFS= read -r dir; do
|
||||||
|
mkdir -p "$dir"
|
||||||
|
done < <(artifact::dirs)
|
||||||
|
}
|
||||||
107
dxkit/modules/composer.module.sh
Normal file
107
dxkit/modules/composer.module.sh
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# modules/composer.module.sh
|
||||||
|
#
|
||||||
|
# Composer wrapper for both in-runtime and bootstrap operations.
|
||||||
|
#
|
||||||
|
# Two execution modes:
|
||||||
|
# composer::run — runs inside the project's runtime
|
||||||
|
# (use when the runtime is up)
|
||||||
|
# composer::run_isolated — runs in a one-shot Docker container
|
||||||
|
# (use for bootstrap before runtime exists)
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Execution modes
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Run composer inside the project's runtime.
|
||||||
|
# Requires the runtime to be up.
|
||||||
|
function composer::run() {
|
||||||
|
runtime::exec composer "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run composer in a one-shot Docker container.
|
||||||
|
# Use for scaffold and other operations that happen before the runtime exists.
|
||||||
|
function composer::run_isolated() {
|
||||||
|
docker::raw run --rm \
|
||||||
|
-v "$(ctx::root):/app" \
|
||||||
|
-w /app \
|
||||||
|
composer:latest "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Auth
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function composer::config_auth() {
|
||||||
|
local mode="cache"
|
||||||
|
|
||||||
|
if [[ "$1" == "cache" || "$1" == "nocache" ]]; then
|
||||||
|
mode="$1"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
local domain="$1"
|
||||||
|
local id="$2"
|
||||||
|
local pw="$3"
|
||||||
|
|
||||||
|
if [[ -z "$domain" || -z "$id" || -z "$pw" ]]; then
|
||||||
|
log::error "composer::config_auth: domain, id, and password are required"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "$mode" == "nocache" ]] && \
|
||||||
|
runtime::exec rm -f /root/.composer/auth.json
|
||||||
|
|
||||||
|
runtime::exec composer config \
|
||||||
|
--global \
|
||||||
|
--auth "http-basic.${domain}" \
|
||||||
|
"$id" \
|
||||||
|
"$pw"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Vendor management
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function composer::install() {
|
||||||
|
runtime::exec composer install \
|
||||||
|
--prefer-dist \
|
||||||
|
--no-interaction \
|
||||||
|
--no-progress \
|
||||||
|
--optimize-autoloader \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function composer::update() {
|
||||||
|
runtime::exec composer update \
|
||||||
|
--prefer-dist \
|
||||||
|
--no-interaction \
|
||||||
|
--no-progress \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function composer::require() {
|
||||||
|
runtime::exec composer require \
|
||||||
|
--no-interaction \
|
||||||
|
--no-progress \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function composer::delete_lock() {
|
||||||
|
runtime::exec rm -f composer.lock
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Inspection
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function composer::version() { runtime::exec composer --version; }
|
||||||
|
function composer::validate() { runtime::exec composer validate; }
|
||||||
|
|
||||||
|
function composer::has_lockfile() {
|
||||||
|
[[ -f "$(ctx::root)/composer.lock" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
function composer::has_manifest() {
|
||||||
|
[[ -f "$(ctx::root)/composer.json" ]]
|
||||||
|
}
|
||||||
305
dxkit/modules/docker.module.sh
Normal file
305
dxkit/modules/docker.module.sh
Normal file
|
|
@ -0,0 +1,305 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# shellcheck disable=SC2120
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Load
|
||||||
|
# ===========================================
|
||||||
|
|
||||||
|
function docker::on_load() { load_module 'docker/*'; }
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Docker
|
||||||
|
# ===========================================
|
||||||
|
# Non-interactive docker calls — bypasses all shell aliases.
|
||||||
|
function docker::raw() {
|
||||||
|
command docker "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Interactive docker calls that need a TTY.
|
||||||
|
# On Windows: prepends winpty to allocate a pseudo-terminal.
|
||||||
|
# On Unix: same as docker::raw.
|
||||||
|
function docker::tty() {
|
||||||
|
if platform::is_windows; then
|
||||||
|
winpty docker "$@"
|
||||||
|
else
|
||||||
|
command docker "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker::exec() {
|
||||||
|
local container="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
docker::tty exec -it "$container" "$@"
|
||||||
|
}
|
||||||
|
function docker::container_running() {
|
||||||
|
local name="$1"
|
||||||
|
|
||||||
|
docker::raw ps --format '{{.Names}}' \
|
||||||
|
| grep -qx "$name"
|
||||||
|
}
|
||||||
|
function docker::container_exists() {
|
||||||
|
local name="$1"
|
||||||
|
|
||||||
|
docker::raw ps -a --format '{{.Names}}' \
|
||||||
|
| grep -qx "$name"
|
||||||
|
}
|
||||||
|
function docker::build() {
|
||||||
|
local dockerfile=$(docker::image::file)
|
||||||
|
|
||||||
|
local tag="${APP_IMAGE}:${ENVIRONMENT}"
|
||||||
|
|
||||||
|
require_file "$dockerfile" || {
|
||||||
|
log::docker_error "Dockerfile not found for environment $ENVIRONMENT (Dockerfile-${ENVIRONMENT})!"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log::docker_build "Building image ${tag}..."
|
||||||
|
|
||||||
|
if docker::raw build -f "$dockerfile" -t "$tag" "$(ctx::root)"; then
|
||||||
|
log::docker_success "Image built: ${tag}"
|
||||||
|
else
|
||||||
|
log::docker_error "Failed to build image: ${tag}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
function docker::list_containers() {
|
||||||
|
docker::raw ps --filter "name=${PROJECT_NAME}" \
|
||||||
|
--format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
|
||||||
|
}
|
||||||
|
function docker::status() {
|
||||||
|
local container="$1"
|
||||||
|
|
||||||
|
if ! docker::raw inspect "$container" >/dev/null 2>&1; then
|
||||||
|
echo "missing"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker::raw inspect -f '{{.State.Status}}' "$container"
|
||||||
|
}
|
||||||
|
function docker::health() {
|
||||||
|
local container="$1"
|
||||||
|
|
||||||
|
local health
|
||||||
|
health="$(docker::raw inspect -f '{{.State.Health.Status}}' "$container" 2>/dev/null)"
|
||||||
|
|
||||||
|
if [[ -z "$health" || "$health" == "<no value>" ]]; then
|
||||||
|
echo "unknown"
|
||||||
|
else
|
||||||
|
echo "$health"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
function docker::inside_container() {
|
||||||
|
[[ -f /.dockerenv ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Compose
|
||||||
|
# ===========================================
|
||||||
|
function docker::compose() {
|
||||||
|
local stackName="docker-compose-${ENVIRONMENT}.yml"
|
||||||
|
|
||||||
|
require_file "$(docker::compose::file)" || {
|
||||||
|
log::docker_error "Docker Compose Stack not found for Environment $ENVIRONMENT ($stackName)!"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if core::has_tty; then
|
||||||
|
docker::tty compose -p "${PROJECT_NAME}_${ENVIRONMENT}" -f "$(docker::compose::file)" "$@"
|
||||||
|
else
|
||||||
|
docker::raw compose -p "${PROJECT_NAME}_${ENVIRONMENT}" -f "$(docker::compose::file)" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
function docker::compose::interactive() {
|
||||||
|
local file
|
||||||
|
file="$(docker::compose::file)"
|
||||||
|
|
||||||
|
require_file "$file" || {
|
||||||
|
log::docker_error "Missing compose file"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
docker::tty compose \
|
||||||
|
-p "${PROJECT_NAME}_${ENVIRONMENT}" \
|
||||||
|
-f "$file" \
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
function docker::compose::default_service() { echo "app"; }
|
||||||
|
function docker::compose::service_running() {
|
||||||
|
local service="${1:-app}"
|
||||||
|
|
||||||
|
local id
|
||||||
|
id="$(docker::compose ps -q "$service" 2>/dev/null)"
|
||||||
|
id="${id//$'\r'/}" # strip carriage returns using parameter expansion, no pipe
|
||||||
|
|
||||||
|
[[ -n "$id" ]]
|
||||||
|
}
|
||||||
|
function docker::compose::ensure_running() {
|
||||||
|
local service="${1:-app}"
|
||||||
|
|
||||||
|
if ! docker::compose::service_running "$service"; then
|
||||||
|
log::docker_start "Stack not running, starting..."
|
||||||
|
docker::compose up -d
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
function docker::compose::logs() {
|
||||||
|
docker::compose logs -f
|
||||||
|
}
|
||||||
|
function docker::compose::up() {
|
||||||
|
docker::compose up -d --remove-orphans \
|
||||||
|
&& { log::docker_start "Stack started for Environment $ENVIRONMENT"; } \
|
||||||
|
|| { log::docker_error "Failed to start Stack for Environment $ENVIRONMENT"; return 1; }
|
||||||
|
}
|
||||||
|
function docker::compose::down() {
|
||||||
|
docker::compose down \
|
||||||
|
&& { log::docker_stop "Stack stopped for Environment $ENVIRONMENT"; } \
|
||||||
|
|| { log::docker_error "Failed to stop Stack for Environment $ENVIRONMENT"; return 1; }
|
||||||
|
}
|
||||||
|
function docker::compose::status() {
|
||||||
|
local service="${1:-$(docker::compose::default_service)}"
|
||||||
|
|
||||||
|
local id
|
||||||
|
id="$(docker::compose::container "$service")" || {
|
||||||
|
echo "missing"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
docker::raw inspect -f '{{.State.Status}}' "$id"
|
||||||
|
}
|
||||||
|
function docker::compose::exec() {
|
||||||
|
local service="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
docker::compose::interactive exec "$service" "$@"
|
||||||
|
}
|
||||||
|
function docker::compose::container() {
|
||||||
|
local service="$1"
|
||||||
|
docker::compose ps -q "$service"
|
||||||
|
}
|
||||||
|
function docker::compose::service_exists() {
|
||||||
|
local service="${1:-$(docker::compose::default_service)}"
|
||||||
|
[[ -n "$(docker::compose::container "$service")" ]]
|
||||||
|
}
|
||||||
|
function docker::compose::list_containers() {
|
||||||
|
docker::compose ps \
|
||||||
|
--format "table {{.Name}}\t{{.Service}}\t{{.Status}}\t{{.Ports}}"
|
||||||
|
}
|
||||||
|
function docker::compose::ports() {
|
||||||
|
local service="${1:-$(docker::compose::default_service)}"
|
||||||
|
|
||||||
|
local id
|
||||||
|
id="$(docker::compose::container "$service")" || return 1
|
||||||
|
|
||||||
|
docker::raw port "$id" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Paths
|
||||||
|
# ===========================================
|
||||||
|
function docker::path() {
|
||||||
|
local dir=""
|
||||||
|
local file=""
|
||||||
|
|
||||||
|
if [[ $# -eq 1 ]]; then
|
||||||
|
file="$1"
|
||||||
|
else
|
||||||
|
dir="$1"
|
||||||
|
file="$2"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$dir" ]]; then
|
||||||
|
echo "$(ctx::docker)/$dir/$file"
|
||||||
|
else
|
||||||
|
echo "$(ctx::docker)/$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
function docker::path::relative() { path::relative_to "$(ctx::docker)" "$1"; }
|
||||||
|
function docker::image::file() { platform::docker_path "$(ctx::docker)/Dockerfile-${ENVIRONMENT}"; }
|
||||||
|
function docker::compose::file() { platform::docker_path "$(ctx::docker)/docker-compose-${ENVIRONMENT}.yml"; }
|
||||||
|
function docker::image::path() { path::relative_to_root "$(ctx::docker)/Dockerfile-${ENVIRONMENT}"; }
|
||||||
|
function docker::compose::path() { path::relative_to_root "$(ctx::docker)/docker-compose-${ENVIRONMENT}.yml"; }
|
||||||
|
function docker::app::path() { docker::path::relative "$(ctx::root)"; }
|
||||||
|
function docker::apache::logs_path() { docker::path::relative "$(ctx::artifact)/apache/logs"; }
|
||||||
|
function docker::apache::vhosts_path() { docker::path::relative "$(ctx::artifact)/apache/vhosts"; }
|
||||||
|
function docker::apache::certs_path() { docker::path::relative "$(ctx::artifact)/apache/certificates"; }
|
||||||
|
function docker::apache::xdebug_path() { docker::path::relative "$(ctx::artifact)/php/xdebug.ini"; }
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Render
|
||||||
|
# ===========================================
|
||||||
|
function docker::compose::render_volume() {
|
||||||
|
local host="$1"
|
||||||
|
local container="$2"
|
||||||
|
|
||||||
|
echo "- ${host}:${container}"
|
||||||
|
}
|
||||||
|
function docker::compose::db_volume() {
|
||||||
|
docker::compose::render_volume \
|
||||||
|
"$(docker::path::relative "$(db::volume_host_path)")" \
|
||||||
|
"$(db::volume_container_path)"
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker::compose::volume() { docker::compose::render_volume "$(docker::path::relative "$1")" "$2"; }
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Generation
|
||||||
|
# ===========================================
|
||||||
|
function docker::image::generate() {
|
||||||
|
local tag="${APP_IMAGE}:${ENVIRONMENT}"
|
||||||
|
log::docker_build "Generating Docker Image ($tag)..."
|
||||||
|
|
||||||
|
template::render \
|
||||||
|
"$(template::path docker Dockerfile)" \
|
||||||
|
"$(docker::image::path)"
|
||||||
|
}
|
||||||
|
function docker::compose::generate() {
|
||||||
|
log::docker_build "Generating Docker Compose Stack (${ENVIRONMENT})..."
|
||||||
|
|
||||||
|
template::render \
|
||||||
|
"$(template::path docker docker-compose.yml)" \
|
||||||
|
"$(docker::compose::path)" \
|
||||||
|
ENVIRONMENT="$ENVIRONMENT" \
|
||||||
|
APP_IMAGE="$APP_IMAGE" \
|
||||||
|
CTX_BUILD="$(docker::app::path)" \
|
||||||
|
CTX_APP="$(docker::app::path)" \
|
||||||
|
CTX_APACHE_LOGS="$(docker::apache::logs_path)" \
|
||||||
|
CTX_APACHE_VHOSTS="$(docker::apache::vhosts_path)" \
|
||||||
|
CTX_APACHE_CERTS="$(docker::apache::certs_path)" \
|
||||||
|
CTX_XDEBUG="$(docker::apache::xdebug_path)" \
|
||||||
|
APP_PATH="$(ctx::container::root)" \
|
||||||
|
IMAGE_PATH="$(docker::image::path)" \
|
||||||
|
TRAEFIK_LABELS="$(docker::proxy::traefik_labels "$PROJECT_NAME" "$DOMAIN" "$BACKEND_DOMAIN" 80)" \
|
||||||
|
DB_ENVIRONMENT="$(docker::db::environment)" \
|
||||||
|
DB_VOLUMES="$(docker::compose::volume "$(docker::db::external_path)" "$(docker::db::internal_path)")" \
|
||||||
|
DB_STACK_PORT="$(docker::db::external_port):$(docker::db::internal_port)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Build
|
||||||
|
# ===========================================
|
||||||
|
function docker::image::build() {
|
||||||
|
local dockerfile
|
||||||
|
dockerfile="$(path::relative_to_root "$(docker::image::path)")"
|
||||||
|
|
||||||
|
local tag="${APP_IMAGE}:${ENVIRONMENT}"
|
||||||
|
|
||||||
|
require_file "$dockerfile" || {
|
||||||
|
log::docker_error "Dockerfile not found for environment $ENVIRONMENT"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log::docker_build "Building image ${tag}..."
|
||||||
|
|
||||||
|
local context
|
||||||
|
context="$(platform::docker_path "$(ctx::root)")"
|
||||||
|
|
||||||
|
if docker::raw build -f "$dockerfile" -t "$tag" "$context"; then
|
||||||
|
log::docker_success "Image built: ${tag}"
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
function docker::compose::build() {
|
||||||
|
docker::compose build
|
||||||
|
}
|
||||||
69
dxkit/modules/docker/db.module.sh
Normal file
69
dxkit/modules/docker/db.module.sh
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Engine config
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
MYSQL_INTERNAL_PORT=3306
|
||||||
|
MSSQL_INTERNAL_PORT=1433
|
||||||
|
|
||||||
|
function docker::db::internal_port() {
|
||||||
|
case "$DB_ENGINE" in
|
||||||
|
mysql) echo $MYSQL_INTERNAL_PORT ;;
|
||||||
|
mssql) echo $MSSQL_INTERNAL_PORT ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker::db::external_port() {
|
||||||
|
# If already resolved to a plain number, use it
|
||||||
|
if [[ -n "$DB_PORT" && "$DB_PORT" != "auto" ]]; then
|
||||||
|
echo "$DB_PORT"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Otherwise resolve from internal port as base
|
||||||
|
network::ports::resolve "auto:$(docker::db::internal_port)"
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker::db::internal_path() {
|
||||||
|
case "$DB_ENGINE" in
|
||||||
|
mysql) echo "/var/lib/mysql" ;;
|
||||||
|
mssql) echo "/var/opt/mssql/data" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker::db::external_path() { ctx::artifact::path database; }
|
||||||
|
|
||||||
|
function docker::db::_mysql_vars() {
|
||||||
|
cat << EOF
|
||||||
|
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
|
||||||
|
MYSQL_DATABASE: "${DB_NAME}"
|
||||||
|
MYSQL_USER: "${DB_USER}"
|
||||||
|
MYSQL_PASSWORD: "${DB_PASS}"
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker::db::_mssql_vars() {
|
||||||
|
cat << EOF
|
||||||
|
SA_PASSWORD: ${DB_PASS}
|
||||||
|
ACCEPT_EULA: 'Y'
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Generation
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function docker::db::environment() {
|
||||||
|
case "$DB_ENGINE" in
|
||||||
|
mysql) docker::db::_mysql_vars ;;
|
||||||
|
mssql) docker::db::_mssql_vars ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
# ============================================
|
||||||
|
# Load
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
#function docker::db::on_load() {
|
||||||
|
#
|
||||||
|
#}
|
||||||
93
dxkit/modules/docker/proxy.module.sh
Normal file
93
dxkit/modules/docker/proxy.module.sh
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# modules/docker/proxy.module.sh
|
||||||
|
#
|
||||||
|
# Proxy docker module.
|
||||||
|
# Configures proxy, generates what's needed to compose docker stack --
|
||||||
|
# and starts/stops the proxy service.
|
||||||
|
|
||||||
|
function docker::proxy::running() { docker::raw ps --format '{{.Names}}' | grep -qx "${PROXY_SERVICE}"; }
|
||||||
|
function docker::proxy::exists() { docker::raw ps -a --format '{{.Names}}' | grep -qx "${PROXY_SERVICE}"; }
|
||||||
|
|
||||||
|
function docker::proxy::traefik_labels() {
|
||||||
|
local service="$1"
|
||||||
|
local domain="$2"
|
||||||
|
local backend_domain="$3"
|
||||||
|
local port="$4"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=${PROXY_NETWORK}"
|
||||||
|
|
||||||
|
- "traefik.http.routers.${service}-frontend.rule=Host(\`${domain}\`)"
|
||||||
|
- "traefik.http.routers.${service}-frontend.entrypoints=web"
|
||||||
|
- "traefik.http.routers.${service}-frontend.service=${service}"
|
||||||
|
|
||||||
|
- "traefik.http.routers.${service}-backend.rule=Host(\`${backend_domain}\`)"
|
||||||
|
- "traefik.http.routers.${service}-backend.entrypoints=web"
|
||||||
|
- "traefik.http.routers.${service}-backend.service=${service}"
|
||||||
|
|
||||||
|
- "traefik.http.services.${service}.loadbalancer.server.port=${port}"
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker::proxy::start() {
|
||||||
|
docker::proxy::running && return 0
|
||||||
|
docker::proxy::exists && {
|
||||||
|
docker::raw start "$PROXY_SERVICE" > /dev/null 2>&1
|
||||||
|
return 0
|
||||||
|
};
|
||||||
|
|
||||||
|
docker::proxy::_create_container
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker::proxy::stop() {
|
||||||
|
if docker::proxy::running "${PROXY_SERVICE}"; then
|
||||||
|
docker::raw stop "${PROXY_SERVICE}" > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker::proxy::restart() {
|
||||||
|
if docker::proxy::running "${PROXY_SERVICE}"; then
|
||||||
|
docker::raw restart "${PROXY_SERVICE}" > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker::proxy::ensure_network() {
|
||||||
|
docker::raw network inspect "${PROXY_NETWORK}" >/dev/null 2>&1 || \
|
||||||
|
docker::raw network create "${PROXY_NETWORK}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function docker::proxy::ensure_proxy_running() {
|
||||||
|
docker::proxy::ensure_network
|
||||||
|
docker::proxy::start
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Logs
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function docker::proxy::logs() {
|
||||||
|
docker::raw logs -f "${PROXY_SERVICE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Private
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function docker::proxy::_create_container() {
|
||||||
|
docker::raw run -d \
|
||||||
|
--name "$PROXY_SERVICE" \
|
||||||
|
--restart unless-stopped \
|
||||||
|
-p 80:80 -p 443:443 -p 8080:8080 \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||||
|
--network "$PROXY_NETWORK" \
|
||||||
|
traefik:v3.0 \
|
||||||
|
--api.dashboard=true --api.insecure=true \
|
||||||
|
--providers.docker=true \
|
||||||
|
--providers.docker.exposedbydefault=false \
|
||||||
|
--providers.docker.network="$PROXY_NETWORK" \
|
||||||
|
--entrypoints.web.address=:80 \
|
||||||
|
--entrypoints.websecure.address=:443 \
|
||||||
|
--log.level=DEBUG \
|
||||||
|
> /dev/null 2>&1
|
||||||
|
}
|
||||||
92
dxkit/modules/env.module.sh
Normal file
92
dxkit/modules/env.module.sh
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function env::on_load() {
|
||||||
|
env::defaults
|
||||||
|
env::load_globals
|
||||||
|
}
|
||||||
|
|
||||||
|
function env::artifact::path() { path::relative_to_root "$(ctx::artifact::path env)"; }
|
||||||
|
function env::artifact::resolved() { path::relative_to_root "$(ctx::artifact::path env)/${ENVIRONMENT}.resolved.env"; }
|
||||||
|
function env::project::name() { echo "${PROJECT_NAME}"; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Defaults
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function env::defaults() {
|
||||||
|
: "${PROJECT_NAME:=$(basename "$(ctx::root)")}"
|
||||||
|
: "${APP_FRAMEWORK:=yii2-advanced}"
|
||||||
|
: "${APP_PORT:=auto:8080}"
|
||||||
|
: "${TLD:="lan"}"
|
||||||
|
: "${DB_DRIVER:=db}"
|
||||||
|
: "${DB_IMAGE:=mariadb:latest}"
|
||||||
|
: "${DB_ENGINE:=mysql}"
|
||||||
|
: "${DB_PORT:=auto}"
|
||||||
|
: "${PROXY_SERVICE:=traefik}"
|
||||||
|
: "${PROXY_NETWORK:=proxy}"
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
: "${ENVIRONMENT:=dev}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function env::defaults::derived() {
|
||||||
|
APP_IMAGE="${APP_IMAGE:-${PROJECT_NAME}-app}"
|
||||||
|
DB_NAME="${DB_NAME:-${PROJECT_NAME}}"
|
||||||
|
DB_HOST="${DB_HOST:-${PROJECT_NAME}-${DB_DRIVER}}"
|
||||||
|
DOMAIN="${DOMAIN:-${PROJECT_NAME}.${TLD}}"
|
||||||
|
BACKEND_DOMAIN="${BACKEND_DOMAIN:-admin.${DOMAIN}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function env::load_globals() {
|
||||||
|
load_file required "$(ctx::env)/globals.env";
|
||||||
|
env::defaults::derived
|
||||||
|
}
|
||||||
|
|
||||||
|
function env::load_environment() {
|
||||||
|
load_file optional "$(ctx::artifact::path env)/${ENVIRONMENT}.env"
|
||||||
|
env::defaults::derived
|
||||||
|
}
|
||||||
|
|
||||||
|
function env::load_environment_resolved() {
|
||||||
|
load_file optional "$(env::artifact::resolved)"
|
||||||
|
}
|
||||||
|
|
||||||
|
function env::load_env_files() {
|
||||||
|
set -a
|
||||||
|
|
||||||
|
env::load_globals || { set +a; exit 1; }
|
||||||
|
env::load_environment
|
||||||
|
env::load_environment_resolved
|
||||||
|
|
||||||
|
set +a
|
||||||
|
}
|
||||||
|
|
||||||
|
function env::set_env_var() {
|
||||||
|
local key="$1"
|
||||||
|
local value="$2"
|
||||||
|
local file="${3:-$(ctx::artifact::path env)/${ENVIRONMENT}.env}"
|
||||||
|
|
||||||
|
touch "$file"
|
||||||
|
|
||||||
|
if grep -q "^${key}=" "$file"; then
|
||||||
|
fs::sed_inplace "s/^${key}=.*/${key}=${value}/" "$file"
|
||||||
|
else
|
||||||
|
printf "\n%s=%s" "$key" "$value" >> "$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Derived
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function env::derive() {
|
||||||
|
APP_IMAGE="${PROJECT_NAME}-app"
|
||||||
|
DOMAIN="${PROJECT_NAME}.${TLD}"
|
||||||
|
BACKEND_DOMAIN="admin-${PROJECT_NAME}.${TLD}"
|
||||||
|
DB_HOST="${PROJECT_NAME}-db"
|
||||||
|
DB_NAME="${PROJECT_NAME}"
|
||||||
|
|
||||||
|
export APP_IMAGE DOMAIN BACKEND_DOMAIN DB_HOST DB_NAME
|
||||||
|
}
|
||||||
125
dxkit/modules/framework/laravel.module.sh
Normal file
125
dxkit/modules/framework/laravel.module.sh
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# framework/laravel.module.sh
|
||||||
|
#
|
||||||
|
# Laravel framework implementation.
|
||||||
|
# Implements the framework:: interface for Laravel projects.
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Skip messages
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::laravel::init_skip_message() { echo "Skipping Laravel bootstrap (--skip-framework-init)"; }
|
||||||
|
function framework::laravel::config_skip_message() { echo "Skipping Laravel config (--skip-framework-config)"; }
|
||||||
|
function framework::laravel::vendor_skip_message() { echo "Skipping Vendors (--skip-vendors)"; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Scaffold
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::laravel::scaffold() {
|
||||||
|
local target="$1"
|
||||||
|
|
||||||
|
log::run_step env "Building Laravel application (${target})..." \
|
||||||
|
composer::run_isolated create-project laravel/laravel "$target" --no-install
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Init
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::laravel::init() {
|
||||||
|
local env="$1"
|
||||||
|
local laravel_env
|
||||||
|
laravel_env="$(framework::laravel::resolve_env "$env")"
|
||||||
|
|
||||||
|
log::run_step env "Bootstrapping Laravel (${laravel_env})..." \
|
||||||
|
framework::laravel::_write_env_file "$env"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Config
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::laravel::config() {
|
||||||
|
log::run_step env "Generating Laravel config cache..." \
|
||||||
|
framework::laravel::console config:cache
|
||||||
|
|
||||||
|
log::run_step env "Generating Laravel route cache..." \
|
||||||
|
framework::laravel::console route:cache
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Console
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::laravel::console_name() { echo "artisan"; }
|
||||||
|
function framework::laravel::console() { runtime::exec php artisan "$@"; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Resolve env
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::laravel::resolve_env() {
|
||||||
|
case "$1" in
|
||||||
|
dev) echo "local" ;;
|
||||||
|
qly) echo "staging" ;;
|
||||||
|
prd) echo "production" ;;
|
||||||
|
*) echo "$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Vendors
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::laravel::install_vendors() {
|
||||||
|
log::run_step auth info "Setting Composer auth for domain ${GIT_DOMAIN}..." \
|
||||||
|
composer::config_auth nocache "$GIT_DOMAIN" "$GIT_AUTH_TOKEN_ID" "$GIT_AUTH_TOKEN_PW"
|
||||||
|
|
||||||
|
log::run_step fs "Installing vendors..." \
|
||||||
|
composer::install
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Post-init hint
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::laravel::post_init_hint() {
|
||||||
|
cat <<EOF
|
||||||
|
Next steps (run manually after):
|
||||||
|
dx artisan migrate
|
||||||
|
dx artisan db:seed
|
||||||
|
dx artisan key:generate
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Private
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::laravel::_write_env_file() {
|
||||||
|
local env="$1"
|
||||||
|
local env_file
|
||||||
|
env_file="$(ctx::root)/.env"
|
||||||
|
|
||||||
|
if [[ -f "$env_file" ]]; then
|
||||||
|
log::info ".env already exists — skipping"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$(ctx::root)/.env.example" ]]; then
|
||||||
|
log::error ".env.example not found — cannot generate .env"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "$(ctx::root)/.env.example" "$env_file"
|
||||||
|
|
||||||
|
local laravel_env
|
||||||
|
laravel_env="$(framework::laravel::resolve_env "$env")"
|
||||||
|
|
||||||
|
# Patch APP_ENV and APP_URL in the generated .env
|
||||||
|
sed -i "s/^APP_ENV=.*/APP_ENV=${laravel_env}/" "$env_file"
|
||||||
|
sed -i "s|^APP_URL=.*|APP_URL=http://${DOMAIN}|" "$env_file"
|
||||||
|
|
||||||
|
log::success ".env generated for environment: ${laravel_env}"
|
||||||
|
}
|
||||||
176
dxkit/modules/framework/yii2-advanced.module.sh
Normal file
176
dxkit/modules/framework/yii2-advanced.module.sh
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# framework/yii2-advanced.module.sh
|
||||||
|
#
|
||||||
|
# Yii2 Advanced framework implementation.
|
||||||
|
# Implements the framework:: interface for Yii2 Advanced projects.
|
||||||
|
# Owns config generation, init lifecycle, and template handling
|
||||||
|
# specific to the advanced template's directory layout.
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Load
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-advanced::on_load() {
|
||||||
|
load_module yii
|
||||||
|
load_module template
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Skip messages
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-advanced::init_skip_message() { echo "Skipping Yii init (--skip-framework-init)"; }
|
||||||
|
function framework::yii2-advanced::config_skip_message() { echo "Skipping Yii config (--skip-framework-config)"; }
|
||||||
|
function framework::yii2-advanced::vendor_skip_message() { echo "Skipping Vendors (--skip-vendors)"; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Templates
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-advanced::vhost_template_path() { echo "$(ctx::templates)/apache/yii2-advanced/vhosts.conf.template"; }
|
||||||
|
|
||||||
|
function framework::yii2-advanced::_template_root() { echo "$(ctx::templates)/yii2-advanced"; }
|
||||||
|
function framework::yii2-advanced::_components_template_root() { echo "$(framework::yii2-advanced::_template_root)/components"; }
|
||||||
|
function framework::yii2-advanced::_config_root() { echo "$(ctx::root)/common/config"; }
|
||||||
|
|
||||||
|
function framework::yii2-advanced::_template() {
|
||||||
|
local templateFile="$1"
|
||||||
|
local path="$(framework::yii2-advanced::_template_root)/${templateFile}.php.template"
|
||||||
|
|
||||||
|
require_file "$path" || {
|
||||||
|
log::fs_error "${templateFile}.php.template does not exist!"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "%s" "$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
function framework::yii2-advanced::_template_component() {
|
||||||
|
local templateFile="$1"
|
||||||
|
local path="$(framework::yii2-advanced::_components_template_root)/${templateFile}.php.template"
|
||||||
|
|
||||||
|
require_file "$path" || {
|
||||||
|
log::fs_error "${templateFile}.php.template does not exist!"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "%s" "$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
function framework::yii2-advanced::_components() {
|
||||||
|
printf '%s\n' \
|
||||||
|
"db.${DB_ENGINE}" \
|
||||||
|
"log"
|
||||||
|
}
|
||||||
|
|
||||||
|
function framework::yii2-advanced::_build_components() {
|
||||||
|
local components=()
|
||||||
|
|
||||||
|
for component in $(framework::yii2-advanced::_components); do
|
||||||
|
local comp
|
||||||
|
comp=$(envsubst < "$(framework::yii2-advanced::_template_component "$component")")
|
||||||
|
|
||||||
|
# remove blank lines
|
||||||
|
comp=$(printf "%s" "$comp" | sed '/^[[:space:]]*$/d')
|
||||||
|
|
||||||
|
components+=("$comp")
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "%s\n" "${components[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Scaffold
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-advanced::scaffold() {
|
||||||
|
local target="$1"
|
||||||
|
|
||||||
|
log::run_step env "Building Yii2 Advanced application ($target)..." \
|
||||||
|
composer::run_isolated create-project yiisoft/yii2-app-advanced "$target" --no-install
|
||||||
|
}
|
||||||
|
|
||||||
|
function framework::yii2-advanced::scaffold() {
|
||||||
|
local target="$1"
|
||||||
|
local tmp="_scaffold_tmp"
|
||||||
|
|
||||||
|
log::run_step env "Scaffolding Yii2 Advanced (${target})..." \
|
||||||
|
composer::run_isolated sh -c \
|
||||||
|
'composer create-project yiisoft/yii2-app-advanced "$1" --no-install && cp -rn "$1/." . && rm -rf "$1"' \
|
||||||
|
-- "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Init
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-advanced::init() {
|
||||||
|
local env="$1"
|
||||||
|
local yii_env
|
||||||
|
yii_env="$(yii::resolve_env "$env")"
|
||||||
|
|
||||||
|
log::run_step env "Initializing Yii2 Advanced (${yii_env})..." \
|
||||||
|
runtime::exec php init \
|
||||||
|
--env="$yii_env" \
|
||||||
|
--overwrite=All \
|
||||||
|
--interactive=0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Config
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-advanced::config() {
|
||||||
|
log::run_step env "Generating Yii config..." \
|
||||||
|
framework::yii2-advanced::_generate_config main-local.php
|
||||||
|
}
|
||||||
|
|
||||||
|
function framework::yii2-advanced::_generate_config() {
|
||||||
|
local output="$(framework::yii2-advanced::_config_root)/$1"
|
||||||
|
local components="$(framework::yii2-advanced::_build_components)"
|
||||||
|
|
||||||
|
# indent components for placement inside the components: array
|
||||||
|
local indented_components
|
||||||
|
indented_components=$(printf "%s\n" "$components" | sed 's/^/ /')
|
||||||
|
|
||||||
|
template::render \
|
||||||
|
"$(framework::yii2-advanced::_template main-local)" \
|
||||||
|
"$output" \
|
||||||
|
YII_MAIN_LOCAL_COMPONENTS="$indented_components"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Console / Env
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-advanced::console_name() { echo "yii"; }
|
||||||
|
function framework::yii2-advanced::console() { yii::exec "$@"; }
|
||||||
|
function framework::yii2-advanced::resolve_env() { yii::resolve_env "$@"; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Vendors
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-advanced::install_vendors() {
|
||||||
|
log::run_step auth info "Setting Composer auth for domain ${GIT_DOMAIN}..." \
|
||||||
|
composer::config_auth nocache "$GIT_DOMAIN" "$GIT_AUTH_TOKEN_ID" "$GIT_AUTH_TOKEN_PW"
|
||||||
|
|
||||||
|
log::run_step fs "Installing vendors..." \
|
||||||
|
composer::install
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Post-init hint
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-advanced::post_init_hint() {
|
||||||
|
cat <<EOF
|
||||||
|
Next steps (run manually after):
|
||||||
|
dx yii migrate
|
||||||
|
dx yii gatekeeper/sync-permissions
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Private
|
||||||
|
# ============================================
|
||||||
131
dxkit/modules/framework/yii2-basic.module.sh
Normal file
131
dxkit/modules/framework/yii2-basic.module.sh
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# modules/framework/yii2-basic.module.sh
|
||||||
|
#
|
||||||
|
# Yii2 Basic framework implementation.
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Skip messages
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-basic::init_skip_message() { echo "Skipping Yii init (--skip-framework-init)"; }
|
||||||
|
function framework::yii2-basic::config_skip_message() { echo "Skipping Yii config (--skip-framework-config)"; }
|
||||||
|
function framework::yii2-basic::vendor_skip_message() { echo "Skipping Vendors (--skip-vendors)"; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Identity
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-basic::console_name() { echo "yii"; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Templates
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-basic::vhost_template_path() {
|
||||||
|
echo "$(ctx::templates)/apache/yii2-basic/vhosts.conf.template"
|
||||||
|
}
|
||||||
|
|
||||||
|
function framework::yii2-basic::_template_root() { echo "$(ctx::templates)/yii2-basic"; }
|
||||||
|
function framework::yii2-basic::_config_root() { echo "$(ctx::root)/config"; }
|
||||||
|
|
||||||
|
function framework::yii2-basic::_template() {
|
||||||
|
local templateFile="$1"
|
||||||
|
local path="$(framework::yii2-basic::_template_root)/${templateFile}.php.template"
|
||||||
|
|
||||||
|
require_file "$path" || {
|
||||||
|
log::fs_error "${templateFile}.php.template does not exist!"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "%s" "$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Scaffold
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-basic::scaffold() {
|
||||||
|
local target="$1"
|
||||||
|
|
||||||
|
log::run_step env "Building Yii2 Basic application (${target})..." \
|
||||||
|
composer::run_isolated create-project yiisoft/yii2-app-basic "$target" --no-install
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Init
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-basic::init() {
|
||||||
|
local env="$1"
|
||||||
|
local yii_env
|
||||||
|
yii_env="$(framework::yii2-basic::resolve_env "$env")"
|
||||||
|
|
||||||
|
log::run_step env "Initializing Yii2 Basic (${yii_env})..." \
|
||||||
|
framework::yii2-basic::_ensure_runtime_dirs
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Config
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-basic::config() {
|
||||||
|
log::run_step env "Generating Yii config..." \
|
||||||
|
framework::yii2-basic::_generate_config db.php
|
||||||
|
}
|
||||||
|
|
||||||
|
function framework::yii2-basic::_generate_config() {
|
||||||
|
local output="$(framework::yii2-basic::_config_root)/$1"
|
||||||
|
|
||||||
|
template::render \
|
||||||
|
"$(framework::yii2-basic::_template "db.${DB_ENGINE}")" \
|
||||||
|
"$output" \
|
||||||
|
DB_NAME="$DB_NAME" \
|
||||||
|
DB_USER="$DB_USER" \
|
||||||
|
DB_PASS="$DB_PASS"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Console + env resolution
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-basic::console() { yii::exec "$@"; }
|
||||||
|
function framework::yii2-basic::resolve_env() { yii::resolve_env "$@"; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Vendors
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-basic::install_vendors() {
|
||||||
|
log::run_step auth info "Setting Composer auth for domain ${GIT_DOMAIN}..." \
|
||||||
|
composer::config_auth nocache "$GIT_DOMAIN" "$GIT_AUTH_TOKEN_ID" "$GIT_AUTH_TOKEN_PW"
|
||||||
|
|
||||||
|
log::run_step fs "Installing vendors..." \
|
||||||
|
composer::install
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Post-init hint
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-basic::post_init_hint() {
|
||||||
|
cat <<EOF
|
||||||
|
Next steps (run manually after):
|
||||||
|
dx yii migrate
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Private
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function framework::yii2-basic::_ensure_runtime_dirs() {
|
||||||
|
local dirs=(
|
||||||
|
"$(ctx::root)/runtime"
|
||||||
|
"$(ctx::root)/web/assets"
|
||||||
|
)
|
||||||
|
|
||||||
|
local dir
|
||||||
|
for dir in "${dirs[@]}"; do
|
||||||
|
[[ -d "$dir" ]] || mkdir -p "$dir"
|
||||||
|
done
|
||||||
|
}
|
||||||
83
dxkit/modules/fs.module.sh
Normal file
83
dxkit/modules/fs.module.sh
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function fs::write_file() {
|
||||||
|
local src="$1"
|
||||||
|
local dest="$2"
|
||||||
|
|
||||||
|
if platform::is_windows; then
|
||||||
|
# Windows: no sudo, just overwrite (assumes elevated shell if needed)
|
||||||
|
mv "$src" "$dest"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# macOS / Linux
|
||||||
|
sudo tee "$dest" >/dev/null < "$src"
|
||||||
|
}
|
||||||
|
function fs::sed_inplace() {
|
||||||
|
local use_sudo=false
|
||||||
|
local suffix=""
|
||||||
|
local expr
|
||||||
|
local file
|
||||||
|
|
||||||
|
# Detect sudo flag
|
||||||
|
if [[ "$1" == "--sudo" ]]; then
|
||||||
|
use_sudo=true
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect suffix
|
||||||
|
if [[ "$1" == .* ]]; then
|
||||||
|
suffix="$1"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
expr="$1"
|
||||||
|
file="$2"
|
||||||
|
|
||||||
|
local SUDO=""
|
||||||
|
if $use_sudo && ! platform::is_windows; then
|
||||||
|
SUDO="sudo"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if platform::is_macos; then
|
||||||
|
if [[ -z "$suffix" ]]; then
|
||||||
|
$SUDO sed -i '' "$expr" "$file"
|
||||||
|
else
|
||||||
|
$SUDO sed -i "$suffix" "$expr" "$file"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ -z "$suffix" ]]; then
|
||||||
|
$SUDO sed -i "$expr" "$file"
|
||||||
|
else
|
||||||
|
$SUDO sed -i"$suffix" "$expr" "$file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
function fs::replace_block() {
|
||||||
|
local file="$1"
|
||||||
|
local start_marker="$2"
|
||||||
|
local end_marker="$3"
|
||||||
|
local content="$4"
|
||||||
|
|
||||||
|
local tmp
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
|
||||||
|
# Remove existing block safely (no regex, no sed)
|
||||||
|
awk -v start="$start_marker" -v end="$end_marker" '
|
||||||
|
$0 == start {skip=1; next}
|
||||||
|
$0 == end {skip=0; next}
|
||||||
|
!skip
|
||||||
|
' "$file" > "$tmp"
|
||||||
|
|
||||||
|
# Only add leading newline if file has content and doesn't already end with one
|
||||||
|
if [[ -s "$tmp" ]]; then
|
||||||
|
local last_char
|
||||||
|
last_char="$(tail -c1 "$tmp")"
|
||||||
|
[[ "$last_char" != "" ]] && printf "\n" >> "$tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s\n" "$content" >> "$tmp"
|
||||||
|
|
||||||
|
echo "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
282
dxkit/modules/log.module.sh
Normal file
282
dxkit/modules/log.module.sh
Normal file
|
|
@ -0,0 +1,282 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Core
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Internal
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function internal::get_log_priority() {
|
||||||
|
case "$1" in
|
||||||
|
DEBUG) echo 0 ;;
|
||||||
|
INFO) echo 1 ;;
|
||||||
|
WARN) echo 2 ;;
|
||||||
|
ERROR) echo 3 ;;
|
||||||
|
SUCCESS) echo 4 ;;
|
||||||
|
*) echo 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
function internal::log() {
|
||||||
|
local level="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
local current_priority
|
||||||
|
local message_priority
|
||||||
|
|
||||||
|
current_priority=$(internal::get_log_priority "$LOG_LEVEL")
|
||||||
|
message_priority=$(internal::get_log_priority "$level")
|
||||||
|
|
||||||
|
if (( message_priority < current_priority )); then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$level" in
|
||||||
|
DEBUG) color="\033[0;37m" ;; # cyan
|
||||||
|
INFO) color="\033[1;34m" ;; # blue
|
||||||
|
WARN) color="\033[1;33m" ;; # yellow
|
||||||
|
ERROR) color="\033[1;31m" ;; # red
|
||||||
|
SUCCESS)color="\033[1;32m" ;; # red
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo -e "${color}=> ${level}:\033[0m $*"
|
||||||
|
}
|
||||||
|
function internal::icon() {
|
||||||
|
local context="$1"
|
||||||
|
local action="$2"
|
||||||
|
|
||||||
|
case "$context:$action" in
|
||||||
|
docker:log) echo "🐳 " ;;
|
||||||
|
docker:start) echo "🟢 🐳 " ;;
|
||||||
|
docker:stop) echo "🔴 🐳 " ;;
|
||||||
|
docker:success) echo "✅ 🐳 " ;;
|
||||||
|
docker:warning) echo "⚠️ 🐳 " ;;
|
||||||
|
docker:error) echo "❌ 🐳 " ;;
|
||||||
|
docker:logs) echo "📜 🐳 " ;;
|
||||||
|
docker:list) echo "🔍 🐳 " ;;
|
||||||
|
docker:build) echo "📦 🐳 " ;;
|
||||||
|
|
||||||
|
build:log) echo "🏗️ " ;;
|
||||||
|
build:start) echo "🟢 🏗️ " ;;
|
||||||
|
build:stop) echo "🔴 🏗️ " ;;
|
||||||
|
build:success) echo "✅ 🏗️ " ;;
|
||||||
|
build:warning) echo "⚠️ 🏗️ " ;;
|
||||||
|
build:error) echo "❌ 🏗️ " ;;
|
||||||
|
|
||||||
|
network:log) echo "🌐 " ;;
|
||||||
|
network:setup) echo "⚙️ 🌐 " ;;
|
||||||
|
network:stop) echo "🔴 🌐 " ;;
|
||||||
|
network:success) echo "✅ 🌐 " ;;
|
||||||
|
network:warning) echo "⚠️ 🌐 " ;;
|
||||||
|
network:error) echo "❌ 🌐 " ;;
|
||||||
|
|
||||||
|
auth:log) echo "🔑 " ;;
|
||||||
|
auth:setup) echo "⚙️ 🔑 " ;;
|
||||||
|
auth:login) echo "🔐 🔑 " ;;
|
||||||
|
auth:success) echo "✅ 🔑 " ;;
|
||||||
|
auth:warning) echo "⚠️ 🔑 " ;;
|
||||||
|
auth:error) echo "❌ 🔑 " ;;
|
||||||
|
|
||||||
|
env:log) echo "⚙️ " ;;
|
||||||
|
env:load) echo "📥 ⚙️ " ;;
|
||||||
|
env:success) echo "✅ ⚙️ " ;;
|
||||||
|
env:warning) echo "⚠️ ⚙️ " ;;
|
||||||
|
env:error) echo "❌ ⚙️ " ;;
|
||||||
|
|
||||||
|
fs:log) echo "📁 " ;;
|
||||||
|
fs:read) echo "📥 📁 " ;;
|
||||||
|
fs:write) echo "📤 📁 " ;;
|
||||||
|
fs:success) echo "✅ 📁 " ;;
|
||||||
|
fs:warning) echo "⚠️ 📁 " ;;
|
||||||
|
fs:error) echo "❌ 📁 " ;;
|
||||||
|
|
||||||
|
db:log) echo "🗄️ " ;;
|
||||||
|
db:start) echo "🟢 🗄️ " ;;
|
||||||
|
db:migrate) echo "📜 🗄️ " ;;
|
||||||
|
db:success) echo "✅ 🗄️ " ;;
|
||||||
|
db:warning) echo "⚠️ 🗄️ " ;;
|
||||||
|
db:error) echo "❌ 🗄️ " ;;
|
||||||
|
|
||||||
|
log:info) echo "🔹 " ;;
|
||||||
|
log:warn) echo "⚠️ " ;;
|
||||||
|
log:error) echo "❌ " ;;
|
||||||
|
log:success) echo "✅ " ;;
|
||||||
|
log:debug) echo "🔍 " ;;
|
||||||
|
|
||||||
|
|
||||||
|
*) echo "🔹" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
function internal::get_context_icon() {
|
||||||
|
case "$1" in
|
||||||
|
docker) echo "🐳" ;;
|
||||||
|
build) echo "🏗️" ;;
|
||||||
|
network) echo "🌐" ;;
|
||||||
|
auth) echo "🔑" ;;
|
||||||
|
env) echo "⚙️" ;;
|
||||||
|
fs) echo "📁" ;;
|
||||||
|
db) echo "🗄️" ;;
|
||||||
|
*) echo "🔹" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Loggers
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function internal::log::info() { internal::log INFO "$*"; }
|
||||||
|
function internal::log::warn() { internal::log WARN "$*"; }
|
||||||
|
function internal::log::error() { internal::log ERROR "$*"; }
|
||||||
|
function internal::log::success() { internal::log SUCCESS "$*"; }
|
||||||
|
function internal::log::debug() { internal::log DEBUG "$*"; }
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Context Loggers
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function log::context() {
|
||||||
|
local context="$1"
|
||||||
|
local action="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
internal::log::info "$(internal::icon "$context" "$action") $*"
|
||||||
|
}
|
||||||
|
function log::warn_context() {
|
||||||
|
local context="$1"
|
||||||
|
local action="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
internal::log::warn "$(internal::icon "$context" "$action") $*"
|
||||||
|
}
|
||||||
|
function log::error_context() {
|
||||||
|
local context="$1"
|
||||||
|
local action="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
internal::log::error "$(internal::icon "$context" "$action") $*"
|
||||||
|
}
|
||||||
|
function log::success_context() {
|
||||||
|
local context="$1"
|
||||||
|
local action="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
internal::log::success "$(internal::icon "$context" "$action") $*"
|
||||||
|
}
|
||||||
|
function log::debug_context() {
|
||||||
|
local context="$1"
|
||||||
|
local action="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
internal::log::debug "$(internal::icon "$context" "$action") $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Logger Helpers
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function log::info() { log::context log info "$@"; }
|
||||||
|
function log::warn() { log::warn_context log warn "$@"; }
|
||||||
|
function log::error() { log::error_context log error "$@"; }
|
||||||
|
function log::success() { log::context log success "$@"; }
|
||||||
|
function log::debug() { log::debug_context log debug "$@"; }
|
||||||
|
|
||||||
|
function log::docker() { log::context docker log "$@"; }
|
||||||
|
function log::docker_start() { log::context docker start "$@"; }
|
||||||
|
function log::docker_stop() { log::context docker stop "$@"; }
|
||||||
|
function log::docker_success() { log::context docker success "$@"; }
|
||||||
|
function log::docker_logs() { log::context docker logs "$@"; }
|
||||||
|
function log::docker_list() { log::context docker list "$@"; }
|
||||||
|
function log::docker_build() { log::context docker build "$@"; }
|
||||||
|
function log::docker_warning() { log::warn_context docker warning "$@"; }
|
||||||
|
function log::docker_error() { log::error_context docker error "$@"; }
|
||||||
|
|
||||||
|
function log::build() { log::context build log "$@"; }
|
||||||
|
function log::build_start() { log::context build start "$@"; }
|
||||||
|
function log::build_stop() { log::context build stop "$@"; }
|
||||||
|
function log::build_success() { log::context build success "$@"; }
|
||||||
|
function log::build_warning() { log::warn_context build warning "$@"; }
|
||||||
|
function log::build_error() { log::error_context build error "$@"; }
|
||||||
|
|
||||||
|
function log::network() { log::context network log "$@"; }
|
||||||
|
function log::network_setup() { log::context network setup "$@"; }
|
||||||
|
function log::network_stop() { log::context network stop "$@"; }
|
||||||
|
function log::network_success() { log::context network success "$@"; }
|
||||||
|
function log::network_warning() { log::warn_context network warning "$@"; }
|
||||||
|
function log::network_error() { log::error_context network error "$@"; }
|
||||||
|
|
||||||
|
function log::auth() { log::context auth log "$@"; }
|
||||||
|
function log::auth_setup() { log::context auth setup "$@"; }
|
||||||
|
function log::auth_login() { log::context auth login "$@"; }
|
||||||
|
function log::auth_success() { log::context auth success "$@"; }
|
||||||
|
function log::auth_warning() { log::warn_context auth warning "$@"; }
|
||||||
|
function log::auth_error() { log::error_context auth error "$@"; }
|
||||||
|
|
||||||
|
function log::env() { log::context env log "$@"; }
|
||||||
|
function log::env_load() { log::context env load "$@"; }
|
||||||
|
function log::env_success() { log::context env success "$@"; }
|
||||||
|
function log::env_warning() { log::warn_context env warning "$@"; }
|
||||||
|
function log::env_error() { log::error_context env error "$@"; }
|
||||||
|
|
||||||
|
function log::fs() { log::context fs log "$@"; }
|
||||||
|
function log::fs_read() { log::context fs read "$@"; }
|
||||||
|
function log::fs_write() { log::context fs write "$@"; }
|
||||||
|
function log::fs_success() { log::context fs success "$@"; }
|
||||||
|
function log::fs_warning() { log::warn_context fs warning "$@"; }
|
||||||
|
function log::fs_error() { log::error_context fs error "$@"; }
|
||||||
|
|
||||||
|
function log::db() { log::context database log "$@"; }
|
||||||
|
function log::db_start() { log::context database start "$@"; }
|
||||||
|
function log::db_migrate() { log::context database migrate "$@"; }
|
||||||
|
function log::db_success() { log::context database success "$@"; }
|
||||||
|
function log::db_warning() { log::warn_context database warning "$@"; }
|
||||||
|
function log::db_error() { log::error_context database error "$@"; }
|
||||||
|
|
||||||
|
function log::run_step() {
|
||||||
|
local context="$1"
|
||||||
|
local mode="strict"
|
||||||
|
local description
|
||||||
|
|
||||||
|
shift
|
||||||
|
|
||||||
|
if [[ "$1" == "soft" || "$1" == "strict" || "$1" == "info" ]]; then
|
||||||
|
mode="$1"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
description="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
local icon=$(internal::get_context_icon "$context")
|
||||||
|
|
||||||
|
if [[ "$mode" == "info" ]]; then
|
||||||
|
internal::log::info "$icon $description"
|
||||||
|
else
|
||||||
|
internal::log::info "🔄 $icon $description"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$@"
|
||||||
|
local status=$?
|
||||||
|
|
||||||
|
# SUCCESS
|
||||||
|
if [[ $status -eq 0 ]]; then
|
||||||
|
if [[ "$mode" == "info" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
internal::log::info "✅ $icon $description"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# FAILURE
|
||||||
|
if [[ "$mode" == "soft" || "$mode" == "info" ]]; then
|
||||||
|
internal::log::info "⚠️ $icon $description → skipped"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
internal::log::info "❌ $icon $description → failed"
|
||||||
|
return $status
|
||||||
|
}
|
||||||
|
|
||||||
97
dxkit/modules/network.module.sh
Normal file
97
dxkit/modules/network.module.sh
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Ports
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
#function network::ports::in_use() {
|
||||||
|
# local port="$1"
|
||||||
|
#
|
||||||
|
# if command -v lsof >/dev/null 2>&1; then
|
||||||
|
# lsof -iTCP:"$port" -sTCP:LISTEN -t >/dev/null 2>&1
|
||||||
|
# else
|
||||||
|
# netstat -an | grep -q ":$port "
|
||||||
|
# fi
|
||||||
|
#}
|
||||||
|
|
||||||
|
function network::ports::in_use() {
|
||||||
|
local port="$1"
|
||||||
|
|
||||||
|
# Check Docker port bindings — reliable across platforms
|
||||||
|
if command -v docker >/dev/null 2>&1; then
|
||||||
|
docker ps --format '{{.Ports}}' | grep -q "0.0.0.0:${port}->" && return 0
|
||||||
|
docker ps --format '{{.Ports}}' | grep -q "\[::\]:${port}->" && return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback to netstat — handles both Unix and Windows output formats
|
||||||
|
netstat -an 2>/dev/null | grep -qE ":${port}[^0-9].*LISTEN" && return 0
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function network::ports::resolve() {
|
||||||
|
local value="$1"
|
||||||
|
|
||||||
|
# auto:X → dynamic allocation
|
||||||
|
if [[ "$value" == auto:* ]]; then
|
||||||
|
local base="${value#auto:}"
|
||||||
|
|
||||||
|
if ! [[ "$base" =~ ^[0-9]+$ ]]; then
|
||||||
|
log::network_error "Invalid auto port syntax: $value"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# fallback if malformed (optional safety)
|
||||||
|
[[ -z "$base" ]] && base=8000
|
||||||
|
|
||||||
|
local p="$base"
|
||||||
|
|
||||||
|
while network::ports::in_use "$p"; do
|
||||||
|
((p++))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$p"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# static port
|
||||||
|
echo "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
function network::ports::resolve_many() {
|
||||||
|
local var
|
||||||
|
|
||||||
|
for var in "$@"; do
|
||||||
|
local value="${!var}"
|
||||||
|
local resolved
|
||||||
|
|
||||||
|
resolved="$(network::ports::resolve "$value")" || return 1
|
||||||
|
|
||||||
|
printf -v "$var" "%s" "$resolved"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function network::ports::persist() {
|
||||||
|
local ports_file
|
||||||
|
ports_file="$(ctx::artifact)/ports"
|
||||||
|
|
||||||
|
cat > "$ports_file" <<EOF
|
||||||
|
APP_PORT=${APP_PORT}
|
||||||
|
DB_PORT=${DB_PORT}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
function network::ports::load() {
|
||||||
|
local ports_file
|
||||||
|
ports_file="$(ctx::artifact)/ports"
|
||||||
|
[[ -f "$ports_file" ]] || return 0
|
||||||
|
source "$ports_file"
|
||||||
|
export APP_PORT DB_PORT
|
||||||
|
}
|
||||||
|
|
||||||
|
function network::ports::prepare() {
|
||||||
|
[[ "$DB_PORT" == "auto" ]] && export DB_PORT="auto:$(docker::db::internal_port)"
|
||||||
|
|
||||||
|
network::ports::resolve_many APP_PORT DB_PORT
|
||||||
|
network::ports::persist
|
||||||
|
}
|
||||||
27
dxkit/modules/phpstorm.module.sh
Normal file
27
dxkit/modules/phpstorm.module.sh
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
phpstorm::config_path() { echo "$(ctx::root)/.idea"; }
|
||||||
|
|
||||||
|
function phpstorm::db_config() {
|
||||||
|
cat << EOF
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="cimlt_elevadores2026" uuid="4835f100-344a-4aa8-b98c-7c956c74c3e3">
|
||||||
|
<driver-ref>mariadb</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:mariadb://localhost:3309</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="database.introspection.mysql.dbe5060" value="true" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
<project>
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
function phpstorm::generate_db_config() {
|
||||||
|
phpstorm::db_config > "$(phpstorm::config_path)/dataSources.xml"
|
||||||
|
}
|
||||||
10
dxkit/modules/proxy.module.sh
Normal file
10
dxkit/modules/proxy.module.sh
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# modules/proxy.module.sh
|
||||||
|
|
||||||
|
function proxy::driver() { echo "${PROXY_DRIVER:-docker}"; }
|
||||||
|
|
||||||
|
function proxy::start() { "$(proxy::driver)::proxy::start"; }
|
||||||
|
function proxy::stop() { "$(proxy::driver)::proxy::stop"; }
|
||||||
|
function proxy::restart() { "$(proxy::driver)::proxy::restart"; }
|
||||||
|
function proxy::running() { "$(proxy::driver)::proxy::running"; }
|
||||||
|
function proxy::ensure() { proxy::running || proxy::start; }
|
||||||
|
function proxy::logs() { "$(proxy::driver)::proxy::logs"; }
|
||||||
65
dxkit/modules/template.module.sh
Normal file
65
dxkit/modules/template.module.sh
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function template::path() {
|
||||||
|
local dir=""
|
||||||
|
local file=""
|
||||||
|
|
||||||
|
if [[ $# -eq 1 ]]; then
|
||||||
|
file="$1"
|
||||||
|
else
|
||||||
|
dir="$1"
|
||||||
|
file="$2"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$dir" ]]; then
|
||||||
|
echo "$(ctx::templates)/$dir/$file.template"
|
||||||
|
else
|
||||||
|
echo "$(ctx::templates)/$file.template"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function template::render() {
|
||||||
|
local template="$1"
|
||||||
|
local target="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
require_file "$template" || {
|
||||||
|
log::fs_error "Template not found: $template"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$target")"
|
||||||
|
|
||||||
|
# Build env assignment list
|
||||||
|
local env_vars=()
|
||||||
|
for var in "$@"; do
|
||||||
|
env_vars+=("$var")
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! env "${env_vars[@]}" envsubst < "$template" > "$target"; then
|
||||||
|
log::fs_error "Failed rendering template: $template"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#function template::render() {
|
||||||
|
# local template="$1"
|
||||||
|
# local target="$2"
|
||||||
|
# shift 2
|
||||||
|
#
|
||||||
|
# require_file "$template" || {
|
||||||
|
# log::fs_error "Template not found: $template"
|
||||||
|
# return 1
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# mkdir -p "$(dirname "$target")"
|
||||||
|
#
|
||||||
|
# for var in "$@"; do
|
||||||
|
# export "$var"
|
||||||
|
# done
|
||||||
|
#
|
||||||
|
# if ! envsubst < "$template" > "$target"; then
|
||||||
|
# log::fs_error "Failed rendering template: $template"
|
||||||
|
# return 1
|
||||||
|
# fi
|
||||||
|
#}
|
||||||
31
dxkit/modules/yii.module.sh
Normal file
31
dxkit/modules/yii.module.sh
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# modules/yii.module.sh
|
||||||
|
#
|
||||||
|
# Shared Yii utilities for both yii2-basic and yii2-advanced.
|
||||||
|
# Anything specific to one variant lives in its respective framework module.
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Console runner
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function yii::exec() {
|
||||||
|
runtime::exec php yii "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Environment resolution
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# Maps shorthand env names (dev/qly/prd) to Yii's expected names.
|
||||||
|
# Used by both basic and advanced framework modules.
|
||||||
|
function yii::resolve_env() {
|
||||||
|
case "$(string::lowercase "$1")" in
|
||||||
|
dev|development) echo "Development" ;;
|
||||||
|
qly|qa|quality) echo "Quality" ;;
|
||||||
|
prd|prod|production) echo "Production" ;;
|
||||||
|
*)
|
||||||
|
log::error "Unknown Yii environment: '$1'"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
58
dxkit/runtime/docker.runtime.sh
Normal file
58
dxkit/runtime/docker.runtime.sh
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# dxkit/runtime/docker.runtime.sh
|
||||||
|
#
|
||||||
|
# Docker Compose runtime driver.
|
||||||
|
# Implements the runtime:: interface using docker compose.
|
||||||
|
|
||||||
|
function runtime::docker::up() {
|
||||||
|
local args=()
|
||||||
|
flag::enabled --recreate && args+=(--force-recreate)
|
||||||
|
flag::enabled --build && args+=(--build)
|
||||||
|
flag::enabled --foreground || args+=(-d)
|
||||||
|
|
||||||
|
docker::compose::up "${args[@]+"${args[@]}"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::docker::down() {
|
||||||
|
local args=()
|
||||||
|
flag::enabled --volumes && args+=(--volumes)
|
||||||
|
flag::enabled --orphans && args+=(--remove-orphans)
|
||||||
|
|
||||||
|
docker::compose::down "${args[@]+"${args[@]}"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::docker::logs() {
|
||||||
|
local args=()
|
||||||
|
flag::enabled --follow && args+=(-f)
|
||||||
|
|
||||||
|
docker::compose::logs "${args[@]+"${args[@]}"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::docker::list() {
|
||||||
|
docker::list_containers
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::docker::status() {
|
||||||
|
docker::compose::ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::docker::build() {
|
||||||
|
docker::compose::build "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::docker::ensure() {
|
||||||
|
docker::compose::ensure_running
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::docker::is_inside() {
|
||||||
|
docker::inside_container
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::docker::exec() {
|
||||||
|
docker::compose::exec "$(runtime::service)" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::docker::shell() {
|
||||||
|
docker::compose::exec -it "$(runtime::service)" bash 2>/dev/null \
|
||||||
|
|| docker::compose::exec -it "$(runtime::service)" sh
|
||||||
|
}
|
||||||
222
dxkit/runtime/native.runtime.sh
Normal file
222
dxkit/runtime/native.runtime.sh
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# dxkit/runtime/native.runtime.sh
|
||||||
|
#
|
||||||
|
# Native runtime driver — runs services directly on the host
|
||||||
|
# without containerization. Supports macOS (Homebrew) and Linux (systemd).
|
||||||
|
#
|
||||||
|
# To activate:
|
||||||
|
# export RUNTIME_DRIVER=native # in globals.env or local.env
|
||||||
|
# export NATIVE_WEB_SERVER=apache # apache | nginx (auto-detected if unset)
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# macOS — Homebrew, httpd or nginx, php
|
||||||
|
# Linux — systemd, apache2 or nginx, php-fpm
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Platform + web server detection
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function runtime::native::_platform() {
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Darwin) echo "macos" ;;
|
||||||
|
Linux) echo "linux" ;;
|
||||||
|
*)
|
||||||
|
log::error "Unsupported platform: $(uname -s)"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::_webserver() {
|
||||||
|
if [[ -n "${NATIVE_WEB_SERVER:-}" ]]; then
|
||||||
|
echo "$NATIVE_WEB_SERVER"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local platform
|
||||||
|
platform="$(runtime::native::_platform)"
|
||||||
|
|
||||||
|
if [[ "$platform" == "macos" ]]; then
|
||||||
|
brew list httpd &>/dev/null && echo "apache" && return
|
||||||
|
brew list nginx &>/dev/null && echo "nginx" && return
|
||||||
|
else
|
||||||
|
systemctl list-unit-files apache2.service &>/dev/null && echo "apache" && return
|
||||||
|
systemctl list-unit-files nginx.service &>/dev/null && echo "nginx" && return
|
||||||
|
fi
|
||||||
|
|
||||||
|
log::error "No supported web server found (apache2 / nginx)"
|
||||||
|
log::error "Install one or set NATIVE_WEB_SERVER=apache|nginx"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Service management — platform dispatch
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function runtime::native::_service_start() {
|
||||||
|
local service="$1"
|
||||||
|
case "$(runtime::native::_platform)" in
|
||||||
|
macos) brew services start "$service" ;;
|
||||||
|
linux) sudo systemctl start "$service" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::_service_stop() {
|
||||||
|
local service="$1"
|
||||||
|
case "$(runtime::native::_platform)" in
|
||||||
|
macos) brew services stop "$service" ;;
|
||||||
|
linux) sudo systemctl stop "$service" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::_service_running() {
|
||||||
|
local service="$1"
|
||||||
|
case "$(runtime::native::_platform)" in
|
||||||
|
macos) brew services list | awk -v s="$service" '$1==s{print $2}' | grep -q "started" ;;
|
||||||
|
linux) systemctl is-active --quiet "$service" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::_service_logs() {
|
||||||
|
local service="$1"
|
||||||
|
case "$(runtime::native::_platform)" in
|
||||||
|
macos)
|
||||||
|
local log_dir
|
||||||
|
log_dir="$(brew --prefix)/var/log"
|
||||||
|
tail -f "${log_dir}/${service}/error.log" "${log_dir}/${service}/access.log" 2>/dev/null \
|
||||||
|
|| tail -f "${log_dir}/${service}.log" 2>/dev/null
|
||||||
|
;;
|
||||||
|
linux)
|
||||||
|
sudo journalctl -u "$service" -f --no-pager
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Service name resolution
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function runtime::native::_apache_service() {
|
||||||
|
case "$(runtime::native::_platform)" in
|
||||||
|
macos) echo "httpd" ;;
|
||||||
|
linux) echo "apache2" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::_nginx_service() { echo "nginx"; }
|
||||||
|
|
||||||
|
function runtime::native::_fpm_service() {
|
||||||
|
local php_ver
|
||||||
|
php_ver="$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' 2>/dev/null || echo "8.3")"
|
||||||
|
case "$(runtime::native::_platform)" in
|
||||||
|
macos) echo "php@${php_ver}" ;;
|
||||||
|
linux) echo "php${php_ver}-fpm" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::_active_services() {
|
||||||
|
local ws
|
||||||
|
ws="$(runtime::native::_webserver)"
|
||||||
|
|
||||||
|
case "$ws" in
|
||||||
|
apache) echo "$(runtime::native::_apache_service)" ;;
|
||||||
|
nginx) echo "$(runtime::native::_fpm_service)"
|
||||||
|
echo "$(runtime::native::_nginx_service)" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Interface implementation
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function runtime::native::up() {
|
||||||
|
log::info "Starting native runtime ($(runtime::native::_webserver))..."
|
||||||
|
|
||||||
|
local service
|
||||||
|
while IFS= read -r service; do
|
||||||
|
runtime::native::_service_start "$service"
|
||||||
|
done < <(runtime::native::_active_services)
|
||||||
|
|
||||||
|
log::success "Stack up"
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::down() {
|
||||||
|
log::info "Stopping native runtime..."
|
||||||
|
|
||||||
|
# Stop in reverse order
|
||||||
|
local services=()
|
||||||
|
while IFS= read -r service; do
|
||||||
|
services+=("$service")
|
||||||
|
done < <(runtime::native::_active_services)
|
||||||
|
|
||||||
|
for ((i=${#services[@]}-1; i>=0; i--)); do
|
||||||
|
runtime::native::_service_stop "${services[i]}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::ensure() {
|
||||||
|
local service
|
||||||
|
while IFS= read -r service; do
|
||||||
|
if ! runtime::native::_service_running "$service"; then
|
||||||
|
runtime::native::_service_start "$service"
|
||||||
|
fi
|
||||||
|
done < <(runtime::native::_active_services)
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::logs() {
|
||||||
|
local ws
|
||||||
|
ws="$(runtime::native::_webserver)"
|
||||||
|
|
||||||
|
case "$ws" in
|
||||||
|
apache)
|
||||||
|
runtime::native::_service_logs "$(runtime::native::_apache_service)"
|
||||||
|
;;
|
||||||
|
nginx)
|
||||||
|
runtime::native::_service_logs "$(runtime::native::_nginx_service)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::list() {
|
||||||
|
printf "%-25s %s\n" "SERVICE" "STATUS"
|
||||||
|
printf "%-25s %s\n" "-------" "------"
|
||||||
|
|
||||||
|
local service
|
||||||
|
while IFS= read -r service; do
|
||||||
|
if runtime::native::_service_running "$service"; then
|
||||||
|
printf "%-25s %s\n" "$service" "running"
|
||||||
|
else
|
||||||
|
printf "%-25s %s\n" "$service" "stopped"
|
||||||
|
fi
|
||||||
|
done < <(runtime::native::_active_services)
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::status() {
|
||||||
|
printf "%-20s %s\n" "Driver:" "native"
|
||||||
|
printf "%-20s %s\n" "Platform:" "$(runtime::native::_platform)"
|
||||||
|
printf "%-20s %s\n" "Web server:" "$(runtime::native::_webserver)"
|
||||||
|
printf "%-20s %s\n" "App root:" "$(ctx::root)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
runtime::native::list
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::build() {
|
||||||
|
# Native has no images to build — but vhost generation still needs to happen
|
||||||
|
log::info "Native runtime has no build step (services run directly)"
|
||||||
|
log::info "Reload configs with: dx proxy restart"
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::is_inside() {
|
||||||
|
# Native always runs on host — there's no "inside" concept
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::exec() {
|
||||||
|
# No container boundary — run directly in the app root
|
||||||
|
cd "$(ctx::root)" && "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::native::shell() {
|
||||||
|
cd "$(ctx::root)" && exec "${SHELL:-bash}"
|
||||||
|
}
|
||||||
89
dxkit/runtime/runtime.sh
Normal file
89
dxkit/runtime/runtime.sh
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# dxkit/runtime/runtime.sh
|
||||||
|
#
|
||||||
|
# Runtime abstraction layer.
|
||||||
|
# Commands and modules call runtime::* — this file dispatches to
|
||||||
|
# the active driver based on RUNTIME_DRIVER (default: docker).
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Driver loader
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
RUNTIME_DRIVER="${RUNTIME_DRIVER:-docker}"
|
||||||
|
|
||||||
|
function runtime::driver() { echo "${RUNTIME_DRIVER}"; }
|
||||||
|
function runtime::service() { echo "${APP_SERVICE:-app}"; }
|
||||||
|
|
||||||
|
function runtime::_load_driver() {
|
||||||
|
local driver_file
|
||||||
|
driver_file="$(ctx::dxkit)/runtime/${RUNTIME_DRIVER}.runtime.sh"
|
||||||
|
|
||||||
|
if [[ ! -f "$driver_file" ]]; then
|
||||||
|
log::error "Unknown runtime driver: '${RUNTIME_DRIVER}'"
|
||||||
|
log::error "Expected: ${driver_file}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source "$driver_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime::_load_driver
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Interface
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function runtime::up() { "runtime::$(runtime::driver)::up" "$@"; }
|
||||||
|
function runtime::down() { "runtime::$(runtime::driver)::down" "$@"; }
|
||||||
|
function runtime::logs() { "runtime::$(runtime::driver)::logs" "$@"; }
|
||||||
|
function runtime::list() { "runtime::$(runtime::driver)::list" "$@"; }
|
||||||
|
function runtime::status() { "runtime::$(runtime::driver)::status" "$@"; }
|
||||||
|
function runtime::build() { "runtime::$(runtime::driver)::build" "$@"; }
|
||||||
|
function runtime::ensure() { "runtime::$(runtime::driver)::ensure" "$@"; }
|
||||||
|
function runtime::is_inside() { "runtime::$(runtime::driver)::is_inside" "$@"; }
|
||||||
|
|
||||||
|
# Exec and shell are special — they handle "running inside the runtime"
|
||||||
|
# vs "running on host" transparently
|
||||||
|
function runtime::exec() {
|
||||||
|
local args=("$@")
|
||||||
|
[[ "${#args[@]}" -eq 0 ]] && return 1
|
||||||
|
|
||||||
|
if runtime::is_inside; then
|
||||||
|
"${args[@]}"
|
||||||
|
else
|
||||||
|
runtime::ensure
|
||||||
|
"runtime::$(runtime::driver)::exec" "${args[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtime::shell() {
|
||||||
|
if runtime::is_inside; then
|
||||||
|
runtime::_local_shell
|
||||||
|
else
|
||||||
|
runtime::ensure
|
||||||
|
"runtime::$(runtime::driver)::shell"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Conditionally run a function based on a flag value.
|
||||||
|
# Driver-agnostic — useful in build pipelines.
|
||||||
|
function runtime::run_if() {
|
||||||
|
local flag="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
if [[ "${!flag:-}" == true ]]; then
|
||||||
|
"$@" || return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Internal helpers
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
function runtime::_local_shell() {
|
||||||
|
if command -v bash >/dev/null 2>&1; then
|
||||||
|
bash
|
||||||
|
else
|
||||||
|
sh
|
||||||
|
fi
|
||||||
|
}
|
||||||
119
dxkit/templates/apache/yii2-advanced/vhosts.conf.template
Normal file
119
dxkit/templates/apache/yii2-advanced/vhosts.conf.template
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
# ===========================================
|
||||||
|
# Frontend — ${DOMAIN}
|
||||||
|
# ===========================================
|
||||||
|
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName ${DOMAIN}
|
||||||
|
ServerAdmin webmaster@localhost
|
||||||
|
AddDefaultCharset UTF-8
|
||||||
|
|
||||||
|
DocumentRoot ${APP_PATH}/frontend/web/
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Backend + API aliases (path-based routing)
|
||||||
|
# ===========================================
|
||||||
|
Alias /admin ${APP_PATH}/backend/web/
|
||||||
|
Alias /api ${APP_PATH}/api/web/
|
||||||
|
|
||||||
|
<Directory ${APP_PATH}/frontend/web/>
|
||||||
|
Options FollowSymLinks
|
||||||
|
AllowOverride none
|
||||||
|
Require all granted
|
||||||
|
|
||||||
|
DirectoryIndex index.php index.html
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
# Don't rewrite requests headed for /admin or /api
|
||||||
|
RewriteCond %{REQUEST_URI} !^/admin
|
||||||
|
RewriteCond %{REQUEST_URI} !^/api
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^ index.php [L]
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
<Directory ${APP_PATH}/backend/web/>
|
||||||
|
Options FollowSymLinks
|
||||||
|
AllowOverride none
|
||||||
|
Require all granted
|
||||||
|
|
||||||
|
DirectoryIndex index.php index.html
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^ index.php [L]
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
<Directory ${APP_PATH}/api/web/>
|
||||||
|
Options FollowSymLinks
|
||||||
|
AllowOverride none
|
||||||
|
Require all granted
|
||||||
|
|
||||||
|
DirectoryIndex index.php index.html
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^ index.php [L]
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Security: block dotfiles globally
|
||||||
|
# ===========================================
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteRule (^|/)\. - [F,L]
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Friendly route fixes
|
||||||
|
# ===========================================
|
||||||
|
RewriteCond %{REQUEST_URI} ^/admin$
|
||||||
|
RewriteRule ^ /admin/ [R=301,L]
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_URI} ^/api$
|
||||||
|
RewriteRule ^ /api/ [R=301,L]
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Logs
|
||||||
|
# ===========================================
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Backend — ${BACKEND_DOMAIN}
|
||||||
|
# ===========================================
|
||||||
|
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName ${BACKEND_DOMAIN}
|
||||||
|
ServerAdmin webmaster@localhost
|
||||||
|
AddDefaultCharset UTF-8
|
||||||
|
|
||||||
|
DocumentRoot ${APP_PATH}/backend/web/
|
||||||
|
|
||||||
|
<Directory ${APP_PATH}/backend/web/>
|
||||||
|
Options FollowSymLinks
|
||||||
|
AllowOverride none
|
||||||
|
Require all granted
|
||||||
|
|
||||||
|
DirectoryIndex index.php index.html
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^ index.php [L]
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Security: block dotfiles globally
|
||||||
|
# ===========================================
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteRule (^|/)\. - [F,L]
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Logs
|
||||||
|
# ===========================================
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||||
|
</VirtualHost>
|
||||||
36
dxkit/templates/apache/yii2-basic/vhosts.conf.template
Normal file
36
dxkit/templates/apache/yii2-basic/vhosts.conf.template
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# ===========================================
|
||||||
|
# Frontend — ${DOMAIN}
|
||||||
|
# ===========================================
|
||||||
|
|
||||||
|
<VirtualHost *:80>
|
||||||
|
ServerName ${DOMAIN}
|
||||||
|
ServerAdmin webmaster@localhost
|
||||||
|
AddDefaultCharset UTF-8
|
||||||
|
|
||||||
|
DocumentRoot ${APP_PATH}/web/
|
||||||
|
|
||||||
|
<Directory ${APP_PATH}/web/>
|
||||||
|
Options FollowSymLinks
|
||||||
|
AllowOverride none
|
||||||
|
Require all granted
|
||||||
|
|
||||||
|
DirectoryIndex index.php index.html
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^ index.php [L]
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Security: block dotfiles globally
|
||||||
|
# ===========================================
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteRule (^|/)\. - [F,L]
|
||||||
|
|
||||||
|
# ===========================================
|
||||||
|
# Logs
|
||||||
|
# ===========================================
|
||||||
|
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||||
|
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||||
|
</VirtualHost>
|
||||||
50
dxkit/templates/docker/Dockerfile.template
Normal file
50
dxkit/templates/docker/Dockerfile.template
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
FROM ${BASE_APP_IMAGE}
|
||||||
|
|
||||||
|
RUN groupmod -g 1000 www-data && usermod -u 1000 -g www-data www-data
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||||
|
apt-transport-https \
|
||||||
|
gettext \
|
||||||
|
git \
|
||||||
|
unzip \
|
||||||
|
vim \
|
||||||
|
tree \
|
||||||
|
wget \
|
||||||
|
gnupg2 \
|
||||||
|
dialog \
|
||||||
|
nano \
|
||||||
|
libldap2-dev \
|
||||||
|
cron \
|
||||||
|
curl \
|
||||||
|
openssl \
|
||||||
|
jq \
|
||||||
|
openssh-server
|
||||||
|
|
||||||
|
# Install Xdebug
|
||||||
|
#RUN pecl install xdebug
|
||||||
|
#RUN docker-php-ext-enable xdebug
|
||||||
|
|
||||||
|
# Enable Apache mod_rewrite
|
||||||
|
RUN a2enmod rewrite
|
||||||
|
|
||||||
|
# Enable Apache SSL module
|
||||||
|
RUN a2enmod ssl
|
||||||
|
RUN a2dissite 000-default default-ssl
|
||||||
|
|
||||||
|
# Install Composer
|
||||||
|
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
COPY dxkit/docker/entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
RUN echo "root:Docker!" | chpasswd && chmod u+x /entrypoint.sh
|
||||||
|
|
||||||
|
RUN apt-get install -y locales && \
|
||||||
|
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \
|
||||||
|
echo "pt_PT.UTF-8 UTF-8" >> /etc/locale.gen && \
|
||||||
|
locale-gen && \
|
||||||
|
apt-get update
|
||||||
|
|
||||||
|
RUN rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
EXPOSE 80 443 2222
|
||||||
|
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||||
38
dxkit/templates/docker/docker-compose.yml.template
Normal file
38
dxkit/templates/docker/docker-compose.yml.template
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ${CTX_BUILD}
|
||||||
|
dockerfile: ${IMAGE_PATH}
|
||||||
|
image: ${APP_IMAGE}:${ENVIRONMENT}
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${APP_PORT}:80"
|
||||||
|
volumes:
|
||||||
|
- ${CTX_APP}:${APP_PATH}
|
||||||
|
- ${CTX_APACHE_LOGS}:/var/log/apache2
|
||||||
|
- ${CTX_APACHE_VHOSTS}:/etc/apache2/sites-enabled
|
||||||
|
- ${CTX_APACHE_CERTS}:/usr/local/share/ca-certificates/custom:ro
|
||||||
|
labels:
|
||||||
|
${TRAEFIK_LABELS}
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
- proxy
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: ${DB_IMAGE}
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
${DB_ENVIRONMENT}
|
||||||
|
volumes:
|
||||||
|
${DB_VOLUMES}
|
||||||
|
ports:
|
||||||
|
- "${DB_STACK_PORT}"
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
'db' => [
|
||||||
|
'class' => \yii\db\Connection::class,
|
||||||
|
'driverName' => 'sqlsrv',
|
||||||
|
'dsn' => 'sqlsrv:server=db;database=${PROJECT_NAME};encrypt=false',
|
||||||
|
'username' => '${DB_USER}',
|
||||||
|
'password' => '${DB_PASS}',
|
||||||
|
'charset' => 'utf8',
|
||||||
|
'attributes' => [
|
||||||
|
\PDO::SQLSRV_ATTR_ENCODING => \PDO::SQLSRV_ENCODING_SYSTEM
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
'db' => [
|
||||||
|
'class' => \yii\db\Connection::class,
|
||||||
|
'dsn' => 'mysql:host=db;dbname=${DB_NAME}',
|
||||||
|
'username' => '${DB_USER}',
|
||||||
|
'password' => '${DB_PASS}',
|
||||||
|
'charset' => 'utf8',
|
||||||
|
],
|
||||||
27
dxkit/templates/yii2-advanced/components/log.php.template
Normal file
27
dxkit/templates/yii2-advanced/components/log.php.template
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
'log' => [
|
||||||
|
'traceLevel' => YII_DEBUG ? 3 : 0,
|
||||||
|
'targets' => [
|
||||||
|
[
|
||||||
|
'class' => 'yii\log\FileTarget',
|
||||||
|
'levels' => ['error', 'warning', 'info'],
|
||||||
|
],
|
||||||
|
'sync' => [
|
||||||
|
'class' => 'yii\log\FileTarget',
|
||||||
|
'levels' => ['error', 'warning'],
|
||||||
|
'logFile' => '@runtime/logs/sync.log',
|
||||||
|
'exportInterval' => YII_DEBUG ? 1 : 100,
|
||||||
|
'maxLogFiles' => 100,
|
||||||
|
'categories' => ['common\user\*'],
|
||||||
|
'logVars' => []
|
||||||
|
],
|
||||||
|
'requests' => [
|
||||||
|
'class' => 'yii\log\FileTarget',
|
||||||
|
'levels' => ['info'],
|
||||||
|
'logFile' => '@runtime/logs/requests.log',
|
||||||
|
'exportInterval' => YII_DEBUG ? 1 : 100,
|
||||||
|
'maxLogFiles' => 100,
|
||||||
|
//'categories' => ['common\user\*'],
|
||||||
|
'logVars' => []
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
7
dxkit/templates/yii2-advanced/main-local.php.template
Normal file
7
dxkit/templates/yii2-advanced/main-local.php.template
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'components' => [
|
||||||
|
${YII_MAIN_LOCAL_COMPONENTS}
|
||||||
|
],
|
||||||
|
];
|
||||||
18
dxkit/templates/yii2-basic/db.mssql.php.template
Normal file
18
dxkit/templates/yii2-basic/db.mssql.php.template
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'class' => \yii\db\Connection::class,
|
||||||
|
'driverName' => 'sqlsrv',
|
||||||
|
'dsn' => 'sqlsrv:server=db;database=${PROJECT_NAME};encrypt=false',
|
||||||
|
'username' => '${DB_USER}',
|
||||||
|
'password' => '${DB_PASS}',
|
||||||
|
'charset' => 'utf8',
|
||||||
|
'attributes' => [
|
||||||
|
\PDO::SQLSRV_ATTR_ENCODING => \PDO::SQLSRV_ENCODING_SYSTEM
|
||||||
|
]
|
||||||
|
|
||||||
|
// Schema cache options (for production environment)
|
||||||
|
//'enableSchemaCache' => true,
|
||||||
|
//'schemaCacheDuration' => 60,
|
||||||
|
//'schemaCache' => 'cache',
|
||||||
|
];
|
||||||
14
dxkit/templates/yii2-basic/db.mysql.php.template
Normal file
14
dxkit/templates/yii2-basic/db.mysql.php.template
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'class' => \yii\db\Connection::class,
|
||||||
|
'dsn' => 'mysql:host=db;dbname=${DB_NAME}',
|
||||||
|
'username' => '${DB_USER}',
|
||||||
|
'password' => '${DB_PASS}',
|
||||||
|
'charset' => 'utf8',
|
||||||
|
|
||||||
|
// Schema cache options (for production environment)
|
||||||
|
//'enableSchemaCache' => true,
|
||||||
|
//'schemaCacheDuration' => 60,
|
||||||
|
//'schemaCache' => 'cache',
|
||||||
|
];
|
||||||
Loading…
Add table
Reference in a new issue