diff --git a/docs/concepts/architecture.rst b/docs/concepts/architecture.rst index f70063c..00a6c79 100644 --- a/docs/concepts/architecture.rst +++ b/docs/concepts/architecture.rst @@ -3,7 +3,7 @@ Architecture LocalDevStack is composed of: -- The **orchestrator**: ``server`` / ``server.bat`` (selects profiles, runs Compose, common workflows) +- The **orchestrator**: ``lds`` / ``lds.bat`` (selects profiles, runs Compose, common workflows) - The **HTTP layer**: Nginx (front proxy) and optionally Apache (backend HTTP) depending on your stack choice - The **runtimes**: PHP (FPM) and Node (and future stacks) - The **control plane**: the **server-tools** image (domain/vhost generation, TLS automation, secrets helpers) @@ -21,12 +21,13 @@ Instead of a monolithic "one container does everything" model, LocalDevStack use How containers cooperate ------------------------ -1. You generate or edit vhost configs (usually via ``mkhost`` in the Tools container). -2. The Tools container can scan all vhosts and generate certificates (``certify`` + ``mkcert``). -3. Nginx loads vhosts and routes requests either: +1. You generate vhost configs (via ``server setup domain``). +2. The Tools container can scan all vhosts and generate certificates. +3. Nginx loads hosts and routes requests either: - - directly to PHP-FPM (fastcgi), or - - to Apache (reverse proxy) when Apache mode is enabled, or + - directly to PHP-FPM (fastcgi) or + - to Apache (reverse proxy) when Apache mode is enabled or - to a Node service (reverse proxy). 4. The Runner handles background services (cron/logrotate) and gives you a consistent place for helper utilities. +5. You also get following services: EMail, DB, Caching diff --git a/docs/concepts/profiles-and-env.rst b/docs/concepts/profiles-and-env.rst index 26c0968..310978e 100644 --- a/docs/concepts/profiles-and-env.rst +++ b/docs/concepts/profiles-and-env.rst @@ -3,37 +3,35 @@ Profiles and Environment LocalDevStack uses Docker Compose **profiles** so you can enable only the services you want for a given project. -Where profiles are set +Setting up Environment usable by Your Projects ---------------------- -- Primary: ``docker/.env`` (this repo) -- Optional: root ``.env`` (project-level overrides) +- Use root ``.env`` (project-level overrides) +- These are common & directly shared to projects you host +- If you want project specific only a single project you need to handle that inside your project -The key variable is typically: - -- ``COMPOSE_PROFILES``: comma-separated profile list +Guided setup for on demands services (DB, Cache) +------------ -Examples --------- +The ``lds`` CLI typically includes helpers like: -Enable Nginx + PHP 8.4 + tools + MariaDB + Redis: +- ``lds setup profiles`` -.. code-block:: none +These helpers are opinionated: they try to keep profiles and generated configs consistent. - COMPOSE_PROFILES=nginx,php,php84,tools,mariadb,redis +Manual setup (Don't use unless you are fully aware of internals) +------------ -Enable Apache mode (Nginx -> Apache -> PHP-FPM): +Enable PHP 8.4 + MariaDB + Redis: .. code-block:: none - COMPOSE_PROFILES=nginx,apache,php,php84,tools + COMPOSE_PROFILES=php84,mariadb,redis -Guided setup ------------- +Enable Apache mode (Nginx -> Apache -> PHP-FPM): -The ``server`` CLI typically includes helpers like: +.. code-block:: none -- ``server setup profiles`` -- ``server setup domain`` + COMPOSE_PROFILES=apache,php84 -These helpers are opinionated: they try to keep profiles and generated configs consistent. +These are just some of the samples. diff --git a/docs/concepts/storage-layout.rst b/docs/concepts/storage-layout.rst index bd51924..592c548 100644 --- a/docs/concepts/storage-layout.rst +++ b/docs/concepts/storage-layout.rst @@ -1,23 +1,26 @@ Storage Layout ============== -To keep LocalDevStack reproducible, generated artifacts should be persisted on the host and mounted into containers. +To keep LocalDevStack reproducible, generated artifacts are persisted on the host and mounted into containers. -Recommended host folders +Directories ------------------------ -This repository uses a ``configuration/`` root (mounted into the stack): +LocalDevStack uses a ``configuration/`` root (mounted into the stack). Keep all user-managed and generated +artifacts here: -- ``configuration/nginx``: Nginx vhost configs -- ``configuration/apache``: Apache vhost configs (only if Apache mode is used) -- ``configuration/ssl``: generated certificates (mkcert output) -- ``configuration/rootCA``: mkcert Root CA store -- ``configuration/php``: PHP runtime ini overrides -- ``configuration/ssh``: optional SSH mount (e.g., for Node deps or private repos) +- ``configuration/nginx``: Nginx host configs (primary entry in most setups) +- ``configuration/apache``: Apache host configs (only if Apache mode is enabled) +- ``configuration/ssl``: generated certificates +- ``configuration/rootCA``: Root CA store (persist this to keep browser trust stable, see :doc:`tls-and-certificates`.) +- ``configuration/php``: PHP runtime ini overrides (e.g., ``php.ini``) +- ``configuration/ssh``: optional SSH mount (useful for private repos, git over SSH or tooling) +- ``configuration/sops``: optional SOPS/Age keys + config (if you use the secrets workflow) Why this matters ---------------- -- Keeping vhosts stable keeps cert generation stable. -- Persisting Root CA avoids repeated trust resets. +- Keeping hosts stable keeps host and certificate generation stable. +- Persisting the Root CA avoids repeated trust resets and browser warnings. - Persisting ``php.ini`` overrides keeps runtime behavior consistent between rebuilds. +- Persisting SOPS/Age configuration avoids re-creating keys and keeps secrets workflows predictable. diff --git a/docs/guides/domain-setup.rst b/docs/guides/domain-setup.rst index 8f05e02..7846778 100644 --- a/docs/guides/domain-setup.rst +++ b/docs/guides/domain-setup.rst @@ -1,18 +1,68 @@ Domain Setup ============ -Most users create domains and vhosts through the Tools container (``mkhost``). +LocalDevStack creates domains and vhosts, when you run ``lds setup domain``. -What ``mkhost`` typically does +How ``lds setup domain`` works ------------------------------ -- Generates Nginx vhost config for your domain. -- Generates Apache vhost config when Apache mode is enabled. -- Generates a Node compose fragment when you choose a Node app. -- Optionally triggers certificate generation workflow (see :doc:`tls-and-certificates`). +1. Run the interactive wizard. +2. Enable the selected profiles. +3. Bring the stack up and reload HTTP. + +Wizard flow (what the user answers) +----------------------------------- + +``lds setup domain`` runs an interactive 8-step flow: + +1. Domain name +2. App type (PHP or NodeJs) +3. Runtime version (PHP Major.Minor, or Node major/tags) +4. Server type (PHP: Nginx or Apache; Node: Nginx forced + optional Node start command) +5. Protocol (HTTP only / HTTPS only / both + optional redirect) +6. Document root (relative path mapped under ``/app``) +7. Client max body size +8. Mutual TLS toggle (only available when HTTPS is enabled; this requires client side certificate) + +What it generates +------------------------- + +Vhost configs +~~~~~~~~~~~~~~~~~~ + +Writes generated vhost files: + +- Nginx vhost: + ``configuration/nginx/.conf`` + +- Apache vhost (only when Apache mode is selected): + ``configuration/apache/.conf`` + +TLS handling (HTTPS) +~~~~~~~~~~~~~~~~~~~~ + +If you select HTTPS in the wizard, after writing the HTTPS config; +this generates/refreshes certificates for all known hosts. + +See: :doc:`tls-and-certificates` + +Node apps (optional) +~~~~~~~~~~~~~~~~~~~~ + +If you choose **NodeJs** app type: + +- It generates a Node compose fragment: + + ``docker/extras/.yaml`` + +The token is derived from the domain (slugified). +This compose fragment defines a Node service (internal port is always ``3000``) and sets a profile like: + +- ``node_`` Tips ---- -- Keep vhost configs under ``configuration/nginx`` / ``configuration/apache``. -- Prefer a consistent domain scheme (e.g., ``project.localhost``) for easy routing. +- Prefer a consistent domain scheme (e.g., ``project.localhost``) so your routing stays predictable. +- After any vhost/cert changes, ``lds`` will run ``lds http reload`` automatically as part of setup; + you can also run it manually when you edit configs yourself. diff --git a/docs/images/apache.rst b/docs/images/apache.rst deleted file mode 100644 index 813e6e7..0000000 --- a/docs/images/apache.rst +++ /dev/null @@ -1,16 +0,0 @@ -apache Image -============ - -Purpose -------- - -The Apache image is used when you run LocalDevStack in Apache mode: - -- Nginx terminates HTTP/HTTPS -- Nginx reverse-proxies to Apache -- Apache forwards PHP requests to PHP-FPM (proxy_fcgi) - -Why use Apache mode -------------------- - -Apache mode is useful if you need Apache-specific behaviors/modules while still keeping Nginx as the edge proxy. diff --git a/docs/images/nginx.rst b/docs/images/nginx.rst deleted file mode 100644 index 2aa9b25..0000000 --- a/docs/images/nginx.rst +++ /dev/null @@ -1,20 +0,0 @@ -nginx Image -=========== - -Purpose -------- - -The Nginx image provides the front proxy for LocalDevStack. - -Typical responsibilities: - -- Serve as the main entry point (80/443) -- Reverse proxy to Node services -- FastCGI to PHP-FPM when in Nginx+FPM mode -- Reverse proxy to Apache when in Nginx->Apache mode - -Local routing helpers ---------------------- - -This image can generate an include file (e.g., ``locals.conf``) for convenience routes under ``*.localhost``. -This is useful for local dashboards (db UI, mail UI, redis UI, etc.). diff --git a/docs/images/runner.rst b/docs/images/runner.rst deleted file mode 100644 index bc8ee13..0000000 --- a/docs/images/runner.rst +++ /dev/null @@ -1,18 +0,0 @@ -runner Image -============ - -Purpose -------- - -The Runner image provides background process management and housekeeping: - -- ``supervisord`` as PID 1 -- cron daemon -- logrotate worker - -It is also a convenient place for helper wrappers around ``docker exec``. - -Healthcheck ------------ - -Runner images commonly expose a healthcheck that verifies supervisor is responsive via ``supervisorctl``. diff --git a/docs/images/server-tools.rst b/docs/images/server-tools.rst deleted file mode 100644 index 5be9de5..0000000 --- a/docs/images/server-tools.rst +++ /dev/null @@ -1,18 +0,0 @@ -server-tools Image -================== - -Purpose -------- - -The ``server-tools`` image acts as LocalDevStack's control plane: - -- Domain/vhost generation (e.g., ``mkhost`` / ``delhost``) -- TLS automation (mkcert + ``certify``) -- Secrets helpers (SOPS + Age wrappers) -- Optional notification utilities - -How it fits ------------ - -LocalDevStack mounts shared folders (vhosts, ssl, rootCA, etc.) into the Tools container so that generated artifacts -are persisted on the host and visible to the HTTP containers. diff --git a/docs/index.rst b/docs/index.rst index a2e8ec2..84dae69 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -30,12 +30,3 @@ that work together (tools, HTTP, runner). guides/node-apps guides/secrets-sops-age guides/notifications - -.. toctree:: - :maxdepth: 2 - :caption: Images - - images/server-tools - images/nginx - images/apache - images/runner diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 173c6fd..74269f9 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -1,22 +1,46 @@ -Quickstart -========== +Getting Started +=============== -This quickstart is intentionally short. It gets you running with a basic HTTP + runtime stack, then points you to the -guides for domains, TLS, Node, secrets, and notifications. +Lets start with simple basics first without too many fuzz. LocalDevStack is a modular Docker-based +local dev stack orchestrated by the ``lds`` CLI and powered by Compose profiles. Prerequisites ------------- -- Docker (Docker Engine recommended; Docker Desktop is fine) +Install Docker on your system first. If you already have Docker installed, you can skip this step. -Typical flow ------------- +- Recommended: Docker Engine (Linux) for best performance and lowest overhead. +- If Docker Engine is not supported on your OS, use Docker Desktop (Windows/macOS; can also be used on Linux). -1. Configure profiles (what services to run). -2. Start the stack. -3. Add a domain + vhost config. -4. (Optional) Generate and trust TLS certificates. -5. Reload HTTP services. +Quick Start +----------- + +1. Make ``lds`` executable and apply permissions + + Linux/macOS:: + + chmod +x ./lds + sudo ./lds setup permissions + + Notes: + + - On Linux, the permissions step is recommended to avoid common volume/UID permission issues. + - On Windows, you typically run the wrapper (example: ``lds.bat``) and may need to add the project root directory + to your Environment PATH if you want ``lds`` usable from any directory. + +2. Start the stack:: + + lds start + +3. Add a domain (generates vhosts and updates stack selection):: + + lds setup domain + +4. (Optional) Generate and trust TLS certificates + + If you enabled HTTPS vhosts and want browser trust, use the TLS workflow described in: + + - :doc:`guides/tls-and-certificates` Next steps ---------- @@ -26,3 +50,88 @@ Next steps - Node apps behind Nginx: :doc:`guides/node-apps` - Encrypted secrets (SOPS + Age): :doc:`guides/secrets-sops-age` - Notifications: :doc:`guides/notifications` + +Directory Structure +------------------- + +Recommended project layout +~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, keep your projects in a sibling directory (simple and predictable):: + + project-root/ + ├─ application/ + │ ├─ site1/ + │ ├─ site2/ + │ └─ ... + └─ LocalDevStack/ (this repository) + +This layout is flexible. If you want a different projects folder, set ``PROJECT_DIR`` in your env. + +Example:: + + PROJECT_DIR=../path/to/your/projects # supports relative/absolute path (recommended to use absolute path for less confusion) + +LocalDevStack layout +~~~~~~~~~~~~~~~~~~~~ + +Where things live:: + + LocalDevStack/ + ├─ bin/ # optional helper binaries / shims (Don't touch) + ├─ configuration/ # These are created and persisted according to the process you follow (generated automatically) + │ ├─ apache/ # Generated apache vhost configs (if you use apache mode) + │ ├─ nginx/ # Generated nginx vhost configs (primary entry in most setups) + │ ├─ php/ # php.ini and php overrides (The php.ini you see here can be modified) + │ ├─ ssh/ # ssh keys (optional; useful for git over ssh inside containers) + │ ├─ ssl/ # generated TLS certs (can be used for trusting systemwide) + │ └─ rootCA/ # local CA store (The generated root certificate) + ├─ docker/ # internal stack definition (Don't touch) + │ ├─ compose/ # main.yaml + service fragments (http/php/db/tools/... etc) + │ ├─ conf/ # container configuration templates/snippets + │ ├─ data/ # persistent service data (db volumes etc.) + │ └─ logs/ # container logs (if your stack writes here) + ├─ .env # Docker only env (generated by tools, Don't touch) + ├─ lds # main CLI (Linux/macOS) (Don't touch) + └─ lds.bat # Windows wrapper (Don't touch) + +Run the server (the easy way) +----------------------------- + +1. Create or update env files (minimum: profiles + project dir). + + Typical locations used by this stack: + + - ``docker/.env`` (stack settings / profiles) + - ``.env`` (project-level env, exposed to your projects) + +2. Start the stack:: + + lds start + +3. Add domains via the wizard (recommended):: + + lds setup domain + +Usage +----- + +Common commands:: + + lds start + lds stop + lds reload + lds restart + lds rebuild + +HTTP utilities:: + + lds http reload + +Shells:: + + lds core + +Notes: + +- ``lds core `` is intended to open the right runtime container shell for that domain. diff --git a/server b/lds similarity index 96% rename from server rename to lds index ad6d64a..a22af96 100755 --- a/server +++ b/lds @@ -313,9 +313,9 @@ fix_perms() { chmod 755 "$DIR/bin" find "$DIR/bin" -type f -exec chmod +x {} + - chmod +x "$DIR/server" + chmod +x "$DIR/lds" - ln -fs "$DIR/server" /usr/local/bin/server + ln -fs "$DIR/lds" /usr/local/bin/lds printf "%bPermissions assigned.%b\n" "$GREEN" "$NC" } @@ -342,7 +342,7 @@ setup_domain() { cmd_delhost() { local domain="${1:-}" - [[ -n "$domain" ]] || die "Usage: server delhost " + [[ -n "$domain" ]] || die "Usage: lds delhost " delhost "$domain" delhost --RESET >/dev/null 2>&1 || true @@ -498,7 +498,7 @@ launch_php() { ############################################################################### launch_node() { local domain="${1:-}" - [[ -n "$domain" ]] || die "Usage: server core " + [[ -n "$domain" ]] || die "Usage: lds core " local nconf="$DIR/configuration/nginx/$domain.conf" [[ -f "$nconf" ]] || die "No Nginx config for $domain" @@ -1198,7 +1198,7 @@ cmd_cli() { local ctr="${1:-}" shift || true - [[ -n "$ctr" ]] || die "Usage: server cli [cmd...]" + [[ -n "$ctr" ]] || die "Usage: lds cli [cmd...]" docker inspect "$ctr" >/dev/null 2>&1 || die "Container not found: $ctr" docker inspect -f '{{.State.Running}}' "$ctr" 2>/dev/null | grep -qx true || die "Container not running: $ctr" @@ -1225,8 +1225,8 @@ cmd_cli() { cmd_core() { # Usage: - # server core -> open correct container for that domain (PHP/Node) - # server core -> auto-detect project by current directory and open correct container + # lds core -> open correct container for that domain (PHP/Node) + # lds core -> auto-detect project by current directory and open correct container local domain="${1:-}" if [[ -n "$domain" ]]; then @@ -1351,7 +1351,7 @@ core_auto() { fi fi - [[ -n "$best_container" ]] || die "No matching domain for current directory: $pwd (try: server core )" + [[ -n "$best_container" ]] || die "No matching domain for current directory: $pwd (try: lds core )" # Map host PWD -> /app path inside container local rel_path container_path @@ -1448,17 +1448,6 @@ cmd_certificate() { esac } -# Backward-compatible aliases (not documented) -cmd_install() { - # deprecated: use `server certificate install` - cmd_certificate install "${@:-}" -} - -cmd_uninstall() { - # deprecated: use `server certificate uninstall` - cmd_certificate uninstall "${@:-}" -} - cmd_doctor() { # Quick environment diagnostics + install hints (best-effort). local os_id os_like family @@ -1506,7 +1495,7 @@ cmd_doctor() { # layout checks [[ -f "$COMPOSE_FILE" ]] && _ok "compose file: $COMPOSE_FILE" || _bad "compose file missing: $COMPOSE_FILE" - [[ -f "$ENV_DOCKER" ]] && _ok "env file: $ENV_DOCKER" || _warn "env file missing: $ENV_DOCKER (run: server setup init)" + [[ -f "$ENV_DOCKER" ]] && _ok "env file: $ENV_DOCKER" || _warn "env file missing: $ENV_DOCKER (run: lds setup init)" # CA tooling by distro family if _has update-ca-certificates; then @@ -1559,8 +1548,8 @@ cmd_doctor() { fi printf "\n%bTips%b\n" "$CYAN" "$NC" - printf " - Use: %bserver certificate uninstall --all%b if you switched distros/paths.\n" "$BLUE" "$NC" - printf " - Use: %bserver run --publish 8025:8025%b to expose ports for 'server run open'.\n" "$BLUE" "$NC" + printf " - Use: %blds certificate uninstall --all%b if you switched distros/paths.\n" "$BLUE" "$NC" + printf " - Use: %blds run --publish 8025:8025%b to expose ports for 'lds run open'.\n" "$BLUE" "$NC" } ############################################################################### @@ -1721,15 +1710,15 @@ run_plan() { local dir="$1" slug slug="$(run_slug "$dir")" printf '%s|%s|%s\n' \ - "server-run-${slug}" \ + "lds-run-${slug}" \ "${slug}:local" \ "$dir" } run_find_container() { local dir="$1" - docker ps -a --filter "label=com.infocyph.server.run=1" \ - --filter "label=com.infocyph.server.dir=${dir}" \ + docker ps -a --filter "label=com.infocyph.lds.run=1" \ + --filter "label=com.infocyph.lds.dir=${dir}" \ --format '{{.Names}}' | head -n 1 } @@ -1744,9 +1733,9 @@ run_start() { shift 5 || true local -a args=(docker run -d --name "$name" - --label "com.infocyph.server.run=1" - --label "com.infocyph.server.dir=$dir" - --label "com.infocyph.server.tag=$tag" + --label "com.infocyph.lds.run=1" + --label "com.infocyph.lds.dir=$dir" + --label "com.infocyph.lds.tag=$tag" -w /workspace -v "$dir:/workspace" ) @@ -1787,12 +1776,12 @@ run_exec_shell() { cmd_run() { # Usage: - # server run # build+start+exec for current directory - # server run stop # stop container for current directory - # server run rm # remove container (and image tag) for current directory - # server run ps # list run containers - # server run logs # follow logs for current directory container - # server run open [--port P] # open first published port (or P) in browser + # lds run # build+start+exec for current directory + # lds run stop # stop container for current directory + # lds run rm # remove container (and image tag) for current directory + # lds run ps # list run containers + # lds run logs # follow logs for current directory container + # lds run open [--port P] # open first published port (or P) in browser # # Flags: # --dir PATH # run from another directory @@ -1886,7 +1875,7 @@ cmd_run() { case "$action" in ps) - docker ps -a --filter "label=com.infocyph.server.run=1" \ + docker ps -a --filter "label=com.infocyph.lds.run=1" \ --format 'table {{.Names}} {{.Image}} {{.Status}} {{.Labels}}' return 0 ;; @@ -1922,7 +1911,7 @@ cmd_run() { if [[ -z "$line" ]]; then printf "%b[run]%b No published ports found. " "$YELLOW" "$NC" - printf "%b[run]%b Tip: start with %bserver run --publish 8025:8025%b then %bserver run open%b + printf "%b[run]%b Tip: start with %blds run --publish 8025:8025%b then %blds run open%b " \ "$YELLOW" "$NC" "$BLUE" "$NC" "$BLUE" "$NC" return 1 @@ -1983,7 +1972,7 @@ cmd_run() { cmd_help() { cat < [options] +${CYAN}Usage:${NC} lds [--verbose|-v] [options] ${CYAN}Default:${NC} quiet docker compose operations. Add ${CYAN}-v${NC} / ${CYAN}--verbose${NC} to see pull/build progress. diff --git a/server.bat b/lds.bat similarity index 83% rename from server.bat rename to lds.bat index 529d308..71d5550 100644 --- a/server.bat +++ b/lds.bat @@ -36,19 +36,19 @@ if not defined BASH_EXE ( exit /b 3 ) -if not exist "%DEVHOME%\server" ( - echo %WARN% Cannot find server script: "%DEVHOME%\server" +if not exist "%DEVHOME%\lds" ( + echo %WARN% Cannot find script: "%DEVHOME%\lds" exit /b 4 ) where docker.exe >nul 2>&1 -if errorlevel 1 echo %WARN% docker.exe not found on Windows PATH. If ./server uses docker, it may fail. +if errorlevel 1 echo %WARN% docker.exe not found on Windows PATH. It may fail. if errorlevel 1 goto :run docker info >nul 2>&1 if errorlevel 1 echo %WARN% Docker installed but NOT running/reachable (docker info failed). Start Docker Desktop / engine. :run -"%BASH_EXE%" -lc "set -euo pipefail; export TERM=xterm-256color; DEVHOME_WIN=\"$1\"; CALLER_WIN=\"$2\"; DEVHOME=$(cygpath -u \"$DEVHOME_WIN\"); CALLER=$(cygpath -u \"$CALLER_WIN\"); cd \"$DEVHOME\"; chmod +x ./server >/dev/null 2>&1 || true; cd \"$CALLER\"; shift 2; exec \"$DEVHOME/server\" --__win_workdir \"$CALLER_WIN\" \"$@\"" bash "%DEVHOME%" "%WORKDIR%" %* +"%BASH_EXE%" -lc "set -euo pipefail; export TERM=xterm-256color; DEVHOME_WIN=\"$1\"; CALLER_WIN=\"$2\"; DEVHOME=$(cygpath -u \"$DEVHOME_WIN\"); CALLER=$(cygpath -u \"$CALLER_WIN\"); cd \"$DEVHOME\"; chmod +x ./lds >/dev/null 2>&1 || true; cd \"$CALLER\"; shift 2; exec \"$DEVHOME/lds\" --__win_workdir \"$CALLER_WIN\" \"$@\"" bash "%DEVHOME%" "%WORKDIR%" %* exit /b %ERRORLEVEL%