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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions .ddev/commands/web/install-magento
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ cd /var/www/html || exit 1
# global config
MAGENTO_FOLDER="magento"

# Guard: verify the MageForge bind-mount is active.
# The Docker bind-mount (../src → .../MageForge) is a kernel-level mount established
# when the containers start. If magento/ was deleted while DDEV was running, the mount
# becomes orphaned: its inode is gone, and recreating the directory produces a new inode
# not covered by the old mount. The module source would then be invisible to Magento.
# The only fix is a container restart, which re-establishes the mount on the new inode.
if [[ ! -f "${MAGENTO_FOLDER}/app/code/OpenForgeProject/MageForge/registration.php" ]]; then
echo "ERROR: The MageForge bind-mount is not active."
# Guard: verify the module source mount is active.
# The mount (repo root → magento/mageforge-source, read-only) is established when the
# containers start. If magento/ was deleted while DDEV was running, the mount can become
# orphaned: its inode is gone, and a recreated directory is not covered by the old mount.
# A container restart re-establishes the mount on the new inode.
if [[ ! -f "${MAGENTO_FOLDER}/mageforge-source/composer.json" ]]; then
echo "ERROR: The MageForge source mount is not active."
echo ""
echo "This happens when magento/ was deleted while DDEV was still running."
echo ""
Expand Down Expand Up @@ -72,17 +71,22 @@ composer config repositories.hyva-themes/magento2-theme-fallback git https://git
composer config repositories.hyva-themes/magento2-order-cancellation-webapi git https://github.com/hyva-themes/magento2-order-cancellation-webapi.git
composer config repositories.hyva-themes/magento2-email-module git https://github.com/hyva-themes/magento2-email-module.git

# Remove *.sample extension
find . -name "*.sample" -type f -exec sh -c 'mv "$1" "${1%.sample}"' _ {} \;
# Remove *.sample extension (skip the read-only module source mount)
find . -path ./mageforge-source -prune -o -name "*.sample" -type f -exec sh -c 'mv "$1" "${1%.sample}"' _ {} \;
rm -f package-lock.json # remove basic package-lock.json to avoid conflicts

# create missing local-themes.js file for grunt tasks
echo 'module.exports = {};' >dev/tools/grunt/configs/local-themes.js

# Require Hyvä theme and install MageForge module deps before setup:install so that
# both are included in the initial schema-upgrade pass (avoiding a redundant second pass).
composer require 'hyva-themes/magento2-default-theme'
/var/www/html/.ddev/commands/web/install-module-deps
# Register the module source mount as a Composer path repository. Composer symlinks
# vendor/openforgeproject/mageforge → ../../mageforge-source, which resolves within the
# Magento root (satisfying Magento's path validator), and resolves the module's
# third-party dependencies (e.g. laravel/prompts) like any regular package.
composer config repositories.mageforge '{"type": "path", "url": "mageforge-source", "options": {"symlink": true}}'

# Require Hyvä theme and MageForge before setup:install so that both are included
# in the initial schema-upgrade pass (avoiding a redundant second pass).
composer require 'hyva-themes/magento2-default-theme' 'openforgeproject/mageforge:@dev'

# install magento
bin/magento setup:install \
Expand All @@ -108,9 +112,7 @@ bin/magento deploy:mode:set developer
# disable 2FA
bin/magento module:disable Magento_TwoFactorAuth Magento_AdminAdobeImsTwoFactorAuth

# Enable MageForge: the module is bind-mounted under app/code/ and therefore not registered
# via Composer autoload. bin/magento module:enable discovers it through the component registrar
# (registration.php), writes the entry to app/etc/config.php, and exits non-zero on any error.
# Enable MageForge (Composer-installed modules are disabled until enabled in app/etc/config.php)
bin/magento module:enable OpenForgeProject_MageForge

# install sample data
Expand Down
75 changes: 0 additions & 75 deletions .ddev/commands/web/install-module-deps

This file was deleted.

13 changes: 4 additions & 9 deletions .ddev/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,12 @@ database:
version: "10.6"
hooks:
pre-start:
# Pre-create the MageForge bind-mount target directory on the host.
# Docker creates missing mount-target paths as root; by creating them here
# (as the host user) they get the correct ownership for the DDEV web container.
# MageForge/ is the actual bind-mount target (../src → .../MageForge), so we
# must create it explicitly – creating only the parent is not enough.
- exec-host: mkdir -p magento/app/code/OpenForgeProject/MageForge
# Pre-create the mageforge-source mount target on the host. Docker creates
# missing mount-target paths as root; creating it here (as the host user)
# gives it the correct ownership for the DDEV web container.
- exec-host: mkdir -p magento/mageforge-source
post-start:
- exec-host: ddev npx skills experimental_install
# Install MageForge module dependencies (e.g. laravel/prompts) that are not
# resolved automatically because the module is bind-mounted, not Composer-installed.
- exec: /var/www/html/.ddev/commands/web/install-module-deps
use_dns_when_possible: true
composer_root: magento
composer_version: "2"
Expand Down
12 changes: 9 additions & 3 deletions .ddev/docker-compose.mageforge-source.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
services:
web:
volumes:
# Bind-mount the module source directly into app/code/ so realpath() returns
# a path within the Magento root, bypassing Magento's filesystem path validator.
- ../src:/var/www/html/magento/app/code/OpenForgeProject/MageForge:cached
# Mount the module repository read-only inside the Magento root. The Composer
# path-repository symlink (vendor/openforgeproject/mageforge → ../../mageforge-source)
# then resolves to a path within the Magento root, satisfying Magento's path
# validator. Read-only also protects the module source from an accidental
# `rm -rf magento` inside the container: deletion stops at the mount.
- ..:/var/www/html/magento/mageforge-source:ro,cached
# The repository contains magento/ itself; shadow it with an anonymous volume
# so the mount does not recurse (mageforge-source/magento/mageforge-source/…).
- /var/www/html/magento/mageforge-source/magento
37 changes: 24 additions & 13 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Welcome to the MageForge development repository. This guide covers everything yo
>
> - All module development happens in `/src/` — this is where you write code.
> - Testing happens in `/magento/` — a full Magento 2 installation wired up for local use.
> - `/src/` is **bind-mounted by Docker** into `/magento/app/code/OpenForgeProject/MageForge/`. Changes are visible instantly; no Composer step needed. The mount is established on `ddev start` / `ddev restart`.
> - The repository is **mounted read-only by Docker** into `/magento/mageforge-source/` and installed into Magento as a regular Composer package (path repository with symlink). Changes in `/src/` are visible instantly; no Composer step needed. The mount is established on `ddev start` / `ddev restart`.
> - `/magento/` is never included in a release. It exists solely for local development.

---
Expand Down Expand Up @@ -83,12 +83,12 @@ Basic familiarity with Magento 2 module development is assumed.

This script will:
- Install a fresh Magento 2 instance inside `/magento/`
- Install third-party module dependencies via `ddev install-module-deps`
- Install MageForge via Composer (path repository pointing at the mounted module source)
- Install Magento sample data
- Enable the MageForge module (`bin/magento module:enable`)
- Set developer mode and disable 2FA

> **Note:** `ddev start` must run before this command to establish the Docker bind-mount.
> **Note:** `ddev start` must run before this command to establish the Docker mount.

4. **Verify the installation:**

Expand All @@ -100,26 +100,37 @@ You now have a fully functional local development environment.

---

## How the Bind-Mount Works
## How the Module Is Installed

`/src/` is bind-mounted by Docker (`.ddev/docker-compose.mageforge-source.yaml`) into the Magento app/code directory:
The repository root is bind-mounted **read-only** by Docker (`.ddev/docker-compose.mageforge-source.yaml`) into the Magento root:

```
../src → magento/app/code/OpenForgeProject/MageForge/
.. → magento/mageforge-source/ (read-only)
```

A bind-mount is used instead of a Composer symlink because Magento's path validator rejects paths that resolve outside the Magento root.
`ddev install-magento` registers this directory as a Composer **path repository** and installs the module with `composer require openforgeproject/mageforge:@dev`. Composer creates a symlink:

```
magento/vendor/openforgeproject/mageforge → ../../mageforge-source
```

Why this construction?

- **Magento's path validator** rejects paths that resolve (via `realpath()`) outside the Magento root. A plain Composer symlink to the repository root would fail; the mount point lives *inside* the Magento root, so validation passes.
- **Read-only protects the source**: an accidental `rm -rf magento` inside the container stops at the mount instead of deleting the repository through it.
- **Real Composer install**: third-party dependencies of the module (e.g. `laravel/prompts`) are resolved by Composer like for any end-user installation — no extra sync scripts needed.
- An anonymous volume shadows `mageforge-source/magento/` so the mount does not recurse into itself.

**The mount is (re-)established every time the containers start.** Always run `ddev start` or `ddev restart` when:

- setting up for the first time
- pulling config changes from the repository
- `/magento/` was deleted while DDEV was running (see [Common Issues](#common-issues))

**Third-party dependencies** (e.g. `laravel/prompts`) are not resolved automatically for bind-mounted modules. A `post-start` hook runs `ddev install-module-deps` on every start to keep them in sync. You can also trigger it manually:
**Changed module dependencies** (`composer.json` in the repository root) are picked up with:

```bash
ddev install-module-deps
ddev composer update openforgeproject/mageforge
```

---
Expand Down Expand Up @@ -233,25 +244,25 @@ ddev poweroff
ddev start
```

**MageForge bind-mount not active / `registration.php` not found:**
**MageForge source mount not active / `mageforge-source` empty:**

This happens when `/magento/` was deleted while DDEV was still running. Docker holds the old inode; the new directory is not covered by the mount.

```bash
ddev restart # Re-establishes the bind-mount on the new inode
ddev restart # Re-establishes the mount on the new inode
ddev install-magento # Re-installs Magento with the mount now active
```

**Module dependencies missing after updating `composer.json`:**

```bash
ddev install-module-deps # Installs / updates third-party deps declared in src/composer.json
ddev composer update openforgeproject/mageforge # Re-resolves the module's dependencies
```

**Need to reinstall Magento from scratch:**

```bash
ddev restart # Ensure the bind-mount is fresh first
ddev restart # Ensure the source mount is fresh first
ddev install-magento # Handles cleanup automatically
```

Expand Down
Loading