_mage-mirror.sh v1.0
local Magento 2 + Hyvä dev stack in one command — with optional remote clone, one-step core upgrade, and multi-store routing.
This repo ships a single, powerful installer:
_mage-mirror.sh– a guided setup script for macOS/linux + Docker + Warden.
Repo: j-scriptz/mage-mirror
- 🧱 Fresh Magento 2 install via
composer create-project - 🔁 Clone an existing site (code + DB) from:
- Local SQL dump +
env.php/config.php, or - A remote server via SSH (rsync + tar + remote
mysqldump)
- Local SQL dump +
- ⬆️ Upgrade mode (clone + upgrade):
- Imports your current code & DB
- Updates Magento core with Composer (e.g. to latest
2.4.x) - Runs
setup:upgrade, DI compile, static deploy, reindex, etc.
- 🎨 Hyvä theme integration:
- Optional install via OSS composer mirrors
- Automatically sets Hyvä Default as the storefront theme when available
- 🌐 Optional multi-store:
https://<project>.test→ base websitehttps://app.<project>.test→ optionalsubwebsites- Host-based routing patch in
pub/index.php(only when enabled)
- 🧪 Search & OpenSearch sanity:
- Configures Magento to use
opensearch(Warden’s service) - Rebuilds
catalogsearch_fulltextindex
- Configures Magento to use
- 🔐 Remote sync with SSH keys or interactive password fallback:
- If
REMOTE_SSH_KEYis set → uses key-based auth - If not, and you’re in a TTY → falls back to password/agent-based SSH (ssh/rsync will prompt)
- In non-interactive runs, a key is still required
- If
- 🧾 Single, central config file:
_mage-mirror.config
- macOS (Apple Silicon or Intel) or Linux or Ubuntu / WSL2 on Windows
- Docker Desktop for Mac or Docker on Linux
- Docker Compose v2 enabled
- Enough resources (6GB+ RAM recommended)
- Homebrew (optional; script can install it)
- Warden (installed via Homebrew if missing)
If
docker infofails, fix Docker Desktop first.
The installer will exit early with a clear error if Docker/Warden aren’t ready.
# 1) Clone the project
git clone https://github.com/j-scriptz/mage-mirror.git
cd mage-mirror
# 2) Configure (optional but recommended)
# edit _mage-mirror.config to match your setup
# 3) Make the installer executable
chmod +x _mage-mirror.sh
# 4) Run it
./_mage-mirror.shThe script will:
- Load
_mage-mirror.config(if present) - Ask for anything left as
ask - Spin up a full Magento + Hyvä + OpenSearch Warden environment
Instead of multiple env files, everything is driven from one optional config:
CONFIG_FILE="${CONFIG_FILE:-_mage-mirror.config}"
if [[ -f "${CONFIG_FILE}" ]]; then
echo "ℹ️ Loading configuration from ${CONFIG_FILE}..."
# shellcheck disable=SC1090
source "${CONFIG_FILE}"
fiYou control everything via simple key/value pairs.
# Name of the Warden env / project (warden env-init <PROJECT_NAME> magento2)
PROJECT_NAME=mage
# Magento metapackage & version (fresh install + upgrade target)
MAGENTO_PACKAGE=magento/project-community-edition
# Use a Composer version constraint; 2.4.* resolves to the newest 2.4.x
MAGENTO_VERSION=2.4.*
# Admin frontname for Magento backend
ADMIN_FRONTNAME=mage_admin# Use an existing DB + env.php/config.php instead of setup:install
# yes = always use existing DB (non-interactive)
# no = always run fresh install
# ask = prompt at runtime
USE_EXISTING_DB=ask
# When USE_EXISTING_DB=yes and NOT using remote DB dump,
# point to local files (relative to repo root):
EXISTING_DB_SQL=db/mage-multi-hyva.sql
EXISTING_ENV_PHP=config/env.php
EXISTING_CONFIG_PHP=config/config.php# WITH_SAMPLE_DATA:
# yes = always install sample data on fresh installs
# no = never install
# ask = prompt at runtime
WITH_SAMPLE_DATA=ask
# INSTALL_HYVA:
# yes = install Hyvä theme from OSS repos and set as default
# no = skip Hyvä install
# ask = prompt at runtime
INSTALL_HYVA=ask# ENABLE_MULTISTORE:
# yes = configure app.${PROJECT_NAME}.test + ${PROJECT_NAME}.test with a secondary 'subcats' website
# no = single-store only (no sub websites or host-based routing)
# ask = prompt at runtime
ENABLE_MULTISTORE=ask- When enabled:
app.${PROJECT_NAME}.test→ base website${PROJECT_NAME}.test→subwebsitepub/index.phpgets a small host switch to setMAGE_RUN_CODEcorrectly.
- When disabled:
- Only the base website is configured
- No host-based routing or sub URLs are applied
# UPGRADE_MAGENTO:
# yes = after importing existing code/DB, attempt composer-based Magento core upgrade
# no = skip upgrade and keep current version
# ask = prompt at runtime in existing-DB path
UPGRADE_MAGENTO=ask
# UPGRADE_MAGENTO_VERSION:
# Target core version for upgrade. If empty, MAGENTO_VERSION is used.
# Example: 2.4.* (newest 2.4.x), or a specific release like 2.4.7
UPGRADE_MAGENTO_VERSION=2.4.*How it works (existing DB path):
- After code/DB import and env/config wiring, the script asks (if
ask). - If enabled:
- Figures out a target version:
TARGET_VER="${UPGRADE_MAGENTO_VERSION:-${MAGENTO_VERSION:-2.4.*}}" - Updates the Magento core package constraint in
composer.json:magento/product-community-edition:${TARGET_VER}ormagento/magento2-base:${TARGET_VER}
- Runs:
composer update "magento/*" --with-all-dependencies - Then runs:
bin/magento setup:upgrade bin/magento setup:di:compile bin/magento setup:static-content:deploy -f bin/magento cache:flush
- Figures out a target version:
Result: your cloned site is moved forward to the version you specify while keeping app/code and app/design modules.
When USE_EXISTING_DB=yes, you can optionally pull code/DB from an existing remote instance.
Key flags:
# Use remote Magento code instead of composer create-project
# yes = always rsync from remote
# no = never rsync; always use composer locally
# ask = prompt at runtime if REMOTE_* is configured
USE_RSYNC_MAGENTO=ask
# How to combine rsync/tar + remote DB when using remote Magento:
# ask = prompt with menu (recommended)
# (others are set internally by the script: everything | code_only | db_code | db_only)
USE_RSYNC_TAR_MAGENTO=ask
# Whether to pull the DB via remote mysqldump over SSH:
# yes = run mysqldump on remote host and pipe into warden db import
# no = use local SQL file (EXISTING_DB_SQL)
USE_REMOTE_DB_DUMP=no
# If you want to exclude pub/media from the tarball when copying code:
# yes = exclude pub/media (smaller tar, no media sync)
# no = include pub/media
EXCLUDE_MEDIA_FROM_TAR=noRemote host/SSH:
# Where the remote Magento instance lives
REMOTE_HOST=your.server.com
REMOTE_USER=sshuser
REMOTE_PATH=/var/www/html/magento
# Path to your PRIVATE SSH key on the host running this script
# If unset or invalid, the script will:
# - In interactive runs: fall back to password/agent-based SSH
# - In non-interactive runs: exit with an error
REMOTE_SSH_KEY=/Users/yourname/.ssh/id_ed25519
# Remote DB settings (used when USE_REMOTE_DB_DUMP=yes)
REMOTE_DB_HOST=127.0.0.1
REMOTE_DB_NAME=magento
REMOTE_DB_USER=magento
REMOTE_DB_PASSWORD=secret
REMOTE_DB_PORT=3306When rsync is enabled:
- If
REMOTE_SSH_KEYexists:- The key is copied into the php-fpm container and used for all SSH/rsync calls.
- If
REMOTE_SSH_KEYis missing/invalid:- If a TTY is attached, the script warns and falls back to password/agent-based SSH.
Expectssh/rsyncto prompt you. - If no TTY (CI/headless), the script exits and tells you to configure
REMOTE_SSH_KEY.
- If a TTY is attached, the script warns and falls back to password/agent-based SSH.
The script supports four remote copy modes (chosen from an interactive menu when USE_RSYNC_TAR_MAGENTO=ask):
- Everything → code +
pub/media+ remote DB - Code only → code (no media), DB from local SQL
- DB + Code only → code (no media) + remote DB via
mysqldump - DB only → DB via
mysqldump, code via composer
By default:
PROJECT_NAME=mage→ base domainmage.test, subdomainapp- The script updates
/etc/hostswith:127.0.0.1 mage.test app.mage.test
When ENABLE_MULTISTORE=yes:
- Magento base URLs are configured so that:
- Default scope & website
base→https://app.mage.test/ - Website
subcats→https://mage.test/
- Default scope & website
pub/index.phpis patched to route:app.mage.test→ website codebasemage.test→ website codesubcats- Anything else →
base
When ENABLE_MULTISTORE=no:
- Only the base website is configured
- No
subcatsbase URLs or host-based routing are applied
You can override Traefik values by setting:
# TRAEFIK_DOMAIN=mage.test
# TRAEFIK_SUBDOMAIN=app
# SUBCATS_URL=https://mage.test/in _mage-mirror.config if needed.
For both fresh installs and existing DB imports, the script standardizes on OpenSearch:
- Fresh installs use
--search-engine=opensearchwith the right flags onsetup:install. - Existing DB path:
-
Waits for
opensearch:9200 -
Forces:
bin/magento config:set catalog/search/engine opensearch bin/magento config:set catalog/search/opensearch_server_hostname opensearch bin/magento config:set catalog/search/opensearch_server_port 9200 bin/magento config:set catalog/search/opensearch_index_prefix magento2 bin/magento config:set catalog/search/opensearch_enable_auth 0
-
Rebuilds
catalogsearch_fulltext
-
If you see “No alive nodes found in your cluster”, this is the part that rewires Magento back to Warden’s OpenSearch instance.
Docker Desktop 29 tightened container isolation and Docker socket access.
If your Warden setup suddenly starts misbehaving after upgrading Docker Desktop (e.g. permission errors, services not starting as expected):
- Try downgrading Docker Desktop to a pre-29 build (e.g. 28) known to work with your Warden version.
- Or adjust Enhanced Container Isolation / socket mount settings if you’re on a plan that exposes them.
- Keep Warden relatively up-to-date to match Docker changes.
On my macOS with Docker 29.0.1 under Settings > Docker Engine:
{
"builder": {
"gc": {
"defaultKeepStorage": "20GB",
"enabled": true
}
},
"experimental": false,
"min-api-version": "1.24"
}
The key part was adding the "min-api-version"
This script just shells out to docker / warden — if those tools are happy, the installer will be too.
PROJECT_NAME=mage USE_EXISTING_DB=no WITH_SAMPLE_DATA=yes INSTALL_HYVA=yes ENABLE_MULTISTORE=no ./_mage-mirror.shPROJECT_NAME=mage USE_EXISTING_DB=yes EXISTING_DB_SQL=db/mage-multi-hyva.sql EXISTING_ENV_PHP=config/env.php EXISTING_CONFIG_PHP=config/config.php WITH_SAMPLE_DATA=no INSTALL_HYVA=no ENABLE_MULTISTORE=no UPGRADE_MAGENTO=no ./_mage-mirror.shPROJECT_NAME=mage USE_EXISTING_DB=yes USE_RSYNC_MAGENTO=yes USE_RSYNC_TAR_MAGENTO=yes USE_REMOTE_DB_DUMP=yes WITH_SAMPLE_DATA=no INSTALL_HYVA=yes ENABLE_MULTISTORE=yes UPGRADE_MAGENTO=yes UPGRADE_MAGENTO_VERSION=2.4.* ./_mage-mirror.sh(Assumes _mage-mirror.config defines working REMOTE_* values.)
- One script:
_mage-mirror.sh - One config:
_mage-mirror.config - Three core modes:
- Fresh install (Magento + Hyvä)
- Clone existing site (code + DB)
- Clone and upgrade to a newer Magento version
Perfect for quickly spinning up mirror environments, rehearsal upgrades, or portfolio-ready Hyvä stores on macOS with Warden.
# SSH Key Setup Guide
This guide walks you through:
1. Generating an SSH key on your **local machine**
2. Adding the public key to **your server** for passwordless login
3. (Optional) Configuring a convenient `~/.ssh/config` alias
Examples assume macOS / Linux. Windows notes are at the bottom.
---
## 1. SSH Key Basics
You will create an SSH **key pair**:
- **Private key** – stays on your local machine (e.g. `~/.ssh/id_ed25519`)
> Never copy this to any server or share it.
- **Public key** – safe to share (e.g. `~/.ssh/id_ed25519.pub`)
> This goes into `~/.ssh/authorized_keys` on your server.
SSH uses the private key locally to prove your identity; the server only needs the public key.
---
## 2. Check for Existing Keys
On your **local machine** (macOS / Linux / WSL):
```bash
ls -al ~/.sshIf you see any of the following, you may already have keys:
id_ed25519/id_ed25519.pubid_rsa/id_rsa.pub
You can reuse those if you like. If you want a fresh key just for this setup, continue to the next step.
On your local machine:
ssh-keygen -t ed25519 -C "your_email@example.com"You’ll be prompted:
-
Enter file in which to save the key
- Press Enter to accept the default:
~/.ssh/id_ed25519
- Press Enter to accept the default:
-
Enter passphrase (empty for no passphrase)
- For more security, set a passphrase (recommended for your main dev machine).
- For fully non-interactive scripts, you may leave this empty (less secure).
This will create:
~/.ssh/id_ed25519– private key~/.ssh/id_ed25519.pub– public key
This lets your OS cache the decrypted key, so you don’t have to re-enter the passphrase constantly.
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519Note: On macOS, you can add this to your shell profile later for automatic agent setup.
We’ll use the following placeholders:
- Server user:
YOUR_USER - Server host:
your.server.com
Replace both with your real values.
On your local machine:
ssh-copy-id -i ~/.ssh/id_ed25519.pub YOUR_USER@your.server.comYou’ll be prompted for your server password one last time.
ssh-copy-id will:
- Create
~/.sshon the server if it doesn’t exist - Append your key to
~/.ssh/authorized_keys - Set correct file permissions automatically
If ssh-copy-id is not available, use Option B.
-
Print your public key locally:
cat ~/.ssh/id_ed25519.pubCopy the entire line to your clipboard.
-
SSH into the server using your password:
ssh YOUR_USER@your.server.com
-
On the server, create the
.sshdirectory if needed:mkdir -p ~/.ssh chmod 700 ~/.ssh
-
Open (or create)
authorized_keyson the server:nano ~/.ssh/authorized_keys # or use vim, etc.
Paste the public key line you copied earlier, then save and exit.
-
Fix permissions on the server:
chmod 600 ~/.ssh/authorized_keys -
Exit the server:
exit
Back on your local machine:
ssh YOUR_USER@your.server.comExpected behavior:
- You should not be asked for the server’s password.
- If you set a passphrase on the key, you may be asked for that (once per session if using
ssh-agent).
If you are still prompted for the server password, check:
-
Correct username (
YOUR_USERvsroot, etc.) -
Public key is actually present in
~/.ssh/authorized_keyson the server -
File permissions on the server side:
~/.ssh→700~/.ssh/authorized_keys→600
-
SSH configuration on the server (
/etc/ssh/sshd_config) allows key authentication:PubkeyAuthentication yesAuthorizedKeysFile .ssh/authorized_keys
If you change
sshd_config, remember to restart the SSH service (e.g.,sudo systemctl restart sshd).
This lets you use a short alias (ssh webetta) instead of typing the full user + host each time and lets you pin a specific key.
On your local machine:
nano ~/.ssh/configAdd:
Host my-server
HostName your.server.com
User YOUR_USER
Port 22
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yesNow you can connect with:
ssh my-serverYou can follow this guide on Windows in two main ways:
If you’re using Windows Subsystem for Linux (WSL):
- Open your WSL terminal (Ubuntu, etc.)
- Follow the Linux instructions exactly
- Keys are stored in:
/home/<your-username>/.ssh
From PowerShell:
ssh-keygen -t ed25519 -C "your_email@example.com"By default, keys are stored in:
C:\Users\<YourUser>\.ssh\id_ed25519
C:\Users\<YourUser>\.ssh\id_ed25519.pub
You can then:
- Use
type $env:USERPROFILE\.ssh\id_ed25519.pubto print the public key - Copy it into
~/.ssh/authorized_keyson the server using the same manual copy process as above
ssh-keygen -t ed25519 -C "your_email@example.com"eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519ssh-copy-id -i ~/.ssh/id_ed25519.pub YOUR_USER@your.server.commkdir -p ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys # paste key, save
chmod 600 ~/.ssh/authorized_keysssh YOUR_USER@your.server.comHost my-server
HostName your.server.com
User YOUR_USER
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yesOptional but recommended: Jscriptz Subcats
During setup, you can have mage-mirror auto-install Jscriptz Subcats (Hyvä & Luma compatible). It adds beautiful subcategory cards in minutes. License includes 5 domains.
