{"id":34867143,"url":"https://github.com/numencode/wn-syncops-plugin","last_synced_at":"2026-01-20T16:25:48.599Z","repository":{"id":236312628,"uuid":"433362656","full_name":"numencode/wn-syncops-plugin","owner":"numencode","description":"NumenCode SyncOps plugin for Winter CMS","archived":false,"fork":false,"pushed_at":"2025-12-12T13:36:05.000Z","size":237,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-27T07:21:03.723Z","etag":null,"topics":["php","winter","wintercms","wintercms-plugin"],"latest_commit_sha":null,"homepage":"https://www.numencode.com","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/numencode.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":"support/MysqlCommandBuilder.php","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-11-30T09:07:49.000Z","updated_at":"2025-12-12T12:52:30.000Z","dependencies_parsed_at":"2024-04-26T15:26:55.968Z","dependency_job_id":"75c02f5f-bd0c-41d2-9287-e722ff857633","html_url":"https://github.com/numencode/wn-syncops-plugin","commit_stats":null,"previous_names":["numencode/wn-backup-plugin","numencode/wn-syncops-plugin"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/numencode/wn-syncops-plugin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/numencode%2Fwn-syncops-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/numencode%2Fwn-syncops-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/numencode%2Fwn-syncops-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/numencode%2Fwn-syncops-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/numencode","download_url":"https://codeload.github.com/numencode/wn-syncops-plugin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/numencode%2Fwn-syncops-plugin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28607016,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T16:10:39.856Z","status":"ssl_error","status_checked_at":"2026-01-20T16:10:39.493Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["php","winter","wintercms","wintercms-plugin"],"created_at":"2025-12-25T22:39:12.777Z","updated_at":"2026-01-20T16:25:48.579Z","avatar_url":"https://github.com/numencode.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SyncOps Plugin\n\nThe **NumenCode SyncOps** plugin for Winter CMS offers a powerful and streamlined solution for managing backups,\ndeployments, and environment synchronization. Designed for developers, it simplifies syncing databases, media\nfiles, and code between environments, enabling safer and more efficient DevOps workflows within Winter CMS.\n\n[![Version](https://img.shields.io/github/v/release/numencode/wn-syncops-plugin?style=flat-square\u0026color=0099FF)](https://github.com/numencode/wn-syncops-plugin/releases)\n[![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/numencode/wn-syncops-plugin?style=flat-square\u0026color=0099FF)](https://packagist.org/packages/numencode/wn-syncops-plugin)\n[![Checks](https://img.shields.io/github/check-runs/numencode/wn-syncops-plugin/main?style=flat-square)](https://github.com/numencode/wn-syncops-plugin/actions)\n[![Tests](https://img.shields.io/github/actions/workflow/status/numencode/wn-syncops-plugin/tests.yml?branch=main\u0026label=tests\u0026style=flat-square)](https://github.com/numencode/wn-syncops-plugin/actions)\n[![License](https://img.shields.io/github/license/numencode/wn-syncops-plugin?label=open%20source\u0026style=flat-square\u0026color=0099FF)](https://github.com/numencode/wn-syncops-plugin/blob/main/LICENSE.md)\n\n---\n\n## Target Audience\n\nThe target audience for this plugin includes Winter CMS developers, DevOps engineers, and technical teams who manage\nmultiple environments (local, staging, production) and require reliable tools for synchronization and deployment.\nSyncOps is ideal for teams working on complex, multi-instance projects where keeping databases, media assets,\nand codebases aligned is critical. It streamlines routine operations and reduces the risk of manual errors,\nsupporting a more automated and professional development workflow.\n\n## Installation and setup\n\nThis package requires [Winter CMS](https://wintercms.com/) application.\n\nInstall the package with Composer:\n\n```bash\ncomposer require numencode/wn-syncops-plugin\n```\n\nRun the command:\n\n```bash\nphp artisan vendor:publish --tag=syncops-config\n```\n\nThis command will create a new configuration file, located at `/config/syncops.php`, that contains all the options\nyou need in order to configure your remote connections. The connections array contains a list of your servers keyed\nby name. Simply populate the credentials in the connections array via your environment variables in the `.env` file.\n\n## Configuration\n\nBefore using SyncOps, you must define your **remote servers and project settings** in `config/syncops.php`.\nEach connection entry (e.g. `production`, `staging`) describes how SyncOps should connect and operate on that server.\n\n### Timestamp\n\n```php\n'timestamp' =\u003e 'Y-m-d_H_i_s',\n```\n\nDefines the default timestamp format used for naming files (backups, database dumps, archives).  \nDefaults to `Y-m-d_H_i_s`.\n\n---\n\n### Connections\n\nAll remote servers are defined under the `connections` array.  \nEach server is keyed by a name (e.g. `production`, `staging`) and contains:\n\n#### SSH Credentials\n\n* `host` → Remote server host (IP or domain)\n* `port` → SSH port (default: `22`)\n* `username` → SSH username\n* `password` → (Optional) SSH password (not needed with key auth)\n* `key_path` → (Optional) Path to private key file\n\n\u003e 🔒 For security, these values are typically provided via environment variables rather than hardcoded.\n\n#### Project Settings\n\n* `path` → Absolute path to the project root on the remote server\n* `branch_main` → Name of the main development branch (default: `main`)\n* `branch_prod` → Name of the production branch (default: `prod`)\n\n#### Permissions (Optional)\n\nIf your server uses different users for deployment vs. web server runtime, you can define ownership rules:\n\n* `root_user` → Superuser and group with full access, e.g. `root:root`\n* `web_user` → Web server user/group, e.g. `www-data:www-data`\n* `web_folders` → Array of folders owned by the web user (defaults: `storage`, `themes`)\n\n#### Remote Database (Optional)\n\nRequired only when using `syncops:db-pull` or related database commands.\n\n* `database` → Database name on the remote server\n* `username` → Database username\n* `password` → Database password\n* `tables` → (Optional) Restrict synchronization to specific tables\n\n---\n\n### Environment Variables\n\nTypical `.env` configuration for a **production server** might look like this:\n\n```dotenv\nSYNCOPS_PRODUCTION_HOST=123.456.789.10\nSYNCOPS_PRODUCTION_PORT=22\nSYNCOPS_PRODUCTION_USERNAME=deploy\nSYNCOPS_PRODUCTION_KEY=C:\\Users\\me\\.ssh\\id_rsa\nSYNCOPS_PRODUCTION_PATH=/var/www/example.com\nSYNCOPS_PRODUCTION_BRANCH_PROD=prod\nSYNCOPS_PRODUCTION_BRANCH_MAIN=main\n\n# Optional permission settings\n# REMOTE_PRODUCTION_ROOT_USER=root:root\n# REMOTE_PRODUCTION_WEB_USER=www-data:www-data\n\n# Optional database settings\nSYNCOPS_PRODUCTION_DB_DATABASE=example_db\nSYNCOPS_PRODUCTION_DB_USERNAME=example_user\nSYNCOPS_PRODUCTION_DB_PASSWORD=\"secret\"\n```\n\n⚠️ On the **remote server**, you generally don’t need to replicate these environment variables —\nthey are only required in your **local/dev environment** to allow SyncOps to connect.\n\n---\n\n## Commands overview\n\n| Command                                   | Description                                                                                                                 |\n|:------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------|\n| [syncops:db-pull](#db-pull)               | Create a database dump on a remote server,\u003cbr\u003edownload it, and import it locally.                                           |\n| [syncops:db-push](#db-push)               | Create a database dump (compressed by default)\u003cbr\u003eand optionally upload it to cloud storage.                                |\n| [syncops:media-pull](#media-pull)         | Download media files from the remote server\u003cbr\u003evia SFTP into local storage.                                                 |\n| [syncops:media-push](#media-push)         | Back up all media files\u003cbr\u003eto the specified cloud storage.                                                                  |\n| [syncops:project-backup](#project-backup) | Create a compressed archive of project files\u003cbr\u003eand optionally upload it to cloud storage.                                  |\n| [syncops:project-deploy](#project-deploy) | Deploy the project to a remote server via Git, with optional\u003cbr\u003ecache clearing, Composer install, and migrations.           |\n| [syncops:project-pull](#project-pull)     | Commit untracked changes on the remote server, push them\u003cbr\u003eto the origin, and optionally merge them into the local branch. |\n| [syncops:project-push](#project-push)     | Add and commit project changes locally\u003cbr\u003eand push them to the remote repository.                                           |\n| [syncops:remote-artisan](#remote-artisan) | Execute a `php artisan` command on a remote server\u003cbr\u003eand stream the output back to your local console.                     |\n| [syncops:remote-health](#remote-health)   | Run health checks (system, PHP, database, Git, project)\u003cbr\u003eon a remote server.                                              |\n| [syncops:validate](#validate)             | Validate SyncOps configuration (connections, SSH, paths)\u003cbr\u003eand optionally test SSH connectivity.                           |\n\n---\n\n\u003ca name=\"db-pull\"\u003e\u003c/a\u003e\n### Command: `syncops:db-pull`\n\nCreate a database dump on a **remote server**, download it to your local project,\nand (by default) import it into your local database.\n\nThis command is especially useful for **synchronizing development environments** with production or staging data.\n\nThis command performs the following actions:\n\n1. Connects to the specified remote server.\n2. Creates a database dump on the remote server.\n    - By default, the dump is compressed as a `.sql.gz` file.\n    - With the `--no-gzip` option, the dump is saved as a plain `.sql` file.\n    - Optionally, you can define specific tables to pull by configuring them in `config/syncops.php`\n      under `database.tables`. Only the tables listed there will be included in the dump.\n3. Downloads the dump via SFTP to your local project folder.\n4. **Optionally** unzips the dump locally (if compression was used).\n5. **Optionally** imports the dump into your configured local database.\n6. Cleans up temporary dump files both remotely and locally.\n\n#### Configuration\n\nBefore using this command, ensure your remote servers are properly configured in your\nLaravel application's `config/syncops.php` file. The command will use the SSH connection\ndetails from this configuration to connect to the remote host.\n\nExample `config/syncops.php` snippet for database tables:\n\n```php\n'database' =\u003e [\n    'username' =\u003e 'dbuser',\n    'password' =\u003e 'dbpass',\n    'database' =\u003e 'my_database',\n    'tables'   =\u003e [\n        'users',\n        'posts',\n        'comments',\n    ],\n],\n```\n\nIf the `tables` array is provided, only these tables will be included in the dump; otherwise, the entire\ndatabase will be dumped.\n\n#### Usage\n\n```bash\nphp artisan syncops:db-pull {server} [options]\n````\n\n#### Arguments\n\n| Argument | Description                                                                                                                              |\n|----------|------------------------------------------------------------------------------------------------------------------------------------------|\n| `server` | The name of the remote server, as defined in your `syncops.php` configuration file.\u003cbr\u003eExample: `php artisan syncops:db-pull production` |\n\n#### Options\n\n| Option              | Description                                                                                                                                                                            |\n|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `--timestamp=`      | Date format used for naming the dump file. The default is defined in `config/syncops.php`.\u003cbr\u003eExample: `php artisan syncops:db-pull production --timestamp=d-m-Y`                      |\n| `-g`, `--no-gzip`   | Skips the gzip compression process and transfers the database dump as a plain `.sql` file.\u003cbr\u003eExample: `php artisan syncops:db-pull production --no-gzip`                              |\n| `-i`, `--no-import` | Prevents the database dump from being automatically imported\u003cbr\u003einto the local database after it has been downloaded.\u003cbr\u003eExample: `php artisan syncops:db-pull production --no-import` |\n\n#### Note\n\n\u003e This command currently **only supports MariaDB and MySQL** databases.\n\u003e Other database types supported by Laravel (PostgreSQL, SQLite, SQL Server, Redis, etc.) are not compatible.\n\n---\n\n\u003ca name=\"db-push\"\u003e\u003c/a\u003e\n### Command: `syncops:db-push`\n\nCreate a database dump (compressed by default) of your project’s default MySQL/MariaDB database,\nand, if specified, upload it to a cloud storage provider.\n\nThis command is especially useful for **scheduled backups**, ensuring your production database\nis safely stored locally and in the cloud.\n\nThis command performs the following actions:\n\n1. Creates a database dump of the configured default database.\n   - By default, the dump is compressed as a `.sql.gz` file.\n   - With the `--no-gzip` option, the dump is saved as a plain `.sql` file.\n2. Names the file using a timestamp (format configurable via option).\n3. **Optionally** uploads the file to a cloud storage provider.\n4. **Optionally** deletes the local file after upload, unless instructed to keep it.\n5. **Optionally** moves the file to a local folder if `--folder` is specified.\n\n#### Configuration\n\nThis command relies on Laravel’s **Filesystem configuration** if uploading to cloud storage.\nMake sure the chosen cloud storage disk is defined in `config/filesystems.php`.\n\n#### Usage\n\n```bash\nphp artisan syncops:db-push {cloud?} [options]\n````\n\n#### Arguments\n\n| Argument | Description                                                                                                                                                                                     |\n|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `cloud`  | The optional name of the cloud storage disk where the database dump file will be uploaded.\u003cbr\u003eMust be configured in `config/filesystems.php`.\u003cbr\u003eExample: `php artisan syncops:db-push dropbox` |\n\n#### Options\n\n| Option              | Description                                                                                                                                                                     |\n|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `--folder=`         | The name of the folder where the dump file will be stored both locally and on the cloud.\u003cbr\u003eExample: `php artisan syncops:db-push dropbox --folder=database`                    |\n| `--timestamp=`      | Date format used for naming the dump file. The default is defined in `config/syncops.php`.\u003cbr\u003eExample: `php artisan syncops:db-push dropbox --timestamp=d-m-Y`                  |\n| `-g`, `--no-gzip`   | Skips the gzip compression process, saving the dump file\u003cbr\u003eas a plain `.sql` file instead of a compressed archive.\u003cbr\u003eExample: `php artisan syncops:db-push dropbox --no-gzip` |\n| `-d`, `--no-delete` | Prevents the local dump file from being deleted after it has been\u003cbr\u003esuccessfully uploaded to the cloud storage.\u003cbr\u003eExample: `php artisan syncops:db-push dropbox --no-delete`  |\n\n#### Usage in Scheduler\n\nTo automate your database backups, add the command to your Winter CMS Scheduler\n(typically in `app/Plugin.php` or a dedicated service provider's `boot()` method).\n\n```php\n$schedule-\u003ecommand('syncops:db-push dropbox --folder=database')-\u003edailyAt('02:00');\n```\n\nThis example schedules a daily backup of your database to the `dropbox` disk every day at 2:00 AM.\n\n#### Note\n\n\u003e This command currently **only supports MariaDB and MySQL** databases.\n\u003e Other database types supported by Laravel (PostgreSQL, SQLite, SQL Server, Redis, etc.) are not compatible.\n\n---\n\n\u003ca name=\"media-pull\"\u003e\u003c/a\u003e\n### Command: `syncops:media-pull`\n\nDownload media files from a **remote server** via SFTP directly into your local `storage/app` directory.\n\nThis command is especially useful for **synchronizing media files** in development\nenvironments with production data without including them in your Git repository.\n\nThis command performs the following actions:\n\n1. Connects to the specified remote server via SFTP.\n2. Recursively fetches all files under the remote `storage/app` directory.\n3. **Skips certain directories and files** according to the rules below.\n4. Downloads each file into your local `storage/app` folder, creating directories as needed.\n5. Optionally skips overwriting local files if `--no-overwrite` is provided.\n6. Ensures file sizes match to avoid unnecessary downloads when the local file is identical.\n\n#### Configuration\n\nBefore using this command, ensure your remote servers are properly configured in your\nLaravel application's `config/syncops.php` file. The command will use the SSH connection\ndetails from this configuration to connect to the remote host.\n\n#### Usage\n\n```bash\nphp artisan syncops:media-pull {server} [options]\n````\n\n#### Arguments\n\n| Argument | Description                                                                                                                                 |\n|----------|---------------------------------------------------------------------------------------------------------------------------------------------|\n| `server` | The name of the remote server, as defined in your `syncops.php` configuration file.\u003cbr\u003eExample: `php artisan syncops:media-pull production` |\n\n#### Options\n\n| Option           | Description                                                                                                                                                                                                |\n|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `--no-overwrite` | Prevents the command from overwriting local files that already exist,\u003cbr\u003ewhich is useful for only downloading new or updated files.\u003cbr\u003eExample: `php artisan syncops:media-pull production --no-overwrite` |\n\n#### Behavior\n\n* The remote path scanned is always `storage/app`.\n* Files inside any `/thumb/` or `/resized/` directories are **skipped**.\n* Files starting with a dot (hidden files) are **skipped**, **except** `.gitignore` files which are always downloaded.\n* Only files that either do not exist locally or have a different file size are downloaded (unless `--no-overwrite` is used).\n* All directory structures from the remote server are preserved locally.\n* Files are downloaded **directly via SFTP**, no intermediate cloud storage is used.\n\n---\n\n\u003ca name=\"media-push\"\u003e\u003c/a\u003e\n### Command: `syncops:media-push`\n\nEfficiently upload all media files from your Winter CMS installation (specifically from the `storage/app` directory)\nto a specified cloud storage disk.\n\nThis command is ideal for creating routine media backups. When integrated with the Winter CMS Scheduler, it enables\nautomated daily backups to your chosen cloud storage, ensuring your media assets are always safe and accessible.\n\nBy default, the command excludes `.gitignore` files and any files located within `/thumb/` directories\n(which typically contain generated image thumbnails) to optimize upload times and storage space.\n\n#### Configuration\n\nThis command relies on Laravel’s **Filesystem configuration**.\nMake sure the chosen cloud storage disk is defined in `config/filesystems.php`.\n\n#### Usage\n\nTo run the command, you need to specify the cloud storage disk name (as defined in your `config/filesystems.php`).\n\n```bash\nphp artisan syncops:media-push {cloud} [options]\n```\n\nYou can also specify an optional target folder within your cloud storage which will serve as the base directory for\nyour uploaded media files. If no folder is specified, the default target folder on the cloud storage will be `storage/`.\n\n#### Arguments\n\n| Argument | Description                                                                                                                                                                                   |\n|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `cloud`  | The required name of the cloud storage disk. Must be configured in `config/filesystems.php`.\u003cbr\u003eExample: `php artisan syncops:media-push dropbox`                                             |\n\n#### Options\n\n| Option            | Description                                                                                                                                                                                   |\n|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `--folder=`       | The optional target folder where the media files will be stored.\u003cbr\u003eApplies both locally and on cloud storage.\u003cbr\u003eExample: `php artisan syncops:media-push dropbox --folder=media/my-project` |\n| `-l`, `--log`     | Displays details for each file as it is processed, including files\u003cbr\u003ethat are uploaded and those that are skipped.\u003cbr\u003eExample: `php artisan syncops:media-push dropbox --log`                |\n| `-d`, `--dry-run` | Simulates the upload process without actually transferring any files,\u003cbr\u003ewhich is useful for testing and verifying your setup.\u003cbr\u003eExample: `php artisan syncops:media-push dropbox --dry-run` |\n\n#### Usage in Scheduler\n\nTo automate your media backups, add the command to your Winter CMS Scheduler\n(typically in `app/Plugin.php` or a dedicated service provider's `boot()` method).\n\n```php\n$schedule-\u003ecommand('syncops:media-push dropbox')-\u003edailyAt('03:00');\n```\n\nThis example schedules a daily backup of your media files to the `dropbox` disk every day at 3:00 AM.\n\n---\n\n\u003ca name=\"project-backup\"\u003e\u003c/a\u003e\n### Command: `syncops:project-backup`\n\nCreate a compressed archive of all project files and optionally upload it to the configured cloud storage.\n\n#### Configuration\n\nThis command relies on Laravel’s **Filesystem configuration**.\nMake sure the chosen cloud storage disk is defined in `config/filesystems.php`.\n\n#### Usage\n\n```bash\nphp artisan syncops:project-backup {cloud?} [options]\n```\n\n#### Arguments\n\n| Argument | Description                                                                                                                                                                              |\n|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `cloud`  | The optional name of the cloud storage disk where the archive will be uploaded. Must be configured in `config/filesystems.php`.\u003cbr\u003eExample: `php artisan syncops:project-backup dropbox` |\n\n#### Options\n\n| Option              | Description                                                                                                                                                                                                                                                                          |\n|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `--folder=`         | The folder where the archive will be stored.\u003cbr\u003eApplies both locally and on cloud storage.\u003cbr\u003eExample: `php artisan syncops:project-backup --folder=backups/my-project`                                                                                                              |\n| `--timestamp=`      | Date format used for naming the archive file.\u003cbr\u003eThe default is defined in `config/syncops.php`.\u003cbr\u003eExample: `php artisan syncops:project-backup --timestamp=Y-m-d`                                                                                                                  |\n| `--exclude=`        | Comma-separated list of folders to exclude from the archive.\u003cbr\u003eFolders `storage/framework/cache`, `/vendor` and backup folder\u003cbr\u003e(as defined with `--folder`) are always excluded by default.\u003cbr\u003eExample: `php artisan syncops:project-backup --exclude=node_modules,storage,tests` |\n| `-d`, `--no-delete` | If set, the local archive file will **not** be deleted after upload.\u003cbr\u003eInstead, it will be moved into the `--folder` (if specified).\u003cbr\u003eExample: `php artisan syncops:project-backup --no-delete`                                                                                   |\n\n#### Usage in Scheduler\n\nTo automate your project backups, add the command to your Winter CMS Scheduler\n(typically in `app/Plugin.php` or a dedicated service provider's `boot()` method).\n\n```php\n$schedule-\u003ecommand('syncops:project-backup dropbox --folder=_backup')-\u003eweekly()-\u003emondays()-\u003eat('2:00')\n```\n\nThis example schedules a daily backup of your project to the `dropbox` disk every monday at 2:00 AM.\n\n---\n\n\u003ca name=\"project-deploy\"\u003e\u003c/a\u003e\n### Command: `syncops:project-deploy`\n\nDeploy the project to a remote server via **Git**, with optional cache clearing, Composer install, and migrations.\nThis command ensures a clean remote working tree before continuing. If uncommitted changes are detected on the server,\nthe deployment will be aborted with a notice to run [`syncops:project-pull`](#project-pull).\n\n#### Usage\n\n```bash\nphp artisan syncops:project-deploy {server} [options]\n```\n\n#### Configuration\n\nBefore using this command, ensure your remote servers are properly configured in your\nLaravel application's `config/syncops.php` file. The command will use the SSH connection\ndetails from this configuration to connect to the remote host.\n\n#### Arguments\n\n| Argument | Description                                                                                                                          |\n|----------|--------------------------------------------------------------------------------------------------------------------------------------|\n| `server` | The name of the remote server (as defined in `config/syncops.php`).\u003cbr\u003eExample: `php artisan syncops:project-pull production --pull` |\n\n#### Options\n\n| Option             | Description                                                                                                                             |\n|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------|\n| `-f`, `--fast`     | Perform a fast deploy without clearing or rebuilding the cache.\u003cbr\u003eExample: `php artisan syncops:project-pull production --fast`        |\n| `-c`, `--composer` | Force `composer install --no-dev` on the remote server.\u003cbr\u003eExample: `php artisan syncops:project-pull production --composer`            |\n| `-m`, `--migrate`  | Run database migrations (`php artisan winter:up`) after deployment.\u003cbr\u003eExample: `php artisan syncops:project-pull production --migrate` |\n| `-x`, `--sudo`     | Execute remote commands as super user (`sudo`).\u003cbr\u003eExample: `php artisan syncops:project-pull production --sudo`                        |\n\n#### Behavior\n\n* Checks if the remote working directory is **clean**:\n  - If not, the process is aborted, and you will be prompted to run `syncops:project-pull`.\n* Two deployment modes are supported:\n  - **Full deploy (default)**: Puts the app in maintenance mode, clears caches,\n      updates via Git, rebuilds caches, and brings the app back up.\n  - **Fast deploy (`--fast`)**: Skips cache clearing/rebuilding, updates via Git directly.\n* **Deployment strategies**:\n  - **Pull mode** (`branch_main` set to `false` in config): Runs `git pull`.\n  - **Merge mode** (`branch_main` set to a branch name): Runs `git fetch` and `git merge origin/{branch}`.\n  - If `branch` or `branch_prod` is configured, the remote branch is pushed back to origin after a successful merge.\n* **Post-deploy steps**:\n  - Runs Composer install (with `--no-dev` option) if `--composer` is specified or if `composer.lock` changed.\n  - Runs migrations if `--migrate` is specified.\n  - Adjusts file/folder ownership according to `permissions` config.\n\n---\n\n\u003ca name=\"project-pull\"\u003e\u003c/a\u003e\n### Command: `syncops:project-pull`\n\nThe `syncops:project-pull` command automates the process of synchronizing a remote production environment's code\nchanges with your local development project. It's particularly useful for Winter CMS projects where content changes\nmade in the backend (pages, layouts, etc.) are saved as static `.htm` files.\n\nThis command performs the following actions:\n\n1. Checks for and commits any untracked changes on the remote server.\n2. **Optionally** executes a `git pull` on the remote to ensure it's up-to-date before pushing.\n3. Pushes the committed changes from the remote server to your Git repository.\n4. **Optionally** fetches the changes and merges them into your current local branch.\n\nThis streamlined workflow ensures you can quickly and safely retrieve and integrate content updates made directly\non your production site, without manual intervention.\n\n#### Usage\n\nTo run the command, you need to specify the name of the remote server, as configured in your `config/remote.php` file.\n\n```bash\nphp artisan syncops:project-pull {server} [options]\n```\n\n#### Configuration\n\nBefore using this command, ensure your remote servers are properly configured in your\nLaravel application's `config/syncops.php` file. The command will use the SSH connection\ndetails from this configuration to connect to the remote host.\n\n#### Arguments\n\n| Argument | Description                                                                                                                          |\n|----------|--------------------------------------------------------------------------------------------------------------------------------------|\n| `server` | The name of the remote server (as defined in `config/syncops.php`).\u003cbr\u003eExample: `php artisan syncops:project-pull production --pull` |\n\n#### Options\n\n| Option             | Description                                                                                                                                                                                                                                                                                           |\n|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `-p`, `--pull`     | Executes a `git pull` on the remote server before pushing changes.\u003cbr\u003eUse this to ensure the remote server's branch is fully up-to-date\u003cbr\u003ewith the repository's origin before pushing its new changes.\u003cbr\u003eExample: `php artisan syncops:project-pull production --pull`                              |\n| `-m`, `--no-merge` | Prevents the command from automatically merging changes into your local\u003cbr\u003ebranch after pushing them to the repository. This is useful if you want to\u003cbr\u003einspect the changes on your local machine before merging them manually.\u003cbr\u003eExample: `php artisan syncops:project-pull production --no-merge` |\n| `--message=`       | Specify a custom commit message for the changes on the remote server.\u003cbr\u003eIf this option is not used, the default commit message is **\"Server changes\"**.\u003cbr\u003eExample: `php artisan syncops:project-pull production --message=\"Updated content\"`                                                        |\n\n---\n\n\u003ca name=\"project-push\"\u003e\u003c/a\u003e\n### Command: `syncops:project-push`\n\nThe `syncops:project-push` command automates the process of committing and pushing local project changes to your\nGit repository. It’s especially useful when working on Winter CMS projects where content or code adjustments\nhave been made directly on a development or staging server, and you want to persist those changes in Git.\n\nThis command performs the following actions:\n\n1. Checks for uncommitted changes in your local working directory.\n2. If changes are detected, stages all modified and new files (`git add --all`).\n3. Commits the changes with either a default or custom commit message.\n4. Pushes the commit to your remote Git repository.\n\nThis ensures that local adjustments are always tracked and versioned,\nkeeping your project repository in sync with your environment.\n\n#### Configuration\n\nThis command runs entirely **locally** and does not require a remote server configuration.\nIt uses your current Git settings (branch, remote, and authentication) to push changes.\n\n#### Usage\n\nTo run the command:\n\n```bash\nphp artisan syncops:project-push [options]\n```\n\nBy default, this will commit with the message **\"Server changes\"** and push them to your configured remote.\n\n#### Options\n\n| Option       | Description                                                                                                                                                                                                               |\n|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `--message=` | Specify a custom commit message for the changes.\u003cbr\u003eIf this option is not used, the default commit message is **\"Server changes\"**.\u003cbr\u003eExample: `php artisan syncops:project-push --message=\"Updated content and layout\"` |\n\n---\n\n\u003ca name=\"remote-artisan\"\u003e\u003c/a\u003e\n### Command: `syncops:remote-artisan`\n\nRun a `php artisan` command **directly on a remote server** via SSH and stream the output back to your local console.\n\nThis is especially useful when you need to quickly run framework tasks on production or staging environments – such as\nclearing caches, running migrations, or inspecting configuration – without manually SSH’ing into the server.\n\n#### Usage\n\n```bash\nphp artisan syncops:remote-artisan {server} {artisanCommand*}\n````\n\n#### Examples\n\n```bash\n# Clear cache on the \"production\" server\nphp artisan syncops:remote-artisan production cache:clear\n\n# Run migrations on the remote server\nphp artisan syncops:remote-artisan production migrate --force\n\n# Show the remote environment\nphp artisan syncops:remote-artisan production env\n```\n\nEverything after `{server}` is forwarded directly to `php artisan` on the remote machine.\n\n#### Configuration\n\nThis command uses the same **SSH** and **project settings**, defined in `config/syncops.php` under `connections`.\n\nFor the given `{server}`:\n\n* `ssh.host`, `ssh.port`, `ssh.username` (and `password` / `key_path`) must be valid.\n* `project.path` must point to the root of your Winter CMS project on the remote server.\n* Running `php artisan` from within `project.path` on the remote server must work.\n\n#### Arguments\n\n| Argument          | Description                                                                                                                                               |\n|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `server`          | The name of the remote server (as defined in `config/syncops.php`).\u003cbr\u003eExample: `php artisan syncops:remote-artisan production cache:clear`               |\n| `artisanCommand*` | The artisan sub-command and its arguments/options to run remotely,\u003cbr\u003e**without** the leading `php artisan`.\u003cbr\u003eExample: `migrate --force`, `cache:clear` |\n\n#### Behavior\n\n* The command connects to the configured remote server via SSH.\n* It changes directory to the configured `project.path`.\n* It executes `php artisan {artisanCommand...}` on the remote server.\n* The remote output is streamed back to your local console.\n* If the SSH connection or the remote command fails, the command will print an error and return a non-zero exit code.\n\n---\n\n\u003ca name=\"remote-health\"\u003e\u003c/a\u003e\n### Command: `syncops:remote-health`\n\nRun a set of **health checks on a remote server** to quickly verify that the environment is in good shape for running\nyour Winter CMS project.\n\nThis command is intended as a **diagnostic tool** when something feels “off” on a remote server (slow responses,\ndeployment issues, unexpected PHP behavior, etc.) and you want a quick, scriptable overview of the system.\n\nThis command performs the following actions:\n\n1. **System checks**\n   - Shows uptime (`uptime`)\n   - Prints disk usage (`df -h`)\n\n2. **PHP checks**\n   - Always shows PHP version (`php -v`)\n   - With `--full`, also lists loaded extensions (`php -m`)\n\n3. **Database checks** (only when a database is configured for the given connection)\n   - Detects available client: **prefers `mariadb`**, falls back to `mysql`\n   - Shows client version  \n   - With `--full`, performs a `SELECT 1` connectivity probe using the configured username/password/database from `config/syncops.php`\n\n4. **Project checks**\n   - Shows current working directory (`pwd`)\n   - Reports Git status and current branch (clean/dirty)\n   - Shows Laravel framework version (`php artisan --version`)\n   - Shows Winter CMS build (parsed from `php artisan winter:version`)\n\n#### Configuration\n\nThis command reuses the configuration from `config/syncops.php`.\n\n*If the `database` block is missing or incomplete, database checks are skipped, but other checks still run.*\n\n#### Usage\n\n```bash\nphp artisan syncops:remote-health {server} [options]\n```\n\n#### Arguments\n\n| Argument | Description                                                                                                                    |\n|----------|--------------------------------------------------------------------------------------------------------------------------------|\n| `server` | The name of the remote server (as defined in `config/syncops.php`).\u003cbr\u003eExample: `php artisan syncops:remote-health production` |\n\n#### Options\n\n| Option   | Description                                                                                                                                                                                                                     |\n|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `--full` | Enables extended checks:\u003cbr\u003e- Lists loaded PHP extensions (`php -m`)\u003cbr\u003e- Performs a `SELECT 1` database connectivity probe using the configured credentials.\u003cbr\u003eExample: `php artisan syncops:remote-health production --full` |\n\n#### Behavior\n\n* **Light mode (default)**:\n\n  * System: uptime, disk usage\n  * PHP: version only\n  * Database: client + version (if database is configured), no connectivity probe\n  * Project: working directory, Git clean/dirty + branch, framework and Winter CMS versions\n\n* **Full mode (`--full`)** additionally:\n\n  * Prints full list of PHP modules (`php -m`)\n  * Performs a `SELECT 1` against the configured database using the preferred client (`mariadb` or `mysql`)\n  * Reports database connectivity as **OK** or a warning with the error message\n\n\u003e This command is read-only from the perspective of your project and database.\n\u003e It does **not** run migrations, modify files, or alter the database — it only inspects and reports.\n\n---\n\n\u003ca name=\"validate\"\u003e\u003c/a\u003e\n### Command: `syncops:validate`\n\nThe `syncops:validate` command helps you verify that your SyncOps configuration is correct and usable **before**\nrunning deployments, pulls, or backups. It performs static checks on your `config/syncops.php` connections and can\noptionally attempt live SSH connections to confirm that connectivity works as expected.\n\nThis command is especially useful when:\n\n* Setting up SyncOps for the first time.\n* Adding or updating a server configuration (e.g. new production or staging server).\n* Debugging connection problems (`ssh.host`, `ssh.key_path`, or `project.path` issues).\n\n#### Usage\n\n```bash\nphp artisan syncops:validate [options]\n````\n\nYou can validate **all** configured servers, or filter the validation to a single connection key.\n\n#### Options\n\n| Option      | Description                                                                                                                                                                                                   |\n|-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `--server=` | Restrict validation to a single server key\u003cbr\u003eas defined in `config/syncops.php` under `connections`.\u003cbr\u003eExample: `php artisan syncops:validate --server=production`                                          |\n| `--connect` | In addition to static checks, attempt a real SSH + SFTP connection for each\u003cbr\u003evalidated server using `RemoteExecutor::connectBoth()`.\u003cbr\u003eUseful to confirm that credentials, host, and key path are working. |\n\n#### Behavior\n\n* Reads `config/syncops.php` and inspects each entry under `connections`.\n* For each server, it performs **static validation**:\n  * Confirms the presence of the `ssh` block and required fields like `ssh.host` and `ssh.username`.\n  * Warns if neither `ssh.password` nor `ssh.key_path` is set.\n  * If `ssh.key_path` is configured, checks if the file exists and is readable.\n  * Confirms that the `project` block exists and that `project.path` is set.\n  * Performs light sanity checks on optional sections like `database` and `permissions`.\n* When the `--connect` flag is used:\n  * For each server that passes static validation, it instantiates a `RemoteExecutor` and calls `connectBoth()`.\n  * Reports a **success** message when SSH connectivity works.\n  * Reports an **error** if connectivity fails (e.g. wrong host, invalid key, firewall).\n* If any configuration errors or connection failures are detected, the command exits with **FAILURE**.\n* If all checked servers pass, the command exits with **SUCCESS** and prints a summary message.\n\n#### Examples\n\n```bash\n# Validate all configured servers with static checks only\nphp artisan syncops:validate\n\n# Validate only the `production` connection and test live SSH connectivity\nphp artisan syncops:validate --server=production --connect\n```\n\nUse this command as a quick “pre-flight check” whenever you change your SyncOps configuration or deploy to new infrastructure.\n\n---\n\n## Recommended Scheduler settings\n\nThe recommended entries for the Scheduler are as follows:\n- create a complete project backup every monday at 1 am and push it to a cloud\n- create a backup of all media files every day at 2 am and push it to a cloud\n- create a backup of the database every day at 3 am  and push it to a cloud\n- commit changes from the production environment every day at 4 am\n\n```php\n$schedule-\u003ecommand('syncops:project-backup dropbox --folder=files')-\u003eweeklyOn(1, '01:00');\n$schedule-\u003ecommand('syncops:db-push dropbox --folder=database')-\u003edaily()-\u003eat('02:00');\n$schedule-\u003ecommand('syncops:media-push dropbox')-\u003edaily()-\u003eat('03:00');\n$schedule-\u003ecommand('syncops:project-push')-\u003edaily()-\u003eat('04:00');\n```\n\n---\n\n## Dropbox setup\n\nDropbox is very easy to configure and upon the registration on [Dropbox website](https://www.dropbox.com/register)\nit offers free 2GB of cloud storage space.\nIn order to setup the Dropbox, complete the registration, add the\n[NumenCode Dropbox Adapter Plugin](https://packagist.org/packages/numencode/wn-dropboxadapter-plugin)\nand follow the [Installation steps here](https://github.com/numencode/wn-dropboxadapter-plugin/blob/main/README.md).\n\n---\n\n## Changelog\n\nAll notable changes are documented in the [CHANGELOG](CHANGELOG.md).\n\n---\n\n## Contributing\n\nPlease refer to the [CONTRIBUTING](CONTRIBUTING.md) guide for details on contributing to this project.\n\n---\n\n## Security\n\nIf you identify any security issues, email info@numencode.com rather than using the issue tracker.\n\n---\n\n## Author\n\nThe **NumenCode.SyncOps** plugin is created and maintained by [Blaz Orazem](https://orazem.si/).\n\nFor inquiries, contact: info@numencode.com\n\n---\n\n## License\n\nThis project is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).\n\n[![License](https://img.shields.io/github/license/numencode/wn-syncops-plugin?style=flat-square\u0026color=0099FF)](https://github.com/numencode/wn-syncops-plugin/blob/main/LICENSE.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnumencode%2Fwn-syncops-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnumencode%2Fwn-syncops-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnumencode%2Fwn-syncops-plugin/lists"}