Installation & Setup Guide
Complete guide for installing, configuring, and getting started with MPM - Mehr's Package Manager.
Installation
Quick Install
# Download mpm.php
wget https://raw.githubusercontent.com/mehrnet/mpm/main/mpm.php
# Set permissions
chmod 644 mpm.php
# Test installation
php mpm.php echo "Hello, World!"
Manual Install
- Download
mpm.phpfrom the repository - Place it in your project root or web server directory
- Ensure PHP 7.0+ is installed
- Verify
ZipArchiveextension is enabled (for package management)
Requirements
- PHP: 7.0 or higher
- Extensions:
ZipArchive(for package management) - Permissions: Read/write access to project directory
First Run
On first execution, MPM automatically generates configuration files:
# Run any command to initialize
php mpm.php ls
This creates:
.config/
├── key # API key (64 hex characters)
├── repos.json # Repository mirrors configuration
├── packages.json # Installed packages registry
└── path.json # Command handler patterns
API Key
Your API key is displayed on first HTTP access or saved to .config/key on first CLI run:
# View your API key
cat .config/key
Important: Keep your API key secure. Anyone with access can execute commands.
Configuration
Repository Mirrors
Edit .config/repos.json to customize package repositories:
{
"main": [
"https://raw.githubusercontent.com/mehrnet/mpm-repo/refs/heads/main/main"
]
}
Each entry is a full URL to a repository directory containing database.json and package ZIP files. Mirrors are tried sequentially on failure.
Command Handler Patterns
Edit .config/path.json to customize where the shell looks for custom commands:
[
"app/packages/[name]/handler.php",
"bin/[name].php",
"custom/handlers/[name].php"
]
The [name] placeholder is replaced with the command name.
Environment-Specific Configuration
For different environments, you can:
Use different config directories:
// Modify constants in mpm.php (not recommended) const DIR_CONFIG = '.config';Symlink configuration:
ln -s .config.production .configEnvironment variables: Access via
php mpm.php env list
Getting Started
CLI Mode (Recommended for Development)
# List files
php mpm.php ls
# Read file contents
php mpm.php cat mpm.php | head -20
# Create directory
php mpm.php mkdir tmp
# Copy files
php mpm.php cp mpm.php shell.backup.php
# Package management
php mpm.php pkg search database
php mpm.php pkg add users
php mpm.php pkg list
php mpm.php pkg upgrade
HTTP Mode (Production)
Start PHP development server (testing):
php -S localhost:8000Get your API key:
KEY=$(cat .config/key)Execute commands:
# List files curl "http://localhost:8000/mpm.php/$KEY/ls" # Read file curl "http://localhost:8000/mpm.php/$KEY/cat/README.md" # Package management curl "http://localhost:8000/mpm.php/$KEY/pkg/list" curl "http://localhost:8000/mpm.php/$KEY/pkg/add/users"
Production Web Server
For production, use a proper web server:
Nginx:
server {
listen 80;
server_name your-domain.com;
root /var/www/shell;
index mpm.php;
location / {
try_files $uri $uri/ /mpm.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index mpm.php;
include fastcgi_params;
}
}
Apache (.htaccess):
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ mpm.php/$1 [L,QSA]
Package Development
Creating a Package
Create package structure:
mypackage/ ├── handler.php # Required: Entry point ├── package.json # Required: Metadata ├── lib/ # Optional: Library code └── data/ # Optional: Data filesWrite handler.php:
<?php return function(array $args): string { $action = $args[0] ?? 'help'; switch ($action) { case 'list': return "Item 1\nItem 2\nItem 3"; case 'create': $name = $args[1] ?? null; if (!$name) { throw new \RuntimeException('Name required', 400); } return "Created: $name"; default: return "Actions: list, create"; } };Create package.json:
{ "id": "mypackage", "name": "My Package", "version": "1.0.0", "description": "Package description", "author": "Your Name", "license": "MIT", "dependencies": [] }
Package Metadata Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Lowercase, no spaces, used in URLs |
name |
string | Yes | Human-readable name |
version |
string | Yes | Semantic versioning (X.Y.Z) |
description |
string | Yes | Brief description |
author |
string | No | Package author |
license |
string | No | License identifier (MIT, Apache-2.0, etc.) |
dependencies |
array | No | Array of package IDs |
Testing Your Package
CLI Mode:
# Install manually
mkdir -p app/packages/mypackage
cp -r mypackage/* app/packages/mypackage/
# Test commands
php mpm.php mypackage list
php mpm.php mypackage create item-name
HTTP Mode:
KEY=$(cat .config/key)
curl "http://localhost:8000/mpm.php/$KEY/mypackage/list"
curl "http://localhost:8000/mpm.php/$KEY/mypackage/create/item-name"
Best Practices
Input Validation:
$id = $args[0] ?? null; if (!$id || !is_numeric($id)) { throw new \RuntimeException('Invalid ID', 400); }Error Handling:
try { $data = json_decode(file_get_contents('data.json'), true); if (!$data) { throw new \RuntimeException('Invalid JSON', 400); } } catch (\Throwable $e) { throw new \RuntimeException("Error: {$e->getMessage()}", 400); }Security:
// BAD: Direct use of user input $file = $args[0]; return file_get_contents($file); // Vulnerable to path traversal // GOOD: Validate and restrict paths $file = basename($args[0] ?? ''); // Remove path components $path = "app/data/$file"; if (!file_exists($path)) { throw new \RuntimeException('File not found', 404); } return file_get_contents($path);
Repository Setup
Creating a Package Repository
Create repository structure:
my-repo/ └── main/ ├── database.json ├── mypackage-1.0.0.zip └── otherapkg-1.0.0.zipBuild package ZIP:
cd mypackage zip -r ../my-repo/main/mypackage-1.0.0.zip .Calculate checksum:
sha256sum my-repo/main/mypackage-1.0.0.zip # Output: abc123def456... mypackage-1.0.0.zipCreate database.json:
{ "version": 1, "packages": { "mypackage": { "name": "My Package", "description": "Package description", "versions": { "1.0.0": { "version": "1.0.0", "dependencies": [], "checksum": "sha256:abc123def456...", "size": 2048, "download_url": "mypackage-1.0.0.zip", "released_at": "2025-01-15" } }, "latest": "1.0.0", "author": "Your Name", "license": "MIT" } } }Host the repository:
- Upload to GitHub:
https://github.com/user/repo/raw/main/main/ - Use CDN or static hosting
- Ensure HTTPS is enabled
- Upload to GitHub:
Configure shell to use your repository: Edit
.config/repos.json:{ "main": ["https://github.com/user/repo/raw/main/main"] }
GitHub Hosting Example
# Create repository
mkdir -p my-packages/main
cd my-packages
# Add packages
cp /path/to/mypackage-1.0.0.zip main/
cat > main/database.json << 'EOF'
{
"version": 1,
"packages": { ... }
}
EOF
# Push to GitHub
git init
git add .
git commit -m "Add packages"
git remote add origin https://github.com/user/my-packages.git
git push -u origin main
# Users configure: https://github.com/user/my-packages/raw/main/main
Deployment
Development
# Run PHP built-in server
php -S localhost:8000
# Test commands
php mpm.php pkg list
curl "http://localhost:8000/mpm.php/$(cat .config/key)/pkg/list"
Staging/Production
Use proper web server (Nginx, Apache, Caddy)
Enable HTTPS for API key security
Set restrictive permissions:
chmod 644 mpm.php chmod 700 .config chmod 600 .config/keyConfigure PHP-FPM for better performance
Set up monitoring and logging
Regular backups of
.config/directory
Docker Deployment
FROM php:8.1-fpm
# Install extensions
RUN docker-php-ext-install zip
# Copy shell
COPY mpm.php /var/www/html/
# Set permissions
RUN chown -R www-data:www-data /var/www/html
WORKDIR /var/www/html
Security Hardening
Restrict API key access:
chmod 600 .config/key chown www-data:www-data .config/keyUse environment-specific keys
Enable HTTPS only in production
Rate limiting at web server level
Monitor failed auth attempts
Regular security updates
Troubleshooting
Common Issues
"Permission denied" errors:
# Fix permissions
chmod 755 .
chmod 644 mpm.php
chmod -R 755 .config
"ZipArchive not found":
# Install extension
sudo apt-get install php-zip # Debian/Ubuntu
sudo yum install php-zip # CentOS/RHEL
"API key not found" (CLI mode):
# Generate key manually
php mpm.php ls # This creates .config/key
Lock file issues:
# Force unlock if stuck
php mpm.php pkg unlock
Usage Reference
Complete command reference and API documentation for MPM - Mehr's Package Manager.
Request Formats
HTTP Mode
Format:
GET /mpm.php/API_KEY/COMMAND/ARG0/ARG1/ARG2/...
Examples:
# Get API key
KEY=$(cat .config/key)
# List files
curl "http://localhost/mpm.php/$KEY/ls"
# Read file
curl "http://localhost/mpm.php/$KEY/cat/README.md"
# Echo with multiple arguments
curl "http://localhost/mpm.php/$KEY/echo/hello/world"
# Package management
curl "http://localhost/mpm.php/$KEY/pkg/search/database"
curl "http://localhost/mpm.php/$KEY/pkg/add/users"
Response:
- Success: HTTP 200 with plain text response
- Error: HTTP 4xx/5xx with error message
CLI Mode
Format:
php mpm.php COMMAND ARG0 ARG1 ARG2 ...
Examples:
# List files
php mpm.php ls
# Read file
php mpm.php cat README.md
# Echo with multiple arguments
php mpm.php echo hello world
# Package management
php mpm.php pkg search database
php mpm.php pkg add users
Response:
- Success: Output to STDOUT, exit code 0
- Error: Output to STDERR with "Error: " prefix, exit code 1
Built-in Commands
ls [path]
List directory contents.
Arguments:
path(optional): Directory to list (default: current directory)
Examples:
# CLI
php mpm.php ls
php mpm.php ls app
php mpm.php ls app/packages
# HTTP
curl "http://localhost/mpm.php/$KEY/ls"
curl "http://localhost/mpm.php/$KEY/ls/app"
Output:
Space-separated filenames (excludes . and ..)
Errors:
- 404: Directory not found
- 400: Cannot read directory
- 403: Path validation failed (absolute path or
..)
cat
Read file contents.
Arguments:
file(required): Path to file
Examples:
# CLI
php mpm.php cat README.md
php mpm.php cat .config/key
# HTTP
curl "http://localhost/mpm.php/$KEY/cat/README.md"
Output: File contents (preserves formatting and newlines)
Errors:
- 400: Missing file argument or not a file
- 404: File not found
- 403: Path validation failed
rm
Delete file.
Arguments:
file(required): Path to file
Examples:
# CLI
php mpm.php rm temp.txt
php mpm.php rm logs/old.log
# HTTP
curl "http://localhost/mpm.php/$KEY/rm/temp.txt"
Output: Empty string on success (POSIX behavior)
Errors:
- 400: Missing file argument, not a file, or cannot delete
- 404: File not found
- 403: Path validation failed
mkdir
Create directory (recursive).
Arguments:
path(required): Directory path to create
Examples:
# CLI
php mpm.php mkdir uploads
php mpm.php mkdir app/data/cache
# HTTP
curl "http://localhost/mpm.php/$KEY/mkdir/uploads"
Output: Empty string on success
Errors:
- 400: Missing path argument, already exists, or cannot create
- 403: Path validation failed
cp
Copy file.
Arguments:
src(required): Source file pathdst(required): Destination file path
Examples:
# CLI
php mpm.php cp config.json config.backup.json
php mpm.php cp README.md docs/README.md
# HTTP
curl "http://localhost/mpm.php/$KEY/cp/config.json/config.backup.json"
Output: Empty string on success
Errors:
- 400: Missing arguments, source not a file, or cannot copy
- 404: Source file not found
- 403: Path validation failed
echo ...
Echo text arguments.
Arguments:
text...(required): One or more text arguments
Examples:
# CLI
php mpm.php echo hello
php mpm.php echo hello world "from shell"
# HTTP
curl "http://localhost/mpm.php/$KEY/echo/hello"
curl "http://localhost/mpm.php/$KEY/echo/hello/world"
Output: Space-separated arguments
env [action] [name]
Manage environment variables.
Actions:
list(default): List all environment variablesget <name>: Get specific variable value
Examples:
# CLI
php mpm.php env list
php mpm.php env get PATH
php mpm.php env get HOME
# HTTP
curl "http://localhost/mpm.php/$KEY/env/list"
curl "http://localhost/mpm.php/$KEY/env/get/PATH"
Output:
list: One variable per line (KEY=value)get: Variable value only
Errors:
- 404: Variable not found or unknown action
- 400: Missing variable name for
get
Package Manager
pkg add [PACKAGE...]
Install packages with automatic dependency resolution.
Arguments:
PACKAGE...(required): One or more package names
Examples:
# CLI
php mpm.php pkg add users
php mpm.php pkg add users auth database
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/add/users"
curl "http://localhost/mpm.php/$KEY/pkg/add/users/auth"
Process:
- Fetch repository database
- Resolve dependencies (DFS topological sort)
- Download all packages with checksum verification
- Extract all packages to project root
- Register packages atomically
Output:
Packages to install: dependency1, dependency2, users
Downloading packages...
All packages downloaded and verified
Extracting packages...
Extracted dependency1 (1.0.0)
Extracted dependency2 (2.0.0)
Extracted users (3.0.0)
Registering packages...
Successfully installed 3 package(s): dependency1, dependency2, users
Errors:
- 404: Package not found
- 400: Circular dependency detected
- 503: All mirrors failed or lock held
pkg del
Remove package (fails if other packages depend on it).
Arguments:
PACKAGE(required): Package name
Examples:
# CLI
php mpm.php pkg del users
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/del/users"
Output:
Removed package: users (version 1.0.0) - 15 files deleted, 3 empty directories removed
Errors:
- 404: Package not installed
- 400: Package required by other packages
- 503: Lock held
pkg upgrade [PACKAGE]
Upgrade all packages or specific package to latest version.
Arguments:
PACKAGE(optional): Package name (if omitted, upgrades all)
Examples:
# CLI
php mpm.php pkg upgrade # Upgrade all
php mpm.php pkg upgrade users # Upgrade specific
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/upgrade"
curl "http://localhost/mpm.php/$KEY/pkg/upgrade/users"
Output:
Will upgrade users from 1.0.0 to 1.1.0
Downloading package updates...
All package updates downloaded and verified
Extracting package updates...
Extracted users upgrade to 1.1.0
Registering upgrades...
Successfully upgraded 1 package(s): users
pkg update
Refresh repository cache (fetch latest package database).
Examples:
# CLI
php mpm.php pkg update
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/update"
Output:
Repository cache refreshed - 42 packages available
pkg list [FILTER]
List installed packages.
Arguments:
FILTER(optional): Filter by package name (case-insensitive)
Examples:
# CLI
php mpm.php pkg list
php mpm.php pkg list auth
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/list"
curl "http://localhost/mpm.php/$KEY/pkg/list/auth"
Output:
Installed packages:
users v1.0.0 installed: 2025-01-15
auth v2.0.0 installed: 2025-01-15 (depends: users)
database v1.5.0 installed: 2025-01-15 (depends: users)
pkg search
Search available packages.
Arguments:
KEYWORD(required): Search term
Examples:
# CLI
php mpm.php pkg search database
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/search/database"
Output:
Found 3 packages:
mysql-driver v1.0.0 MySQL Database Driver [installed]
Provides MySQL connectivity for applications
postgres-driver v1.0.0 PostgreSQL Driver
PostgreSQL database connector
database-tools v2.0.0 Database Management Tools
Common database utilities and helpers
pkg info
Show detailed package information.
Arguments:
PACKAGE(required): Package name
Examples:
# CLI
php mpm.php pkg info users
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/info/users"
Output:
Package: User Management System
ID: users
Latest: 1.0.0
Description: Complete user management with authentication
Author: Mehrnet Team
License: MIT
Installed: v1.0.0 (on 2025-01-15T10:30:00Z)
Dependencies: none
Available versions:
v1.0.0 - released: 2025-01-15
v0.9.0 - released: 2025-01-01 (requires: auth-lib)
Errors:
- 404: Package not found in repository
pkg unlock
Force remove lock file (for manual recovery).
Examples:
# CLI
php mpm.php pkg unlock
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/unlock"
Output: Empty string on success
When to use: If a package operation crashes, the lock file may remain. This command removes it.
pkg help
Show package manager help.
Examples:
# CLI
php mpm.php pkg help
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/help"
pkg version
Show package manager version.
Examples:
# CLI
php mpm.php pkg version
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/version"
Response Protocol
HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad Request (invalid arguments, operational errors) |
| 403 | Forbidden (invalid API key, path validation failed) |
| 404 | Not Found (command, package, or file not found) |
| 503 | Service Unavailable (all mirrors failed, lock held) |
CLI Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Error (any type) |
POSIX Semantics
- Commands with output return data
- Commands without output return empty string (but still exit 0/200)
- Errors throw exceptions with appropriate codes
Configuration Files
.config/key
Plain text file containing 64-character hex API key.
Format:
abc123def456789... (64 hex characters)
Generation:
$key = bin2hex(random_bytes(32));
Security:
- Auto-generated on first run
- Used for HTTP authentication
- Auto-loaded for CLI mode
- Timing-safe comparison via
hash_equals()
.config/repos.json
Repository mirrors configuration.
Format:
{
"main": [
"https://primary-mirror.com/packages",
"https://secondary-mirror.com/packages"
],
"extra": [
"https://extra-repo.com/packages"
]
}
Mirror Selection:
- Mirrors tried sequentially in array order
- No backoff delay on failure
- First successful mirror wins
.config/packages.json
Installed packages registry.
Format:
{
"createdAt": "2025-01-15T10:00:00Z",
"updatedAt": "2025-01-15T10:30:00Z",
"packages": {
"users": {
"version": "1.0.0",
"installed_at": "2025-01-15T10:30:00Z",
"dependencies": [],
"files": [
"./app/packages/users/handler.php",
"./app/packages/users/module.php"
],
"download_url": "https://mirror.com/users-1.0.0.zip",
"download_time": "2025-01-15T10:29:00Z",
"checksum": "sha256:abc123...",
"repository": "main"
}
}
}
Metadata:
createdAt: Registry creation timeupdatedAt: Last modification timefiles: Array of installed file paths (for removal)download_url: Source mirror usedchecksum: Verified checksum
.config/path.json
Command handler discovery patterns.
Format:
[
"app/packages/[name]/handler.php",
"bin/[name].php"
]
Pattern Matching:
[name]replaced with command name- Patterns searched in array order
- First match wins
Example:
Command users searches:
app/packages/users/handler.phpbin/users.php
Custom Commands
Handler Format
Handlers are PHP files returning a callable:
<?php
return function(array $args): string {
// Process arguments
// Return response or throw exception
return "output";
};
Argument Processing
HTTP Mode:
GET /mpm.php/KEY/mycommand/action/arg1/arg2
↓ ↓ ↓ ↓
command args[0] [1] [2]
CLI Mode:
php mpm.php mycommand action arg1 arg2
↓ ↓ ↓ ↓
command args[0] [1] [2]
Handler receives:
function(array $args): string {
$action = $args[0]; // 'action'
$arg1 = $args[1]; // 'arg1'
$arg2 = $args[2]; // 'arg2'
}
Error Handling
Throw exceptions with HTTP status codes:
// Bad request
throw new \RuntimeException('Invalid input', 400);
// Not found
throw new \RuntimeException('Item not found', 404);
// Forbidden
throw new \RuntimeException('Permission denied', 403);
Note: CLI mode converts all error codes to exit code 1.
Example Handler
<?php
return function(array $args): string {
$action = $args[0] ?? 'help';
switch ($action) {
case 'list':
// Read data
$data = json_decode(
file_get_contents('app/data/items.json'),
true
);
return json_encode($data);
case 'get':
$id = $args[1] ?? null;
if (!$id) {
throw new \RuntimeException('ID required', 400);
}
// Fetch item
return "Item: $id";
case 'create':
$name = $args[1] ?? null;
if (!$name) {
throw new \RuntimeException('Name required', 400);
}
// Create item
return "Created: $name";
case 'help':
return "Actions: list, get <id>, create <name>";
default:
throw new \RuntimeException("Unknown action: $action", 404);
}
};
Architecture
File Structure
.
├── mpm.php # Main executable (2300 lines)
├── .config/ # Configuration (auto-created)
│ ├── key # API key
│ ├── repos.json # Repository mirrors
│ ├── packages.json # Installed packages
│ └── path.json # Handler patterns
├── .cache/ # Cache (auto-created)
│ ├── mpm.lock # Lock file
│ └── *.zip # Downloaded packages
└── app/packages/ # Installed packages
└── [package]/
└── handler.php
Execution Flow
1. Runtime Detection (HTTP/CLI)
↓
2. Initialize Config Files
↓
3. Parse Request (PATH_INFO or argv)
↓
4. Validate API Key
↓
5. Execute Command
├─ Built-in Command
├─ Package Manager
└─ Custom Handler
↓
6. Send Response (HTTP headers or STDOUT/STDERR)
Package Manager Flow
Installation:
1. Resolve Dependencies (DFS topological sort)
↓
2. Download All Packages (with checksum verification)
↓
3. Extract All Packages (with conflict resolution)
↓
4. Register in .config/packages.json (atomic)
Key Features:
- Dependency Resolution: O(V + E) complexity
- Circular Detection: O(1) set lookups
- Checksum Verification: SHA256 with timing-safe comparison
- Mirror Failover: Sequential retry, no backoff
- Atomic Operations: All-or-nothing installations
- Conflict Resolution: Existing files renamed with
-2,-3, etc.
Security Model
Authentication:
- 64-character random API key
hash_equals()timing-safe comparison- Auto-loaded in CLI mode
Path Validation:
- Blocks absolute paths (
/etc/passwd) - Blocks directory traversal (
../../../) - Applied to all file operations
- Blocks absolute paths (
Package Security:
- SHA256 checksum verification
- Zip traversal detection
- HTTPS mirror enforcement
Performance
Benchmarks
- Application execution: <1ms (measured via profiling)
- Built-in server overhead: 1000ms+ (use proper web server in production)
- Package download: Depends on network and mirror speed
- Dependency resolution: O(V + E) where V = packages, E = dependencies
Optimization Tips
- Use production web server (Nginx, Apache)
- Enable PHP opcode cache (OPcache)
- Use CDN for package mirrors
- Minimize custom handler complexity
- Cache package database locally
Troubleshooting
"Invalid API key"
HTTP 403
Cause: Missing or incorrect API key
Solution:
# Check key
cat .config/key
# Regenerate key (delete and run again)
rm .config/key
php mpm.php ls
"API key not found" (CLI)
Exit code 1
Cause: .config/key file doesn't exist
Solution:
# Initialize shell
php mpm.php ls
"Command not found"
HTTP 404 / Exit code 1
Cause: Handler file not found
Solution:
# Check handler patterns
cat .config/path.json
# Verify handler exists
ls app/packages/[command]/handler.php
ls bin/[command].php
"Another pkg operation is in progress"
HTTP 503 / Exit code 1
Cause: Lock file exists
Solution:
# Wait for operation to complete, or force unlock
php mpm.php pkg unlock
# Or manually remove
rm .cache/mpm.lock
"Package not found"
HTTP 404 / Exit code 1
Cause: Package not in repository
Solution:
# Refresh repository cache
php mpm.php pkg update
# Search for package
php mpm.php pkg search keyword
"Circular dependency"
HTTP 400 / Exit code 1
Cause: Package dependency cycle (A → B → C → A)
Solution: Review package dependencies and remove the cycle. This is a package repository issue.
"Cannot remove - required by"
HTTP 400 / Exit code 1
Cause: Other packages depend on the target package
Solution:
# Remove dependent packages first
php mpm.php pkg del dependent-package
php mpm.php pkg del target-package
Permission Errors
Cause: File system permissions
Solution:
# Fix permissions
chmod 755 .
chmod 644 mpm.php
chmod 755 .config
chmod 644 .config/*
chmod 755 .cache
Package Development Guide
Complete guide for creating, testing, and distributing MPM (Mehr's Package Manager) packages.
Quick Start
Create a minimal package in 3 steps:
# 1. Create package structure
mkdir -p mypackage
cd mypackage
# 2. Create handler
cat > handler.php << 'EOF'
<?php
return function(array $args): string {
return "Hello from mypackage!";
};
EOF
# 3. Create metadata
cat > package.json << 'EOF'
{
"id": "mypackage",
"name": "My Package",
"version": "1.0.0",
"description": "A simple package",
"author": "Your Name",
"license": "MIT",
"dependencies": []
}
EOF
# Test it
cd ..
mkdir -p app/packages/mypackage
cp -r mypackage/* app/packages/mypackage/
php mpm.php mypackage
Package Structure
Required Files
mypackage/
├── handler.php # REQUIRED: Entry point
└── package.json # REQUIRED: Metadata
Optional Structure
mypackage/
├── handler.php # Entry point (required)
├── package.json # Metadata (required)
├── lib/ # Library code
│ ├── MyClass.php
│ └── helpers.php
├── data/ # Data files
│ └── config.json
├── views/ # Templates
│ └── template.html
└── README.md # Package documentation
File Extraction
Packages extract to project root preserving paths:
Package contains:
handler.php
module.php
lib/Database.php
data/schema.json
Extracts to:
./handler.php
./module.php
./lib/Database.php
./data/schema.json
For Mehr framework modules, use:
app/packages/[name]/handler.php
app/packages/[name]/module.php
Handler Implementation
Basic Handler
<?php
return function(array $args): string {
// Single action
return "Hello, World!";
};
Multi-Action Handler
<?php
return function(array $args): string {
$action = $args[0] ?? 'help';
switch ($action) {
case 'list':
return handleList();
case 'get':
$id = $args[1] ?? null;
if (!$id) {
throw new \RuntimeException('ID required', 400);
}
return handleGet($id);
case 'create':
$name = $args[1] ?? null;
if (!$name) {
throw new \RuntimeException('Name required', 400);
}
return handleCreate($name);
case 'delete':
$id = $args[1] ?? null;
if (!$id) {
throw new \RuntimeException('ID required', 400);
}
return handleDelete($id);
case 'help':
return <<<'EOF'
Available actions:
list - List all items
get <id> - Get item by ID
create <name> - Create new item
delete <id> - Delete item
EOF;
default:
throw new \RuntimeException("Unknown action: $action", 404);
}
};
function handleList(): string {
return "item1\nitem2\nitem3";
}
function handleGet(string $id): string {
// Load from file, database, etc.
return "Item data for: $id";
}
function handleCreate(string $name): string {
// Create and persist
return "Created: $name";
}
function handleDelete(string $id): string {
// Remove item
return "Deleted: $id";
}
Argument Processing
Arguments are identical in both HTTP and CLI modes:
HTTP:
GET /mpm.php/KEY/users/create/alice/alice@example.com
↓ ↓ ↓ ↓
command args[0] args[1] args[2]
CLI:
php mpm.php users create alice alice@example.com
↓ ↓ ↓ ↓
command args[0] args[1] args[2]
Handler:
function(array $args): string {
$action = $args[0]; // 'create'
$name = $args[1]; // 'alice'
$email = $args[2]; // 'alice@example.com'
}
File Operations
Handlers execute in mpm.php directory. Use relative paths:
<?php
return function(array $args): string {
// Read data file
$data = json_decode(
file_get_contents('app/data/items.json'),
true
);
// Modify data
$data['new_item'] = $args[0] ?? 'default';
// Write back
file_put_contents(
'app/data/items.json',
json_encode($data, JSON_PRETTY_PRINT)
);
return "Updated";
};
Error Handling
<?php
return function(array $args): string {
$id = $args[0] ?? null;
// Validation
if (!$id) {
throw new \RuntimeException('ID required', 400);
}
if (!is_numeric($id)) {
throw new \RuntimeException('ID must be numeric', 400);
}
// File operations
$file = "app/data/$id.json";
if (!file_exists($file)) {
throw new \RuntimeException('Item not found', 404);
}
try {
$content = file_get_contents($file);
$data = json_decode($content, true);
if (!$data) {
throw new \RuntimeException('Invalid JSON', 400);
}
return json_encode($data);
} catch (\Throwable $e) {
throw new \RuntimeException(
"Failed to read item: {$e->getMessage()}",
400
);
}
};
Response Formats
Plain Text:
return "item1\nitem2\nitem3";
JSON:
return json_encode([
'status' => 'success',
'items' => ['item1', 'item2', 'item3']
]);
Empty (POSIX):
return ''; // Success with no output
Package Metadata
package.json Format
{
"id": "mypackage",
"name": "My Package",
"version": "1.0.0",
"description": "Package description",
"author": "Your Name",
"license": "MIT",
"dependencies": ["dependency1", "dependency2"]
}
Field Specifications
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Lowercase, no spaces, used in URLs |
name |
string | Yes | Human-readable name |
version |
string | Yes | Semantic versioning (X.Y.Z) |
description |
string | Yes | Brief description |
author |
string | No | Package author |
license |
string | No | License identifier (MIT, Apache-2.0, etc.) |
dependencies |
array | No | Array of package IDs |
Dependency Declaration
{
"id": "auth",
"dependencies": ["users", "session"]
}
The package manager:
- Resolves transitive dependencies automatically
- Detects circular dependencies
- Installs in correct order (dependencies before dependents)
- Skips already-installed packages
Testing Packages
Manual Testing (CLI)
# Install package manually
mkdir -p app/packages/mypackage
cp -r mypackage/* app/packages/mypackage/
# Test commands
php mpm.php mypackage
php mpm.php mypackage list
php mpm.php mypackage get 123
php mpm.php mypackage create "Test Item"
Manual Testing (HTTP)
# Start server
php -S localhost:8000
# Get API key
KEY=$(cat .config/key)
# Test commands
curl "http://localhost:8000/mpm.php/$KEY/mypackage"
curl "http://localhost:8000/mpm.php/$KEY/mypackage/list"
curl "http://localhost:8000/mpm.php/$KEY/mypackage/get/123"
Testing Checklist
- All actions work correctly
- Error handling works (invalid input)
- Required arguments are validated
- File operations succeed
- Both CLI and HTTP modes work identically
- Help action documents all commands
- Dependencies are declared in package.json
Distribution
Creating Package ZIP
cd mypackage
zip -r ../mypackage-1.0.0.zip .
# Verify contents
unzip -l ../mypackage-1.0.0.zip
Calculate Checksum
sha256sum mypackage-1.0.0.zip
# Output: abc123def456... mypackage-1.0.0.zip
Automated Build Scripts
For reusable packaging, create shell scripts that automate the build process. This is especially useful for packaging third-party assets like icons, fonts, or JavaScript libraries.
Basic structure:
#!/bin/sh
set -e
PKG="mypackage"
DES="Package description"
VER="1.0.0"
URL="https://example.com/source.zip"
OUT="${PKG}-${VER}.zip"
TMP="$(mktemp -d)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$TMP"
# Download and extract source
wget -q "$URL" -O src.zip
unzip -q src.zip
# Restructure for your format
mkdir -p "assets/category/${PKG}"
mv source-files/* "assets/category/${PKG}/"
# Create package zip
zip -rq "$OUT" assets
# Calculate metadata
CHECKSUM=$(sha256sum "$OUT" | cut -d' ' -f1)
SIZE=$(wc -c < "$OUT" | tr -d ' ')
RELEASED=$(date +%Y-%m-%d)
# Move to repository
mv "$OUT" "${SCRIPT_DIR}/../main/"
# Update database.json
DB="${SCRIPT_DIR}/../main/database.json"
jq --arg id "$PKG" \
--arg name "$PKG" \
--arg desc "$DES" \
--arg ver "$VER" \
--arg checksum "sha256:$CHECKSUM" \
--argjson size "$SIZE" \
--arg url "$OUT" \
--arg released "$RELEASED" \
'.packages[$id] = {
name: $name,
description: $desc,
author: "mpm-bot",
license: "MIT",
versions: { ($ver): { version: $ver, dependencies: [], checksum: $checksum, size: $size, download_url: $url, released_at: $released } },
latest: $ver
}' "$DB" > "${DB}.tmp" && mv "${DB}.tmp" "$DB"
rm -rf "$TMP"
echo "[+] Built ${PKG} v${VER}"
Example: Icon package (heroicons)
#!/bin/sh
set -e
PKG="heroicons"
DES="A set of free MIT-licensed high-quality SVG icons for UI development."
VER="2.2.0"
URL="https://github.com/tailwindlabs/heroicons/archive/refs/tags/v${VER}.zip"
OUT="${PKG}-${VER}.zip"
TMP="$(mktemp -d)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$TMP"
wget -q "$URL" -O src.zip
unzip -q src.zip
mkdir -p "assets/icons/${PKG}"
mv "heroicons-${VER}/src/"* "assets/icons/${PKG}/"
zip -rq "$OUT" assets
CHECKSUM=$(sha256sum "$OUT" | cut -d' ' -f1)
SIZE=$(wc -c < "$OUT" | tr -d ' ')
RELEASED=$(date +%Y-%m-%d)
mv "$OUT" "${SCRIPT_DIR}/../main/"
DB="${SCRIPT_DIR}/../main/database.json"
jq --arg id "$PKG" --arg name "$PKG" --arg desc "$DES" --arg ver "$VER" \
--arg checksum "sha256:$CHECKSUM" --argjson size "$SIZE" \
--arg url "$OUT" --arg released "$RELEASED" \
'.packages[$id] = {
name: $name, description: $desc, author: "mpm-bot", license: "MIT",
versions: { ($ver): { version: $ver, dependencies: [], checksum: $checksum, size: $size, download_url: $url, released_at: $released } },
latest: $ver
}' "$DB" > "${DB}.tmp" && mv "${DB}.tmp" "$DB"
rm -rf "$TMP"
echo "[+] Built ${PKG} v${VER}"
Example: JavaScript library (htmx)
#!/bin/sh
set -e
PKG="htmx"
DES="High power tools for HTML."
VER="2.0.4"
URL="https://unpkg.com/htmx.org@${VER}/dist/htmx.min.js"
OUT="${PKG}-${VER}.zip"
TMP="$(mktemp -d)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$TMP"
mkdir -p "assets/js"
wget -q "$URL" -O "assets/js/htmx.min.js"
zip -rq "$OUT" assets
CHECKSUM=$(sha256sum "$OUT" | cut -d' ' -f1)
SIZE=$(wc -c < "$OUT" | tr -d ' ')
RELEASED=$(date +%Y-%m-%d)
mv "$OUT" "${SCRIPT_DIR}/../main/"
DB="${SCRIPT_DIR}/../main/database.json"
jq --arg id "$PKG" --arg name "$PKG" --arg desc "$DES" --arg ver "$VER" \
--arg checksum "sha256:$CHECKSUM" --argjson size "$SIZE" \
--arg url "$OUT" --arg released "$RELEASED" \
'.packages[$id] = {
name: $name, description: $desc, author: "mpm-bot", license: "BSD-2-Clause",
versions: { ($ver): { version: $ver, dependencies: [], checksum: $checksum, size: $size, download_url: $url, released_at: $released } },
latest: $ver
}' "$DB" > "${DB}.tmp" && mv "${DB}.tmp" "$DB"
rm -rf "$TMP"
echo "[+] Built ${PKG} v${VER}"
Repository structure:
mpm-repo/
├── scripts/
│ ├── heroicons.sh
│ ├── htmx.sh
│ ├── alpinejs.sh
│ └── ...
└── main/
├── database.json
├── heroicons-2.2.0.zip
├── htmx-2.0.4.zip
└── ...
Build all packages:
for script in scripts/*.sh; do ./$script; done
Repository database.json
{
"version": 1,
"packages": {
"mypackage": {
"name": "My Package",
"description": "Package description",
"author": "Your Name",
"license": "MIT",
"versions": {
"1.0.0": {
"version": "1.0.0",
"dependencies": [],
"checksum": "sha256:abc123def456...",
"size": 2048,
"download_url": "mypackage-1.0.0.zip",
"released_at": "2025-01-15"
}
},
"latest": "1.0.0"
}
}
}
Files are tracked locally during extraction for clean uninstallation.
GitHub Hosting
# Create repository structure
mkdir -p my-packages/main
cd my-packages/main
# Add package and database
cp /path/to/mypackage-1.0.0.zip .
cat > database.json << 'EOF'
{
"version": 1,
"packages": { ... }
}
EOF
# Push to GitHub
cd ..
git init
git add .
git commit -m "Add mypackage"
git remote add origin https://github.com/user/my-packages.git
git push -u origin main
# Users configure:
# "main": ["https://github.com/user/my-packages/raw/main/main"]
Best Practices
Input Validation
// BAD: No validation
$id = $args[0];
return processId($id);
// GOOD: Validate and sanitize
$id = $args[0] ?? null;
if (!$id || !is_numeric($id) || $id < 1) {
throw new \RuntimeException('Invalid ID', 400);
}
return processId((int)$id);
Security
// BAD: Arbitrary file access
$file = $args[0];
return file_get_contents($file); // Vulnerable to ../../../etc/passwd
// GOOD: Restrict to safe directory
$file = basename($args[0] ?? ''); // Remove path components
$path = "app/data/$file";
if (!file_exists($path)) {
throw new \RuntimeException('File not found', 404);
}
return file_get_contents($path);
Error Messages
// BAD: Generic error
throw new \RuntimeException('Error', 400);
// GOOD: Descriptive error
throw new \RuntimeException('User ID must be a positive integer', 400);
Code Organization
// BAD: Everything in handler
return function(array $args): string {
// 500 lines of code...
};
// GOOD: Use lib/ directory
return function(array $args): string {
require_once __DIR__ . '/lib/Manager.php';
$manager = new Manager();
return $manager->handle($args);
};
Dependencies
// GOOD: Check if Composer libraries available
if (file_exists(__DIR__ . '/../../vendor/autoload.php')) {
require_once __DIR__ . '/../../vendor/autoload.php';
// Use libraries
} else {
throw new \RuntimeException('Composer dependencies missing', 500);
}
Examples
Simple Counter
<?php
return function(array $args): string {
$file = 'app/data/counter.txt';
if (!file_exists($file)) {
file_put_contents($file, '0');
}
$count = (int)file_get_contents($file);
$count++;
file_put_contents($file, (string)$count);
return "Count: $count";
};
JSON CRUD
<?php
return function(array $args): string {
$action = $args[0] ?? 'list';
$file = 'app/data/items.json';
// Load data
$items = [];
if (file_exists($file)) {
$items = json_decode(file_get_contents($file), true) ?? [];
}
switch ($action) {
case 'list':
return json_encode($items);
case 'add':
$name = $args[1] ?? null;
if (!$name) {
throw new \RuntimeException('Name required', 400);
}
$items[] = [
'id' => count($items) + 1,
'name' => $name,
'created_at' => date('Y-m-d H:i:s')
];
file_put_contents($file, json_encode($items, JSON_PRETTY_PRINT));
return "Added: $name";
case 'get':
$id = (int)($args[1] ?? 0);
foreach ($items as $item) {
if ($item['id'] === $id) {
return json_encode($item);
}
}
throw new \RuntimeException('Item not found', 404);
default:
throw new \RuntimeException('Unknown action', 404);
}
};
Mehr Framework Module
mymodule/
├── app/
│ └── packages/
│ └── mymodule/
│ ├── handler.php # Shell command handler
│ ├── module.php # Module implementation
│ ├── manifest.json # Mehr manifest
│ └── migrations/ # Database migrations
│ └── 001_create_table.php
└── package.json # Distribution metadata
handler.php:
<?php
return function(array $args): string {
require_once __DIR__ . '/module.php';
$module = new MyModule();
return $module->handleCommand($args);
};
package.json:
{
"id": "mymodule",
"name": "My Module",
"version": "1.0.0",
"description": "Mehr framework module",
"dependencies": ["core"]
}
راهنمای نصب و تنظیمات
راهنمای کامل برای نصب، تنظیم و شروع استفاده از MPM - مدیریت بسته های Mehr.
نصب
نصب سریع
# دانلود mpm.php
wget https://raw.githubusercontent.com/mehrnet/mpm/main/mpm.php
# تعیین دسترسی ها
chmod 644 mpm.php
# تست نصب
php mpm.php echo "Hello, World!"
نصب دستی
- فایل
mpm.phpرا از مخزن دانلود کنید - آن را در ریشه پروژه یا دایرکتوری سرور وب قرار دهید
- اطمینان حاصل کنید که PHP 7.0+ نصب است
- تایید کنید که افزونه
ZipArchiveفعال است (برای مدیریت بسته)
الزامات
- PHP: نسخه 7.0 یا بالاتر
- افزونه ها:
ZipArchive(برای مدیریت بسته) - دسترسی ها: دسترسی خواندن/نوشتن به دایرکتوری پروژه
اولین اجرا
در اولین اجرا، MPM به طور خودکار فایل های پیکربندی را تولید می کند:
# هر فرمندی را برای مقدار دهی اولیه اجرا کنید
php mpm.php ls
این فایل های زیر را ایجاد می کند:
.config/
├── key # کلید API (64 کاراکتر هگزادسیمال)
├── repos.json # پیکربندی آینه های مخزن
├── packages.json # ثبت بسته های نصب شده
└── path.json # الگوهای کننده فرماند
کلید API
کلید API شما در اولین دسترسی HTTP نمایش داده می شود یا به فایل .config/key در اولین اجرای CLI ذخیره می شود:
# مشاهده کلید API شما
cat .config/key
مهم: کلید API خود را محفوظ نگه دارید. هر کسی با دسترسی می تواند فرماند اجرا کند.
پیکربندی
آینه های مخزن
برای سفارشی کردن مخازن بسته، .config/repos.json را ویرایش کنید:
{
"main": [
"https://raw.githubusercontent.com/mehrnet/mpm-repo/refs/heads/main/main"
]
}
هر ورودی یک URL کامل به پوشه مخزن است که شامل database.json و فایلهای ZIP بسته میباشد. آینهها به ترتیب در صورت شکست امتحان میشوند.
الگوهای کننده فرماند
برای سفارشی کردن محلی جستجوی shell برای فرماندهای سفارشی، .config/path.json را ویرایش کنید:
[
"app/packages/[name]/handler.php",
"bin/[name].php",
"custom/handlers/[name].php"
]
جانشین [name] با نام فرمند می شود.
پیکربندی خاص محیط
برای محیط های مختلف، می توانید:
دایرکتوری های پیکربندی مختلف استفاده کنید:
// اصلاح ثابت ها در mpm.php (توصیه نشده است) const DIR_CONFIG = '.config';Symlink پیکربندی:
ln -s .config.production .configمتغیرهای محیطی: دسترسی از طریق
php mpm.php env list
شروع کار
حالت CLI (توصیه برای توسعه)
# نمایش فهرست فایل ها
php mpm.php ls
# خواندن محتویات فایل
php mpm.php cat mpm.php | head -20
# ایجاد دایرکتوری
php mpm.php mkdir tmp
# کپی فایل ها
php mpm.php cp mpm.php shell.backup.php
# مدیریت بسته ها
php mpm.php pkg search database
php mpm.php pkg add users
php mpm.php pkg list
php mpm.php pkg upgrade
حالت HTTP (تولید)
سرور توسعه PHP را شروع کنید (تست کردن):
php -S localhost:8000کلید API خود را دریافت کنید:
KEY=$(cat .config/key)فرماند ها را اجرا کنید:
# نمایش فهرست فایل ها curl "http://localhost:8000/mpm.php/$KEY/ls" # خواندن فایل curl "http://localhost:8000/mpm.php/$KEY/cat/README.md" # مدیریت بسته ها curl "http://localhost:8000/mpm.php/$KEY/pkg/list" curl "http://localhost:8000/mpm.php/$KEY/pkg/add/users"
سرور وب تولید
برای تولید، از سرور وب مناسب استفاده کنید:
Nginx:
server {
listen 80;
server_name your-domain.com;
root /var/www/shell;
index mpm.php;
location / {
try_files $uri $uri/ /mpm.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index mpm.php;
include fastcgi_params;
}
}
Apache (.htaccess):
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ mpm.php/$1 [L,QSA]
توسعه بسته
ایجاد بسته
ساختار بسته ایجاد کنید:
mypackage/ ├── handler.php # لازم: نقطه ورود ├── package.json # لازم: فرادادها ├── lib/ # اختیاری: کد کتابخانه └── data/ # اختیاری: فایل های دادهhandler.php بنویسید:
<?php return function(array $args): string { $action = $args[0] ?? 'help'; switch ($action) { case 'list': return "Item 1\nItem 2\nItem 3"; case 'create': $name = $args[1] ?? null; if (!$name) { throw new \RuntimeException('نام لازم است', 400); } return "Created: $name"; default: return "Actions: list, create"; } };package.json ایجاد کنید:
{ "id": "mypackage", "name": "My Package", "version": "1.0.0", "description": "Package description", "author": "Your Name", "license": "MIT", "dependencies": [] }
فیلدهای فرادادهای بسته
| فیلد | نوع | لازم | توضیح |
|---|---|---|---|
id |
رشته | بله | کوچک، بدون فاصله، استفاده شده در URL ها |
name |
رشته | بله | نام قابل خواندن |
version |
رشته | بله | نسخه درجه بندی معنایی (X.Y.Z) |
description |
رشته | بله | توضیح مختصر |
author |
رشته | نه | نویسنده بسته |
license |
رشته | نه | شناسه مجوز (MIT, Apache-2.0, و غیره) |
dependencies |
آرایه | نه | آرایه شناسه های بسته |
تست بسته شما
حالت CLI:
# نصب دستی
mkdir -p app/packages/mypackage
cp -r mypackage/* app/packages/mypackage/
# تست فرماند ها
php mpm.php mypackage list
php mpm.php mypackage create item-name
حالت HTTP:
KEY=$(cat .config/key)
curl "http://localhost:8000/mpm.php/$KEY/mypackage/list"
curl "http://localhost:8000/mpm.php/$KEY/mypackage/create/item-name"
بهترین عملکردها
اعتبارسنجی ورودی:
$id = $args[0] ?? null; if (!$id || !is_numeric($id)) { throw new \RuntimeException('شناسه نامعتبر است', 400); }مدیریت خطا:
try { $data = json_decode(file_get_contents('data.json'), true); if (!$data) { throw new \RuntimeException('JSON نامعتبر است', 400); } } catch (\Throwable $e) { throw new \RuntimeException("Error: {$e->getMessage()}", 400); }امنیت:
// نادرست: استفاده مستقیم از ورودی کاربر $file = $args[0]; return file_get_contents($file); // آسیب پذیر برای پیمایش مسیر // درست: اعتبار سنجی و محدود کردن مسیرها $file = basename($args[0] ?? ''); // حذف اجزای مسیر $path = "app/data/$file"; if (!file_exists($path)) { throw new \RuntimeException('فایل پیدا نشد', 404); } return file_get_contents($path);
تنظیم مخزن
ایجاد مخزن بسته
ساختار مخزن ایجاد کنید:
my-repo/ └── main/ ├── database.json ├── mypackage-1.0.0.zip └── otherapkg-1.0.0.zipZIP بسته بسازید:
cd mypackage zip -r ../my-repo/main/mypackage-1.0.0.zip .Checksum محاسبه کنید:
sha256sum my-repo/main/mypackage-1.0.0.zip # Output: abc123def456... mypackage-1.0.0.zipdatabase.json ایجاد کنید:
{ "version": 1, "packages": { "mypackage": { "name": "My Package", "description": "Package description", "versions": { "1.0.0": { "version": "1.0.0", "dependencies": [], "checksum": "sha256:abc123def456...", "size": 2048, "download_url": "mypackage-1.0.0.zip", "released_at": "2025-01-15" } }, "latest": "1.0.0", "author": "Your Name", "license": "MIT" } } }مخزن را میزبانی کنید:
- آپلود به GitHub:
https://github.com/user/repo/raw/main/main/ - استفاده از CDN یا میزبانی استاتیک
- اطمینان حاصل کنید HTTPS فعال است
- آپلود به GitHub:
پوسته را برای استفاده از مخزن شما تنظیم کنید:
.config/repos.jsonرا ویرایش کنید:{ "main": ["https://github.com/user/repo/raw/main/main"] }
مثال میزبانی GitHub
# مخزن ایجاد کنید
mkdir -p my-packages/main
cd my-packages
# بسته ها اضافه کنید
cp /path/to/mypackage-1.0.0.zip main/
cat > main/database.json << 'EOF'
{
"version": 1,
"packages": { ... }
}
EOF
# به GitHub فشار دهید
git init
git add .
git commit -m "Add packages"
git remote add origin https://github.com/user/my-packages.git
git push -u origin main
# کاربران تنظیم می کنند: https://github.com/user/my-packages/raw/main/main
استقرار
توسعه
# سرور درون ساخت PHP را اجرا کنید
php -S localhost:8000
# فرماند ها را تست کنید
php mpm.php pkg list
curl "http://localhost:8000/mpm.php/$(cat .config/key)/pkg/list"
Staging/تولید
سرور وب مناسب استفاده کنید (Nginx, Apache, Caddy)
HTTPS فعال کنید برای امنیت کلید API
دسترسی های محدود تعیین کنید:
chmod 644 mpm.php chmod 700 .config chmod 600 .config/keyPHP-FPM را پیکربندی کنید برای عملکرد بهتر
نظارت و ثبت را راه اندازی کنید
پشتیبان گیری منظم از دایرکتوری
.config/
استقرار Docker
FROM php:8.1-fpm
# نصب افزونه ها
RUN docker-php-ext-install zip
# کپی shell
COPY mpm.php /var/www/html/
# تعیین دسترسی ها
RUN chown -R www-data:www-data /var/www/html
WORKDIR /var/www/html
تقویت امنیت
محدود کردن دسترسی کلید API:
chmod 600 .config/key chown www-data:www-data .config/keyاستفاده از کلیدهای خاص محیط
فعال کردن HTTPS فقط در تولید
محدود کردن نرخ در سطح سرور وب
نظارت بر تلاش های احراز هویت ناموفق
به روز رسانی های امنیتی منظم
حل مسائل
مسائل عام
خطاهای "اجازه رد شد":
# رفع دسترسی ها
chmod 755 .
chmod 644 mpm.php
chmod -R 755 .config
"ZipArchive پیدا نشد":
# نصب افزونه
sudo apt-get install php-zip # Debian/Ubuntu
sudo yum install php-zip # CentOS/RHEL
"کلید API پیدا نشد" (حالت CLI):
# تولید کلید به صورت دستی
php mpm.php ls # این .config/key را ایجاد می کند
مسائل فایل قفل:
# اگر گیر کرد قفل اجباری باز کنید
php mpm.php pkg unlock
راهنمای مرجع استفاده
مرجع فرماند کامل و مستندات API برای MPM - مدیریت بسته های Mehr.
فرمت های درخواست
حالت HTTP
فرمت:
GET /mpm.php/API_KEY/COMMAND/ARG0/ARG1/ARG2/...
مثال ها:
# دریافت کلید API
KEY=$(cat .config/key)
# نمایش فهرست فایل ها
curl "http://localhost/mpm.php/$KEY/ls"
# خواندن فایل
curl "http://localhost/mpm.php/$KEY/cat/README.md"
# نمایش متن با آرگومان های متعدد
curl "http://localhost/mpm.php/$KEY/echo/hello/world"
# مدیریت بسته ها
curl "http://localhost/mpm.php/$KEY/pkg/search/database"
curl "http://localhost/mpm.php/$KEY/pkg/add/users"
پاسخ:
- موفقیت: HTTP 200 با پاسخ متنی ساده
- خطا: HTTP 4xx/5xx با پیام خطا
حالت CLI
فرمت:
php mpm.php COMMAND ARG0 ARG1 ARG2 ...
مثال ها:
# نمایش فهرست فایل ها
php mpm.php ls
# خواندن فایل
php mpm.php cat README.md
# نمایش متن با آرگومان های متعدد
php mpm.php echo hello world
# مدیریت بسته ها
php mpm.php pkg search database
php mpm.php pkg add users
پاسخ:
- موفقیت: خروجی به STDOUT، کد خروج 0
- خطا: خروجی به STDERR با پیشوند "Error: "، کد خروج 1
فرماندهای درون ساخت
ls [path]
نمایش محتویات پوشه.
آرگومان ها:
path(اختیاری): پوشه ای برای نمایش (پیش فرض: پوشه جاری)
مثال ها:
# CLI
php mpm.php ls
php mpm.php ls app
php mpm.php ls app/packages
# HTTP
curl "http://localhost/mpm.php/$KEY/ls"
curl "http://localhost/mpm.php/$KEY/ls/app"
خروجی:
نام فایل های جدا شده با فاصله (بدون . و ..)
خطاها:
- 404: پوشه پیدا نشد
- 400: نمی توان پوشه را خواند
- 403: اعتبارسنجی مسیر ناموفق (مسیر مطلق یا
..)
cat
خواندن محتویات فایل.
آرگومان ها:
file(اجباری): مسیر فایل
مثال ها:
# CLI
php mpm.php cat README.md
php mpm.php cat .config/key
# HTTP
curl "http://localhost/mpm.php/$KEY/cat/README.md"
خروجی: محتویات فایل (حفظ قالب بندی و خطوط جدید)
خطاها:
- 400: آرگومان فایل وجود ندارد یا فایل نیست
- 404: فایل پیدا نشد
- 403: اعتبارسنجی مسیر ناموفق
rm
حذف فایل.
آرگومان ها:
file(اجباری): مسیر فایل
مثال ها:
# CLI
php mpm.php rm temp.txt
php mpm.php rm logs/old.log
# HTTP
curl "http://localhost/mpm.php/$KEY/rm/temp.txt"
خروجی: رشته خالی در صورت موفقیت (رفتار POSIX)
خطاها:
- 400: آرگومان فایل وجود ندارد، فایل نیست یا نمی توان حذف کرد
- 404: فایل پیدا نشد
- 403: اعتبارسنجی مسیر ناموفق
mkdir
ایجاد پوشه (بصورت بازگشتی).
آرگومان ها:
path(اجباری): مسیر پوشه ای برای ایجاد
مثال ها:
# CLI
php mpm.php mkdir uploads
php mpm.php mkdir app/data/cache
# HTTP
curl "http://localhost/mpm.php/$KEY/mkdir/uploads"
خروجی: رشته خالی در صورت موفقیت
خطاها:
- 400: آرگومان مسیر وجود ندارد، قبلا وجود دارد یا نمی توان ایجاد کرد
- 403: اعتبارسنجی مسیر ناموفق
cp
کپی فایل.
آرگومان ها:
src(اجباری): مسیر فایل منبعdst(اجباری): مسیر فایل مقصد
مثال ها:
# CLI
php mpm.php cp config.json config.backup.json
php mpm.php cp README.md docs/README.md
# HTTP
curl "http://localhost/mpm.php/$KEY/cp/config.json/config.backup.json"
خروجی: رشته خالی در صورت موفقیت
خطاها:
- 400: آرگومان ها وجود ندارند، منبع فایل نیست یا نمی توان کپی کرد
- 404: فایل منبع پیدا نشد
- 403: اعتبارسنجی مسیر ناموفق
echo ...
نمایش آرگومان های متنی.
آرگومان ها:
text...(اجباری): یک یا بیشتر آرگومان متنی
مثال ها:
# CLI
php mpm.php echo hello
php mpm.php echo hello world "from shell"
# HTTP
curl "http://localhost/mpm.php/$KEY/echo/hello"
curl "http://localhost/mpm.php/$KEY/echo/hello/world"
خروجی: آرگومان های جدا شده با فاصله
env [action] [name]
مدیریت متغیرهای محیطی.
اقدام ها:
list(پیش فرض): نمایش تمام متغیرهای محیطیget <name>: دریافت مقدار متغیر خاص
مثال ها:
# CLI
php mpm.php env list
php mpm.php env get PATH
php mpm.php env get HOME
# HTTP
curl "http://localhost/mpm.php/$KEY/env/list"
curl "http://localhost/mpm.php/$KEY/env/get/PATH"
خروجی:
list: یک متغیر در هر سطر (KEY=value)get: فقط مقدار متغیر
خطاها:
- 404: متغیر پیدا نشد یا اقدام نامعلوم
- 400: نام متغیر برای
getوجود ندارد
مدیریت بسته
pkg add [PACKAGE...]
نصب بسته ها با حل خودکار وابستگی ها.
آرگومان ها:
PACKAGE...(اجباری): یک یا بیشتر نام بسته
مثال ها:
# CLI
php mpm.php pkg add users
php mpm.php pkg add users auth database
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/add/users"
curl "http://localhost/mpm.php/$KEY/pkg/add/users/auth"
فرایند:
- بازیابی پایگاه داده مخزن
- حل وابستگی ها (DFS مرتب سازی توپولوژیکی)
- دانلود تمام بسته ها با تایید checksum
- استخراج تمام بسته ها به ریشه پروژه
- ثبت بسته ها به طور اتمی
خروجی:
بسته های نصب شامل: dependency1, dependency2, users
در حال دانلود بسته ها...
تمام بسته ها دانلود و تایید شدند
در حال استخراج بسته ها...
استخراج شده dependency1 (1.0.0)
استخراج شده dependency2 (2.0.0)
استخراج شده users (3.0.0)
در حال ثبت بسته ها...
موفقیت آمیز نصب 3 بسته: dependency1, dependency2, users
خطاها:
- 404: بسته پیدا نشد
- 400: وابستگی دایره ای شناسایی شد
- 503: تمام آینه ها ناموفق بودند یا قفل نگه داشته شده است
pkg del
حذف بسته (در صورتی که بسته های دیگر به آن وابسته باشند، شکست می خورد).
آرگومان ها:
PACKAGE(اجباری): نام بسته
مثال ها:
# CLI
php mpm.php pkg del users
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/del/users"
خروجی:
بسته حذف شد: users (نسخه 1.0.0) - 15 فایل حذف شد، 3 پوشه خالی حذف شد
خطاها:
- 404: بسته نصب نشده است
- 400: بسته توسط بسته های دیگر لازم است
- 503: قفل نگه داشته شده است
pkg upgrade [PACKAGE]
ارتقای تمام بسته ها یا بسته خاص به آخرین نسخه.
آرگومان ها:
PACKAGE(اختیاری): نام بسته (اگر حذف شود، تمام را ارتقا دهد)
مثال ها:
# CLI
php mpm.php pkg upgrade # ارتقای تمام
php mpm.php pkg upgrade users # ارتقای خاص
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/upgrade"
curl "http://localhost/mpm.php/$KEY/pkg/upgrade/users"
خروجی:
ارتقای users از 1.0.0 به 1.1.0
در حال دانلود بروز رسانی بسته ها...
تمام بروز رسانی بسته ها دانلود و تایید شدند
در حال استخراج بروز رسانی بسته ها...
استخراج شده users ارتقا به 1.1.0
در حال ثبت بروز رسانی ها...
موفقیت آمیز ارتقای 1 بسته: users
pkg update
تازه کردن کش مخزن (بازیابی پایگاه داده بسته های جدید).
مثال ها:
# CLI
php mpm.php pkg update
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/update"
خروجی:
کش مخزن تازه شد - 42 بسته دردسترس
pkg list [FILTER]
نمایش بسته های نصب شده.
آرگومان ها:
FILTER(اختیاری): فیلتر بر اساس نام بسته (غیر حساس به بزرگی و کوچکی حروف)
مثال ها:
# CLI
php mpm.php pkg list
php mpm.php pkg list auth
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/list"
curl "http://localhost/mpm.php/$KEY/pkg/list/auth"
خروجی:
بسته های نصب شده:
users v1.0.0 نصب شده: 1403-11-25
auth v2.0.0 نصب شده: 1403-11-25 (وابسته: users)
database v1.5.0 نصب شده: 1403-11-25 (وابسته: users)
pkg search
جستجوی بسته های دردسترس.
آرگومان ها:
KEYWORD(اجباری): واژه جستجو
مثال ها:
# CLI
php mpm.php pkg search database
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/search/database"
خروجی:
3 بسته پیدا شد:
mysql-driver v1.0.0 درایور پایگاه داده MySQL [نصب شده]
اتصال MySQL برای برنامه ها
postgres-driver v1.0.0 درایور PostgreSQL
سازنده پایگاه داده PostgreSQL
database-tools v2.0.0 ابزار مدیریت پایگاه داده
ابزارهای عمومی و کمکی پایگاه داده
pkg info
نمایش اطلاعات دقیق بسته.
آرگومان ها:
PACKAGE(اجباری): نام بسته
مثال ها:
# CLI
php mpm.php pkg info users
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/info/users"
خروجی:
بسته: سیستم مدیریت کاربر
شناسه: users
آخرین: 1.0.0
توضیح: مدیریت کاربر کامل با احراز هویت
نویسنده: تیم Mehrnet
مجوز: MIT
نصب شده: v1.0.0 (در 1403-11-25 10:30:00)
وابستگی ها: هیچ
نسخه های دردسترس:
v1.0.0 - منتشر شده: 1403-11-25
v0.9.0 - منتشر شده: 1403-11-11 (نیاز دارد: auth-lib)
خطاها:
- 404: بسته پیدا نشد
pkg help
نمایش کمک مدیریت بسته.
مثال ها:
# CLI
php mpm.php pkg help
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/help"
pkg version
نمایش نسخه مدیریت بسته.
مثال ها:
# CLI
php mpm.php pkg version
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/version"
پروتکل پاسخ
کدهای وضعیت HTTP
| کد | معنی |
|---|---|
| 200 | موفقیت |
| 400 | درخواست نامعتبر (آرگومان های نامعتبر، خطاهای عملیاتی) |
| 403 | ممنوع (کلید API نامعتبر، اعتبارسنجی مسیر ناموفق) |
| 404 | پیدا نشد (فرمند، بسته یا فایل پیدا نشد) |
| 503 | سرویس در دسترس نیست (تمام آینه ها ناموفق، قفل نگه داشته شده) |
کدهای خروج CLI
| کد | معنی |
|---|---|
| 0 | موفقیت |
| 1 | خطا (هر نوع) |
معنی شناسی POSIX
- فرماندهایی که خروجی دارند داده بر می گردانند
- فرماندهایی که خروجی ندارند رشته خالی برمی گردانند (اما همچنان 0/200 خروج دارند)
- خطاها استثنایی با کدهای مناسب پرتاب می کنند
فایل های تنظیمات
.config/key
فایل متنی ساده حاوی کلید API 64 کاراکتری هگزادسیمال.
فرمت:
abc123def456789... (64 کاراکتر هگزادسیمال)
تولید:
$key = bin2hex(random_bytes(32));
امنیت:
- به طور خودکار در اولین اجرا تولید می شود
- برای احراز هویت HTTP استفاده می شود
- به طور خودکار برای حالت CLI بارگذاری می شود
- مقایسه ایمن زمانی از طریق
hash_equals()
.config/repos.json
پیکربندی آینه های مخزن.
فرمت:
{
"main": [
"https://primary-mirror.com/packages",
"https://secondary-mirror.com/packages"
],
"extra": [
"https://extra-repo.com/packages"
]
}
انتخاب آینه:
- آینه ها به ترتیب آرایه در صورت شکست امتحان می شوند
- بدون تاخیر عقب نشینی
- اولین آینه موفق برنده می شود
.config/packages.json
ثبت بسته های نصب شده.
فرمت:
{
"createdAt": "2025-01-15T10:00:00Z",
"updatedAt": "2025-01-15T10:30:00Z",
"packages": {
"users": {
"version": "1.0.0",
"installed_at": "2025-01-15T10:30:00Z",
"dependencies": [],
"files": [
"./app/packages/users/handler.php",
"./app/packages/users/module.php"
],
"download_url": "https://mirror.com/users-1.0.0.zip",
"download_time": "2025-01-15T10:29:00Z",
"checksum": "sha256:abc123...",
"repository": "main"
}
}
}
فرادادها:
createdAt: زمان ایجاد ثبتupdatedAt: آخرین زمان تغییرfiles: آرایه مسیرهای فایل های نصب شده (برای حذف)download_url: آینه منبع استفاده شدهchecksum: checksum تایید شده
.config/path.json
الگوهای کشف کننده فرمند.
فرمت:
[
"app/packages/[name]/handler.php",
"bin/[name].php"
]
تطابق الگو:
[name]با نام فرمند جایگزین می شود- الگوها به ترتیب آرایه جستجو می شوند
- اولین تطابق برنده می شود
مثال:
فرمند users جستجو می کند:
app/packages/users/handler.phpbin/users.php
فرماندهای سفارشی
فرمت کننده
کننده ها فایل های PHP هستند که قابل فراخوانی بر می گردانند:
<?php
return function(array $args): string {
// پردازش آرگومان ها
// بازگشت پاسخ یا پرتاب استثنا
return "output";
};
پردازش آرگومان
حالت HTTP:
GET /mpm.php/KEY/mycommand/action/arg1/arg2
↓ ↓ ↓ ↓
فرمند args[0] [1] [2]
حالت CLI:
php mpm.php mycommand action arg1 arg2
↓ ↓ ↓ ↓
فرمند args[0] [1] [2]
کننده دریافت می کند:
function(array $args): string {
$action = $args[0]; // 'action'
$arg1 = $args[1]; // 'arg1'
$arg2 = $args[2]; // 'arg2'
}
مدیریت خطا
پرتاب استثناهای داخل کدهای وضعیت HTTP:
// درخواست نامعتبر
throw new \RuntimeException('Invalid input', 400);
// پیدا نشد
throw new \RuntimeException('Item not found', 404);
// ممنوع
throw new \RuntimeException('Permission denied', 403);
توجه: حالت CLI تمام کدهای خطا را به کد خروج 1 تبدیل می کند.
مثال کننده
<?php
return function(array $args): string {
$action = $args[0] ?? 'help';
switch ($action) {
case 'list':
// خواندن داده ها
$data = json_decode(
file_get_contents('app/data/items.json'),
true
);
return json_encode($data);
case 'get':
$id = $args[1] ?? null;
if (!$id) {
throw new \RuntimeException('شناسه لازم است', 400);
}
// واکشی آیتم
return "Item: $id";
case 'create':
$name = $args[1] ?? null;
if (!$name) {
throw new \RuntimeException('نام لازم است', 400);
}
// ایجاد آیتم
return "Created: $name";
case 'help':
return "اقدام ها: list, get <id>, create <name>";
default:
throw new \RuntimeException("اقدام نامعلوم: $action", 404);
}
};
معماری
ساختار فایل
.
├── mpm.php # اجرایی اصلی (2300 سطر)
├── .config/ # پیکربندی (ایجاد خودکار)
│ ├── key # کلید API
│ ├── repos.json # آینه های مخزن
│ ├── packages.json # بسته های نصب شده
│ └── path.json # الگوهای کننده
├── .cache/ # کش (ایجاد خودکار)
│ ├── mpm.lock # فایل قفل
│ └── *.zip # بسته های دانلود شده
└── app/packages/ # بسته های نصب شده
└── [package]/
└── handler.php
جریان اجرا
1. شناسایی Runtime (HTTP/CLI)
↓
2. مقدار دهی اولیه فایل های پیکربندی
↓
3. تجزیه درخواست (PATH_INFO یا argv)
↓
4. اعتبارسنجی کلید API
↓
5. اجرای فرمند
├─ فرمند درون ساخت
├─ مدیریت بسته
└─ کننده سفارشی
↓
6. ارسال پاسخ (سرصفحه های HTTP یا STDOUT/STDERR)
جریان مدیریت بسته
نصب:
1. حل وابستگی ها (DFS مرتب سازی توپولوژیکی)
↓
2. دانلود تمام بسته ها (با تایید checksum)
↓
3. استخراج تمام بسته ها (با حل تضاد)
↓
4. ثبت در .config/packages.json (اتمی)
ویژگی های کلیدی:
- حل وابستگی: پیچیدگی O(V + E)
- شناسایی دایره ای: جستجوهای مجموعه O(1)
- تایید Checksum: SHA256 با مقایسه ایمن زمانی
- عقب نشینی آینه: تلاش مجدد ترتیبی، بدون تاخیر
- عملیات اتمی: نصب تمام یا هیچ
- حل تضاد: فایل های موجود با
-2،-3و غیره نام گذاری می شوند
مدل امنیت
احراز هویت:
- کلید API تصادفی 64 کاراکتری
- مقایسه ایمن زمانی از طریق
hash_equals() - بارگذاری خودکار در حالت CLI
اعتبارسنجی مسیر:
- مسدود کردن مسیرهای مطلق (
/etc/passwd) - مسدود کردن پیمایش دایرکتوری (
../../../) - اعمال بر تمام عملیات فایل
- مسدود کردن مسیرهای مطلق (
امنیت بسته:
- تایید checksum SHA256
- شناسایی پیمایش Zip
- اجرای HTTPS آینه
کارایی
معیارهای عملکرد
- اجرای برنامه: <1ms (اندازه گیری شده از طریق نمایه گری)
- سربار سرور درون ساخت: 1000ms+ (در تولید از سرور وب مناسب استفاده کنید)
- دانلود بسته: بستگی به سرعت شبکه و آینه دارد
- حل وابستگی: O(V + E) که V = بسته ها، E = وابستگی ها
نکات بهینه سازی
- استفاده از سرور وب تولید (Nginx, Apache)
- فعال کردن کش کد PHP (OPcache)
- استفاده از CDN برای آینه های بسته
- کاهش پیچیدگی کننده سفارشی
- ذخیره سازی پایگاه داده بسته به طور محلی
حل مسائل
"کلید API نامعتبر"
HTTP 403
علت: کلید API وجود ندارد یا نامعتبر است
حل:
# بررسی کلید
cat .config/key
# تولید مجدد کلید (حذف و دوباره اجرا کنید)
rm .config/key
php mpm.php ls
"کلید API پیدا نشد" (CLI)
کد خروج 1
علت: فایل .config/key وجود ندارد
حل:
# مقدار دهی اولیه shell
php mpm.php ls
"فرمند پیدا نشد"
HTTP 404 / کد خروج 1
علت: فایل کننده پیدا نشد
حل:
# بررسی الگوهای کننده
cat .config/path.json
# تایید وجود کننده
ls app/packages/[command]/handler.php
ls bin/[command].php
"عملیات pkg دیگری در حال انجام است"
HTTP 503 / کد خروج 1
علت: فایل قفل وجود دارد
حل:
# منتظر تکمیل عملیات یا قفل اجباری باز کنید
php mpm.php pkg unlock
# یا به صورت دستی حذف کنید
rm .cache/mpm.lock
"بسته پیدا نشد"
HTTP 404 / کد خروج 1
علت: بسته در مخزن نیست
حل:
# تازه کردن کش مخزن
php mpm.php pkg update
# جستجوی بسته
php mpm.php pkg search keyword
"وابستگی دایره ای"
HTTP 400 / کد خروج 1
علت: چرخه وابستگی بسته (A → B → C → A)
حل: بررسی وابستگی های بسته و حذف چرخه. این یک مسئله مخزن بسته است.
"نمی توان حذف کرد - موارد دیگر نیاز دارند"
HTTP 400 / کد خروج 1
علت: بسته های دیگر به بسته هدف وابسته اند
حل:
# اولا بسته های وابسته را حذف کنید
php mpm.php pkg del dependent-package
php mpm.php pkg del target-package
خطاهای اجازه
علت: دسترسی نقش های سیستم فایل
حل:
# رفع اجازه ها
chmod 755 .
chmod 644 mpm.php
chmod 755 .config
chmod 644 .config/*
chmod 755 .cache
راهنمای توسعه بسته
راهنمای کامل برای ایجاد، تست و توزیع بسته های MPM (مدیریت بسته های Mehr).
شروع سریع
بسته حداقل را در 3 مرحله ایجاد کنید:
# 1. ساختار بسته ایجاد کنید
mkdir -p mypackage
cd mypackage
# 2. کننده ایجاد کنید
cat > handler.php << 'EOF'
<?php
return function(array $args): string {
return "سلام از mypackage!";
};
EOF
# 3. فرادادها ایجاد کنید
cat > package.json << 'EOF'
{
"id": "mypackage",
"name": "My Package",
"version": "1.0.0",
"description": "یک بسته ساده",
"author": "نام شما",
"license": "MIT",
"dependencies": []
}
EOF
# تست کنید
cd ..
mkdir -p app/packages/mypackage
cp -r mypackage/* app/packages/mypackage/
php mpm.php mypackage
ساختار بسته
فایل های لازم
mypackage/
├── handler.php # لازم: نقطه ورود
└── package.json # لازم: فرادادها
ساختار اختیاری
mypackage/
├── handler.php # نقطه ورود (لازم)
├── package.json # فرادادها (لازم)
├── lib/ # کد کتابخانه
│ ├── MyClass.php
│ └── helpers.php
├── data/ # فایل های داده
│ └── config.json
├── views/ # الگو ها
│ └── template.html
└── README.md # مستندات بسته
استخراج فایل
بسته ها به ریشه پروژه استخراج می شوند و مسیرها حفظ می شوند:
بسته شامل:
handler.php
module.php
lib/Database.php
data/schema.json
استخراج به:
./handler.php
./module.php
./lib/Database.php
./data/schema.json
برای ماژول های قالب Mehr، استفاده کنید:
app/packages/[name]/handler.php
app/packages/[name]/module.php
پیاده سازی کننده
کننده پایه
<?php
return function(array $args): string {
// اقدام واحد
return "سلام، دنیا!";
};
کننده چند اقدامی
<?php
return function(array $args): string {
$action = $args[0] ?? 'help';
switch ($action) {
case 'list':
return handleList();
case 'get':
$id = $args[1] ?? null;
if (!$id) {
throw new \RuntimeException('شناسه لازم است', 400);
}
return handleGet($id);
case 'create':
$name = $args[1] ?? null;
if (!$name) {
throw new \RuntimeException('نام لازم است', 400);
}
return handleCreate($name);
case 'delete':
$id = $args[1] ?? null;
if (!$id) {
throw new \RuntimeException('شناسه لازم است', 400);
}
return handleDelete($id);
case 'help':
return <<<'EOF'
اقدام های دردسترس:
list - نمایش تمام آیتم ها
get <id> - دریافت آیتم با شناسه
create <name> - ایجاد آیتم جدید
delete <id> - حذف آیتم
EOF;
default:
throw new \RuntimeException("اقدام نامعلوم: $action", 404);
}
};
function handleList(): string {
return "item1\nitem2\nitem3";
}
function handleGet(string $id): string {
// بارگذاری از فایل، پایگاه داده، و غیره
return "Item data for: $id";
}
function handleCreate(string $name): string {
// ایجاد و ذخیره
return "Created: $name";
}
function handleDelete(string $id): string {
// حذف آیتم
return "Deleted: $id";
}
پردازش آرگومان
آرگومان ها در هر دو حالت HTTP و CLI یکسان اند:
HTTP:
GET /mpm.php/KEY/users/create/alice/alice@example.com
↓ ↓ ↓ ↓
فرمند args[0] args[1] args[2]
CLI:
php mpm.php users create alice alice@example.com
↓ ↓ ↓ ↓
فرمند args[0] args[1] args[2]
کننده:
function(array $args): string {
$action = $args[0]; // 'create'
$name = $args[1]; // 'alice'
$email = $args[2]; // 'alice@example.com'
}
عملیات فایل
کننده ها در دایرکتوری mpm.php اجرا می شوند. از مسیرهای نسبی استفاده کنید:
<?php
return function(array $args): string {
// خواندن فایل داده
$data = json_decode(
file_get_contents('app/data/items.json'),
true
);
// تغییر داده ها
$data['new_item'] = $args[0] ?? 'default';
// بازنویسی
file_put_contents(
'app/data/items.json',
json_encode($data, JSON_PRETTY_PRINT)
);
return "بروز رسانی شد";
};
مدیریت خطا
<?php
return function(array $args): string {
$id = $args[0] ?? null;
// اعتبارسنجی
if (!$id) {
throw new \RuntimeException('شناسه لازم است', 400);
}
if (!is_numeric($id)) {
throw new \RuntimeException('شناسه باید عددی باشد', 400);
}
// عملیات فایل
$file = "app/data/$id.json";
if (!file_exists($file)) {
throw new \RuntimeException('آیتم پیدا نشد', 404);
}
try {
$content = file_get_contents($file);
$data = json_decode($content, true);
if (!$data) {
throw new \RuntimeException('JSON نامعتبر است', 400);
}
return json_encode($data);
} catch (\Throwable $e) {
throw new \RuntimeException(
"خرابی در خواندن آیتم: {$e->getMessage()}",
400
);
}
};
فرمت های پاسخ
متن ساده:
return "item1\nitem2\nitem3";
JSON:
return json_encode([
'status' => 'success',
'items' => ['item1', 'item2', 'item3']
]);
خالی (POSIX):
return ''; // موفقیت بدون خروجی
فرادادهای بسته
فرمت package.json
{
"id": "mypackage",
"name": "My Package",
"version": "1.0.0",
"description": "توضیح بسته",
"author": "نام شما",
"license": "MIT",
"dependencies": ["dependency1", "dependency2"]
}
مشخصات فیلد
| فیلد | نوع | لازم | توضیح |
|---|---|---|---|
id |
رشته | بله | کوچک، بدون فاصله، استفاده در URL ها |
name |
رشته | بله | نام قابل خواندن |
version |
رشته | بله | نسخه درجه بندی معنایی (X.Y.Z) |
description |
رشته | بله | توضیح مختصر |
author |
رشته | نه | نویسنده بسته |
license |
رشته | نه | شناسه مجوز (MIT, Apache-2.0, و غیره) |
dependencies |
آرایه | نه | آرایه شناسه های بسته |
اعلان وابستگی
{
"id": "auth",
"dependencies": ["users", "session"]
}
مدیریت بسته:
- وابستگی های انتقالی را خودکار حل می کند
- وابستگی های دایره ای را شناسایی می کند
- به ترتیب صحیح نصب می کند (وابستگی ها قبل از وابستگان)
- بسته های قبلا نصب شده را نادیده می گیرد
تست بسته ها
تست دستی (CLI)
# نصب دستی بسته
mkdir -p app/packages/mypackage
cp -r mypackage/* app/packages/mypackage/
# تست فرماند ها
php mpm.php mypackage
php mpm.php mypackage list
php mpm.php mypackage get 123
php mpm.php mypackage create "Test Item"
تست دستی (HTTP)
# شروع سرور
php -S localhost:8000
# کلید API را دریافت کنید
KEY=$(cat .config/key)
# تست فرماند ها
curl "http://localhost:8000/mpm.php/$KEY/mypackage"
curl "http://localhost:8000/mpm.php/$KEY/mypackage/list"
curl "http://localhost:8000/mpm.php/$KEY/mypackage/get/123"
چک لیست تست
- تمام اقدام ها به درستی کار می کنند
- مدیریت خطا کار می کند (ورودی نامعتبر)
- آرگومان های لازم اعتبارسنجی می شوند
- عملیات فایل موفق می شوند
- هر دو حالت CLI و HTTP یکسان کار می کنند
- اقدام کمک تمام فرماند ها را مستند می کند
- وابستگی ها در package.json اعلام می شوند
توزیع
ایجاد ZIP بسته
cd mypackage
zip -r ../mypackage-1.0.0.zip .
# تایید محتویات
unzip -l ../mypackage-1.0.0.zip
محاسبه Checksum
sha256sum mypackage-1.0.0.zip
# Output: abc123def456... mypackage-1.0.0.zip
اسکریپتهای ساخت خودکار
برای بستهبندی قابل استفاده مجدد، اسکریپتهای shell ایجاد کنید که فرآیند ساخت را خودکار میکنند. این به ویژه برای بستهبندی داراییهای شخص ثالث مانند آیکونها، فونتها یا کتابخانههای JavaScript مفید است.
ساختار پایه:
#!/bin/sh
set -e
PKG="mypackage"
DES="توضیح بسته"
VER="1.0.0"
URL="https://example.com/source.zip"
OUT="${PKG}-${VER}.zip"
TMP="$(mktemp -d)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$TMP"
# دانلود و استخراج منبع
wget -q "$URL" -O src.zip
unzip -q src.zip
# بازسازی ساختار
mkdir -p "assets/category/${PKG}"
mv source-files/* "assets/category/${PKG}/"
# ایجاد zip بسته
zip -rq "$OUT" assets
# محاسبه متادیتا
CHECKSUM=$(sha256sum "$OUT" | cut -d' ' -f1)
SIZE=$(wc -c < "$OUT" | tr -d ' ')
RELEASED=$(date +%Y-%m-%d)
# انتقال به مخزن
mv "$OUT" "${SCRIPT_DIR}/../main/"
# بروزرسانی database.json
DB="${SCRIPT_DIR}/../main/database.json"
jq --arg id "$PKG" \
--arg name "$PKG" \
--arg desc "$DES" \
--arg ver "$VER" \
--arg checksum "sha256:$CHECKSUM" \
--argjson size "$SIZE" \
--arg url "$OUT" \
--arg released "$RELEASED" \
'.packages[$id] = {
name: $name,
description: $desc,
author: "mpm-bot",
license: "MIT",
versions: { ($ver): { version: $ver, dependencies: [], checksum: $checksum, size: $size, download_url: $url, released_at: $released } },
latest: $ver
}' "$DB" > "${DB}.tmp" && mv "${DB}.tmp" "$DB"
rm -rf "$TMP"
echo "[+] Built ${PKG} v${VER}"
مثال: بسته آیکون (heroicons)
#!/bin/sh
set -e
PKG="heroicons"
DES="مجموعه آیکونهای SVG رایگان با کیفیت بالا برای توسعه UI."
VER="2.2.0"
URL="https://github.com/tailwindlabs/heroicons/archive/refs/tags/v${VER}.zip"
OUT="${PKG}-${VER}.zip"
TMP="$(mktemp -d)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$TMP"
wget -q "$URL" -O src.zip
unzip -q src.zip
mkdir -p "assets/icons/${PKG}"
mv "heroicons-${VER}/src/"* "assets/icons/${PKG}/"
zip -rq "$OUT" assets
CHECKSUM=$(sha256sum "$OUT" | cut -d' ' -f1)
SIZE=$(wc -c < "$OUT" | tr -d ' ')
RELEASED=$(date +%Y-%m-%d)
mv "$OUT" "${SCRIPT_DIR}/../main/"
DB="${SCRIPT_DIR}/../main/database.json"
jq --arg id "$PKG" --arg name "$PKG" --arg desc "$DES" --arg ver "$VER" \
--arg checksum "sha256:$CHECKSUM" --argjson size "$SIZE" \
--arg url "$OUT" --arg released "$RELEASED" \
'.packages[$id] = {
name: $name, description: $desc, author: "mpm-bot", license: "MIT",
versions: { ($ver): { version: $ver, dependencies: [], checksum: $checksum, size: $size, download_url: $url, released_at: $released } },
latest: $ver
}' "$DB" > "${DB}.tmp" && mv "${DB}.tmp" "$DB"
rm -rf "$TMP"
echo "[+] Built ${PKG} v${VER}"
مثال: کتابخانه JavaScript (htmx)
#!/bin/sh
set -e
PKG="htmx"
DES="ابزارهای قدرتمند برای HTML."
VER="2.0.4"
URL="https://unpkg.com/htmx.org@${VER}/dist/htmx.min.js"
OUT="${PKG}-${VER}.zip"
TMP="$(mktemp -d)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$TMP"
mkdir -p "assets/js"
wget -q "$URL" -O "assets/js/htmx.min.js"
zip -rq "$OUT" assets
CHECKSUM=$(sha256sum "$OUT" | cut -d' ' -f1)
SIZE=$(wc -c < "$OUT" | tr -d ' ')
RELEASED=$(date +%Y-%m-%d)
mv "$OUT" "${SCRIPT_DIR}/../main/"
DB="${SCRIPT_DIR}/../main/database.json"
jq --arg id "$PKG" --arg name "$PKG" --arg desc "$DES" --arg ver "$VER" \
--arg checksum "sha256:$CHECKSUM" --argjson size "$SIZE" \
--arg url "$OUT" --arg released "$RELEASED" \
'.packages[$id] = {
name: $name, description: $desc, author: "mpm-bot", license: "BSD-2-Clause",
versions: { ($ver): { version: $ver, dependencies: [], checksum: $checksum, size: $size, download_url: $url, released_at: $released } },
latest: $ver
}' "$DB" > "${DB}.tmp" && mv "${DB}.tmp" "$DB"
rm -rf "$TMP"
echo "[+] Built ${PKG} v${VER}"
ساختار مخزن:
mpm-repo/
├── scripts/
│ ├── heroicons.sh
│ ├── htmx.sh
│ ├── alpinejs.sh
│ └── ...
└── main/
├── database.json
├── heroicons-2.2.0.zip
├── htmx-2.0.4.zip
└── ...
ساخت همه بستهها:
for script in scripts/*.sh; do ./$script; done
database.json مخزن
{
"version": 1,
"packages": {
"mypackage": {
"name": "My Package",
"description": "توضیح بسته",
"author": "نام شما",
"license": "MIT",
"versions": {
"1.0.0": {
"version": "1.0.0",
"dependencies": [],
"checksum": "sha256:abc123def456...",
"size": 2048,
"download_url": "mypackage-1.0.0.zip",
"released_at": "2025-01-15"
}
},
"latest": "1.0.0"
}
}
}
فایلها در زمان استخراج به صورت محلی ردیابی میشوند تا حذف تمیز انجام شود.
میزبانی GitHub
# ساختار مخزن ایجاد کنید
mkdir -p my-packages/main
cd my-packages/main
# بسته و پایگاه داده اضافه کنید
cp /path/to/mypackage-1.0.0.zip .
cat > database.json << 'EOF'
{
"version": 1,
"packages": { ... }
}
EOF
# فشار به GitHub
cd ..
git init
git add .
git commit -m "Add mypackage"
git remote add origin https://github.com/user/my-packages.git
git push -u origin main
# کاربران تنظیم می کنند:
# "main": ["https://github.com/user/my-packages/raw/main/main"]
بهترین عملکردها
اعتبارسنجی ورودی
// نادرست: بدون اعتبارسنجی
$id = $args[0];
return processId($id);
// درست: اعتبارسنجی و تمیز کردن
$id = $args[0] ?? null;
if (!$id || !is_numeric($id) || $id < 1) {
throw new \RuntimeException('شناسه نامعتبر است', 400);
}
return processId((int)$id);
امنیت
// نادرست: دسترسی فایل دلخواه
$file = $args[0];
return file_get_contents($file); // آسیب پذیر برای ../../../etc/passwd
// درست: محدود کردن به دایرکتوری امن
$file = basename($args[0] ?? ''); // حذف اجزای مسیر
$path = "app/data/$file";
if (!file_exists($path)) {
throw new \RuntimeException('فایل پیدا نشد', 404);
}
return file_get_contents($path);
پیام های خطا
// نادرست: خطای عمومی
throw new \RuntimeException('خطا', 400);
// درست: خطای توصیفی
throw new \RuntimeException('شناسه کاربر باید عدد مثبت باشد', 400);
سازمان بندی کد
// نادرست: همه چیز در کننده
return function(array $args): string {
// 500 سطر کد...
};
// درست: استفاده از دایرکتوری lib/
return function(array $args): string {
require_once __DIR__ . '/lib/Manager.php';
$manager = new Manager();
return $manager->handle($args);
};
وابستگی ها
// درست: بررسی اگر کتابخانه های Composer در دسترس باشند
if (file_exists(__DIR__ . '/../../vendor/autoload.php')) {
require_once __DIR__ . '/../../vendor/autoload.php';
// استفاده از کتابخانه ها
} else {
throw new \RuntimeException('وابستگی های Composer وجود ندارند', 500);
}
مثال ها
شمارنده ساده
<?php
return function(array $args): string {
$file = 'app/data/counter.txt';
if (!file_exists($file)) {
file_put_contents($file, '0');
}
$count = (int)file_get_contents($file);
$count++;
file_put_contents($file, (string)$count);
return "تعداد: $count";
};
CRUD JSON
<?php
return function(array $args): string {
$action = $args[0] ?? 'list';
$file = 'app/data/items.json';
// بارگذاری داده ها
$items = [];
if (file_exists($file)) {
$items = json_decode(file_get_contents($file), true) ?? [];
}
switch ($action) {
case 'list':
return json_encode($items);
case 'add':
$name = $args[1] ?? null;
if (!$name) {
throw new \RuntimeException('نام لازم است', 400);
}
$items[] = [
'id' => count($items) + 1,
'name' => $name,
'created_at' => date('Y-m-d H:i:s')
];
file_put_contents($file, json_encode($items, JSON_PRETTY_PRINT));
return "اضافه شد: $name";
case 'get':
$id = (int)($args[1] ?? 0);
foreach ($items as $item) {
if ($item['id'] === $id) {
return json_encode($item);
}
}
throw new \RuntimeException('آیتم پیدا نشد', 404);
default:
throw new \RuntimeException('اقدام نامعلوم', 404);
}
};
ماژول قالب Mehr
mymodule/
├── app/
│ └── packages/
│ └── mymodule/
│ ├── handler.php # کننده فرماند shell
│ ├── module.php # پیاده سازی ماژول
│ ├── manifest.json # Mehr manifest
│ └── migrations/ # انتقال پایگاه داده
│ └── 001_create_table.php
└── package.json # فرادادهای توزیع
handler.php:
<?php
return function(array $args): string {
require_once __DIR__ . '/module.php';
$module = new MyModule();
return $module->handleCommand($args);
};
package.json:
{
"id": "mymodule",
"name": "My Module",
"version": "1.0.0",
"description": "ماژول قالب Mehr",
"dependencies": ["core"]
}
Référence d'utilisation
Référence de commande complète et documentation de l'API pour MPM - Gestionnaire de paquets Mehr.
Formats de requête
Mode HTTP
Format:
GET /mpm.php/API_KEY/COMMAND/ARG0/ARG1/ARG2/...
Exemples:
# Obtenir la clé API
KEY=$(cat .config/key)
# Lister les fichiers
curl "http://localhost/mpm.php/$KEY/ls"
# Lire un fichier
curl "http://localhost/mpm.php/$KEY/cat/README.md"
# Afficher du texte avec plusieurs arguments
curl "http://localhost/mpm.php/$KEY/echo/hello/world"
# Gestion des paquets
curl "http://localhost/mpm.php/$KEY/pkg/search/database"
curl "http://localhost/mpm.php/$KEY/pkg/add/users"
Réponse:
- Succès: HTTP 200 avec réponse en texte brut
- Erreur: HTTP 4xx/5xx avec message d'erreur
Mode CLI
Format:
php mpm.php COMMAND ARG0 ARG1 ARG2 ...
Exemples:
# Lister les fichiers
php mpm.php ls
# Lire un fichier
php mpm.php cat README.md
# Afficher du texte avec plusieurs arguments
php mpm.php echo hello world
# Gestion des paquets
php mpm.php pkg search database
php mpm.php pkg add users
Réponse:
- Succès: Sortie vers STDOUT, code de sortie 0
- Erreur: Sortie vers STDERR avec préfixe "Error: ", code de sortie 1
Commandes intégrées
ls [path]
Lister le contenu d'un répertoire.
Arguments:
path(optionnel): Répertoire à lister (par défaut: répertoire courant)
Exemples:
# CLI
php mpm.php ls
php mpm.php ls app
php mpm.php ls app/packages
# HTTP
curl "http://localhost/mpm.php/$KEY/ls"
curl "http://localhost/mpm.php/$KEY/ls/app"
Sortie:
Noms de fichiers séparés par des espaces (exclut . et ..)
Erreurs:
- 404: Répertoire non trouvé
- 400: Impossible de lire le répertoire
- 403: Validation du chemin échouée (chemin absolu ou
..)
cat
Lire le contenu d'un fichier.
Arguments:
file(obligatoire): Chemin du fichier
Exemples:
# CLI
php mpm.php cat README.md
php mpm.php cat .config/key
# HTTP
curl "http://localhost/mpm.php/$KEY/cat/README.md"
Sortie: Contenu du fichier (préserve la mise en forme et les sauts de ligne)
Erreurs:
- 400: Argument fichier manquant ou ce n'est pas un fichier
- 404: Fichier non trouvé
- 403: Validation du chemin échouée
rm
Supprimer un fichier.
Arguments:
file(obligatoire): Chemin du fichier
Exemples:
# CLI
php mpm.php rm temp.txt
php mpm.php rm logs/old.log
# HTTP
curl "http://localhost/mpm.php/$KEY/rm/temp.txt"
Sortie: Chaîne vide en cas de succès (comportement POSIX)
Erreurs:
- 400: Argument fichier manquant, ce n'est pas un fichier ou impossible de supprimer
- 404: Fichier non trouvé
- 403: Validation du chemin échouée
mkdir
Créer un répertoire (récursivement).
Arguments:
path(obligatoire): Chemin du répertoire à créer
Exemples:
# CLI
php mpm.php mkdir uploads
php mpm.php mkdir app/data/cache
# HTTP
curl "http://localhost/mpm.php/$KEY/mkdir/uploads"
Sortie: Chaîne vide en cas de succès
Erreurs:
- 400: Argument chemin manquant, existe déjà ou impossible de créer
- 403: Validation du chemin échouée
cp
Copier un fichier.
Arguments:
src(obligatoire): Chemin du fichier sourcedst(obligatoire): Chemin du fichier destination
Exemples:
# CLI
php mpm.php cp config.json config.backup.json
php mpm.php cp README.md docs/README.md
# HTTP
curl "http://localhost/mpm.php/$KEY/cp/config.json/config.backup.json"
Sortie: Chaîne vide en cas de succès
Erreurs:
- 400: Arguments manquants, source ce n'est pas un fichier ou impossible de copier
- 404: Fichier source non trouvé
- 403: Validation du chemin échouée
echo ...
Afficher des arguments de texte.
Arguments:
text...(obligatoire): Un ou plusieurs arguments de texte
Exemples:
# CLI
php mpm.php echo hello
php mpm.php echo hello world "from shell"
# HTTP
curl "http://localhost/mpm.php/$KEY/echo/hello"
curl "http://localhost/mpm.php/$KEY/echo/hello/world"
Sortie: Arguments séparés par des espaces
env [action] [name]
Gérer les variables d'environnement.
Actions:
list(par défaut): Lister toutes les variables d'environnementget <name>: Obtenir la valeur d'une variable spécifique
Exemples:
# CLI
php mpm.php env list
php mpm.php env get PATH
php mpm.php env get HOME
# HTTP
curl "http://localhost/mpm.php/$KEY/env/list"
curl "http://localhost/mpm.php/$KEY/env/get/PATH"
Sortie:
list: Une variable par ligne (KEY=value)get: Valeur de la variable uniquement
Erreurs:
- 404: Variable non trouvée ou action inconnue
- 400: Nom de variable manquant pour
get
Gestionnaire de paquets
pkg add [PACKAGE...]
Installer des paquets avec résolution automatique des dépendances.
Arguments:
PACKAGE...(obligatoire): Un ou plusieurs noms de paquets
Exemples:
# CLI
php mpm.php pkg add users
php mpm.php pkg add users auth database
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/add/users"
curl "http://localhost/mpm.php/$KEY/pkg/add/users/auth"
Processus:
- Récupérer la base de données du référentiel
- Résoudre les dépendances (tri topologique DFS)
- Télécharger tous les paquets avec vérification de checksum
- Extraire tous les paquets vers la racine du projet
- Enregistrer les paquets de manière atomique
Sortie:
Paquets à installer: dependency1, dependency2, users
Téléchargement des paquets...
Tous les paquets ont été téléchargés et vérifiés
Extraction des paquets...
Extrait dependency1 (1.0.0)
Extrait dependency2 (2.0.0)
Extrait users (3.0.0)
Enregistrement des paquets...
Installation réussie de 3 paquet(s): dependency1, dependency2, users
Erreurs:
- 404: Paquet non trouvé
- 400: Dépendance circulaire détectée
- 503: Tous les miroirs ont échoué ou verrou maintenu
pkg del
Supprimer un paquet (échoue si d'autres paquets en dépendent).
Arguments:
PACKAGE(obligatoire): Nom du paquet
Exemples:
# CLI
php mpm.php pkg del users
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/del/users"
Sortie:
Paquet supprimé: users (version 1.0.0) - 15 fichiers supprimés, 3 répertoires vides supprimés
Erreurs:
- 404: Paquet non installé
- 400: Paquet requis par d'autres paquets
- 503: Verrou maintenu
pkg upgrade [PACKAGE]
Mettre à jour tous les paquets ou un paquet spécifique vers la dernière version.
Arguments:
PACKAGE(optionnel): Nom du paquet (si omis, met à jour tous)
Exemples:
# CLI
php mpm.php pkg upgrade # Mettre à jour tous
php mpm.php pkg upgrade users # Mettre à jour spécifique
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/upgrade"
curl "http://localhost/mpm.php/$KEY/pkg/upgrade/users"
Sortie:
Mise à jour de users de 1.0.0 vers 1.1.0
Téléchargement des mises à jour de paquets...
Toutes les mises à jour ont été téléchargées et vérifiées
Extraction des mises à jour de paquets...
Extrait users mise à jour vers 1.1.0
Enregistrement des mises à jour...
Mise à jour réussie de 1 paquet(s): users
pkg update
Actualiser le cache du référentiel (récupérer la dernière base de données des paquets).
Exemples:
# CLI
php mpm.php pkg update
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/update"
Sortie:
Cache du référentiel actualisé - 42 paquets disponibles
pkg list [FILTER]
Lister les paquets installés.
Arguments:
FILTER(optionnel): Filtrer par nom de paquet (insensible à la casse)
Exemples:
# CLI
php mpm.php pkg list
php mpm.php pkg list auth
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/list"
curl "http://localhost/mpm.php/$KEY/pkg/list/auth"
Sortie:
Paquets installés:
users v1.0.0 installé: 2025-01-15
auth v2.0.0 installé: 2025-01-15 (dépend: users)
database v1.5.0 installé: 2025-01-15 (dépend: users)
pkg search
Rechercher des paquets disponibles.
Arguments:
KEYWORD(obligatoire): Terme de recherche
Exemples:
# CLI
php mpm.php pkg search database
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/search/database"
Sortie:
3 paquets trouvés:
mysql-driver v1.0.0 Pilote base de données MySQL [installé]
Fournit la connectivité MySQL pour les applications
postgres-driver v1.0.0 Pilote PostgreSQL
Connecteur de base de données PostgreSQL
database-tools v2.0.0 Outils de gestion de base de données
Utilitaires et assistants courants de base de données
pkg info
Afficher les informations détaillées du paquet.
Arguments:
PACKAGE(obligatoire): Nom du paquet
Exemples:
# CLI
php mpm.php pkg info users
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/info/users"
Sortie:
Paquet: Système de gestion des utilisateurs
ID: users
Dernière: 1.0.0
Description: Gestion complète des utilisateurs avec authentification
Auteur: Équipe Mehrnet
Licence: MIT
Installé: v1.0.0 (le 2025-01-15T10:30:00Z)
Dépendances: aucune
Versions disponibles:
v1.0.0 - publié: 2025-01-15
v0.9.0 - publié: 2025-01-01 (requiert: auth-lib)
Erreurs:
- 404: Paquet non trouvé
pkg help
Afficher l'aide du gestionnaire de paquets.
Exemples:
# CLI
php mpm.php pkg help
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/help"
pkg version
Afficher la version du gestionnaire de paquets.
Exemples:
# CLI
php mpm.php pkg version
# HTTP
curl "http://localhost/mpm.php/$KEY/pkg/version"
Protocole de réponse
Codes de statut HTTP
| Code | Signification |
|---|---|
| 200 | Succès |
| 400 | Mauvaise requête (arguments invalides, erreurs opérationnelles) |
| 403 | Interdit (clé API invalide, validation du chemin échouée) |
| 404 | Non trouvé (commande, paquet ou fichier non trouvé) |
| 503 | Service indisponible (tous les miroirs ont échoué, verrou maintenu) |
Codes de sortie CLI
| Code | Signification |
|---|---|
| 0 | Succès |
| 1 | Erreur (tout type) |
Sémantique POSIX
- Les commandes avec sortie retournent les données
- Les commandes sans sortie retournent une chaîne vide (mais sortie toujours 0/200)
- Les erreurs lancent des exceptions avec les codes appropriés
Fichiers de configuration
.config/key
Fichier texte brut contenant la clé API hexadécimale de 64 caractères.
Format:
abc123def456789... (64 caractères hexadécimaux)
Génération:
$key = bin2hex(random_bytes(32));
Sécurité:
- Générée automatiquement au premier lancement
- Utilisée pour l'authentification HTTP
- Chargée automatiquement en mode CLI
- Comparaison sûre aux attaques temporelles via
hash_equals()
.config/repos.json
Configuration des miroirs du référentiel.
Format:
{
"main": [
"https://primary-mirror.com/packages",
"https://secondary-mirror.com/packages"
],
"extra": [
"https://extra-repo.com/packages"
]
}
Sélection du miroir:
- Les miroirs sont essayés séquentiellement en cas d'échec
- Aucun délai de recul
- Le premier miroir réussi gagne
.config/packages.json
Registre des paquets installés.
Format:
{
"createdAt": "2025-01-15T10:00:00Z",
"updatedAt": "2025-01-15T10:30:00Z",
"packages": {
"users": {
"version": "1.0.0",
"installed_at": "2025-01-15T10:30:00Z",
"dependencies": [],
"files": [
"./app/packages/users/handler.php",
"./app/packages/users/module.php"
],
"download_url": "https://mirror.com/users-1.0.0.zip",
"download_time": "2025-01-15T10:29:00Z",
"checksum": "sha256:abc123...",
"repository": "main"
}
}
}
Métadonnées:
createdAt: Heure de création du registreupdatedAt: Heure de dernière modificationfiles: Tableau des chemins de fichiers installés (pour la suppression)download_url: Miroir source utiliséchecksum: Checksum vérifié
.config/path.json
Modèles de découverte de gestionnaire de commandes.
Format:
[
"app/packages/[name]/handler.php",
"bin/[name].php"
]
Correspondance de modèle:
[name]remplacé par le nom de la commande- Les modèles sont recherchés dans l'ordre du tableau
- Le premier correspondance gagne
Exemple:
La commande users recherche:
app/packages/users/handler.phpbin/users.php
Commandes personnalisées
Format du gestionnaire
Les gestionnaires sont des fichiers PHP retournant un callable:
<?php
return function(array $args): string {
// Traiter les arguments
// Retourner la réponse ou lancer une exception
return "output";
};
Traitement des arguments
Mode HTTP:
GET /mpm.php/KEY/mycommand/action/arg1/arg2
↓ ↓ ↓ ↓
command args[0] [1] [2]
Mode CLI:
php mpm.php mycommand action arg1 arg2
↓ ↓ ↓ ↓
command args[0] [1] [2]
Le gestionnaire reçoit:
function(array $args): string {
$action = $args[0]; // 'action'
$arg1 = $args[1]; // 'arg1'
$arg2 = $args[2]; // 'arg2'
}
Gestion des erreurs
Lancer des exceptions avec codes de statut HTTP:
// Mauvaise requête
throw new \RuntimeException('Entrée invalide', 400);
// Non trouvé
throw new \RuntimeException('Élément non trouvé', 404);
// Interdit
throw new \RuntimeException('Accès refusé', 403);
Note: Le mode CLI convertit tous les codes d'erreur au code de sortie 1.
Exemple de gestionnaire
<?php
return function(array $args): string {
$action = $args[0] ?? 'help';
switch ($action) {
case 'list':
// Lire les données
$data = json_decode(
file_get_contents('app/data/items.json'),
true
);
return json_encode($data);
case 'get':
$id = $args[1] ?? null;
if (!$id) {
throw new \RuntimeException('ID requis', 400);
}
// Récupérer l'élément
return "Item: $id";
case 'create':
$name = $args[1] ?? null;
if (!$name) {
throw new \RuntimeException('Nom requis', 400);
}
// Créer l'élément
return "Created: $name";
case 'help':
return "Actions: list, get <id>, create <name>";
default:
throw new \RuntimeException("Action inconnue: $action", 404);
}
};
Architecture
Structure des fichiers
.
├── mpm.php # Exécutable principal (2300 lignes)
├── .config/ # Configuration (création automatique)
│ ├── key # Clé API
│ ├── repos.json # Miroirs du référentiel
│ ├── packages.json # Paquets installés
│ └── path.json # Modèles de gestionnaire
├── .cache/ # Cache (création automatique)
│ ├── mpm.lock # Fichier de verrou
│ └── *.zip # Paquets téléchargés
└── app/packages/ # Paquets installés
└── [package]/
└── handler.php
Flux d'exécution
1. Détection de runtime (HTTP/CLI)
↓
2. Initialiser les fichiers de configuration
↓
3. Analyser la requête (PATH_INFO ou argv)
↓
4. Valider la clé API
↓
5. Exécuter la commande
├─ Commande intégrée
├─ Gestionnaire de paquets
└─ Gestionnaire personnalisé
↓
6. Envoyer la réponse (en-têtes HTTP ou STDOUT/STDERR)
Flux du gestionnaire de paquets
Installation:
1. Résoudre les dépendances (tri topologique DFS)
↓
2. Télécharger tous les paquets (avec vérification de checksum)
↓
3. Extraire tous les paquets (avec résolution de conflit)
↓
4. Enregistrer dans .config/packages.json (atomiquement)
Caractéristiques clés:
- Résolution de dépendances: Complexité O(V + E)
- Détection de cycle: Recherches d'ensemble O(1)
- Vérification de checksum: SHA256 avec comparaison sûre aux attaques temporelles
- Basculement de miroir: Nouvel essai séquentiel, aucun recul
- Opérations atomiques: Installation tout ou rien
- Résolution de conflit: Fichiers existants renommés avec
-2,-3, etc.
Modèle de sécurité
Authentification:
- Clé API aléatoire de 64 caractères
- Comparaison sûre aux attaques temporelles via
hash_equals() - Chargement automatique en mode CLI
Validation du chemin:
- Bloque les chemins absolus (
/etc/passwd) - Bloque la traversée de répertoires (
../../../) - Appliquée à toutes les opérations de fichiers
- Bloque les chemins absolus (
Sécurité des paquets:
- Vérification de checksum SHA256
- Détection de traversée Zip
- Application HTTPS du miroir
Performance
Points de référence
- Exécution de l'application: <1ms (mesurée via profilage)
- Surcharge du serveur intégré: 1000ms+ (utilisez un serveur web approprié en production)
- Téléchargement de paquets: Dépend de la vitesse du réseau et du miroir
- Résolution de dépendances: O(V + E) où V = paquets, E = dépendances
Conseils d'optimisation
- Utiliser un serveur web de production (Nginx, Apache)
- Activer le cache de code PHP (OPcache)
- Utiliser un CDN pour les miroirs de paquets
- Minimiser la complexité du gestionnaire personnalisé
- Mettre en cache la base de données des paquets localement
Dépannage
"Clé API invalide"
HTTP 403
Cause: Clé API manquante ou incorrecte
Solution:
# Vérifier la clé
cat .config/key
# Régénérer la clé (supprimer et réexécuter)
rm .config/key
php mpm.php ls
"Clé API non trouvée" (CLI)
Code de sortie 1
Cause: Le fichier .config/key n'existe pas
Solution:
# Initialiser le shell
php mpm.php ls
"Commande non trouvée"
HTTP 404 / Code de sortie 1
Cause: Fichier gestionnaire non trouvé
Solution:
# Vérifier les modèles de gestionnaire
cat .config/path.json
# Vérifier que le gestionnaire existe
ls app/packages/[command]/handler.php
ls bin/[command].php
"Une autre opération pkg est en cours"
HTTP 503 / Code de sortie 1
Cause: Le fichier de verrou existe
Solution:
# Attendre que l'opération se termine, ou forcer le déverrouillage
php mpm.php pkg unlock
# Ou supprimer manuellement
rm .cache/mpm.lock
"Paquet non trouvé"
HTTP 404 / Code de sortie 1
Cause: Paquet non dans le référentiel
Solution:
# Actualiser le cache du référentiel
php mpm.php pkg update
# Rechercher le paquet
php mpm.php pkg search keyword
"Dépendance circulaire"
HTTP 400 / Code de sortie 1
Cause: Cycle de dépendance de paquet (A → B → C → A)
Solution: Examinez les dépendances des paquets et supprimez le cycle. C'est un problème du référentiel de paquets.
"Impossible de supprimer - requis par"
HTTP 400 / Code de sortie 1
Cause: D'autres paquets dépendent du paquet cible
Solution:
# Supprimer d'abord les paquets dépendants
php mpm.php pkg del dependent-package
php mpm.php pkg del target-package
Erreurs de permission
Cause: Permissions du système de fichiers
Solution:
# Corriger les permissions
chmod 755 .
chmod 644 mpm.php
chmod 755 .config
chmod 644 .config/*
chmod 755 .cache
Guide d'installation et de configuration
Guide complet pour installer, configurer et commencer avec MPM - Gestionnaire de paquets Mehr.
Installation
Installation rapide
# Télécharger mpm.php
wget https://raw.githubusercontent.com/mehrnet/mpm/main/mpm.php
# Définir les permissions
chmod 644 mpm.php
# Tester l'installation
php mpm.php echo "Hello, World!"
Installation manuelle
- Téléchargez
mpm.phpdepuis le référentiel - Placez-le à la racine de votre projet ou dans le répertoire du serveur web
- Assurez-vous que PHP 7.0+ est installé
- Vérifiez que l'extension
ZipArchiveest activée (pour la gestion des paquets)
Exigences
- PHP: 7.0 ou supérieur
- Extensions:
ZipArchive(pour la gestion des paquets) - Permissions: Accès en lecture/écriture au répertoire du projet
Première exécution
À la première exécution, MPM génère automatiquement les fichiers de configuration:
# Exécutez n'importe quelle commande pour initialiser
php mpm.php ls
Cela crée:
.config/
├── key # Clé API (64 caractères hexadécimaux)
├── repos.json # Configuration des miroirs du référentiel
├── packages.json # Registre des paquets installés
└── path.json # Modèles de gestionnaire de commandes
Clé API
Votre clé API est affichée au premier accès HTTP ou enregistrée dans .config/key à la première exécution CLI:
# Afficher votre clé API
cat .config/key
Important: Gardez votre clé API sécurisée. Quiconque y ayant accès peut exécuter des commandes.
Configuration
Miroirs du référentiel
Modifiez .config/repos.json pour personnaliser les référentiels de paquets:
{
"main": [
"https://raw.githubusercontent.com/mehrnet/mpm-repo/refs/heads/main/main"
]
}
Chaque entrée est une URL complète vers un répertoire de dépôt contenant database.json et les fichiers ZIP des paquets. Les miroirs sont essayés séquentiellement en cas d'échec.
Modèles de gestionnaire de commandes
Modifiez .config/path.json pour personnaliser où le shell recherche les commandes personnalisées:
[
"app/packages/[name]/handler.php",
"bin/[name].php",
"custom/handlers/[name].php"
]
Le placeholder [name] est remplacé par le nom de la commande.
Configuration spécifique à l'environnement
Pour différents environnements, vous pouvez:
Utiliser des répertoires de configuration différents:
// Modifier les constantes dans mpm.php (non recommandé) const DIR_CONFIG = '.config';Symlink de configuration:
ln -s .config.production .configVariables d'environnement: Accédez via
php mpm.php env list
Commencer
Mode CLI (Recommandé pour le développement)
# Lister les fichiers
php mpm.php ls
# Lire le contenu des fichiers
php mpm.php cat mpm.php | head -20
# Créer un répertoire
php mpm.php mkdir tmp
# Copier des fichiers
php mpm.php cp mpm.php shell.backup.php
# Gestion des paquets
php mpm.php pkg search database
php mpm.php pkg add users
php mpm.php pkg list
php mpm.php pkg upgrade
Mode HTTP (Production)
Démarrez le serveur de développement PHP (test):
php -S localhost:8000Obtenez votre clé API:
KEY=$(cat .config/key)Exécutez les commandes:
# Lister les fichiers curl "http://localhost:8000/mpm.php/$KEY/ls" # Lire un fichier curl "http://localhost:8000/mpm.php/$KEY/cat/README.md" # Gestion des paquets curl "http://localhost:8000/mpm.php/$KEY/pkg/list" curl "http://localhost:8000/mpm.php/$KEY/pkg/add/users"
Serveur web de production
Pour la production, utilisez un serveur web approprié:
Nginx:
server {
listen 80;
server_name your-domain.com;
root /var/www/shell;
index mpm.php;
location / {
try_files $uri $uri/ /mpm.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index mpm.php;
include fastcgi_params;
}
}
Apache (.htaccess):
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ mpm.php/$1 [L,QSA]
Développement de paquets
Créer un paquet
Créer la structure du paquet:
mypackage/ ├── handler.php # Obligatoire: point d'entrée ├── package.json # Obligatoire: métadonnées ├── lib/ # Optionnel: code de bibliothèque └── data/ # Optionnel: fichiers de donnéesÉcrire handler.php:
<?php return function(array $args): string { $action = $args[0] ?? 'help'; switch ($action) { case 'list': return "Item 1\nItem 2\nItem 3"; case 'create': $name = $args[1] ?? null; if (!$name) { throw new \RuntimeException('Nom requis', 400); } return "Created: $name"; default: return "Actions: list, create"; } };Créer package.json:
{ "id": "mypackage", "name": "My Package", "version": "1.0.0", "description": "Description du paquet", "author": "Votre nom", "license": "MIT", "dependencies": [] }
Champs de métadonnées du paquet
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
id |
chaîne | Oui | Minuscules, sans espaces, utilisé dans les URL |
name |
chaîne | Oui | Nom lisible |
version |
chaîne | Oui | Versioning sémantique (X.Y.Z) |
description |
chaîne | Oui | Description brève |
author |
chaîne | Non | Auteur du paquet |
license |
chaîne | Non | Identifiant de licence (MIT, Apache-2.0, etc.) |
dependencies |
tableau | Non | Tableau d'ID de paquets |
Tester votre paquet
Mode CLI:
# Installer manuellement
mkdir -p app/packages/mypackage
cp -r mypackage/* app/packages/mypackage/
# Tester les commandes
php mpm.php mypackage list
php mpm.php mypackage create item-name
Mode HTTP:
KEY=$(cat .config/key)
curl "http://localhost:8000/mpm.php/$KEY/mypackage/list"
curl "http://localhost:8000/mpm.php/$KEY/mypackage/create/item-name"
Bonnes pratiques
Validation des entrées:
$id = $args[0] ?? null; if (!$id || !is_numeric($id)) { throw new \RuntimeException('ID invalide', 400); }Gestion des erreurs:
try { $data = json_decode(file_get_contents('data.json'), true); if (!$data) { throw new \RuntimeException('JSON invalide', 400); } } catch (\Throwable $e) { throw new \RuntimeException("Error: {$e->getMessage()}", 400); }Sécurité:
// MAUVAIS: Utilisation directe de l'entrée utilisateur $file = $args[0]; return file_get_contents($file); // Vulnérable à la traversée de chemin // BON: Valider et restreindre les chemins $file = basename($args[0] ?? ''); // Supprimer les composants de chemin $path = "app/data/$file"; if (!file_exists($path)) { throw new \RuntimeException('Fichier non trouvé', 404); } return file_get_contents($path);
Configuration du référentiel
Créer un référentiel de paquets
Créer la structure du référentiel:
my-repo/ └── main/ ├── database.json ├── mypackage-1.0.0.zip └── otherapkg-1.0.0.zipConstruire le ZIP du paquet:
cd mypackage zip -r ../my-repo/main/mypackage-1.0.0.zip .Calculer le checksum:
sha256sum my-repo/main/mypackage-1.0.0.zip # Output: abc123def456... mypackage-1.0.0.zipCréer database.json:
{ "version": 1, "packages": { "mypackage": { "name": "My Package", "description": "Description du paquet", "versions": { "1.0.0": { "version": "1.0.0", "dependencies": [], "checksum": "sha256:abc123def456...", "size": 2048, "download_url": "mypackage-1.0.0.zip", "released_at": "2025-01-15" } }, "latest": "1.0.0", "author": "Votre nom", "license": "MIT" } } }Héberger le référentiel:
- Télécharger sur GitHub:
https://github.com/user/repo/raw/main/main/ - Utiliser un CDN ou un hébergement statique
- S'assurer que HTTPS est activé
- Télécharger sur GitHub:
Configurer le shell pour utiliser votre référentiel: Modifiez
.config/repos.json:{ "main": ["https://github.com/user/repo/raw/main/main"] }
Exemple d'hébergement GitHub
# Créer un référentiel
mkdir -p my-packages/main
cd my-packages
# Ajouter des paquets
cp /path/to/mypackage-1.0.0.zip main/
cat > main/database.json << 'EOF'
{
"version": 1,
"packages": { ... }
}
EOF
# Pousser vers GitHub
git init
git add .
git commit -m "Add packages"
git remote add origin https://github.com/user/my-packages.git
git push -u origin main
# Les utilisateurs configurent: https://github.com/user/my-packages/raw/main/main
Déploiement
Développement
# Exécutez le serveur PHP intégré
php -S localhost:8000
# Testez les commandes
php mpm.php pkg list
curl "http://localhost:8000/mpm.php/$(cat .config/key)/pkg/list"
Staging/Production
Utilisez un serveur web approprié (Nginx, Apache, Caddy)
Activez HTTPS pour la sécurité de la clé API
Définissez des permissions restrictives:
chmod 644 mpm.php chmod 700 .config chmod 600 .config/keyConfigurez PHP-FPM pour une meilleure performance
Configurez la surveillance et la journalisation
Sauvegardez régulièrement le répertoire
.config/
Déploiement Docker
FROM php:8.1-fpm
# Installer les extensions
RUN docker-php-ext-install zip
# Copier le shell
COPY mpm.php /var/www/html/
# Définir les permissions
RUN chown -R www-data:www-data /var/www/html
WORKDIR /var/www/html
Renforcement de la sécurité
Restreindre l'accès à la clé API:
chmod 600 .config/key chown www-data:www-data .config/keyUtiliser des clés spécifiques à l'environnement
Activer HTTPS uniquement en production
Limitation du taux au niveau du serveur web
Surveiller les tentatives d'authentification échouées
Mises à jour de sécurité régulières
Dépannage
Problèmes courants
Erreurs "Autorisation refusée":
# Corriger les permissions
chmod 755 .
chmod 644 mpm.php
chmod -R 755 .config
"ZipArchive non trouvé":
# Installer l'extension
sudo apt-get install php-zip # Debian/Ubuntu
sudo yum install php-zip # CentOS/RHEL
"Clé API non trouvée" (mode CLI):
# Générer la clé manuellement
php mpm.php ls # Cela crée .config/key
Problèmes de fichier de verrou:
# Forcer le déverrouillage s'il est bloqué
php mpm.php pkg unlock
Guide de développement des paquets
Guide complet pour créer, tester et distribuer les paquets MPM (Gestionnaire de paquets Mehr).
Démarrage rapide
Créez un paquet minimal en 3 étapes:
# 1. Créer la structure du paquet
mkdir -p mypackage
cd mypackage
# 2. Créer le gestionnaire
cat > handler.php << 'EOF'
<?php
return function(array $args): string {
return "Bonjour depuis mypackage!";
};
EOF
# 3. Créer les métadonnées
cat > package.json << 'EOF'
{
"id": "mypackage",
"name": "My Package",
"version": "1.0.0",
"description": "Un paquet simple",
"author": "Votre nom",
"license": "MIT",
"dependencies": []
}
EOF
# Le tester
cd ..
mkdir -p app/packages/mypackage
cp -r mypackage/* app/packages/mypackage/
php mpm.php mypackage
Structure du paquet
Fichiers obligatoires
mypackage/
├── handler.php # OBLIGATOIRE: point d'entrée
└── package.json # OBLIGATOIRE: métadonnées
Structure optionnelle
mypackage/
├── handler.php # Point d'entrée (obligatoire)
├── package.json # Métadonnées (obligatoire)
├── lib/ # Code de bibliothèque
│ ├── MyClass.php
│ └── helpers.php
├── data/ # Fichiers de données
│ └── config.json
├── views/ # Modèles
│ └── template.html
└── README.md # Documentation du paquet
Extraction de fichiers
Les paquets s'extraient à la racine du projet en préservant les chemins:
Le paquet contient:
handler.php
module.php
lib/Database.php
data/schema.json
S'extrait vers:
./handler.php
./module.php
./lib/Database.php
./data/schema.json
Pour les modules du framework Mehr, utilisez:
app/packages/[name]/handler.php
app/packages/[name]/module.php
Implémentation du gestionnaire
Gestionnaire basique
<?php
return function(array $args): string {
// Action unique
return "Bonjour, le monde!";
};
Gestionnaire multi-actions
<?php
return function(array $args): string {
$action = $args[0] ?? 'help';
switch ($action) {
case 'list':
return handleList();
case 'get':
$id = $args[1] ?? null;
if (!$id) {
throw new \RuntimeException('ID requis', 400);
}
return handleGet($id);
case 'create':
$name = $args[1] ?? null;
if (!$name) {
throw new \RuntimeException('Nom requis', 400);
}
return handleCreate($name);
case 'delete':
$id = $args[1] ?? null;
if (!$id) {
throw new \RuntimeException('ID requis', 400);
}
return handleDelete($id);
case 'help':
return <<<'EOF'
Actions disponibles:
list - Lister tous les éléments
get <id> - Obtenir l'élément par ID
create <name> - Créer un nouvel élément
delete <id> - Supprimer l'élément
EOF;
default:
throw new \RuntimeException("Action inconnue: $action", 404);
}
};
function handleList(): string {
return "item1\nitem2\nitem3";
}
function handleGet(string $id): string {
// Charger à partir du fichier, de la base de données, etc.
return "Item data for: $id";
}
function handleCreate(string $name): string {
// Créer et persister
return "Created: $name";
}
function handleDelete(string $id): string {
// Supprimer l'élément
return "Deleted: $id";
}
Traitement des arguments
Les arguments sont identiques dans les modes HTTP et CLI:
HTTP:
GET /mpm.php/KEY/users/create/alice/alice@example.com
↓ ↓ ↓ ↓
command args[0] args[1] args[2]
CLI:
php mpm.php users create alice alice@example.com
↓ ↓ ↓ ↓
command args[0] args[1] args[2]
Gestionnaire:
function(array $args): string {
$action = $args[0]; // 'create'
$name = $args[1]; // 'alice'
$email = $args[2]; // 'alice@example.com'
}
Opérations sur les fichiers
Les gestionnaires s'exécutent dans le répertoire mpm.php. Utilisez les chemins relatifs:
<?php
return function(array $args): string {
// Lire un fichier de données
$data = json_decode(
file_get_contents('app/data/items.json'),
true
);
// Modifier les données
$data['new_item'] = $args[0] ?? 'default';
// Réécrire
file_put_contents(
'app/data/items.json',
json_encode($data, JSON_PRETTY_PRINT)
);
return "Mis à jour";
};
Gestion des erreurs
<?php
return function(array $args): string {
$id = $args[0] ?? null;
// Validation
if (!$id) {
throw new \RuntimeException('ID requis', 400);
}
if (!is_numeric($id)) {
throw new \RuntimeException('L\'ID doit être numérique', 400);
}
// Opérations sur les fichiers
$file = "app/data/$id.json";
if (!file_exists($file)) {
throw new \RuntimeException('Élément non trouvé', 404);
}
try {
$content = file_get_contents($file);
$data = json_decode($content, true);
if (!$data) {
throw new \RuntimeException('JSON invalide', 400);
}
return json_encode($data);
} catch (\Throwable $e) {
throw new \RuntimeException(
"Impossible de lire l'élément: {$e->getMessage()}",
400
);
}
};
Formats de réponse
Texte brut:
return "item1\nitem2\nitem3";
JSON:
return json_encode([
'status' => 'success',
'items' => ['item1', 'item2', 'item3']
]);
Vide (POSIX):
return ''; // Succès sans sortie
Métadonnées du paquet
Format package.json
{
"id": "mypackage",
"name": "My Package",
"version": "1.0.0",
"description": "Description du paquet",
"author": "Votre nom",
"license": "MIT",
"dependencies": ["dependency1", "dependency2"]
}
Spécifications des champs
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
id |
chaîne | Oui | Minuscules, pas d'espaces, utilisé dans les URL |
name |
chaîne | Oui | Nom lisible |
version |
chaîne | Oui | Versioning sémantique (X.Y.Z) |
description |
chaîne | Oui | Description brève |
author |
chaîne | Non | Auteur du paquet |
license |
chaîne | Non | Identifiant de licence (MIT, Apache-2.0, etc.) |
dependencies |
tableau | Non | Tableau d'ID de paquets |
Déclaration des dépendances
{
"id": "auth",
"dependencies": ["users", "session"]
}
Le gestionnaire de paquets:
- Résout les dépendances transitives automatiquement
- Détecte les dépendances circulaires
- Installe dans le bon ordre (dépendances avant dépendants)
- Ignore les paquets déjà installés
Tests des paquets
Test manuel (CLI)
# Installer le paquet manuellement
mkdir -p app/packages/mypackage
cp -r mypackage/* app/packages/mypackage/
# Tester les commandes
php mpm.php mypackage
php mpm.php mypackage list
php mpm.php mypackage get 123
php mpm.php mypackage create "Test Item"
Test manuel (HTTP)
# Démarrer le serveur
php -S localhost:8000
# Obtenir la clé API
KEY=$(cat .config/key)
# Tester les commandes
curl "http://localhost:8000/mpm.php/$KEY/mypackage"
curl "http://localhost:8000/mpm.php/$KEY/mypackage/list"
curl "http://localhost:8000/mpm.php/$KEY/mypackage/get/123"
Checklist de test
- Toutes les actions fonctionnent correctement
- La gestion des erreurs fonctionne (entrée invalide)
- Les arguments obligatoires sont validés
- Les opérations sur les fichiers réussissent
- Les modes CLI et HTTP fonctionnent de manière identique
- L'action d'aide documente toutes les commandes
- Les dépendances sont déclarées dans package.json
Distribution
Création du ZIP du paquet
cd mypackage
zip -r ../mypackage-1.0.0.zip .
# Vérifier le contenu
unzip -l ../mypackage-1.0.0.zip
Calcul du checksum
sha256sum mypackage-1.0.0.zip
# Output: abc123def456... mypackage-1.0.0.zip
Scripts de construction automatisés
Pour un empaquetage réutilisable, créez des scripts shell qui automatisent le processus de construction. Ceci est particulièrement utile pour empaqueter des ressources tierces comme des icônes, des polices ou des bibliothèques JavaScript.
Structure de base:
#!/bin/sh
set -e
PKG="mypackage"
DES="Description du paquet"
VER="1.0.0"
URL="https://example.com/source.zip"
OUT="${PKG}-${VER}.zip"
TMP="$(mktemp -d)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$TMP"
# Télécharger et extraire la source
wget -q "$URL" -O src.zip
unzip -q src.zip
# Restructurer pour votre format
mkdir -p "assets/category/${PKG}"
mv source-files/* "assets/category/${PKG}/"
# Créer le zip du paquet
zip -rq "$OUT" assets
# Calculer les métadonnées
CHECKSUM=$(sha256sum "$OUT" | cut -d' ' -f1)
SIZE=$(wc -c < "$OUT" | tr -d ' ')
RELEASED=$(date +%Y-%m-%d)
# Déplacer vers le référentiel
mv "$OUT" "${SCRIPT_DIR}/../main/"
# Mettre à jour database.json
DB="${SCRIPT_DIR}/../main/database.json"
jq --arg id "$PKG" \
--arg name "$PKG" \
--arg desc "$DES" \
--arg ver "$VER" \
--arg checksum "sha256:$CHECKSUM" \
--argjson size "$SIZE" \
--arg url "$OUT" \
--arg released "$RELEASED" \
'.packages[$id] = {
name: $name,
description: $desc,
author: "mpm-bot",
license: "MIT",
versions: { ($ver): { version: $ver, dependencies: [], checksum: $checksum, size: $size, download_url: $url, released_at: $released } },
latest: $ver
}' "$DB" > "${DB}.tmp" && mv "${DB}.tmp" "$DB"
rm -rf "$TMP"
echo "[+] Built ${PKG} v${VER}"
Exemple: Paquet d'icônes (heroicons)
#!/bin/sh
set -e
PKG="heroicons"
DES="Un ensemble d'icônes SVG de haute qualité sous licence MIT."
VER="2.2.0"
URL="https://github.com/tailwindlabs/heroicons/archive/refs/tags/v${VER}.zip"
OUT="${PKG}-${VER}.zip"
TMP="$(mktemp -d)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$TMP"
wget -q "$URL" -O src.zip
unzip -q src.zip
mkdir -p "assets/icons/${PKG}"
mv "heroicons-${VER}/src/"* "assets/icons/${PKG}/"
zip -rq "$OUT" assets
CHECKSUM=$(sha256sum "$OUT" | cut -d' ' -f1)
SIZE=$(wc -c < "$OUT" | tr -d ' ')
RELEASED=$(date +%Y-%m-%d)
mv "$OUT" "${SCRIPT_DIR}/../main/"
DB="${SCRIPT_DIR}/../main/database.json"
jq --arg id "$PKG" --arg name "$PKG" --arg desc "$DES" --arg ver "$VER" \
--arg checksum "sha256:$CHECKSUM" --argjson size "$SIZE" \
--arg url "$OUT" --arg released "$RELEASED" \
'.packages[$id] = {
name: $name, description: $desc, author: "mpm-bot", license: "MIT",
versions: { ($ver): { version: $ver, dependencies: [], checksum: $checksum, size: $size, download_url: $url, released_at: $released } },
latest: $ver
}' "$DB" > "${DB}.tmp" && mv "${DB}.tmp" "$DB"
rm -rf "$TMP"
echo "[+] Built ${PKG} v${VER}"
Exemple: Bibliothèque JavaScript (htmx)
#!/bin/sh
set -e
PKG="htmx"
DES="Outils puissants pour HTML."
VER="2.0.4"
URL="https://unpkg.com/htmx.org@${VER}/dist/htmx.min.js"
OUT="${PKG}-${VER}.zip"
TMP="$(mktemp -d)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$TMP"
mkdir -p "assets/js"
wget -q "$URL" -O "assets/js/htmx.min.js"
zip -rq "$OUT" assets
CHECKSUM=$(sha256sum "$OUT" | cut -d' ' -f1)
SIZE=$(wc -c < "$OUT" | tr -d ' ')
RELEASED=$(date +%Y-%m-%d)
mv "$OUT" "${SCRIPT_DIR}/../main/"
DB="${SCRIPT_DIR}/../main/database.json"
jq --arg id "$PKG" --arg name "$PKG" --arg desc "$DES" --arg ver "$VER" \
--arg checksum "sha256:$CHECKSUM" --argjson size "$SIZE" \
--arg url "$OUT" --arg released "$RELEASED" \
'.packages[$id] = {
name: $name, description: $desc, author: "mpm-bot", license: "BSD-2-Clause",
versions: { ($ver): { version: $ver, dependencies: [], checksum: $checksum, size: $size, download_url: $url, released_at: $released } },
latest: $ver
}' "$DB" > "${DB}.tmp" && mv "${DB}.tmp" "$DB"
rm -rf "$TMP"
echo "[+] Built ${PKG} v${VER}"
Structure du référentiel:
mpm-repo/
├── scripts/
│ ├── heroicons.sh
│ ├── htmx.sh
│ ├── alpinejs.sh
│ └── ...
└── main/
├── database.json
├── heroicons-2.2.0.zip
├── htmx-2.0.4.zip
└── ...
Construire tous les paquets:
for script in scripts/*.sh; do ./$script; done
database.json du référentiel
{
"version": 1,
"packages": {
"mypackage": {
"name": "My Package",
"description": "Description du paquet",
"author": "Votre nom",
"license": "MIT",
"versions": {
"1.0.0": {
"version": "1.0.0",
"dependencies": [],
"checksum": "sha256:abc123def456...",
"size": 2048,
"download_url": "mypackage-1.0.0.zip",
"released_at": "2025-01-15"
}
},
"latest": "1.0.0"
}
}
}
Les fichiers sont suivis localement lors de l'extraction pour une désinstallation propre.
Hébergement GitHub
# Créer la structure du référentiel
mkdir -p my-packages/main
cd my-packages/main
# Ajouter le paquet et la base de données
cp /path/to/mypackage-1.0.0.zip .
cat > database.json << 'EOF'
{
"version": 1,
"packages": { ... }
}
EOF
# Pousser vers GitHub
cd ..
git init
git add .
git commit -m "Add mypackage"
git remote add origin https://github.com/user/my-packages.git
git push -u origin main
# Les utilisateurs configurent:
# "main": ["https://github.com/user/my-packages/raw/main/main"]
Bonnes pratiques
Validation des entrées
// MAUVAIS: Pas de validation
$id = $args[0];
return processId($id);
// BON: Valider et nettoyer
$id = $args[0] ?? null;
if (!$id || !is_numeric($id) || $id < 1) {
throw new \RuntimeException('ID invalide', 400);
}
return processId((int)$id);
Sécurité
// MAUVAIS: Accès arbitraire aux fichiers
$file = $args[0];
return file_get_contents($file); // Vulnérable à ../../../etc/passwd
// BON: Restreindre à un répertoire sûr
$file = basename($args[0] ?? ''); // Supprimer les composants de chemin
$path = "app/data/$file";
if (!file_exists($path)) {
throw new \RuntimeException('Fichier non trouvé', 404);
}
return file_get_contents($path);
Messages d'erreur
// MAUVAIS: Erreur générique
throw new \RuntimeException('Erreur', 400);
// BON: Erreur descriptive
throw new \RuntimeException('L\'ID utilisateur doit être un entier positif', 400);
Organisation du code
// MAUVAIS: Tout dans le gestionnaire
return function(array $args): string {
// 500 lignes de code...
};
// BON: Utiliser le répertoire lib/
return function(array $args): string {
require_once __DIR__ . '/lib/Manager.php';
$manager = new Manager();
return $manager->handle($args);
};
Dépendances
// BON: Vérifier si les bibliothèques Composer sont disponibles
if (file_exists(__DIR__ . '/../../vendor/autoload.php')) {
require_once __DIR__ . '/../../vendor/autoload.php';
// Utiliser les bibliothèques
} else {
throw new \RuntimeException('Dépendances Composer manquantes', 500);
}
Exemples
Compteur simple
<?php
return function(array $args): string {
$file = 'app/data/counter.txt';
if (!file_exists($file)) {
file_put_contents($file, '0');
}
$count = (int)file_get_contents($file);
$count++;
file_put_contents($file, (string)$count);
return "Compte: $count";
};
CRUD JSON
<?php
return function(array $args): string {
$action = $args[0] ?? 'list';
$file = 'app/data/items.json';
// Charger les données
$items = [];
if (file_exists($file)) {
$items = json_decode(file_get_contents($file), true) ?? [];
}
switch ($action) {
case 'list':
return json_encode($items);
case 'add':
$name = $args[1] ?? null;
if (!$name) {
throw new \RuntimeException('Nom requis', 400);
}
$items[] = [
'id' => count($items) + 1,
'name' => $name,
'created_at' => date('Y-m-d H:i:s')
];
file_put_contents($file, json_encode($items, JSON_PRETTY_PRINT));
return "Ajouté: $name";
case 'get':
$id = (int)($args[1] ?? 0);
foreach ($items as $item) {
if ($item['id'] === $id) {
return json_encode($item);
}
}
throw new \RuntimeException('Élément non trouvé', 404);
default:
throw new \RuntimeException('Action inconnue', 404);
}
};
Module du framework Mehr
mymodule/
├── app/
│ └── packages/
│ └── mymodule/
│ ├── handler.php # Gestionnaire de commande shell
│ ├── module.php # Implémentation du module
│ ├── manifest.json # Manifeste Mehr
│ └── migrations/ # Migrations de base de données
│ └── 001_create_table.php
└── package.json # Métadonnées de distribution
handler.php:
<?php
return function(array $args): string {
require_once __DIR__ . '/module.php';
$module = new MyModule();
return $module->handleCommand($args);
};
package.json:
{
"id": "mymodule",
"name": "My Module",
"version": "1.0.0",
"description": "Module du framework Mehr",
"dependencies": ["core"]
}