Initial commit
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					/vendor/
 | 
				
			||||||
 | 
					/*.phar
 | 
				
			||||||
							
								
								
									
										49
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					# ServerCtl - Services for Developers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Say you need a Mongodb instance for something. Try this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					$ serverctl start mongo
 | 
				
			||||||
 | 
					Started mongo<default>
 | 
				
			||||||
 | 
					  Mongo client: 27017
 | 
				
			||||||
 | 
					$
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What just happened? Well, the `start` command pulled and started a new container
 | 
				
			||||||
 | 
					from the latest docker image using sensible defaults from the service registry.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Let's start another one:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					$ serverctl start mongo --instance other --portoffset 1
 | 
				
			||||||
 | 
					Started mongo<other>
 | 
				
			||||||
 | 
					  Mongo client: 27018
 | 
				
			||||||
 | 
					$
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Check it out!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					$ serverctl status
 | 
				
			||||||
 | 
					Service   Instance   Ports
 | 
				
			||||||
 | 
					mongo     default    27017
 | 
				
			||||||
 | 
					          other      27018
 | 
				
			||||||
 | 
					$ serverctl stop mongo --all
 | 
				
			||||||
 | 
					Stopped mongo<default>
 | 
				
			||||||
 | 
					Stopped mongo<other>
 | 
				
			||||||
 | 
					$
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## FAQ
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Where is the data stored?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Data goes in `$HOME/.var/serverctl`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								bin/serverctl
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								bin/serverctl
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env php
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use NoccyLabs\Serverctl\ConsoleApplication;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require_once __DIR__."/../vendor/autoload.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$app = new ConsoleApplication();
 | 
				
			||||||
 | 
					$app->run();
 | 
				
			||||||
							
								
								
									
										29
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "name": "noccylabs/serverctl",
 | 
				
			||||||
 | 
					    "description": "Start and stop services for development",
 | 
				
			||||||
 | 
					    "type": "application",
 | 
				
			||||||
 | 
					    "license": "GPL-3.0-or-later",
 | 
				
			||||||
 | 
					    "autoload": {
 | 
				
			||||||
 | 
					        "psr-4": {
 | 
				
			||||||
 | 
					            "NoccyLabs\\Serverctl\\": "src/"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "authors": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "Christopher Vagnetoft",
 | 
				
			||||||
 | 
					            "email": "cvagnetoft@gmail.com"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "require": {
 | 
				
			||||||
 | 
					        "symfony/console": "^6.1",
 | 
				
			||||||
 | 
					        "noccylabs/spinner": "^0.1.0"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "bin": [
 | 
				
			||||||
 | 
					        "bin/serverctl"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "extra": {
 | 
				
			||||||
 | 
					        "phar": {
 | 
				
			||||||
 | 
					            "output": "serverctl.phar"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										761
									
								
								composer.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										761
									
								
								composer.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,761 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "_readme": [
 | 
				
			||||||
 | 
					        "This file locks the dependencies of your project to a known state",
 | 
				
			||||||
 | 
					        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
 | 
				
			||||||
 | 
					        "This file is @generated automatically"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "content-hash": "21d444d8fc7b7dc1b673054b1a48432a",
 | 
				
			||||||
 | 
					    "packages": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "noccylabs/spinner",
 | 
				
			||||||
 | 
					            "version": "0.1.0",
 | 
				
			||||||
 | 
					            "dist": {
 | 
				
			||||||
 | 
					                "type": "zip",
 | 
				
			||||||
 | 
					                "url": "https://dev.noccylabs.info/api/packages/noccy/composer/files/noccylabs%2Fspinner/0.1.0/noccylabs-spinner.0.1.0.zip",
 | 
				
			||||||
 | 
					                "shasum": "5be298051ee740bcb7fa6aac0fa57ae3bbf0771e"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "type": "library",
 | 
				
			||||||
 | 
					            "autoload": {
 | 
				
			||||||
 | 
					                "psr-4": {
 | 
				
			||||||
 | 
					                    "NoccyLabs\\Spinner\\": "src/"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "license": [
 | 
				
			||||||
 | 
					                "GPL-3.0-or-later"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "authors": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Christopher Vagnetoft",
 | 
				
			||||||
 | 
					                    "email": "cvagnetoft@gmail.com"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "description": "Indicate activity in console applications",
 | 
				
			||||||
 | 
					            "time": "2022-09-22T00:11:39+00:00"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "psr/container",
 | 
				
			||||||
 | 
					            "version": "2.0.2",
 | 
				
			||||||
 | 
					            "source": {
 | 
				
			||||||
 | 
					                "type": "git",
 | 
				
			||||||
 | 
					                "url": "https://github.com/php-fig/container.git",
 | 
				
			||||||
 | 
					                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "dist": {
 | 
				
			||||||
 | 
					                "type": "zip",
 | 
				
			||||||
 | 
					                "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
 | 
				
			||||||
 | 
					                "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
 | 
				
			||||||
 | 
					                "shasum": ""
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "require": {
 | 
				
			||||||
 | 
					                "php": ">=7.4.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "type": "library",
 | 
				
			||||||
 | 
					            "extra": {
 | 
				
			||||||
 | 
					                "branch-alias": {
 | 
				
			||||||
 | 
					                    "dev-master": "2.0.x-dev"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "autoload": {
 | 
				
			||||||
 | 
					                "psr-4": {
 | 
				
			||||||
 | 
					                    "Psr\\Container\\": "src/"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "notification-url": "https://packagist.org/downloads/",
 | 
				
			||||||
 | 
					            "license": [
 | 
				
			||||||
 | 
					                "MIT"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "authors": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "PHP-FIG",
 | 
				
			||||||
 | 
					                    "homepage": "https://www.php-fig.org/"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "description": "Common Container Interface (PHP FIG PSR-11)",
 | 
				
			||||||
 | 
					            "homepage": "https://github.com/php-fig/container",
 | 
				
			||||||
 | 
					            "keywords": [
 | 
				
			||||||
 | 
					                "PSR-11",
 | 
				
			||||||
 | 
					                "container",
 | 
				
			||||||
 | 
					                "container-interface",
 | 
				
			||||||
 | 
					                "container-interop",
 | 
				
			||||||
 | 
					                "psr"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "support": {
 | 
				
			||||||
 | 
					                "issues": "https://github.com/php-fig/container/issues",
 | 
				
			||||||
 | 
					                "source": "https://github.com/php-fig/container/tree/2.0.2"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "time": "2021-11-05T16:47:00+00:00"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "symfony/console",
 | 
				
			||||||
 | 
					            "version": "v6.1.4",
 | 
				
			||||||
 | 
					            "source": {
 | 
				
			||||||
 | 
					                "type": "git",
 | 
				
			||||||
 | 
					                "url": "https://github.com/symfony/console.git",
 | 
				
			||||||
 | 
					                "reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "dist": {
 | 
				
			||||||
 | 
					                "type": "zip",
 | 
				
			||||||
 | 
					                "url": "https://api.github.com/repos/symfony/console/zipball/7fccea8728aa2d431a6725b02b3ce759049fc84d",
 | 
				
			||||||
 | 
					                "reference": "7fccea8728aa2d431a6725b02b3ce759049fc84d",
 | 
				
			||||||
 | 
					                "shasum": ""
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "require": {
 | 
				
			||||||
 | 
					                "php": ">=8.1",
 | 
				
			||||||
 | 
					                "symfony/deprecation-contracts": "^2.1|^3",
 | 
				
			||||||
 | 
					                "symfony/polyfill-mbstring": "~1.0",
 | 
				
			||||||
 | 
					                "symfony/service-contracts": "^1.1|^2|^3",
 | 
				
			||||||
 | 
					                "symfony/string": "^5.4|^6.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "conflict": {
 | 
				
			||||||
 | 
					                "symfony/dependency-injection": "<5.4",
 | 
				
			||||||
 | 
					                "symfony/dotenv": "<5.4",
 | 
				
			||||||
 | 
					                "symfony/event-dispatcher": "<5.4",
 | 
				
			||||||
 | 
					                "symfony/lock": "<5.4",
 | 
				
			||||||
 | 
					                "symfony/process": "<5.4"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "provide": {
 | 
				
			||||||
 | 
					                "psr/log-implementation": "1.0|2.0|3.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "require-dev": {
 | 
				
			||||||
 | 
					                "psr/log": "^1|^2|^3",
 | 
				
			||||||
 | 
					                "symfony/config": "^5.4|^6.0",
 | 
				
			||||||
 | 
					                "symfony/dependency-injection": "^5.4|^6.0",
 | 
				
			||||||
 | 
					                "symfony/event-dispatcher": "^5.4|^6.0",
 | 
				
			||||||
 | 
					                "symfony/lock": "^5.4|^6.0",
 | 
				
			||||||
 | 
					                "symfony/process": "^5.4|^6.0",
 | 
				
			||||||
 | 
					                "symfony/var-dumper": "^5.4|^6.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "suggest": {
 | 
				
			||||||
 | 
					                "psr/log": "For using the console logger",
 | 
				
			||||||
 | 
					                "symfony/event-dispatcher": "",
 | 
				
			||||||
 | 
					                "symfony/lock": "",
 | 
				
			||||||
 | 
					                "symfony/process": ""
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "type": "library",
 | 
				
			||||||
 | 
					            "autoload": {
 | 
				
			||||||
 | 
					                "psr-4": {
 | 
				
			||||||
 | 
					                    "Symfony\\Component\\Console\\": ""
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "exclude-from-classmap": [
 | 
				
			||||||
 | 
					                    "/Tests/"
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "notification-url": "https://packagist.org/downloads/",
 | 
				
			||||||
 | 
					            "license": [
 | 
				
			||||||
 | 
					                "MIT"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "authors": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Fabien Potencier",
 | 
				
			||||||
 | 
					                    "email": "fabien@symfony.com"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Symfony Community",
 | 
				
			||||||
 | 
					                    "homepage": "https://symfony.com/contributors"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "description": "Eases the creation of beautiful and testable command line interfaces",
 | 
				
			||||||
 | 
					            "homepage": "https://symfony.com",
 | 
				
			||||||
 | 
					            "keywords": [
 | 
				
			||||||
 | 
					                "cli",
 | 
				
			||||||
 | 
					                "command line",
 | 
				
			||||||
 | 
					                "console",
 | 
				
			||||||
 | 
					                "terminal"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "support": {
 | 
				
			||||||
 | 
					                "source": "https://github.com/symfony/console/tree/v6.1.4"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "funding": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://symfony.com/sponsor",
 | 
				
			||||||
 | 
					                    "type": "custom"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://github.com/fabpot",
 | 
				
			||||||
 | 
					                    "type": "github"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
 | 
				
			||||||
 | 
					                    "type": "tidelift"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "time": "2022-08-26T10:32:31+00:00"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "symfony/deprecation-contracts",
 | 
				
			||||||
 | 
					            "version": "v3.1.1",
 | 
				
			||||||
 | 
					            "source": {
 | 
				
			||||||
 | 
					                "type": "git",
 | 
				
			||||||
 | 
					                "url": "https://github.com/symfony/deprecation-contracts.git",
 | 
				
			||||||
 | 
					                "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "dist": {
 | 
				
			||||||
 | 
					                "type": "zip",
 | 
				
			||||||
 | 
					                "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
 | 
				
			||||||
 | 
					                "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
 | 
				
			||||||
 | 
					                "shasum": ""
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "require": {
 | 
				
			||||||
 | 
					                "php": ">=8.1"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "type": "library",
 | 
				
			||||||
 | 
					            "extra": {
 | 
				
			||||||
 | 
					                "branch-alias": {
 | 
				
			||||||
 | 
					                    "dev-main": "3.1-dev"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "thanks": {
 | 
				
			||||||
 | 
					                    "name": "symfony/contracts",
 | 
				
			||||||
 | 
					                    "url": "https://github.com/symfony/contracts"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "autoload": {
 | 
				
			||||||
 | 
					                "files": [
 | 
				
			||||||
 | 
					                    "function.php"
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "notification-url": "https://packagist.org/downloads/",
 | 
				
			||||||
 | 
					            "license": [
 | 
				
			||||||
 | 
					                "MIT"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "authors": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Nicolas Grekas",
 | 
				
			||||||
 | 
					                    "email": "p@tchwork.com"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Symfony Community",
 | 
				
			||||||
 | 
					                    "homepage": "https://symfony.com/contributors"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "description": "A generic function and convention to trigger deprecation notices",
 | 
				
			||||||
 | 
					            "homepage": "https://symfony.com",
 | 
				
			||||||
 | 
					            "support": {
 | 
				
			||||||
 | 
					                "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "funding": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://symfony.com/sponsor",
 | 
				
			||||||
 | 
					                    "type": "custom"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://github.com/fabpot",
 | 
				
			||||||
 | 
					                    "type": "github"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
 | 
				
			||||||
 | 
					                    "type": "tidelift"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "time": "2022-02-25T11:15:52+00:00"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "symfony/polyfill-ctype",
 | 
				
			||||||
 | 
					            "version": "v1.26.0",
 | 
				
			||||||
 | 
					            "source": {
 | 
				
			||||||
 | 
					                "type": "git",
 | 
				
			||||||
 | 
					                "url": "https://github.com/symfony/polyfill-ctype.git",
 | 
				
			||||||
 | 
					                "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "dist": {
 | 
				
			||||||
 | 
					                "type": "zip",
 | 
				
			||||||
 | 
					                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
 | 
				
			||||||
 | 
					                "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
 | 
				
			||||||
 | 
					                "shasum": ""
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "require": {
 | 
				
			||||||
 | 
					                "php": ">=7.1"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "provide": {
 | 
				
			||||||
 | 
					                "ext-ctype": "*"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "suggest": {
 | 
				
			||||||
 | 
					                "ext-ctype": "For best performance"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "type": "library",
 | 
				
			||||||
 | 
					            "extra": {
 | 
				
			||||||
 | 
					                "branch-alias": {
 | 
				
			||||||
 | 
					                    "dev-main": "1.26-dev"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "thanks": {
 | 
				
			||||||
 | 
					                    "name": "symfony/polyfill",
 | 
				
			||||||
 | 
					                    "url": "https://github.com/symfony/polyfill"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "autoload": {
 | 
				
			||||||
 | 
					                "files": [
 | 
				
			||||||
 | 
					                    "bootstrap.php"
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "psr-4": {
 | 
				
			||||||
 | 
					                    "Symfony\\Polyfill\\Ctype\\": ""
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "notification-url": "https://packagist.org/downloads/",
 | 
				
			||||||
 | 
					            "license": [
 | 
				
			||||||
 | 
					                "MIT"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "authors": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Gert de Pagter",
 | 
				
			||||||
 | 
					                    "email": "BackEndTea@gmail.com"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Symfony Community",
 | 
				
			||||||
 | 
					                    "homepage": "https://symfony.com/contributors"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "description": "Symfony polyfill for ctype functions",
 | 
				
			||||||
 | 
					            "homepage": "https://symfony.com",
 | 
				
			||||||
 | 
					            "keywords": [
 | 
				
			||||||
 | 
					                "compatibility",
 | 
				
			||||||
 | 
					                "ctype",
 | 
				
			||||||
 | 
					                "polyfill",
 | 
				
			||||||
 | 
					                "portable"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "support": {
 | 
				
			||||||
 | 
					                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "funding": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://symfony.com/sponsor",
 | 
				
			||||||
 | 
					                    "type": "custom"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://github.com/fabpot",
 | 
				
			||||||
 | 
					                    "type": "github"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
 | 
				
			||||||
 | 
					                    "type": "tidelift"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "time": "2022-05-24T11:49:31+00:00"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "symfony/polyfill-intl-grapheme",
 | 
				
			||||||
 | 
					            "version": "v1.26.0",
 | 
				
			||||||
 | 
					            "source": {
 | 
				
			||||||
 | 
					                "type": "git",
 | 
				
			||||||
 | 
					                "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
 | 
				
			||||||
 | 
					                "reference": "433d05519ce6990bf3530fba6957499d327395c2"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "dist": {
 | 
				
			||||||
 | 
					                "type": "zip",
 | 
				
			||||||
 | 
					                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2",
 | 
				
			||||||
 | 
					                "reference": "433d05519ce6990bf3530fba6957499d327395c2",
 | 
				
			||||||
 | 
					                "shasum": ""
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "require": {
 | 
				
			||||||
 | 
					                "php": ">=7.1"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "suggest": {
 | 
				
			||||||
 | 
					                "ext-intl": "For best performance"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "type": "library",
 | 
				
			||||||
 | 
					            "extra": {
 | 
				
			||||||
 | 
					                "branch-alias": {
 | 
				
			||||||
 | 
					                    "dev-main": "1.26-dev"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "thanks": {
 | 
				
			||||||
 | 
					                    "name": "symfony/polyfill",
 | 
				
			||||||
 | 
					                    "url": "https://github.com/symfony/polyfill"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "autoload": {
 | 
				
			||||||
 | 
					                "files": [
 | 
				
			||||||
 | 
					                    "bootstrap.php"
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "psr-4": {
 | 
				
			||||||
 | 
					                    "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "notification-url": "https://packagist.org/downloads/",
 | 
				
			||||||
 | 
					            "license": [
 | 
				
			||||||
 | 
					                "MIT"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "authors": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Nicolas Grekas",
 | 
				
			||||||
 | 
					                    "email": "p@tchwork.com"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Symfony Community",
 | 
				
			||||||
 | 
					                    "homepage": "https://symfony.com/contributors"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "description": "Symfony polyfill for intl's grapheme_* functions",
 | 
				
			||||||
 | 
					            "homepage": "https://symfony.com",
 | 
				
			||||||
 | 
					            "keywords": [
 | 
				
			||||||
 | 
					                "compatibility",
 | 
				
			||||||
 | 
					                "grapheme",
 | 
				
			||||||
 | 
					                "intl",
 | 
				
			||||||
 | 
					                "polyfill",
 | 
				
			||||||
 | 
					                "portable",
 | 
				
			||||||
 | 
					                "shim"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "support": {
 | 
				
			||||||
 | 
					                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "funding": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://symfony.com/sponsor",
 | 
				
			||||||
 | 
					                    "type": "custom"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://github.com/fabpot",
 | 
				
			||||||
 | 
					                    "type": "github"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
 | 
				
			||||||
 | 
					                    "type": "tidelift"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "time": "2022-05-24T11:49:31+00:00"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "symfony/polyfill-intl-normalizer",
 | 
				
			||||||
 | 
					            "version": "v1.26.0",
 | 
				
			||||||
 | 
					            "source": {
 | 
				
			||||||
 | 
					                "type": "git",
 | 
				
			||||||
 | 
					                "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
 | 
				
			||||||
 | 
					                "reference": "219aa369ceff116e673852dce47c3a41794c14bd"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "dist": {
 | 
				
			||||||
 | 
					                "type": "zip",
 | 
				
			||||||
 | 
					                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd",
 | 
				
			||||||
 | 
					                "reference": "219aa369ceff116e673852dce47c3a41794c14bd",
 | 
				
			||||||
 | 
					                "shasum": ""
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "require": {
 | 
				
			||||||
 | 
					                "php": ">=7.1"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "suggest": {
 | 
				
			||||||
 | 
					                "ext-intl": "For best performance"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "type": "library",
 | 
				
			||||||
 | 
					            "extra": {
 | 
				
			||||||
 | 
					                "branch-alias": {
 | 
				
			||||||
 | 
					                    "dev-main": "1.26-dev"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "thanks": {
 | 
				
			||||||
 | 
					                    "name": "symfony/polyfill",
 | 
				
			||||||
 | 
					                    "url": "https://github.com/symfony/polyfill"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "autoload": {
 | 
				
			||||||
 | 
					                "files": [
 | 
				
			||||||
 | 
					                    "bootstrap.php"
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "psr-4": {
 | 
				
			||||||
 | 
					                    "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "classmap": [
 | 
				
			||||||
 | 
					                    "Resources/stubs"
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "notification-url": "https://packagist.org/downloads/",
 | 
				
			||||||
 | 
					            "license": [
 | 
				
			||||||
 | 
					                "MIT"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "authors": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Nicolas Grekas",
 | 
				
			||||||
 | 
					                    "email": "p@tchwork.com"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Symfony Community",
 | 
				
			||||||
 | 
					                    "homepage": "https://symfony.com/contributors"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "description": "Symfony polyfill for intl's Normalizer class and related functions",
 | 
				
			||||||
 | 
					            "homepage": "https://symfony.com",
 | 
				
			||||||
 | 
					            "keywords": [
 | 
				
			||||||
 | 
					                "compatibility",
 | 
				
			||||||
 | 
					                "intl",
 | 
				
			||||||
 | 
					                "normalizer",
 | 
				
			||||||
 | 
					                "polyfill",
 | 
				
			||||||
 | 
					                "portable",
 | 
				
			||||||
 | 
					                "shim"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "support": {
 | 
				
			||||||
 | 
					                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "funding": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://symfony.com/sponsor",
 | 
				
			||||||
 | 
					                    "type": "custom"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://github.com/fabpot",
 | 
				
			||||||
 | 
					                    "type": "github"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
 | 
				
			||||||
 | 
					                    "type": "tidelift"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "time": "2022-05-24T11:49:31+00:00"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "symfony/polyfill-mbstring",
 | 
				
			||||||
 | 
					            "version": "v1.26.0",
 | 
				
			||||||
 | 
					            "source": {
 | 
				
			||||||
 | 
					                "type": "git",
 | 
				
			||||||
 | 
					                "url": "https://github.com/symfony/polyfill-mbstring.git",
 | 
				
			||||||
 | 
					                "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "dist": {
 | 
				
			||||||
 | 
					                "type": "zip",
 | 
				
			||||||
 | 
					                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
 | 
				
			||||||
 | 
					                "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
 | 
				
			||||||
 | 
					                "shasum": ""
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "require": {
 | 
				
			||||||
 | 
					                "php": ">=7.1"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "provide": {
 | 
				
			||||||
 | 
					                "ext-mbstring": "*"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "suggest": {
 | 
				
			||||||
 | 
					                "ext-mbstring": "For best performance"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "type": "library",
 | 
				
			||||||
 | 
					            "extra": {
 | 
				
			||||||
 | 
					                "branch-alias": {
 | 
				
			||||||
 | 
					                    "dev-main": "1.26-dev"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "thanks": {
 | 
				
			||||||
 | 
					                    "name": "symfony/polyfill",
 | 
				
			||||||
 | 
					                    "url": "https://github.com/symfony/polyfill"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "autoload": {
 | 
				
			||||||
 | 
					                "files": [
 | 
				
			||||||
 | 
					                    "bootstrap.php"
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "psr-4": {
 | 
				
			||||||
 | 
					                    "Symfony\\Polyfill\\Mbstring\\": ""
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "notification-url": "https://packagist.org/downloads/",
 | 
				
			||||||
 | 
					            "license": [
 | 
				
			||||||
 | 
					                "MIT"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "authors": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Nicolas Grekas",
 | 
				
			||||||
 | 
					                    "email": "p@tchwork.com"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Symfony Community",
 | 
				
			||||||
 | 
					                    "homepage": "https://symfony.com/contributors"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "description": "Symfony polyfill for the Mbstring extension",
 | 
				
			||||||
 | 
					            "homepage": "https://symfony.com",
 | 
				
			||||||
 | 
					            "keywords": [
 | 
				
			||||||
 | 
					                "compatibility",
 | 
				
			||||||
 | 
					                "mbstring",
 | 
				
			||||||
 | 
					                "polyfill",
 | 
				
			||||||
 | 
					                "portable",
 | 
				
			||||||
 | 
					                "shim"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "support": {
 | 
				
			||||||
 | 
					                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "funding": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://symfony.com/sponsor",
 | 
				
			||||||
 | 
					                    "type": "custom"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://github.com/fabpot",
 | 
				
			||||||
 | 
					                    "type": "github"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
 | 
				
			||||||
 | 
					                    "type": "tidelift"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "time": "2022-05-24T11:49:31+00:00"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "symfony/service-contracts",
 | 
				
			||||||
 | 
					            "version": "v3.1.1",
 | 
				
			||||||
 | 
					            "source": {
 | 
				
			||||||
 | 
					                "type": "git",
 | 
				
			||||||
 | 
					                "url": "https://github.com/symfony/service-contracts.git",
 | 
				
			||||||
 | 
					                "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "dist": {
 | 
				
			||||||
 | 
					                "type": "zip",
 | 
				
			||||||
 | 
					                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239",
 | 
				
			||||||
 | 
					                "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239",
 | 
				
			||||||
 | 
					                "shasum": ""
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "require": {
 | 
				
			||||||
 | 
					                "php": ">=8.1",
 | 
				
			||||||
 | 
					                "psr/container": "^2.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "conflict": {
 | 
				
			||||||
 | 
					                "ext-psr": "<1.1|>=2"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "suggest": {
 | 
				
			||||||
 | 
					                "symfony/service-implementation": ""
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "type": "library",
 | 
				
			||||||
 | 
					            "extra": {
 | 
				
			||||||
 | 
					                "branch-alias": {
 | 
				
			||||||
 | 
					                    "dev-main": "3.1-dev"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "thanks": {
 | 
				
			||||||
 | 
					                    "name": "symfony/contracts",
 | 
				
			||||||
 | 
					                    "url": "https://github.com/symfony/contracts"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "autoload": {
 | 
				
			||||||
 | 
					                "psr-4": {
 | 
				
			||||||
 | 
					                    "Symfony\\Contracts\\Service\\": ""
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "exclude-from-classmap": [
 | 
				
			||||||
 | 
					                    "/Test/"
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "notification-url": "https://packagist.org/downloads/",
 | 
				
			||||||
 | 
					            "license": [
 | 
				
			||||||
 | 
					                "MIT"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "authors": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Nicolas Grekas",
 | 
				
			||||||
 | 
					                    "email": "p@tchwork.com"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Symfony Community",
 | 
				
			||||||
 | 
					                    "homepage": "https://symfony.com/contributors"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "description": "Generic abstractions related to writing services",
 | 
				
			||||||
 | 
					            "homepage": "https://symfony.com",
 | 
				
			||||||
 | 
					            "keywords": [
 | 
				
			||||||
 | 
					                "abstractions",
 | 
				
			||||||
 | 
					                "contracts",
 | 
				
			||||||
 | 
					                "decoupling",
 | 
				
			||||||
 | 
					                "interfaces",
 | 
				
			||||||
 | 
					                "interoperability",
 | 
				
			||||||
 | 
					                "standards"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "support": {
 | 
				
			||||||
 | 
					                "source": "https://github.com/symfony/service-contracts/tree/v3.1.1"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "funding": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://symfony.com/sponsor",
 | 
				
			||||||
 | 
					                    "type": "custom"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://github.com/fabpot",
 | 
				
			||||||
 | 
					                    "type": "github"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
 | 
				
			||||||
 | 
					                    "type": "tidelift"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "time": "2022-05-30T19:18:58+00:00"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "symfony/string",
 | 
				
			||||||
 | 
					            "version": "v6.1.4",
 | 
				
			||||||
 | 
					            "source": {
 | 
				
			||||||
 | 
					                "type": "git",
 | 
				
			||||||
 | 
					                "url": "https://github.com/symfony/string.git",
 | 
				
			||||||
 | 
					                "reference": "290972cad7b364e3befaa74ba0ec729800fb161c"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "dist": {
 | 
				
			||||||
 | 
					                "type": "zip",
 | 
				
			||||||
 | 
					                "url": "https://api.github.com/repos/symfony/string/zipball/290972cad7b364e3befaa74ba0ec729800fb161c",
 | 
				
			||||||
 | 
					                "reference": "290972cad7b364e3befaa74ba0ec729800fb161c",
 | 
				
			||||||
 | 
					                "shasum": ""
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "require": {
 | 
				
			||||||
 | 
					                "php": ">=8.1",
 | 
				
			||||||
 | 
					                "symfony/polyfill-ctype": "~1.8",
 | 
				
			||||||
 | 
					                "symfony/polyfill-intl-grapheme": "~1.0",
 | 
				
			||||||
 | 
					                "symfony/polyfill-intl-normalizer": "~1.0",
 | 
				
			||||||
 | 
					                "symfony/polyfill-mbstring": "~1.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "conflict": {
 | 
				
			||||||
 | 
					                "symfony/translation-contracts": "<2.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "require-dev": {
 | 
				
			||||||
 | 
					                "symfony/error-handler": "^5.4|^6.0",
 | 
				
			||||||
 | 
					                "symfony/http-client": "^5.4|^6.0",
 | 
				
			||||||
 | 
					                "symfony/translation-contracts": "^2.0|^3.0",
 | 
				
			||||||
 | 
					                "symfony/var-exporter": "^5.4|^6.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "type": "library",
 | 
				
			||||||
 | 
					            "autoload": {
 | 
				
			||||||
 | 
					                "files": [
 | 
				
			||||||
 | 
					                    "Resources/functions.php"
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "psr-4": {
 | 
				
			||||||
 | 
					                    "Symfony\\Component\\String\\": ""
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "exclude-from-classmap": [
 | 
				
			||||||
 | 
					                    "/Tests/"
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "notification-url": "https://packagist.org/downloads/",
 | 
				
			||||||
 | 
					            "license": [
 | 
				
			||||||
 | 
					                "MIT"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "authors": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Nicolas Grekas",
 | 
				
			||||||
 | 
					                    "email": "p@tchwork.com"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "Symfony Community",
 | 
				
			||||||
 | 
					                    "homepage": "https://symfony.com/contributors"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
 | 
				
			||||||
 | 
					            "homepage": "https://symfony.com",
 | 
				
			||||||
 | 
					            "keywords": [
 | 
				
			||||||
 | 
					                "grapheme",
 | 
				
			||||||
 | 
					                "i18n",
 | 
				
			||||||
 | 
					                "string",
 | 
				
			||||||
 | 
					                "unicode",
 | 
				
			||||||
 | 
					                "utf-8",
 | 
				
			||||||
 | 
					                "utf8"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "support": {
 | 
				
			||||||
 | 
					                "source": "https://github.com/symfony/string/tree/v6.1.4"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "funding": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://symfony.com/sponsor",
 | 
				
			||||||
 | 
					                    "type": "custom"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://github.com/fabpot",
 | 
				
			||||||
 | 
					                    "type": "github"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
 | 
				
			||||||
 | 
					                    "type": "tidelift"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "time": "2022-08-12T18:05:43+00:00"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "packages-dev": [],
 | 
				
			||||||
 | 
					    "aliases": [],
 | 
				
			||||||
 | 
					    "minimum-stability": "stable",
 | 
				
			||||||
 | 
					    "stability-flags": [],
 | 
				
			||||||
 | 
					    "prefer-stable": false,
 | 
				
			||||||
 | 
					    "prefer-lowest": false,
 | 
				
			||||||
 | 
					    "platform": [],
 | 
				
			||||||
 | 
					    "platform-dev": [],
 | 
				
			||||||
 | 
					    "plugin-api-version": "2.3.0"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										30
									
								
								registry/mariadb.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								registry/mariadb.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$type": "service",
 | 
				
			||||||
 | 
					  "name": "mariadb",
 | 
				
			||||||
 | 
					  "description": "MariaDB (MySQL fork)",
 | 
				
			||||||
 | 
					  "author": null,
 | 
				
			||||||
 | 
					  "image": "mariadb",
 | 
				
			||||||
 | 
					  "ports": [ 
 | 
				
			||||||
 | 
					    { "port": 3306, "info": "MySQL client" }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "environment": {
 | 
				
			||||||
 | 
					    "MARIADB_ROOT_PASSWORD": "toor"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "persistence": [
 | 
				
			||||||
 | 
					    { "path": "/data", "hint": "data" }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "mongosh": {
 | 
				
			||||||
 | 
					      "info": "Open the mongo shell",
 | 
				
			||||||
 | 
					      "execute": "mongosh"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "export": {
 | 
				
			||||||
 | 
					      "info": "Export the database",
 | 
				
			||||||
 | 
					      "execute": "mysqldump "
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "import": {
 | 
				
			||||||
 | 
					      "info": "Import the database",
 | 
				
			||||||
 | 
					      "execute": "mongoimport"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								registry/mongo.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								registry/mongo.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$type": "service",
 | 
				
			||||||
 | 
					  "name": "mongo",
 | 
				
			||||||
 | 
					  "description": "MongoDB",
 | 
				
			||||||
 | 
					  "author": null,
 | 
				
			||||||
 | 
					  "image": "mongo",
 | 
				
			||||||
 | 
					  "ports": [ 
 | 
				
			||||||
 | 
					    { "port": 27017, "info": "Mongo client" }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "persistence": [
 | 
				
			||||||
 | 
					    { "path": "/data/db", "hint": "data" }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "environment": {
 | 
				
			||||||
 | 
					    "MONGO_INITDB_ROOT_PASSWORD": "toor",
 | 
				
			||||||
 | 
					    "MONGO_INITDB_ROOT_USERNAME": "root"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "shell": {
 | 
				
			||||||
 | 
					      "info": "Open the mongo shell",
 | 
				
			||||||
 | 
					      "execute": "mongosh"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "export": {
 | 
				
			||||||
 | 
					      "info": "Export the database",
 | 
				
			||||||
 | 
					      "execute": "mongodump"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "import": {
 | 
				
			||||||
 | 
					      "info": "Import the database",
 | 
				
			||||||
 | 
					      "execute": "mongoimport"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										30
									
								
								registry/mysql.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								registry/mysql.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$type": "service",
 | 
				
			||||||
 | 
					  "name": "mysql",
 | 
				
			||||||
 | 
					  "description": "MySQL",
 | 
				
			||||||
 | 
					  "author": null,
 | 
				
			||||||
 | 
					  "image": "mysql",
 | 
				
			||||||
 | 
					  "ports": [ 
 | 
				
			||||||
 | 
					    { "port": 3306, "info": "MySQL client" }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "environment": {
 | 
				
			||||||
 | 
					    "MARIADB_ROOT_PASSWORD": "toor"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "persistence": [
 | 
				
			||||||
 | 
					    { "path": "/data", "hint": "data" }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "shell": {
 | 
				
			||||||
 | 
					      "info": "Open the mongo shell",
 | 
				
			||||||
 | 
					      "execute": "mysql -uroot -p%{MARIADB_ROOT_PASSWORD}"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "export": {
 | 
				
			||||||
 | 
					      "info": "Export the database",
 | 
				
			||||||
 | 
					      "execute": "mysqldump -uroot -p%{MARIADB_ROOT_PASSWORD}"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "import": {
 | 
				
			||||||
 | 
					      "info": "Import the database",
 | 
				
			||||||
 | 
					      "execute": "mysql -uroot -p%{MARIADB_ROOT_PASSWORD}"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										30
									
								
								registry/postgres.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								registry/postgres.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "$type": "service",
 | 
				
			||||||
 | 
					  "name": "postgres",
 | 
				
			||||||
 | 
					  "description": "Postgres SQL server",
 | 
				
			||||||
 | 
					  "author": null,
 | 
				
			||||||
 | 
					  "image": "postgres",
 | 
				
			||||||
 | 
					  "ports": [ 
 | 
				
			||||||
 | 
					    { "port": 5432, "info": "Postgres client" }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "environment": {
 | 
				
			||||||
 | 
					    "POSTGRES_PASSWORD": "password"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "persistence": [
 | 
				
			||||||
 | 
					    { "path": "/data", "hint": "data" }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "mongosh": {
 | 
				
			||||||
 | 
					      "info": "Open a pg shell",
 | 
				
			||||||
 | 
					      "execute": "psql --user postgres"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "export": {
 | 
				
			||||||
 | 
					      "info": "Export the database",
 | 
				
			||||||
 | 
					      "execute": "mysqldump "
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "import": {
 | 
				
			||||||
 | 
					      "info": "Import the database",
 | 
				
			||||||
 | 
					      "execute": "mongoimport"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/Commands/Command.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/Commands/Command.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NoccyLabs\Serverctl\Commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use NoccyLabs\Serverctl\ConsoleApplication;
 | 
				
			||||||
 | 
					use NoccyLabs\Serverctl\Container\ContainerManager;
 | 
				
			||||||
 | 
					use NoccyLabs\Serverctl\Registry\ServiceRegistry;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Command\Command as CommandCommand;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class Command extends CommandCommand
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public function getApplication(): ?ConsoleApplication
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return parent::getApplication();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getServiceRegistry(): ServiceRegistry
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->getApplication()->getServiceRegistry();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function getContainerManager(): ContainerManager
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->getApplication()->getContainerManager();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/Commands/ExecCommand.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/Commands/ExecCommand.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NoccyLabs\Serverctl\Commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Attribute\AsCommand;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputArgument;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputInterface;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputOption;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Output\OutputInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[AsCommand(name:"exec", description:"Execute a shell command or script in the service")]
 | 
				
			||||||
 | 
					class ExecCommand extends Command
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    protected function configure()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->addOption("instance", "I", InputOption::VALUE_REQUIRED, "Specify the instance name", "default");
 | 
				
			||||||
 | 
					        $this->addOption("script", "s", InputOption::VALUE_NONE, "The command is a script (use 'list' for list)");
 | 
				
			||||||
 | 
					        $this->addArgument("service", InputArgument::REQUIRED, "The service name");
 | 
				
			||||||
 | 
					        $this->addArgument("execute", InputArgument::REQUIRED, "The command or script to execute");
 | 
				
			||||||
 | 
					        $this->addArgument("arguments", InputArgument::IS_ARRAY, "Arguments");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function execute(InputInterface $input, OutputInterface $output)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $serviceRegistry = $this->getServiceRegistry();
 | 
				
			||||||
 | 
					        $containerManager = $this->getContainerManager();
 | 
				
			||||||
 | 
					        $instanceName = $input->getOption("instance");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $serviceName = $input->getArgument("service");
 | 
				
			||||||
 | 
					        $command = $input->getArgument("execute");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $serviceInfo = $serviceRegistry->findServiceByName($serviceName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($command == "list") {
 | 
				
			||||||
 | 
					            $scripts = (array)($serviceInfo['scripts']??[]);
 | 
				
			||||||
 | 
					            $output->writeln("Available scripts:");
 | 
				
			||||||
 | 
					            foreach ($scripts as $script=>$meta) {
 | 
				
			||||||
 | 
					                $output->writeln(sprintf("  <comment>%s</> - <info>%s</>", $script, $meta['info']??"?"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return self::SUCCESS;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $cmdl = [ $input->getArgument("execute") ];
 | 
				
			||||||
 | 
					            array_push($cmdl, ...$input->getArgument("arguments"));
 | 
				
			||||||
 | 
					            $containerManager->execute($serviceInfo, $instanceName, $cmdl);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self::SUCCESS;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/Commands/FindCommand.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/Commands/FindCommand.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NoccyLabs\Serverctl\Commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Attribute\AsCommand;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputArgument;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputInterface;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputOption;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Output\OutputInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[AsCommand(name:"find", description:"Find defined services")]
 | 
				
			||||||
 | 
					class FindCommand extends Command
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    protected function configure()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->addArgument("service", InputArgument::OPTIONAL, "Search query");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function execute(InputInterface $input, OutputInterface $output)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $serviceRegistry = $this->getServiceRegistry();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $services = $serviceRegistry->findAllServices();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $output->writeln("Available services:");
 | 
				
			||||||
 | 
					        foreach ($services as $service) {
 | 
				
			||||||
 | 
					            $output->writeln(sprintf("  <comment>%s</> - <info>%s</>", $service['name'], $service['description']??"?"));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self::SUCCESS;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										45
									
								
								src/Commands/StartCommand.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/Commands/StartCommand.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NoccyLabs\Serverctl\Commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Attribute\AsCommand;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputArgument;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputInterface;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputOption;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Output\OutputInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[AsCommand(name:"start", description:"Start a service")]
 | 
				
			||||||
 | 
					class StartCommand extends Command
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    protected function configure()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->addOption("instance", "I", InputOption::VALUE_REQUIRED, "Specify the instance name", "default");
 | 
				
			||||||
 | 
					        $this->addOption("portoffset", "p", InputOption::VALUE_REQUIRED, "Offset port numbers by value", 0);
 | 
				
			||||||
 | 
					        $this->addArgument("service", InputArgument::REQUIRED, "The service name");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function execute(InputInterface $input, OutputInterface $output)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $serviceRegistry = $this->getServiceRegistry();
 | 
				
			||||||
 | 
					        $containerManager = $this->getContainerManager();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $serviceName = $input->getArgument("service");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $serviceInfo = $serviceRegistry->findServiceByName($serviceName);
 | 
				
			||||||
 | 
					        if (!$serviceInfo) {
 | 
				
			||||||
 | 
					            $output->writeln("<error>No such service in registry</>");
 | 
				
			||||||
 | 
					            return self::FAILURE;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $options = [
 | 
				
			||||||
 | 
					            'name' => $input->getOption("instance"),
 | 
				
			||||||
 | 
					            'portoffset' => $input->getOption("portoffset")
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $containerManager->startService($serviceInfo, $options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self::SUCCESS;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										34
									
								
								src/Commands/StatusCommand.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/Commands/StatusCommand.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NoccyLabs\Serverctl\Commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use NoccyLabs\Spinner\Spinner;
 | 
				
			||||||
 | 
					use NoccyLabs\Spinner\Style\BrailleDotsStyle;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Attribute\AsCommand;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputInterface;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputOption;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Output\OutputInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[AsCommand(name:"status", description:"Show the running services")]
 | 
				
			||||||
 | 
					class StatusCommand extends Command
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    protected function configure()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function execute(InputInterface $input, OutputInterface $output)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $spinner = new Spinner(style: BrailleDotsStyle::class, fps: 15);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $output->write("    Getting status\r");
 | 
				
			||||||
 | 
					        for ($n = 0; $n < 250; $n++) {
 | 
				
			||||||
 | 
					            if ($output->isDecorated()) $output->write("({$spinner})\r");
 | 
				
			||||||
 | 
					            usleep(10000);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self::SUCCESS;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										39
									
								
								src/Commands/StopCommand.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/Commands/StopCommand.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NoccyLabs\Serverctl\Commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Attribute\AsCommand;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputArgument;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputInterface;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Input\InputOption;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Output\OutputInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[AsCommand(name:"stop", description:"Stop a running service")]
 | 
				
			||||||
 | 
					class StopCommand extends Command
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    protected function configure()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->addOption("instance", "I", InputOption::VALUE_REQUIRED, "Specify the instance name", "default");
 | 
				
			||||||
 | 
					        $this->addArgument("service", InputArgument::REQUIRED, "The service name");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function execute(InputInterface $input, OutputInterface $output)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $serviceRegistry = $this->getServiceRegistry();
 | 
				
			||||||
 | 
					        $containerManager = $this->getContainerManager();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $serviceName = $input->getArgument("service");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $serviceInfo = $serviceRegistry->findServiceByName($serviceName);
 | 
				
			||||||
 | 
					        if (!$serviceInfo) {
 | 
				
			||||||
 | 
					            $output->writeln("<error>No such service in registry</>");
 | 
				
			||||||
 | 
					            return self::FAILURE;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $containerManager->stopService($serviceInfo, $input->getOption("instance"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self::SUCCESS;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										48
									
								
								src/ConsoleApplication.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/ConsoleApplication.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NoccyLabs\Serverctl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use NoccyLabs\Serverctl\Container\ContainerManager;
 | 
				
			||||||
 | 
					use NoccyLabs\Serverctl\Registry\ServiceRegistry;
 | 
				
			||||||
 | 
					use Symfony\Component\Console\Application;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConsoleApplication extends Application
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ServiceRegistry $serviceRegistry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ContainerManager $containerManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function __construct()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        parent::__construct("Development server utility", "0.1.0");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->serviceRegistry = new ServiceRegistry(
 | 
				
			||||||
 | 
					            paths: [
 | 
				
			||||||
 | 
					                __DIR__."/../registry",
 | 
				
			||||||
 | 
					                dirname(realpath($GLOBALS['argv'][0]))."/registry",
 | 
				
			||||||
 | 
					                "/usr/share/serverctl/registry",
 | 
				
			||||||
 | 
					                getenv("HOME")."/.share/serverctl/registry"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        $this->containerManager = new ContainerManager(
 | 
				
			||||||
 | 
					            dataPath: null
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->add(new Commands\StartCommand());
 | 
				
			||||||
 | 
					        $this->add(new Commands\StopCommand());
 | 
				
			||||||
 | 
					        $this->add(new Commands\ExecCommand());
 | 
				
			||||||
 | 
					        $this->add(new Commands\FindCommand());
 | 
				
			||||||
 | 
					        $this->add(new Commands\StatusCommand());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getServiceRegistry(): ServiceRegistry
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->serviceRegistry;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    public function getContainerManager(): ContainerManager
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->containerManager;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										132
									
								
								src/Container/ContainerManager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/Container/ContainerManager.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NoccyLabs\Serverctl\Container;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ContainerManager
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private string $dataPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function __construct(?string $dataPath=null)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->dataPath = $dataPath ?? (getenv("HOME")."/.var/serverctl");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Start a service
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * Instance options:
 | 
				
			||||||
 | 
					     *   name: Instance name (default)
 | 
				
			||||||
 | 
					     *   portoffset: Port number offset (0)
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param array $service The service definition from the registry  
 | 
				
			||||||
 | 
					     * @param array $options Instance options
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function startService(array $service, array $options)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $args = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $serviceName = $service['name'];
 | 
				
			||||||
 | 
					        $instanceName = $options['name']??'default';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $containerName = "sm_".$serviceName."_".$instanceName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $args[] = 'run';
 | 
				
			||||||
 | 
					        $args[] = '--rm'; // remove container after run
 | 
				
			||||||
 | 
					        $args[] = '-d';
 | 
				
			||||||
 | 
					        $args[] = '--name';
 | 
				
			||||||
 | 
					        $args[] = $containerName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Map the ports
 | 
				
			||||||
 | 
					        $ports = (array)($service['ports']??[]);
 | 
				
			||||||
 | 
					        foreach ($ports as $port) {
 | 
				
			||||||
 | 
					            $args[] = '-p';
 | 
				
			||||||
 | 
					            $args[] = $port['port'];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Get the paths to persist
 | 
				
			||||||
 | 
					        $volumes = (array)($service['persistence']??[]);
 | 
				
			||||||
 | 
					        $volumePath = $this->getServiceDataPath($service)."/".$instanceName;
 | 
				
			||||||
 | 
					        foreach ($volumes as $volume) {
 | 
				
			||||||
 | 
					            // volume { path, hint }
 | 
				
			||||||
 | 
					            $path = $volume['path'];
 | 
				
			||||||
 | 
					            $hint = $volume['hint'] ?? crc32($path);
 | 
				
			||||||
 | 
					            $args[] = '-v'; // add volume
 | 
				
			||||||
 | 
					            $args[] = $volumePath."/".$hint.":".$path;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Get environment
 | 
				
			||||||
 | 
					        $envs = (array)($service['environment']??[]);
 | 
				
			||||||
 | 
					        foreach ($envs as $env=>$value) {
 | 
				
			||||||
 | 
					            $args[] = '-e';
 | 
				
			||||||
 | 
					            // TODO: use environment if set (override)
 | 
				
			||||||
 | 
					            $args[] = sprintf("%s=%s", $env, $value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $args[] = $service['image'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $cmdl = 'docker '.join(' ',array_map('escapeshellarg', $args));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // TODO: Write command line, env and meta to state file
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        echo "$ {$cmdl}\n";
 | 
				
			||||||
 | 
					        passthru($cmdl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Stop a service
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function stopService(array $service, string $instanceName)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $args = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $serviceName = $service['name'];
 | 
				
			||||||
 | 
					        $instanceName = $options['name']??'default';
 | 
				
			||||||
 | 
					        $containerName = "sm_".$serviceName."_".$instanceName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $args[] = 'stop';
 | 
				
			||||||
 | 
					        $args[] = $containerName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $cmdl = 'docker '.join(' ',array_map('escapeshellarg', $args));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        echo "$ {$cmdl}\n";
 | 
				
			||||||
 | 
					        passthru($cmdl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function execute(array $service, string $instanceName, array $command)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $args = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $serviceName = $service['name'];
 | 
				
			||||||
 | 
					        $instanceName = $options['name']??'default';
 | 
				
			||||||
 | 
					        $containerName = "sm_".$serviceName."_".$instanceName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $args[] = 'exec';
 | 
				
			||||||
 | 
					        $args[] = '-it';
 | 
				
			||||||
 | 
					        $args[] = $containerName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        array_push($args, ...$command);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $cmdl = 'docker '.join(' ',array_map('escapeshellarg', $args));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        echo "$ {$cmdl}\n";
 | 
				
			||||||
 | 
					        passthru($cmdl);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get running services
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function getRunningServices(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getServiceDataPath(array $service)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->dataPath."/".$service['name'];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										57
									
								
								src/Registry/ServiceRegistry.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/Registry/ServiceRegistry.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NoccyLabs\Serverctl\Registry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use RecursiveDirectoryIterator;
 | 
				
			||||||
 | 
					use RecursiveIteratorIterator;
 | 
				
			||||||
 | 
					use SplFileInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ServiceRegistry
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    private $services = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function __construct(array $paths)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        foreach ($paths as $path) {
 | 
				
			||||||
 | 
					            if (is_dir($path)) {
 | 
				
			||||||
 | 
					                $this->readPath($path);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function readPath(string $path)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $iter = new RecursiveIteratorIterator(
 | 
				
			||||||
 | 
					            new RecursiveDirectoryIterator(
 | 
				
			||||||
 | 
					                $path
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** @var SplFileInfo $iteminfo */
 | 
				
			||||||
 | 
					        foreach ($iter as $itempath=>$iteminfo) {
 | 
				
			||||||
 | 
					            if (!fnmatch("*.json", $itempath)) continue;
 | 
				
			||||||
 | 
					            $json = file_get_contents($itempath);
 | 
				
			||||||
 | 
					            $parsed = json_decode($json, true);
 | 
				
			||||||
 | 
					            $this->services[$itempath] = $parsed;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        usort($this->services, function ($a,$b) {
 | 
				
			||||||
 | 
					            return $a['name'] <=> $b['name'];
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function findServiceByName(string $name): ?array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        foreach ($this->services as $service) {
 | 
				
			||||||
 | 
					            if ($service['name'] === $name) return $service;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function findAllServices(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->services;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user