diff --git a/.editorconfig b/.editorconfig
index 812c991..50bfd79 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -6,3 +6,6 @@ indent_size = 4
[Makefile]
indent_style = tab
+
+[*.yml]
+indent_size = 4
diff --git a/.gitignore b/.gitignore
index d400d6e..262d491 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
*.phar
*.bak
src/vendor/*
+src/conf/*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d0cd95b..9f9ab1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,29 @@
CliTools Changelog
==================
+2.0.0 - 2015-06-16
+------------------
+- Added GitHub based `self-update`
+- Added `make` (auto search for Makefile in tree)
+- Added `php:composer` (auto search for composer.json in tree)
+- Added `mysql:convert` for automatic changing charset and collation of one database
+- Added `sync:server` for syncing any configured server to your local development system (reads clisync.yml or .clisync.yml)
+- Added `sync:backup` for backup to a shared server (reads clisync.yml or .clisync.yml)
+- Added `sync:restore` for restore from a shared server (reads clisync.yml or .clisync.yml)
+- Added `sync:deploy` for lightweight deployment to a foreign server (reads clisync.yml or .clisync.yml)
+- Added `typo3:domain --list` for only list the domains of one or all databases
+- Added `typo3:domain --remove=domain/pattern` for domain cleanup (eg. vagrant share)
+- Added `typo3:domain --duplication=suffix` for domain duplication
+- Added `typo3:domain --baseurl` for setting config.baseURL in SetupTS
+- Added `vagrant:share` with automatic domain setting for TYPO3 projects (ALPHA! not finished!)
+- TTY banner now will be reloaded (SIGHUB is send to getty tty1)
+- Added docker detection for sync features
+- Updated to Symfony 2.7.1
+- Refactored some classes
+- Fixed some issues
+- Added gzip compression for PHAR
+- SLOC: 5,999
+
1.9.0 - 2015-05-06
------------------
- Added `mysql:backup` (with --filter=typo3, support for plain sql, gzip, bzip2, lzma compression)
@@ -12,6 +35,7 @@ CliTools Changelog
- Refactored shell command execution (again)
- Fixed code styling
- Improved code and fixed some smaller bugs
+- SLOC: 4,038
1.8.0 - 2015-04-26
------------------
@@ -27,36 +51,44 @@ CliTools Changelog
- Implemented command check
- Improved disk usage warning (wall and growl, will trigger when usage is >=90 in local and remote mounts)
- Refactored shell command execution
+- SLOC: 3,562
1.7.4 - 2015-04-21
------------------
- Improved `docker:tshark`
+- SLOC: 2,787
1.7.3 - 2015-04-21
------------------
- Fixed `docker:tshark`
+- SLOC: 2,780
1.7.2 - 2015-04-21
------------------
- Added required php modules checks
- Added interactive error return code check
+- SLOC: 2,777
1.7.0 - 2015-04-19
------------------
- Added `docker:tshark`, easy network sniffing
- Added `php:trace --all`, for immediate tracing all php processes
- Fixed bugs
+- SLOC: 2,755
1.6.3 - 2015-04-16
------------------
- Added `docker:tshark`, easy network sniffing
- Added `php:trace --all`, for immediate tracing all php processes
- Fixed bugs
+- SLOC: 2,832
1.6.2 - 2015-04-15
------------------
- Fixed bugs
+- SLOC: 2,811
1.5.1 - 2015-03-29
------------------
- Added growl support
+- SLOC: 2,773
diff --git a/Documentation/ALIASES.md b/Documentation/ALIASES.md
new file mode 100644
index 0000000..c7b6c9b
--- /dev/null
+++ b/Documentation/ALIASES.md
@@ -0,0 +1,33 @@
+[<-- Back to main section](../README.md)
+
+## Shell aliases
+
+```bash
+# Shortcut for docker-compose (autosearch docker-compose.yml in up-dir, you don't have to be in directory with docker-compose.yml)
+alias dcc='ct docker:compose'
+
+# Startup docker-container (and shutdown previous one, v1.9.0 and up)
+alias dccup='ct docker:up'
+alias dccstop='ct docker:compose stop'
+
+# Enter main docker container (as CLI_USER if available - if not specified then root is used)
+alias dcshell='ct docker:shell'
+alias dcsh='ct docker:shell'
+
+# Enter main docker container (as root)
+alias dcroot='ct docker:root'
+
+# Execute predefined cli in docker container
+alias dccrun='ct docker:cli'
+
+# Run command
+alias dcexec='ct docker:exec'
+
+# Execute mysql client in docker container
+alias dcsql='ct docker:mysql'
+alias dcmysql='ct docker:mysql'
+
+# General shortcuts (with up-dir tree searching)
+alias composer='ct php:composer'
+alias make='ct make'
+```
diff --git a/Documentation/COMMANDS.md b/Documentation/COMMANDS.md
new file mode 100644
index 0000000..42f3922
--- /dev/null
+++ b/Documentation/COMMANDS.md
@@ -0,0 +1,161 @@
+[<-- Back to main section](../README.md)
+
+## Commands
+
+### Special commands
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct self-update | Update ct command (download new version) |
+| ct update | Updates all system components, ssh configuration, ct command update etc. |
+| ct make | Search for "Makefile" in tree and start "make" in this directory |
+
+### System commands
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct shutdown (alias) | Shutdown system |
+
+### Log commands
+
+All log commands are using a grep-filter (specified as optional argument)
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct log:mail | Shows mail logs |
+
+### Docker commands
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct docker:create | Create new docker boilerplate in directory (first argument) |
+| | __ct docker:create projectname__ -> Create new docker boilerplate instance in directory "projectname" |
+| | __ct docker:create projectname --code=git@github.com/foo/bar__ -> Create new docker boilerplate instance in directory "projectname" and git code repository |
+| | __ct docker:create projectname --docker=git@github.com/foo/bar__ -> Create new docker boilerplate instance in directory "projectname" and custom docker boilerplate repository |
+| | __ct docker:create projectname --code=git@github.com/foo/bar --make=build__ -> Create new docker boilerplate instance in directory "projectname" and git code repository, will run automatic make (Makefile) task "build" after checkout |
+| ct docker:shell | Jump into a shell inside a docker container (using predefined user defined with CLI_USER in docker env) |
+| | __ct docker:shell__ -> enter main container |
+| | __ct docker:shell mysql__ -> enter mysql container |
+| | __ct docker:shell --user=www-data -> enter main container as user www-data |
+| ct docker:root | Jump into a shell inside a docker container as root user |
+| ct docker:mysql | Jump into a mysql client inside a docker container |
+| | __ct docker:mysql__ -> execute mysql client inside main container |
+| ct docker:sniff | Start network sniffer for various protocols |
+| | __ct docker:sniff http__ -> start HTTP sniffing |
+| ct docker:exec | Execute command in docker container |
+| | __ct docker:exec ps__ -> run 'ps' inside main container |
+| ct docker:cli | Execute special cli command in docker container |
+| | __ct docker:cli scheduler__ -> run 'scheduler' in TYPO3 CMS |
+| ct docker:compose | Execute docker-compose (recursive up-searching for docker-compose.yml) |
+| | __ct docker:compose ps__ -> list all running docker-compose containers |
+
+### MySQL commands
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct mysql:clear | Clear database (remove all tables in database) |
+| | __ct mysql:clear typo3__ |
+| ct mysql:connections | Lists all current connections |
+| ct mysql:create | Create (and drops if already exists) a database |
+| | __ct mysql:create typo3__ |
+| ct mysql:debug | Shows mysql debug log (lists all queries) with basic filter support |
+| | __ct mysql:debug__ (full log) |
+| | __ct mysql:debug tt_content__ (full log) |
+| ct mysql:slowlog | Shows mysql slow log |
+| | __ct mysql:slowlog__ (show slow queries with 1 sec and more) |
+| | __ct mysql:slowlog --time=10__ (show slow queries with 10 sec and more) |
+| | __ct mysql:slowlog --no-index__ (show not using index and slow (1sec) queries) |
+| ct mysql:drop | Drops a database |
+| | __ct mysql:drop typo3__ |
+| ct mysql:list | Lists all databases with some statitics |
+| ct mysql:restart | Restart MySQL server |
+| ct mysql:backup | Backup a database to file |
+| | Compression type will be detected from file extension (default plain sql) |
+| | __ct mysql:restore typo3 dump.sql__ -> plain sql dump |
+| | __ct mysql:restore typo3 dump.sql.gz__ -> gzip'ed sql dump |
+| | __ct mysql:restore typo3 dump.sql.bzip2__ -> bzip2'ed sql dump |
+| | __ct mysql:restore typo3 dump.sql.xz__ -> xz'ed (lzma'ed) sql dump |
+| | __ct mysql:restore typo3 dump.sql --filter=typo3__ -> No TYPO3 cache tables in dump |
+| ct mysql:restore | Create (and drops if already exists) a database and restore from a dump |
+| | Dump file can be plaintext, gziped, bzip2 or lzma compressed |
+| | and will automatically detected |
+| | __ct mysql:restore typo3 dump.sql.bz2__ |
+| ct mysql:convert | Convert character set and collation of a database |
+| | __ct mysql:convert typo3__ -> Convert typo3 into UTF-8 with utf8_general_ci |
+| | __ct mysql:convert typo3 --charset=latin1__ -> Convert typo3 into LATIN-1 |
+| | __ct mysql:convert typo3 --collation=utf8_unicode_ci__ -> Convert typo3 into UTF-8 with utf8_unicode_ci |
+| | __ct mysql:convert typo3 --stdout__ -> Print sql statements to stdout |
+
+### Sync commands
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct sync:init | Create example clisync.yml in current working directory |
+| ct sync:backup | Search for clisync.yml in tree and start backup to shared server |
+| | __ct sync:backup__ -> Backup files and database from share |
+| | __ct sync:backup --rsync__ -> Backup only files from share |
+| | __ct sync:backup --mysql__ -> Backup only database from share |
+| ct sync:restore | Search for clisync.yml in tree and start restore from shared server |
+| | __ct sync:restore__ -> Restore files and database from share |
+| | __ct sync:restore --rsync__ -> Restore only files from share |
+| | __ct sync:restore --mysql__ -> Restore only database from share |
+| ct sync:server | Search for clisync.yml in tree and start server synchronization (eg. from live or preview to local development instance |
+| | __ct sync:server production__ -> Use "production" configuration and start sync |
+| | __ct sync:server preview --rsync__ -> Use "preview" configuration and start only rsync |
+| | __ct sync:server staging --mysql__ -> Use "staging" configuration and start only mysql sync |
+
+### PHP commands
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct php:trace | Trace syscalls from one or all PHP processes (strace) |
+| | __ct php:trace --all__ -> Trace all php processes immediately |
+| ct php:composer | Search for "composer.yml" in tree and start "composer" in this directory |
+
+### Samba commands
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct samba:restart | Restart Samba server |
+
+
+### System commands
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct system:env | Lists common environment variables |
+| ct system:openfiles | Lists current open files count grouped by process |
+| ct system:shutdown | Shutdown system |
+| ct system:swap | Show swap usage for running processes |
+| ct system:update | Updates all system components, ssh configuration, ct command update etc. |
+| ct system:version | Shows version for common packages |
+
+### TYPO3 commands
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct typo3:beuser | Injects a dev user (pass dev) to all or one specified TYPO3 database |
+| | __ct typo3:beuser__ |
+| | __ct typo3:beuser typo3__ |
+| ct typo3:cleanup | Cleanup command tables to same some table space |
+| | __ct typo3:cleanup__ |
+| | __ct typo3:cleanup typo3__ |
+| ct typo3:domain | Add default suffix to all domains (default: .vm) |
+| | __ct typo3:domain --baseurl__ Also update config.baseURL in SetupTS |
+| | __ct typo3:domain --list__ Print list of domains and exit |
+| | __ct typo3:domain --remove='*.vagrantshare.com'__ Remove all *.vagrantshare.com domains (used by vagrant:share command) |
+| | __ct typo3:domain --duplicate='foobar.vagrantshare.com'__ Duplicates all domains and add suffix 'foobar.vagrantshare.com' (used by vagrant:share command) |
+
+### Vagrant commands
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct vagrant:share | Start sharing (with some workflow stuff) (ALPHA! not finished!) |
+
+### User commands
+
+| Command | Description |
+|----------------------------|---------------------------------------------------------------------------|
+| ct user:rebuildsshconfig | Rebuild SSH config from ct repository (/vagrant/provision/sshconfig) |
+
+
diff --git a/Documentation/Examples/clisync.yml b/Documentation/Examples/clisync.yml
new file mode 100644
index 0000000..59af1c1
--- /dev/null
+++ b/Documentation/Examples/clisync.yml
@@ -0,0 +1,228 @@
+###########################################################
+# Global (applied to all server sections)
+###########################################################
+#
+# Configuration merge order:
+# 1. context configuration will override all settings
+# 2. sync/deploy/share GLOBAL will override default settings
+# 3. GLOBAL are the defaults
+#
+# EXAMPLE:
+# if you ned eg. another mysql hostname just set it in the
+# context:
+#
+# sync:
+# production:
+# mysql:
+# hostname: 192.168.56.2
+#
+# All other configurations can be overwritten as well
+#
+# HINT:
+# You can check the configuration with the "--config" option
+#
+
+GLOBAL:
+ ## MYSQL
+ mysql:
+ # mysql connection
+ hostname: localhost
+
+ # MySQL predefined filter for typo3 (eg. no caching tables)
+ filter: typo3
+
+ # MySQL custom filter (preg_match)
+ #filter:
+ # - "/^cachingframework_.*/i"
+ # - "/^cf_.*/i"
+ # - "/^cache_.*/i"
+ # - "/^index_.*/i"
+ # - "/^sys_log$/i"
+ # - "/^sys_history$/i"
+ # - "/^tx_extbase_cache.*/i"
+
+ # Transfer compression (none if empty, bzip2 or gzip)
+ compression: bzip2
+
+ # specific mysqldump settings
+ mysqldump:
+ option: "--opt --skip-lock-tables --single-transaction"
+
+ ## RSYNC
+ rsync:
+ # set target as sub directroy (will be appended to working directory)
+ workdir: ""
+
+ # exclude list/patterns for files and directories
+ exclude:
+ # Temp files
+ - "*~"
+ - "._*"
+
+ # VCS
+ - ".git*"
+ - ".gitignore"
+ - ".gitmodules"
+ - ".svn"
+
+ # Build files
+ - "composer.json"
+ - "bower.json"
+ - "gulpfile.js"
+ - "Gruntfile.js"
+ - "Makefile"
+
+ # Caches and other files
+ - "node_modules"
+ - ".sass-cache"
+ - ".settings"
+ - ".bowerrc"
+ - ".buildpath"
+ - ".project"
+
+ ## commands
+ command:
+ # Start-Tasks: shell command which should be run before run
+ startup:
+ # add some here
+
+ # Final-Tasks: shell command which should be after run
+ finalize:
+ # add some here
+
+ # EXAMPLE: local task
+ # - date
+
+ # EXAMPLE: remote task (will be send over ssh)
+ #- { type: 'remote', command: 'date' }
+
+ # EXAMPLE: create user "dev" with password "dev"
+ - "ct typo3:beuser"
+ # EXAMPLE: append toplevel-domain .vm to all domains
+ - "ct typo3:domain"
+
+
+
+
+###########################################################
+# Sync from server (eg. live server)
+###########################################################
+sync:
+
+ ##################
+ # Global config (for sync)
+ ##################
+ GLOBAL:
+ mysql:
+ ## put your mysql settings here (see global conf)
+
+ rsync:
+ # directory list/patterns for synchronization
+ directory:
+ - "/fileadmin/"
+ - "/uploads/"
+ - "/typo3conf/l10n/"
+
+ # directory exclude list/patterns
+ exclude:
+ - "/fileadmin/_processed_/**"
+ - "/fileadmin/_temp_/**"
+
+ ##################
+ # Context "production"
+ ##################
+ production:
+ # ssh server host or name (see .ssh/config, eg for mysql/mysqldump)
+ ssh:
+ hostname: live-server
+
+ # rsync for some directories
+ rsync:
+ # server and source directory (server host or name - see .ssh/config)
+ path: "live-server:/var/www/website/htdocs"
+
+ #conf:
+ # maxSize: 20M
+ # minSize: 10kb
+
+ mysql:
+ username: typo3
+ password: loremipsum
+
+ # List of databases for synchronization
+ # examples:
+ # local:foreign
+ # samename
+ database:
+ - typo3:website_live
+
+
+
+
+###########################################################
+# Deployment to server
+###########################################################
+deploy:
+
+ ##################
+ # Global config (for deploy)
+ ##################
+ GLOBAL:
+ mysql:
+ # global mysql configuration
+
+ rsync:
+ # directory list/patterns for synchronization
+ directory:
+ - "/typo3conf/ext/"
+
+ # directory exclude list/patterns
+ exclude:
+ - "/fileadmin/"
+ - "/uploads/"
+ - "/typo3conf/l10n/"
+
+ ##################
+ # Context "production"
+ ##################
+ production:
+ # ssh server host or name (see .ssh/config, eg for mysql/mysqldump)
+ ssh:
+ hostname: live-server
+
+ # rsync for some directories
+ rsync:
+ # server and source directory (server host or name - see .ssh/config)
+ path: "live-server:/var/www/website/htdocs"
+
+
+
+
+###########################################################
+# Shared server (sharing between developers)
+###########################################################
+share:
+
+ ##################
+ # Global config (for share)
+ ##################
+ GLOBAL:
+ mysql:
+ # List of databases for backup
+ database:
+ - typo3
+
+ rsync:
+ # List of directories for backup
+ directory:
+ - "/fileadmin/"
+ - "/uploads/"
+ - "/typo3conf/l10n/"
+
+ ##################
+ # Context "development"
+ ##################
+ development:
+ rsync:
+ # source/target directory or server via ssh (eg. backup-server:/backup/projectname)
+ path: "/tmp/foo/"
diff --git a/Documentation/INSTALL.md b/Documentation/INSTALL.md
new file mode 100644
index 0000000..d0a75d6
--- /dev/null
+++ b/Documentation/INSTALL.md
@@ -0,0 +1,121 @@
+[<-- Back to main section](../README.md)
+
+# Installation
+
+## Requirements
+
+- PHP 5.5 (CLI) with pcntl module
+- Tools
+ - git
+ - wget
+ - multitail
+ - tshark
+ - tcpdump
+ - ngrep
+ - strace
+ - lsof
+ - sudo
+ - moreutils (ifdata)
+ - coreutils (grep, sort, uniq, awk, cat, df, ip, cut, lsb_release, wall)
+ - docker and docker-compose (if you want to use docker)
+ - mysql (if you want to use mysql)
+
+
+## Install clitools
+
+```bash
+# Download latest tools (or in ~/bin if you have it in $PATH)
+wget -O/usr/local/bin/ct https://www.achenar.net/clicommand/clitools.phar
+
+# Set executable bit
+chmod 777 /usr/local/bin/ct
+
+# Download example config
+wget -O"$HOME/.clitools.ini" https://raw.githubusercontent.com/mblaschke/vagrant-development/develop/provision/ansible/roles/clitools/files/clitools.ini
+```
+
+## Aliases
+
+Now you can use following aliases (some aliases requires clitools 1.8.0!):
+
+```bash
+# Shortcut for auto-tree-searching make
+alias make='ct make'
+
+# Shortcut for auto-tree-searching make
+alias composer='ct php:composer'
+
+# Shortcut for docker-compose (autosearch docker-compose.yml in up-dir, you don't have to be in directory with docker-compose.yml)
+alias dcc='ct docker:compose'
+
+# Startup docker-container (and shutdown previous one, v1.9.0 and up)
+alias dccup='ct docker:up'
+alias dccstop='ct docker:compose stop'
+
+# Enter main docker container (as CLI_USER if available - if not specified then root is used)
+alias dcshell='ct docker:shell'
+alias dcsh='ct docker:shell'
+
+# Enter main docker container (as root)
+alias dcroot='ct docker:root'
+
+# Execute predefined cli in docker container
+alias dccrun='ct docker:cli'
+alias dcrun='ct docker:cli'
+
+# Execute mysql client in docker container
+alias dcsql='ct docker:mysql'
+alias dcmysql='ct docker:mysql'
+```
+
+## Configuration
+
+CliTools will read /etc/clitools.ini (system wide) and ~/.clitools.ini (personal) for configuration
+
+The [default configuration](https://github.com/mblaschke/vagrant-clitools/blob/develop/src/config.ini) is inside the phar.
+
+### Docker specific configuration
+```ini
+[config]
+; ssh_conf_path = "/vagrant/provision/sshconfig/"
+
+[db]
+dsn = "mysql:host=127.0.0.1;port=13306"
+username = "root"
+password = "dev"
+debug_log_dir = "/tmp/debug/"
+
+[syscheck]
+enabled = 1
+wall = 1
+growl = 1
+diskusage = 85
+
+[growl]
+server = 192.168.56.1
+password =
+
+[commands]
+; not used commands here
+ignore[] = "CliTools\Console\Command\Log\ApacheCommand"
+ignore[] = "CliTools\Console\Command\Log\PhpCommand"
+ignore[] = "CliTools\Console\Command\Log\DebugCommand"
+ignore[] = "CliTools\Console\Command\Apache\RestartCommand"
+ignore[] = "CliTools\Console\Command\Mysql\RestartCommand"
+ignore[] = "CliTools\Console\Command\Php\RestartCommand"
+ignore[] = "CliTools\Console\Command\System\UpdateCommand"
+ignore[] = "CliTools\Console\Command\System\RebootCommand"
+```
+
+## Update clitools
+
+```bash
+# Stable channel
+ct self-udpate
+
+## Beta channel
+ct self-update --beta
+
+## Fallback update (if GitHub fails)
+ct self-update --fallback
+```
diff --git a/Documentation/USAGE-DOCKER.md b/Documentation/USAGE-DOCKER.md
new file mode 100644
index 0000000..d8eb717
--- /dev/null
+++ b/Documentation/USAGE-DOCKER.md
@@ -0,0 +1,101 @@
+[<-- Back to main section](../README.md)
+
+# Usage of `ct docker:...`
+
+## Docker creation
+
+You can easly create new docker instances (from my or a custom docker boilerplate) also with code intalization
+and Makefile running.
+
+```bash
+# Startup new docker boilerplate into foobar directory
+ct docker:create foobar
+
+# Startup new custom docker boilerplate
+ct docker:create foobar --docker=git...
+
+# Startup new docker boilerplate with code repository
+ct docker:create foobar --code=git...
+
+# Startup new docker boilerplate with code repository and makefile run
+ct docker:create foobar --code=git... --make=build
+```
+
+## Docker startup
+
+The `docker:up` command will search the `docker-compose.yml` in the current parent directroy tree and
+execute `docker-compose` from this directroy - you don't have to change the current directroy.
+
+Also the previous docker instance will be shut down to avoid port conflicts.
+
+```bash
+# Startup docker-compose
+ct docker:up
+```
+
+## Custom docker commands
+
+As `docker:up` the `docker:compose` will search the `docker-compose.yml` and will execute your command
+from this directroy.
+
+```bash
+# Stop docker instance
+ct docker:compose stop
+
+# Show docker container status
+ct docker:compose ps
+```
+
+Hint: You can use `alias dcc='ct docker:compose'` for this.
+
+## Docker shell access
+
+There are many ways to jump into docker containers:
+
+```bash
+# Jump into a root shell
+ct docker:root
+
+# Jump into a root shell in mysql container
+ct docker:root mysql
+
+# Jump into a user shell (defined by CLI_USER as docker env)
+ct docker:shell
+
+# Jump into a root user in mysql container (defined by CLI_USER as docker env)
+ct docker:root mysql
+```
+
+## Docker command execution
+
+```bash
+# Execute command "ps" in "main" container
+ct docker:exec ps
+```
+
+## Docker cli execution
+
+You can define a common CLI script entrypoint with the environment variable CLI_SCRIPT in your docker containers.
+The environment variable will be read by `ct docker:cli` and will be executed - you don't have to jump
+into your containers, you can start your CLI_SCRIPTs from the outide.
+
+```bash
+# Execute predefined cli command with argument "help" in "main" container
+ct docker:cli help
+```
+
+## Docker debugging
+
+If you want to debug a docker application (eg. your webpage inside docker) the `ct docker:sniff` provides you
+a network sniffer set for various protocols (eg. http or mysql).
+
+```bash
+# Show basic http traffic
+ct docker:sniff http
+
+# Show full http traffic
+ct docker:sniff http --full
+
+# Show mysql querys by using network sniffer
+ct docker:sniff mysql
+```
diff --git a/Documentation/USAGE-MYSQL.md b/Documentation/USAGE-MYSQL.md
new file mode 100644
index 0000000..269e482
--- /dev/null
+++ b/Documentation/USAGE-MYSQL.md
@@ -0,0 +1,89 @@
+[<-- Back to main section](../README.md)
+
+# Usage of `ct mysql:...`
+
+# Common commands
+
+```bash
+# Create database typo3 (recreate and clears database if exists)
+ct mysql:create typo3
+
+# Drop database typo3
+ct mysql:drop typo3
+
+# List databases with statistics
+ct mysql:list
+```
+
+# Debugging
+
+The `ct mysql:querylog` and `ct mysql:slowlog` provides a convinent way to access the general query log
+and the slow log.
+
+In the query log you can see all queries send and executed by the MySQL database.
+The slow query log can be used to see long running queries.
+
+```bash
+# Enable and show query log
+ct mysql:querylog
+
+# Enable and show query log
+ct mysql:slowlog
+
+# Enable and show query log for all queries running longer than 1 sec
+ct mysql:slowlog --time=1
+
+# Enable and show query log for all queries which don't uses indizes
+ct mysql:slowlog --no-index
+
+```
+
+# Backup database
+You can easily backup a MySQL database (including compression compressions) and a filter set for tables.
+
+```bash
+# Backup typo3 database (without compression)
+ct mysql:backup typo3 dump.sql
+
+# Backup typo3 database (with gzip)
+ct mysql:backup typo3 dump.sql.gz
+
+# Backup typo3 database (with bzip2)
+ct mysql:backup typo3 dump.sql.bz2
+
+# Backup typo3 database (with LZMA/xz)
+ct mysql:backup typo3 dump.sql.xz
+```
+
+# Restore database
+
+Restoring is as easy as backuping a database, the `ct mysql:restore` will drop the database (if exists),
+recreate it and restores the dump into the database. With this workflow you also removes all tables which
+are not part of the dump file - it's a clean restore of the dump.
+Also the compression is automatically detected by file mime type.
+
+```bash
+# Restore typo3 database (auto compression detection)
+ct mysql:restore typo3 dump.sql
+```
+
+# Datbase charset conversion
+
+```bash
+# Convert database to UTF8
+ct mysql:convert typo3
+
+# Convert database typo3 to UTF8 and collation utf8_unicode_ci
+ct mysql:convert typo3 --collation=utf8_unicode_ci
+
+# Convert database typo3 to Latin1
+ct mysql:convert typo3 --charset=latin1
+
+# Convert database typo3 to Latin1, only show queries
+ct mysql:convert typo3 --stdout
+```
+
+
+
+
+
diff --git a/Documentation/USAGE-PHP.md b/Documentation/USAGE-PHP.md
new file mode 100644
index 0000000..911ec67
--- /dev/null
+++ b/Documentation/USAGE-PHP.md
@@ -0,0 +1,33 @@
+[<-- Back to main section](../README.md)
+
+# Usage `ct php:...`
+
+## Composer (auto searching in path tree)
+
+Because you always need to jump into `composer.json` directroy `ct php:composer` will do this for you
+
+```bash
+# Run composer install task
+ct php:composer install
+
+# Run composer update task
+ct php:composer update
+```
+
+Hint: You can use `alias composer='ct php:composer'` for this.
+
+
+## Sys-Tracing PHP Processes
+
+Because strace'ing already running processes requires some shell knowledge `ct php:trace` will make this handy for you.
+
+```bash
+# Trace one or all php processes (interactive mode)
+ct php:trace
+
+# Trace all processes immediately
+ct php:trace --all
+```
+
+
+
diff --git a/Documentation/USAGE-SYNC.md b/Documentation/USAGE-SYNC.md
new file mode 100644
index 0000000..11bed8d
--- /dev/null
+++ b/Documentation/USAGE-SYNC.md
@@ -0,0 +1,105 @@
+[<-- Back to main section](../README.md)
+
+# Usage `ct sync:...`
+
+## Init and configuration of `sync`
+
+With the `sync` commands you can update your local development installation to the current state of your
+server installations. Currently filesync (rsync) and database fetching is supported.
+
+First you need to create a `clisync.yml` in your project directory, the CliTools will provide your an example
+of this file by using following command:
+
+```bash
+ct sync:init
+```
+
+If you need special SSH settings (ports, compression, identify...) please use your `~/.ssh/config` file
+to such settings.
+
+You can commit this clisync.yml into your project so other developers can use the sync feature, too.
+
+## Synchronisation with servers (ct sync:server)
+
+The synchronisation with your servers is one way only, it just syncs your server installation to your
+local installation (CliTools are no deployment tools!).
+
+In the `clisync.yml` you can specify multiple servers.
+
+Now you can sync your `production` server to your local installation:
+
+```bash
+# Full sync (files and database, with interactive context selection)
+ct sync:server
+
+# Only MySQL (from production context, without interactive context selection)
+ct sync:server production --mysql
+
+# Only Files (from production context, without interactive context selection)
+ct sync:server production --rsync
+```
+
+## Project sharing (ct sync:backup and ct sync:restore)
+
+The sharing can be used to share files (assets) and databases between developers.
+Please use a common development/storage server with ssh access for each developer for this feature.
+
+```bash
+# Make backup of current state and transfer to share server
+ct sync:backup
+
+# ... only MySQL
+ct sync:backup --mysql
+
+# ... only files
+ct sync:backup --rsync
+
+# Restore to state from the share server
+ct sync:restore
+
+# ... only MySQL
+ct sync:restore --mysql
+
+# ... only files
+ct sync:restore --rsync
+
+```
+
+## Lightweight deployment (ct sync:deploy)
+
+With `sync:deploy` you can push your files to your production servers.
+Please keep in mind that this feature is just an wrapped rsync and should only be
+the simplest solution for deployment. For more advanced or centralized deployemnt try
+solutions build on Jenkis, Ansible and others.
+
+```bash
+# Push your project to your servers (with interactive context selection)
+ct sync:deploy
+
+# Push your project to your staging server (without interactive context selection)
+ct sync:deploy staging
+```
+
+## Advanced ssh options
+
+If you need some advaned ssh options (eg. other ports) use your `~/.ssh/config` configuration file:
+
+ Host project-server
+ Hostname project-server.example.com
+ Port 12345
+ User root
+
+If you have a proxy server you can configure it like this:
+
+ Host ssh-proxy
+ Hostname ssh-proxy.example.com
+ User foo
+
+ Host project-server
+ Hostname project-server.example.com
+ Port 12345
+ User root
+ ProxyCommand ssh ssh-proxy -W %h:%p
+
+
+Now you can use `project-server` as ssh-hostname and your settings will automatically used from your `~/.ssh/config`.
diff --git a/Documentation/USAGE-TYPO3.md b/Documentation/USAGE-TYPO3.md
new file mode 100644
index 0000000..75d0acc
--- /dev/null
+++ b/Documentation/USAGE-TYPO3.md
@@ -0,0 +1,46 @@
+[<-- Back to main section](../README.md)
+
+# Usage `ct typo3:...`
+
+## Backend user injection
+
+The `ct typo3:beuser` can be used for creating a backend user in all or a specific TYPO3 database
+(with salted password support).
+
+Default username: dev
+Default password: dev
+
+```bash
+# Create the user in all databases
+ct typo3:beuser
+
+# Create the user in typo3 databases
+ct typo3:beuser typo3
+
+# Create the user in typo3 databases with plain password (no salted password)
+ct typo3:beuser typo3 --plain
+```
+
+## Automatic domain manipulation
+
+The `ct typo3:domain` can be used for manipulation of the domain records eg. for matching your
+development environment.
+
+```bash
+# Add a .vm at the and of all domains in all databases
+ct typo3:domain
+
+# Add a .vm at the and of all domains in typo3 database
+ct typo3:domain typo3
+
+# ... and also add config.baseURL to SetupTS
+ct typo3:domain typo3 --baseurl
+
+# Add a .vm at the and of all domains and remove all *.vagrantshare.com domains (used by vagrant:share)
+ct typo3:domain --remove='*.vagrantshare.com'
+
+# Add a .vm at the and of all domains and duplicate all domains with the suffix .vagrantshare.com (used by vagrant:share)
+ct typo3:domain --duplicate='.vagrantshare.com'
+```
+
+
diff --git a/Makefile b/Makefile
index 789d5f6..58f910f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,10 @@
+all: autoload build install
+
build:
bash compile.sh
install:
cp clitools.phar /usr/local/bin/ct
-all: build install
+autoload:
+ sh -c "cd src ; composer dump-autoload --optimize --no-dev"
diff --git a/README.md b/README.md
index c827801..2258f8b 100644
--- a/README.md
+++ b/README.md
@@ -1,232 +1,37 @@
-# CliTools for Vagrant VM, Debian and Ubuntu (and others)
+# CliTools for Docker, PHP und MySQL development
-![latest v1.9.0](https://img.shields.io/badge/latest-v1.9.0-green.svg?style=flat)
-![License GPL3](https://img.shields.io/badge/license-GPL3-blue.svg?style=flat)
-[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/mblaschke/vagrant-clitools.svg)](http://isitmaintained.com/project/mblaschke/vagrant-clitools "Average time to resolve an issue")
-[![Percentage of issues still open](http://isitmaintained.com/badge/open/mblaschke/vagrant-clitools.svg)](http://isitmaintained.com/project/mblaschke/vagrant-clitools "Percentage of issues still open")
+[![latest v2.0.0](https://img.shields.io/badge/latest-v2.0.0-green.svg?style=flat)](https://github.com/mblaschke/clitools/releases/tag/2.0.0)
+[![License GPL3](https://img.shields.io/badge/license-GPL3-blue.svg?style=flat)](/LICENSE)
+[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/mblaschke/clitools.svg)](http://isitmaintained.com/project/mblaschke/clitools "Average time to resolve an issue")
+[![Percentage of issues still open](http://isitmaintained.com/badge/open/mblaschke/clitools.svg)](http://isitmaintained.com/project/mblaschke/clitools "Percentage of issues still open")
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/9f12f125-3623-4b9d-b01b-07090f91e416/big.png)](https://insight.sensiolabs.com/projects/9f12f125-3623-4b9d-b01b-07090f91e416)
-CliTools is a terminal utility for some handy convierence tasks based on Symfony Components (Console).
+## Introduction
-Documentation is still WIP :)
+CliTools is a terminal utility for faster development. It should make some daily task very easy.
-## Requirements
+CliTools is based on Symfony Components (Console).
-- PHP 5.5 (CLI)
-- Tools
- - git
- - wget
- - multitail
- - tshark
- - tcpdump
- - ngrep
- - strace
- - lsof
- - sudo
- - moreutils (ifdata)
- - coreutils (grep, sort, uniq, awk, cat, df, ip, cut, lsb_release, wall)
- - docker and docker-compose (if you want to use docker)
- - mysql (if you want to use mysql)
+## Table of contents
-## Installation
+- [Installation and requirements](/Documentation/INSTALL.md)
+- [Usage `ct docker:...` commands](/Documentation/USAGE-DOCKER.md)
+- [Usage `ct sync:...` commands](/Documentation/USAGE-SYNC.md)
+- [Usage `ct mysql:...` commands](/Documentation/USAGE-MYSQL.md)
+- [Usage `ct typo3:...` commands](/Documentation/USAGE-TYPO3.md)
+- [Usage `ct php:...` commands](/Documentation/USAGE-PHP.md)
+- [Command overview](/Documentation/COMMANDS.md)
+- [Shell aliases](/Documentation/ALIASES.md)
+## Credits
-```bash
-# Download latest tools (or in ~/bin if you have it in $PATH)
-wget -O/usr/local/bin/ct https://www.achenar.net/clicommand/clitools.phar
-
-# Set executable bit
-chmod 777 /usr/local/bin/ct
-
-# Download example config
-wget -O"$HOME/.clitools.ini" https://raw.githubusercontent.com/mblaschke/vagrant-development/develop/provision/ansible/roles/clitools/files/clitools.ini
-```
-
-Now you can use following aliases (some aliases requires clitools 1.8.0!):
-
-```bash
-# Shortcut for docker-compose (autosearch docker-compose.yml in up-dir, you don't have to be in directory with docker-compose.yml)
-alias dcc='ct docker:compose'
-
-# Startup docker-container (and shutdown previous one, v1.9.0 and up)
-alias dccup='ct docker:up'
-alias dccstop='ct docker:compose stop'
-
-# Enter main docker container (as CLI_USER if available - if not specified then root is used)
-alias dcshell='ct docker:shell'
-alias dcsh='ct docker:shell'
-
-# Enter main docker container (as root)
-alias dcroot='ct docker:root'
-
-# Execute predefined cli in docker container
-alias dccrun='ct docker:cli'
-alias dcrun='ct docker:cli'
-
-# Execute mysql client in docker container
-alias dcsql='ct docker:mysql'
-alias dcmysql='ct docker:mysql'
-```
-
-## Configuration
-
-CliTools will read /etc/clitools.ini (system wide) and ~/.clitools.ini (personal) for configuration
-
-The [default configuration](https://github.com/mblaschke/vagrant-clitools/blob/develop/src/config.ini) is inside the phar.
-
-### Docker specific configuration
-```ini
-[config]
-; ssh_conf_path = "/vagrant/provision/sshconfig/"
-
-[db]
-dsn = "mysql:host=127.0.0.1;port=13306"
-username = "root"
-password = "dev"
-debug_log_dir = "/tmp/debug/"
-
-[syscheck]
-enabled = 1
-wall = 1
-growl = 1
-diskusage = 85
-
-[growl]
-server = 192.168.56.1
-password =
-
-[commands]
-; not used commands here
-ignore[] = "CliTools\Console\Command\Log\ApacheCommand"
-ignore[] = "CliTools\Console\Command\Log\PhpCommand"
-ignore[] = "CliTools\Console\Command\Log\DebugCommand"
-ignore[] = "CliTools\Console\Command\Apache\RestartCommand"
-ignore[] = "CliTools\Console\Command\Mysql\RestartCommand"
-ignore[] = "CliTools\Console\Command\Php\RestartCommand"
-ignore[] = "CliTools\Console\Command\System\UpdateCommand"
-ignore[] = "CliTools\Console\Command\System\RebootCommand"
-```
-
-## Commands
-
-### Special commands
-
-| Command | Description |
-|----------------------------|---------------------------------------------------------------------------|
-| ct self-update | Update ct command (download new version) |
-| ct update | Updates all system components, ssh configuration, ct command update etc. |
-
-### System commands
-
-| Command | Description |
-|----------------------------|---------------------------------------------------------------------------|
-| ct shutdown (alias) | Shutdown system |
-
-### Log commands
-
-All log commands are using a grep-filter (specified as optional argument)
-
-| Command | Description |
-|----------------------------|---------------------------------------------------------------------------|
-| ct log:mail | Shows mail logs |
-
-### Docker commands
-
-| Command | Description |
-|----------------------------|---------------------------------------------------------------------------|
-| ct docker:create | Create new docker boilerplate in directory (first argument) |
-| | __ct docker:create projectname__ -> Create new docker boilerplate instance in directory "projectname" |
-| | __ct docker:create projectname --code=git@github.com/foo/bar__ -> Create new docker boilerplate instance in directory "projectname" and custom code repository |
-| | __ct docker:create projectname --docker=git@github.com/foo/bar__ -> Create new docker boilerplate instance in directory "projectname" and custom docker boilerplate repository |
-| ct docker:shell | Jump into a shell inside a docker container (using predefined user defined with CLI_USER in docker env) |
-| | __ct docker:shell__ -> enter main container |
-| | __ct docker:shell mysql__ -> enter mysql container |
-| | __ct docker:shell --user=www-data -> enter main container as user www-data |
-| ct docker:root | Jump into a shell inside a docker container as root user |
-| ct docker:mysql | Jump into a mysql client inside a docker container |
-| | __ct docker:mysql__ -> execute mysql client inside main container |
-| ct docker:sniff | Start network sniffer for various protocols |
-| | __ct docker:sniff http__ -> start HTTP sniffing |
-| ct docker:exec | Execute command in docker container |
-| | __ct docker:exec ps__ -> run 'ps' inside main container |
-| ct docker:cli | Execute special cli command in docker container |
-| | __ct docker:cli scheduler__ -> run 'scheduler' in TYPO3 CMS |
-| ct docker:compose | Execute docker-compose (recursive up-searching for docker-compose.yml) |
-| | __ct docker:compose ps__ -> list all running docker-compose containers |
-
-### MySQL commands
-
-| Command | Description |
-|----------------------------|---------------------------------------------------------------------------|
-| ct mysql:clear | Clear database (remove all tables in database) |
-| | __ct mysql:clear typo3__ |
-| ct mysql:connections | Lists all current connections |
-| ct mysql:create | Create (and drops if already exists) a database |
-| | __ct mysql:create typo3__ |
-| ct mysql:debug | Shows mysql debug log (lists all queries) with basic filter support |
-| | __ct mysql:debug__ (full log) |
-| | __ct mysql:debug tt_content__ (full log) |
-| ct mysql:slowlog | Shows mysql slow log |
-| | __ct mysql:slowlog__ (show slow queries with 1 sec and more) |
-| | __ct mysql:slowlog --time=10__ (show slow queries with 10 sec and more) |
-| | __ct mysql:slowlog --no-index__ (show not using index and slow (1sec) queries) |
-| ct mysql:drop | Drops a database |
-| | __ct mysql:drop typo3__ |
-| ct mysql:list | Lists all databases with some statitics |
-| ct mysql:restart | Restart MySQL server |
-| ct mysql:backup | Backup a database to file |
-| | Compression type will be detected from file extension (default plain sql) |
-| | __ct mysql:restore typo3 dump.sql__ -> plain sql dump |
-| | __ct mysql:restore typo3 dump.sql.gz__ -> gzip'ed sql dump |
-| | __ct mysql:restore typo3 dump.sql.bzip2__ -> bzip2'ed sql dump |
-| | __ct mysql:restore typo3 dump.sql.xz__ -> xz'ed (lzma'ed) sql dump |
-| | __ct mysql:restore typo3 dump.sql --filter=typo3__ -> No TYPO3 cache tables in dump |
-| ct mysql:restore | Create (and drops if already exists) a database and restore from a dump |
-| | Dump file can be plaintext, gziped, bzip2 or lzma compressed |
-| | and will automatically detected |
-| | __ct mysql:restore typo3 dump.sql.bz2__ |
-
-### PHP commands
-
-| Command | Description |
-|----------------------------|---------------------------------------------------------------------------|
-| ct php:trace | Trace syscalls from one or all PHP processes (strace) |
-| | __ct php:trace --all__ -> Trace all php processes immediately |
-
-### Samba commands
-
-| Command | Description |
-|----------------------------|---------------------------------------------------------------------------|
-| ct samba:restart | Restart Samba server |
-
-
-### System commands
-
-| Command | Description |
-|----------------------------|---------------------------------------------------------------------------|
-| ct system:env | Lists common environment variables |
-| ct system:openfiles | Lists current open files count grouped by process |
-| ct system:shutdown | Shutdown system |
-| ct system:swap | Show swap usage for running processes |
-| ct system:update | Updates all system components, ssh configuration, ct command update etc. |
-| ct system:version | Shows version for common packages |
-
-### TYPO3 commands
-
-| Command | Description |
-|----------------------------|---------------------------------------------------------------------------|
-| ct typo3:beuser | Injects a dev user (pass dev) to all or one specified TYPO3 database |
-| | __ct typo3:beuser__ |
-| | __ct typo3:beuser typo3__ |
-| ct typo3:cleanup | Cleanup command tables to same some table space |
-| | __ct typo3:cleanup__ |
-| | __ct typo3:cleanup typo3__ |
-
-### User commands
-
-| Command | Description |
-|----------------------------|---------------------------------------------------------------------------|
-| ct user:rebuildsshconfig | Rebuild SSH config from ct repository (/vagrant/provision/sshconfig) |
+Thanks for support, ideas and issues ...
+- [Ingo Pfennigstorf](https://twitter.com/krautsock)
+- [Florian Tatzel](https://twitter.com/PanadeEdu)
+- [Philipp Kitzberger](https://github.com/Kitzberger)
+- my (old) colleagues at [Lightwerk GmbH](http://www.lightwerk.de/)
+- my colleagues at [cron IT GmbH](http://www.cron.eu/)
+Did I forget anyone? Send me a tweet or create pull request!
diff --git a/build.json b/box.json
similarity index 87%
rename from build.json
rename to box.json
index 9cdc14b..5a9a2d0 100644
--- a/build.json
+++ b/box.json
@@ -15,5 +15,6 @@
],
"main": "src/command.php",
"output": "clitools.phar",
- "stub": true
+ "stub": true,
+ "compression": "GZ"
}
diff --git a/compile.sh b/compile.sh
index a7cef5f..d1a67b2 100755
--- a/compile.sh
+++ b/compile.sh
@@ -9,10 +9,16 @@ SCRIPT_DIR=$(dirname $(readlink -f "$0"))
OLD_PWD=`pwd`
+## copy configs
+cp "$SCRIPT_DIR/Documentation/Examples/clisync.yml" "$SCRIPT_DIR/src/conf/"
+
+## run composer
cd "$SCRIPT_DIR/src"
-composer install
+composer install --no-dev
+composer dump-autoload --optimize --no-dev
+## create phar
cd "$SCRIPT_DIR/"
-box.phar build -c build.json
+box.phar build -c box.json
cd "$OLD_PWD"
diff --git a/src/app/CliTools/Console/Application.php b/src/app/CliTools/Console/Application.php
index 4d3151a..e1e8920 100644
--- a/src/app/CliTools/Console/Application.php
+++ b/src/app/CliTools/Console/Application.php
@@ -22,6 +22,7 @@
use CliTools\Database\DatabaseConnection;
use CliTools\Service\SettingsService;
+use CliTools\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\ArgvInput;
@@ -98,6 +99,7 @@ public function getConfigValue($area, $confKey, $defaultValue = null) {
* Initialize
*/
public function initialize() {
+ $this->initializeErrorHandler();
$this->initializeChecks();
$this->initializeConfiguration();
$this->initializePosixTrap();
@@ -160,6 +162,9 @@ public function doRun(InputInterface $input, OutputInterface $output) {
} else {
$ret = parent::doRun($input, $output);
}
+ } catch(\CliTools\Exception\StopException $e) {
+ $this->callTearDown();
+ $ret = (int)$e->getMessage();
} catch (\Exception $e) {
$this->callTearDown();
throw $e;
@@ -170,14 +175,44 @@ public function doRun(InputInterface $input, OutputInterface $output) {
return $ret;
}
+
+ /**
+ * Configures the input and output instances based on the user arguments and options.
+ *
+ * @param InputInterface $input An InputInterface instance
+ * @param OutputInterface $output An OutputInterface instance
+ */
+ protected function configureIO(InputInterface $input, OutputInterface $output) {
+ parent::configureIO($input, $output);
+
+ $style = new OutputFormatterStyle();
+ $style->setApplication($this);
+ $style->setWrap('-', '-');
+ $output->getFormatter()->setStyle('h1', $style);
+
+ $style = new OutputFormatterStyle();
+ $style->setPaddingOutside(' ===> ');
+ $output->getFormatter()->setStyle('h2', $style);
+
+ $style = new OutputFormatterStyle();
+ $style->setPaddingOutside(' - ');
+ $output->getFormatter()->setStyle('p', $style);
+
+ $style = new OutputFormatterStyle('white', 'red');
+ $style->setPadding(' [EE] ');
+ $output->getFormatter()->setStyle('p-error', $style);
+ }
+
/**
* Initialize POSIX trap
*/
protected function initializePosixTrap() {
declare(ticks = 1);
- $signalHandler = function ($signal) {
- $this->callTearDown();
+ $me = $this;
+
+ $signalHandler = function ($signal) use($me) {
+ $me->callTearDown();
// Prevent terminal messup
echo "\n";
@@ -187,6 +222,25 @@ protected function initializePosixTrap() {
pcntl_signal(SIGINT, $signalHandler);
}
+ /**
+ * Init error handler
+ */
+ protected function initializeErrorHandler() {
+ $errorHandler = function ($errno, $errstr, $errfile, $errline) {
+ $msg = array(
+ 'Message: ' . $errstr,
+ 'File: ' . $errfile,
+ 'Line: ' . $errline,
+ );
+
+ $msg = implode("\n", $msg);
+
+ throw new \RuntimeException($msg, $errno);
+ };
+
+ set_error_handler($errorHandler);
+ }
+
/**
* PHP Checks
*/
@@ -321,4 +375,14 @@ public function getSettingsService() {
}
return $this->settingsService;
}
+
+ /**
+ * Set terminal title
+ *
+ * @param string $title Title
+ */
+ public function setTerminalTitle($title) {
+ // DECSLPP.
+ echo "\033]0;" . 'ct: ' . $title . "\033\\";
+ }
}
diff --git a/src/app/CliTools/Console/Command/AbstractCommand.php b/src/app/CliTools/Console/Command/AbstractCommand.php
index 9806e45..99873e2 100644
--- a/src/app/CliTools/Console/Command/AbstractCommand.php
+++ b/src/app/CliTools/Console/Command/AbstractCommand.php
@@ -24,11 +24,18 @@
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\FullSelfCommandBuilder;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\FullSelfCommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
abstract class AbstractCommand extends Command {
+ /**
+ * Message list (will be shown at the end)
+ *
+ * @var array
+ */
+ protected $finishMessageList = array();
+
/**
* Input
*
@@ -57,8 +64,44 @@ protected function initialize(InputInterface $input, OutputInterface $output) {
$this->output = $output;
ConsoleUtility::initialize($input, $output);
+
+ // Set default terminal title
+ $this->setTerminalTitle(explode(':', $this->getName()));
}
+ /**
+ * Runs the command.
+ *
+ * The code to execute is either defined directly with the
+ * setCode() method or by overriding the execute() method
+ * in a sub-class.
+ *
+ * @param InputInterface $input An InputInterface instance
+ * @param OutputInterface $output An OutputInterface instance
+ *
+ * @return int The command exit code
+ *
+ * @throws \Exception
+ *
+ * @see setCode()
+ * @see execute()
+ *
+ * @api
+ */
+ public function run(InputInterface $input, OutputInterface $output) {
+
+ try {
+ $ret = parent::run($input, $output);
+ $this->showFinishMessages();
+ } catch (\Exception $e) {
+ $this->showFinishMessages();
+ throw $e;
+ }
+
+ return $ret;
+ }
+
+
/**
* Get full parameter list
*
@@ -99,7 +142,7 @@ protected function elevateProcess(InputInterface $input, OutputInterface $output
} catch (\Exception $e) {
// do not display exception here because it's a child process
}
- exit(0);
+ throw new \CliTools\Exception\StopException(0);
} else {
// running as root
}
@@ -123,12 +166,14 @@ protected function showLog($logList, $input, $output, $grep = null, $optionList
// check if logfiles are accessable
foreach ($logList as $log) {
if (!is_readable($log)) {
- $output->writeln('Can\'t read ' . $log . '');
+ $output->writeln('Can\'t read ' . $log . '');
return 1;
}
}
+ $output->writeln('
Reading logfile with multitail
');
+
$command = new CommandBuilder('multitail', '--follow-all');
// Add grep
@@ -142,4 +187,73 @@ protected function showLog($logList, $input, $output, $grep = null, $optionList
return 0;
}
+
+ /**
+ * Add message to finish list
+ *
+ * @param string $message Message
+ */
+ protected function addFinishMessage($message) {
+ $this->output->writeln($message);
+ $this->finishMessageList[] = $message;
+ }
+
+ /**
+ * Show all finish messages
+ */
+ protected function showFinishMessages() {
+
+ if (!empty($this->finishMessageList)) {
+ $this->output->writeln('');
+ $this->output->writeln('Replay finish message log:');
+
+ foreach ($this->finishMessageList as $message) {
+ $this->output->writeln(' - ' . $message);
+ }
+ }
+
+ $this->finishMessageList = array();
+ }
+
+ /**
+ * Gets the application instance for this command.
+ *
+ * @return \CliTools\Console\Application An Application instance
+ *
+ * @api
+ */
+ public function getApplication() {
+ return parent::getApplication();
+ }
+
+ /**
+ * Sets the terminal title of the command.
+ *
+ * This feature should be used only when creating a long process command,
+ * like a daemon.
+ *
+ * PHP 5.5+ or the proctitle PECL library is required
+ *
+ * @param string $title The terminal title
+ *
+ * @return Command The current instance
+ */
+ public function setTerminalTitle($title) {
+ $args = func_get_args();
+
+ $titleList = array();
+ foreach($args as $value) {
+ if (is_array($value)) {
+ $value = implode(' ', $value);
+ }
+
+ $titleList[] = trim($value);
+ }
+
+ $title = implode(' ', $titleList);
+ $title = trim($title);
+
+ $this->getApplication()->setTerminalTitle($title);
+ return $this;
+ }
}
diff --git a/src/app/CliTools/Console/Command/AbstractTraceCommand.php b/src/app/CliTools/Console/Command/AbstractTraceCommand.php
index fc545b4..13162f4 100644
--- a/src/app/CliTools/Console/Command/AbstractTraceCommand.php
+++ b/src/app/CliTools/Console/Command/AbstractTraceCommand.php
@@ -20,7 +20,7 @@
* along with this program. If not, see .
*/
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -90,21 +90,28 @@ public function execute(InputInterface $input, OutputInterface $output) {
$command->setOutputRedirect(CommandBuilder::OUTPUT_REDIRECT_ALL_STDOUT);
+ $output->writeln('Starting process stracing
');
+
if (empty($pid)) {
list($pidList, $processList) = $this->buildProcessList();
if ($input->getOption('all')) {
$pid = 'all';
} else {
- $question = new ChoiceQuestion('Please choose process for tracing', $processList);
+ try {
+ $question = new ChoiceQuestion('Please choose process for tracing', $processList);
+ $question->setMaxAttempts(1);
- $questionDialog = new QuestionHelper();
+ $questionDialog = new QuestionHelper();
- $pid = $questionDialog->ask($input, $output, $question);
+ $pid = $questionDialog->ask($input, $output, $question);
+ } catch(\InvalidArgumentException $e) {
+ // Invalid value, just stop here
+ throw new \CliTools\Exception\StopException(1);
+ }
}
}
-
if (!empty($pid)) {
switch ($pid) {
case 'all':
@@ -157,7 +164,7 @@ protected function buildProcessList() {
$currentPid = posix_getpid();
$processList = array(
- 'all processes' => 'all',
+ 'all' => 'all processes',
);
$command = new CommandBuilder('ps');
@@ -183,8 +190,8 @@ protected function buildProcessList() {
continue;
}
- $pidList[] = (int)$pid;
- $processList[$cmd] = (int)$pid;
+ $pidList[] = (int)$pid;
+ $processList[(int)$pid] = $cmd;
}
return array($pidList, $processList);
diff --git a/src/app/CliTools/Console/Command/Apache/RestartCommand.php b/src/app/CliTools/Console/Command/Apache/RestartCommand.php
index ec1ee82..4dd389c 100644
--- a/src/app/CliTools/Console/Command/Apache/RestartCommand.php
+++ b/src/app/CliTools/Console/Command/Apache/RestartCommand.php
@@ -22,7 +22,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
class RestartCommand extends \CliTools\Console\Command\AbstractCommand {
@@ -30,7 +30,8 @@ class RestartCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('apache:restart')
+ $this
+ ->setName('apache:restart')
->setDescription('Restart Apache');
}
diff --git a/src/app/CliTools/Console/Command/Apache/TraceCommand.php b/src/app/CliTools/Console/Command/Apache/TraceCommand.php
index 4faf2e8..2330425 100644
--- a/src/app/CliTools/Console/Command/Apache/TraceCommand.php
+++ b/src/app/CliTools/Console/Command/Apache/TraceCommand.php
@@ -33,8 +33,10 @@ class TraceCommand extends \CliTools\Console\Command\AbstractTraceCommand {
* Configure command
*/
protected function configure() {
- $this->setName('apache:trace')
+ $this
+ ->setName('apache:trace')
->setDescription('Debug Apache processes with strace');
+
parent::configure();
}
diff --git a/src/app/CliTools/Console/Command/Docker/UpgradeCommand.php b/src/app/CliTools/Console/Command/Common/MakeCommand.php
similarity index 52%
rename from src/app/CliTools/Console/Command/Docker/UpgradeCommand.php
rename to src/app/CliTools/Console/Command/Common/MakeCommand.php
index 612f3e5..c21c5fd 100644
--- a/src/app/CliTools/Console/Command/Docker/UpgradeCommand.php
+++ b/src/app/CliTools/Console/Command/Common/MakeCommand.php
@@ -1,6 +1,6 @@
.
*/
+use CliTools\Utility\UnixUtility;
+use CliTools\Utility\PhpUtility;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
-class UpgradeCommand extends AbstractCommand {
+class MakeCommand extends \CliTools\Console\Command\AbstractCommand implements \CliTools\Console\Filter\AnyParameterFilterInterface {
/**
* Configure command
*/
protected function configure() {
- $this->setName('docker:upgrade')
- ->setDescription('Upgrade docker version');
+ $this
+ ->setName('make')
+ ->setDescription('Search Makefile updir and start makefile');
}
/**
@@ -43,11 +46,28 @@ protected function configure() {
* @return int|null|void
*/
public function execute(InputInterface $input, OutputInterface $output) {
- $this->elevateProcess($input, $output);
+ $paramList = $this->getFullParameterList();
+ $path = UnixUtility::findFileInDirectortyTree('Makefile');
- $command = new CommandBuilder('wget', '-qO- %s', array('https://get.docker.com/'));
- $command->addPipeCommand(new CommandBuilder('sh'));
- $command->executeInteractive();
+ if (!empty($path)) {
+ $path = dirname($path);
+ $this->output->writeln('Found Makefile directory: ' . $path . '');
+
+ // Switch to directory of docker-compose.yml
+ PhpUtility::chdir($path);
+
+ $command = new CommandBuilder('make');
+
+ if (!empty($paramList)) {
+ $command->setArgumentList($paramList);
+ }
+
+ $command->executeInteractive();
+ } else {
+ $this->output->writeln('No Makefile found in tree');
+
+ return 1;
+ }
return 0;
}
diff --git a/src/app/CliTools/Console/Command/Common/SelfUpdateCommand.php b/src/app/CliTools/Console/Command/Common/SelfUpdateCommand.php
index 2368acb..8daf646 100644
--- a/src/app/CliTools/Console/Command/Common/SelfUpdateCommand.php
+++ b/src/app/CliTools/Console/Command/Common/SelfUpdateCommand.php
@@ -21,6 +21,7 @@
*/
use CliTools\Service\SelfUpdateService;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -30,9 +31,28 @@ class SelfUpdateCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('self-update')
+ $this
+ ->setName('self-update')
->setAliases(array('selfupdate'))
- ->setDescription('Self update of CliTools Command');
+ ->setDescription('Self update of CliTools Command')
+ ->addOption(
+ 'force',
+ 'f',
+ InputOption::VALUE_NONE,
+ 'Force update'
+ )
+ ->addOption(
+ 'beta',
+ null,
+ InputOption::VALUE_NONE,
+ 'Allow update to beta releases'
+ )
+ ->addOption(
+ 'fallback',
+ null,
+ InputOption::VALUE_NONE,
+ 'Fallback to old update url'
+ );
}
/**
@@ -44,8 +64,18 @@ protected function configure() {
* @return int|null|void
*/
public function execute(InputInterface $input, OutputInterface $output) {
+ $force = (bool)$input->getOption('force');
+
$updateService = new SelfUpdateService($this->getApplication(), $output);
+ if ($input->getOption('beta')) {
+ $updateService->enablePreVersions();
+ }
+
+ if ($input->getOption('fallback')) {
+ $updateService->enableUpdateFallback();
+ }
+
// Check if we need root rights
if (!$this->getApplication()->isRunningAsRoot()
&& $updateService->isElevationNeeded())
@@ -53,10 +83,6 @@ public function execute(InputInterface $input, OutputInterface $output) {
$this->elevateProcess($input, $output);
}
- $updateService->update();
-
-
-
-
+ $updateService->update($force);
}
}
diff --git a/src/app/CliTools/Console/Command/Docker/AbstractCommand.php b/src/app/CliTools/Console/Command/Docker/AbstractCommand.php
index 6e70126..1cf5d23 100644
--- a/src/app/CliTools/Console/Command/Docker/AbstractCommand.php
+++ b/src/app/CliTools/Console/Command/Docker/AbstractCommand.php
@@ -20,8 +20,8 @@
* along with this program. If not, see .
*/
-use CliTools\Console\Builder\CommandBuilder;
-use CliTools\Console\Builder\CommandBuilderInterface;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilderInterface;
use CliTools\Utility\PhpUtility;
abstract class AbstractCommand extends \CliTools\Console\Command\AbstractCommand {
@@ -40,8 +40,12 @@ abstract class AbstractCommand extends \CliTools\Console\Command\AbstractCommand
*/
protected function getDockerPath() {
if ($this->dockerPath === null) {
- $this->dockerPath = \CliTools\Utility\DockerUtility::searchDockerDirectoryRecursive();
- $this->output->writeln('Found docker directory: ' . $this->dockerPath . '');
+ $composePath = \CliTools\Utility\DockerUtility::searchDockerDirectoryRecursive();
+
+ if (!empty($composePath)) {
+ $this->dockerPath = dirname($composePath);
+ $this->output->writeln('Found docker directory: ' . $this->dockerPath . '');
+ }
}
return $this->dockerPath;
@@ -59,22 +63,26 @@ protected function getDockerEnv($containerName, $envName) {
$ret = null;
if (empty($containerName)) {
- $this->output->writeln('No container specified');
+ $this->output->writeln('No container specified');
return false;
}
if (empty($envName)) {
- $this->output->writeln('No environment name specified');
+ $this->output->writeln('No environment name specified');
return false;
}
+ // Search updir for docker-compose.yml
$path = $this->getDockerPath();
if (!empty($path)) {
+ // Genrate full docker container name
$dockerContainerName = \CliTools\Utility\DockerUtility::getDockerInstanceName($containerName, 1, $path);
+ // Switch to directory of docker-compose.yml
PhpUtility::chdir($path);
+ // Get docker confguration (fetched directly from docker)
$conf = \CliTools\Utility\DockerUtility::getDockerConfiguration($dockerContainerName);
if (empty($conf)) {
@@ -99,20 +107,23 @@ protected function getDockerEnv($containerName, $envName) {
*/
protected function executeDockerExec($containerName, CommandBuilderInterface $command) {
if (empty($containerName)) {
- $this->output->writeln('No container specified');
+ $this->output->writeln('No container specified');
return 1;
}
if (!$command->isExecuteable()) {
- $this->output->writeln('No command specified or not executeable');
+ $this->output->writeln('No command specified or not executeable');
return 1;
}
+ // Search updir for docker-compose.yml
$path = $this->getDockerPath();
if (!empty($path)) {
+ // Genrate full docker container name
$dockerContainerName = \CliTools\Utility\DockerUtility::getDockerInstanceName($containerName, 1, $path);
+ // Switch to directory of docker-compose.yml
PhpUtility::chdir($path);
$this->output->writeln('Executing "' . $command->getCommand() . '" in docker container "' . $dockerContainerName . '" ...');
@@ -121,7 +132,7 @@ protected function executeDockerExec($containerName, CommandBuilderInterface $co
$dockerCommand->append($command, false);
$dockerCommand->executeInteractive();
} else {
- $this->output->writeln('No docker-compose.yml found in tree');
+ $this->output->writeln('No docker-compose.yml found in tree');
return 1;
}
@@ -137,16 +148,20 @@ protected function executeDockerExec($containerName, CommandBuilderInterface $co
* @return int|null|void
*/
protected function executeDockerCompose(CommandBuilderInterface $command = null) {
+ // Search updir for docker-compose.yml
$path = \CliTools\Utility\DockerUtility::searchDockerDirectoryRecursive();
if (!empty($path)) {
+ $path = dirname($path);
$this->output->writeln('Found docker directory: ' . $path . '');
+
+ // Switch to directory of docker-compose.yml
PhpUtility::chdir($path);
$command->setCommand('docker-compose');
$command->executeInteractive();
} else {
- $this->output->writeln('No docker-compose.yml found in tree');
+ $this->output->writeln('No docker-compose.yml found in tree');
return 1;
}
@@ -163,9 +178,11 @@ protected function executeDockerCompose(CommandBuilderInterface $command = null)
* @return int|null|void
*/
protected function executeDockerComposeRun($containerName, CommandBuilderInterface $command) {
+ // Search updir for docker-compose.yml
$path = $this->getDockerPath();
if (!empty($path)) {
+ // Switch to directory of docker-compose.yml
PhpUtility::chdir($path);
$this->output->writeln('Executing "' . $command->getCommand() . '" in docker container "' . $containerName . '" ...');
@@ -174,7 +191,7 @@ protected function executeDockerComposeRun($containerName, CommandBuilderInterfa
$dockerCommand->append($command, false);
$dockerCommand->executeInteractive();
} else {
- $this->output->writeln('No docker-compose.yml found in tree');
+ $this->output->writeln('No docker-compose.yml found in tree');
return 1;
}
diff --git a/src/app/CliTools/Console/Command/Docker/CliCommand.php b/src/app/CliTools/Console/Command/Docker/CliCommand.php
index 79f455b..38f1844 100644
--- a/src/app/CliTools/Console/Command/Docker/CliCommand.php
+++ b/src/app/CliTools/Console/Command/Docker/CliCommand.php
@@ -20,9 +20,9 @@
* along with this program. If not, see .
*/
+use CliTools\Shell\CommandBuilder\RemoteCommandBuilder;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\RemoteCommandBuilder;
class CliCommand extends AbstractCommand implements \CliTools\Console\Filter\AnyParameterFilterInterface {
@@ -30,8 +30,9 @@ class CliCommand extends AbstractCommand implements \CliTools\Console\Filter\Any
* Configure command
*/
protected function configure() {
- $this->setName('docker:cli')
- ->setDescription('Run cli command in docker container (defined by CLI_SCRIPT and CLI_USER as docker environment variable)');
+ $this
+ ->setName('docker:cli')
+ ->setDescription('Run cli command in docker container (defined by CLI_SCRIPT and CLI_USER as docker environment variable)');
}
/**
@@ -58,7 +59,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
$cliUser = $this->getDockerEnv($container, 'CLI_USER');
if (empty($cliScript)) {
- $output->writeln('Docker container "' . $container . '" doesn\'t have environment variable "CLI_SCRIPT"');
+ $output->writeln('Docker container "' . $container . '" doesn\'t have environment variable "CLI_SCRIPT"');
return 1;
}
@@ -88,7 +89,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
break;
default:
- $output->writeln('CliMethod "' . $cliMethod .'" not defined');
+ $output->writeln('CliMethod "' . $cliMethod .'" not defined');
$ret = 1;
break;
}
diff --git a/src/app/CliTools/Console/Command/Docker/ComposeCommand.php b/src/app/CliTools/Console/Command/Docker/ComposeCommand.php
index 6f9c88b..9d1d716 100644
--- a/src/app/CliTools/Console/Command/Docker/ComposeCommand.php
+++ b/src/app/CliTools/Console/Command/Docker/ComposeCommand.php
@@ -22,7 +22,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
class ComposeCommand extends AbstractCommand implements \CliTools\Console\Filter\AnyParameterFilterInterface {
@@ -30,8 +30,9 @@ class ComposeCommand extends AbstractCommand implements \CliTools\Console\Filter
* Configure command
*/
protected function configure() {
- $this->setName('docker:compose')
- ->setDescription('Run general docker-compose command in docker container');
+ $this
+ ->setName('docker:compose')
+ ->setDescription('Run general docker-compose command in docker container');
}
/**
@@ -51,6 +52,8 @@ public function execute(InputInterface $input, OutputInterface $output) {
$command->setArgumentList($paramList);
}
+ $this->setTerminalTitle('docker-compose', $paramList);
+
$ret = $this->executeDockerCompose($command);
return $ret;
diff --git a/src/app/CliTools/Console/Command/Docker/CreateCommand.php b/src/app/CliTools/Console/Command/Docker/CreateCommand.php
index aee7af4..0dc5757 100644
--- a/src/app/CliTools/Console/Command/Docker/CreateCommand.php
+++ b/src/app/CliTools/Console/Command/Docker/CreateCommand.php
@@ -20,9 +20,10 @@
* along with this program. If not, see .
*/
-use CliTools\Console\Builder\CommandBuilder;
-use CliTools\Console\Builder\SelfCommandBuilder;
use CliTools\Utility\PhpUtility;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\SelfCommandBuilder;
+use CliTools\Shell\CommandBuilder\EditorCommandBuilder;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
@@ -34,7 +35,8 @@ class CreateCommand extends AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('docker:create')
+ $this
+ ->setName('docker:create')
->setDescription('Create new docker boilerplate')
->addArgument(
'path',
@@ -52,6 +54,12 @@ protected function configure() {
'c',
InputOption::VALUE_REQUIRED,
'Code repository'
+ )
+ ->addOption(
+ 'make',
+ 'm',
+ InputOption::VALUE_REQUIRED,
+ 'Makefile command'
);
}
@@ -64,6 +72,8 @@ protected function configure() {
* @return int|null|void
*/
public function execute(InputInterface $input, OutputInterface $output) {
+ $currDir = getcwd();
+
$path = $input->getArgument('path');
if ($this->input->getOption('docker')) {
@@ -74,20 +84,73 @@ public function execute(InputInterface $input, OutputInterface $output) {
$boilerplateRepo = $this->getApplication()->getConfigValue('docker', 'boilerplate');
}
+ $output->writeln('Creating new docker boilerplate instance in "' . $path . '"
');
+
// Init docker boilerplate
$this->createDockerInstance($path, $boilerplateRepo);
+ PhpUtility::chdir($currDir);
// Init code
if ($this->input->getOption('code')) {
+
+ $output->writeln('Init code repository
');
$this->initCode($path, $input->getOption('code'));
+ PhpUtility::chdir($currDir);
+
+ $output->writeln('Init document root
');
+ // detect document root
+ $this->initDocumentRoot($path);
+ PhpUtility::chdir($currDir);
+
+ // Run makefile
+ if ($this->input->getOption('make')) {
+ try {
+ $output->writeln('Run Makefile
');
+ $this->runMakefile($path, $input->getOption('make'));
+ PhpUtility::chdir($currDir);
+ } catch (\Exception $e) {
+ $this->addFinishMessage('Make command failed: ' . $e->getMessage() . '');
+ }
+ }
}
+ // Start interactive editor
+ $this->startInteractiveEditor($path . '/docker-compose.yml');
+ $this->startInteractiveEditor($path . '/docker-env.yml');
+
// Start docker
+ $output->writeln('Build and start docker containers
');
+ PhpUtility::chdir($currDir);
$this->startDockerInstance($path);
return 0;
}
+ /**
+ * Start interactive editor
+ *
+ * @param string $path Path to file
+ */
+ protected function startInteractiveEditor($path) {
+ if (file_exists($path)) {
+ // Start editor with file (if $EDITOR is set)
+ try {
+ $editor = new EditorCommandBuilder();
+
+ $this->setTerminalTitle('Edit', basename($path));
+
+ $this->output->writeln('Starting interactive EDITOR for file ' .$path . '
');
+ sleep(1);
+
+ $editor
+ ->addArgument($path)
+ ->executeInteractive();
+ } catch (\Exception $e) {
+ $this->addFinishMessage('' . $e->getMessage() . '');
+ }
+ }
+ }
+
/**
* Create docker instance from git repository
*
@@ -95,7 +158,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
* @param string $repo Repository
*/
protected function createDockerInstance($path, $repo) {
- $this->output->writeln('Create new docker boilerplate in "' . $path . '"');
+ $this->setTerminalTitle('Cloning docker');
$command = new CommandBuilder('git','clone --branch=master --recursive %s %s', array($repo, $path));
$command->executeInteractive();
@@ -108,6 +171,8 @@ protected function createDockerInstance($path, $repo) {
* @param string $repo Repository
*/
protected function initCode($path, $repo) {
+ $this->setTerminalTitle('Cloning code');
+
$path .= '/code';
$this->output->writeln('Initialize new code instance in "' . $path . '"');
@@ -120,7 +185,8 @@ protected function initCode($path, $repo) {
// Remove code directory
$command = new CommandBuilder('rmdir');
- $command->addArgumentSeparator()
+ $command
+ ->addArgumentSeparator()
->addArgument($path)
->executeInteractive();
}
@@ -129,12 +195,77 @@ protected function initCode($path, $repo) {
$command->executeInteractive();
}
+
+ /**
+ * Create docker instance from git repository
+ *
+ * @param string $path Path
+ */
+ protected function initDocumentRoot($path) {
+ $codePath = $path . '/code';
+ $dockerEnvFile = $path . '/docker-env.yml';
+
+ $documentRoot = null;
+
+ // try to detect document root
+ if (is_dir($codePath . '/html')) {
+ $documentRoot = 'code/html';
+ } elseif (is_dir($codePath . '/htdocs')) {
+ $documentRoot = 'code/htdocs';
+ } elseif (is_dir($codePath . '/Web')) {
+ $documentRoot = 'code/Web';
+ } elseif (is_dir($codePath . '/web')) {
+ $documentRoot = 'code/web';
+ }
+
+ if ($documentRoot && is_file($dockerEnvFile) ) {
+ $dockerEnv = PhpUtility::fileGetContentsArray($dockerEnvFile);
+
+ unset($line);
+ foreach ($dockerEnv as &$line) {
+ $line = preg_replace('/^[\s]*DOCUMENT_ROOT[\s]*=code\/?[\s]*$/ms', 'DOCUMENT_ROOT=' . $documentRoot, $line);
+ }
+ unset($line);
+
+ $dockerEnv = implode("\n", $dockerEnv);
+
+ PhpUtility::filePutContents($dockerEnvFile, $dockerEnv);
+ }
+ }
+
+ /**
+ * Run make task
+ *
+ * @param string $path Path of code
+ * @param string $makeCommand Makefile command
+ */
+ protected function runMakefile($path, $makeCommand) {
+ $this->setTerminalTitle('Run make');
+
+ $path .= '/code';
+
+ $this->output->writeln('Running make with command "' . $makeCommand . '"');
+ try {
+ PhpUtility::chdir($path);
+
+ // Remove code directory
+ $command = new CommandBuilder('make');
+ $command
+ ->addArgument($makeCommand)
+ ->executeInteractive();
+ } catch (\Exception $e) {
+ $this->addFinishMessage('Make command failed: ' . $e->getMessage() . '');
+ }
+ }
+
/**
* Build and startup docker instance
*
* @param string $path Path
*/
protected function startDockerInstance($path) {
+ $this->setTerminalTitle('Start docker');
+
$this->output->writeln('Building docker containers "' . $path . '"');
PhpUtility::chdir($path);
@@ -143,4 +274,5 @@ protected function startDockerInstance($path) {
$command->addArgument('docker:up');
$command->executeInteractive();
}
+
}
diff --git a/src/app/CliTools/Console/Command/Docker/ExecCommand.php b/src/app/CliTools/Console/Command/Docker/ExecCommand.php
index f6547f6..19a7a1f 100644
--- a/src/app/CliTools/Console/Command/Docker/ExecCommand.php
+++ b/src/app/CliTools/Console/Command/Docker/ExecCommand.php
@@ -22,7 +22,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\RemoteCommandBuilder;
+use CliTools\Shell\CommandBuilder\RemoteCommandBuilder;
class ExecCommand extends AbstractCommand implements \CliTools\Console\Filter\AnyParameterFilterInterface {
@@ -30,7 +30,8 @@ class ExecCommand extends AbstractCommand implements \CliTools\Console\Filter\An
* Configure command
*/
protected function configure() {
- $this->setName('docker:exec')
+ $this
+ ->setName('docker:exec')
->setDescription('Run defined command in docker container');
}
@@ -46,6 +47,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
$paramList = $this->getFullParameterList();
$container = $this->getApplication()->getConfigValue('docker', 'container');
+
if (!empty($paramList)) {
$firstParam = array_shift($paramList);
@@ -53,7 +55,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
$ret = $this->executeDockerExec($container, $command);
} else {
- $output->writeln('No command/parameter specified');
+ $output->writeln('No command/parameter specified');
$ret = 1;
}
diff --git a/src/app/CliTools/Console/Command/Docker/IftopCommand.php b/src/app/CliTools/Console/Command/Docker/IftopCommand.php
index 0c260db..6c9da42 100644
--- a/src/app/CliTools/Console/Command/Docker/IftopCommand.php
+++ b/src/app/CliTools/Console/Command/Docker/IftopCommand.php
@@ -22,7 +22,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
class IftopCommand extends \CliTools\Console\Command\AbstractCommand {
@@ -30,8 +30,9 @@ class IftopCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('docker:iftop')
- ->setDescription('Exec iftop for Docker');
+ $this
+ ->setName('docker:iftop')
+ ->setDescription('Exec iftop for Docker');
}
/**
diff --git a/src/app/CliTools/Console/Command/Docker/MysqlCommand.php b/src/app/CliTools/Console/Command/Docker/MysqlCommand.php
index 4ba4fcf..85fe6e0 100644
--- a/src/app/CliTools/Console/Command/Docker/MysqlCommand.php
+++ b/src/app/CliTools/Console/Command/Docker/MysqlCommand.php
@@ -22,7 +22,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\RemoteCommandBuilder;
+use CliTools\Shell\CommandBuilder\RemoteCommandBuilder;
class MysqlCommand extends AbstractCommand {
@@ -30,7 +30,8 @@ class MysqlCommand extends AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('docker:mysql')
+ $this
+ ->setName('docker:mysql')
->setDescription('Enter mysql in docker container');
}
diff --git a/src/app/CliTools/Console/Command/Docker/RootCommand.php b/src/app/CliTools/Console/Command/Docker/RootCommand.php
index a6ab553..de8fdf8 100644
--- a/src/app/CliTools/Console/Command/Docker/RootCommand.php
+++ b/src/app/CliTools/Console/Command/Docker/RootCommand.php
@@ -23,7 +23,7 @@
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\RemoteCommandBuilder;
+use CliTools\Shell\CommandBuilder\RemoteCommandBuilder;
class RootCommand extends AbstractCommand {
@@ -31,7 +31,8 @@ class RootCommand extends AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('docker:root')
+ $this
+ ->setName('docker:root')
->setDescription('Enter shell as root in docker container')
->addArgument(
'container',
@@ -55,6 +56,8 @@ public function execute(InputInterface $input, OutputInterface $output) {
$container = $input->getArgument('container');
}
+ $this->setTerminalTitle('docker', 'root', $container);
+
$command = new RemoteCommandBuilder('bash');
$ret = $this->executeDockerExec($container, $command);
diff --git a/src/app/CliTools/Console/Command/Docker/ShellCommand.php b/src/app/CliTools/Console/Command/Docker/ShellCommand.php
index 73c6963..575285c 100644
--- a/src/app/CliTools/Console/Command/Docker/ShellCommand.php
+++ b/src/app/CliTools/Console/Command/Docker/ShellCommand.php
@@ -24,7 +24,7 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\RemoteCommandBuilder;
+use CliTools\Shell\CommandBuilder\RemoteCommandBuilder;
class ShellCommand extends AbstractCommand {
@@ -32,7 +32,8 @@ class ShellCommand extends AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('docker:shell')
+ $this
+ ->setName('docker:shell')
->setDescription('Enter shell in docker container')
->addArgument(
'container',
@@ -70,6 +71,8 @@ public function execute(InputInterface $input, OutputInterface $output) {
$cliUser = $this->getDockerEnv($container, 'CLI_USER');
}
+ $this->setTerminalTitle('docker', 'shell', $container);
+
$command = new RemoteCommandBuilder('bash');
if (!empty($cliUser)) {
diff --git a/src/app/CliTools/Console/Command/Docker/SniffCommand.php b/src/app/CliTools/Console/Command/Docker/SniffCommand.php
index d297f42..81129c4 100644
--- a/src/app/CliTools/Console/Command/Docker/SniffCommand.php
+++ b/src/app/CliTools/Console/Command/Docker/SniffCommand.php
@@ -21,10 +21,11 @@
*/
use Symfony\Component\Console\Input\InputArgument;
-use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Helper\QuestionHelper;
+use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
class SniffCommand extends AbstractCommand {
@@ -32,18 +33,13 @@ class SniffCommand extends AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('docker:sniff')
+ $this
+ ->setName('docker:sniff')
->setDescription('Start network sniffing with docker')
->addArgument(
'protocol',
- InputArgument::REQUIRED,
+ InputArgument::OPTIONAL,
'Protocol'
- )
- ->addOption(
- 'full',
- null,
- InputOption::VALUE_NONE,
- 'Show full output (if supported by protocol)'
);
}
@@ -60,8 +56,9 @@ public function execute(InputInterface $input, OutputInterface $output) {
$dockerInterface = $this->getApplication()->getConfigValue('docker', 'interface');
- $protocol = $input->getArgument('protocol');
- $fullOutput = $input->getOption('full');
+ $output->writeln('Starting network sniffing
');
+
+ $protocol = $this->getProtocol();
$command = new CommandBuilder();
@@ -74,6 +71,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// ARP
// ##############
case 'arp':
+ $output->writeln('Using protocol "arp"
');
$command->setCommand('tshark');
$command->addArgument('arp');
break;
@@ -86,6 +84,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// ICMP
// ##############
case 'icmp':
+ $output->writeln('Using protocol "icmp"
');
$command->setCommand('tshark');
$command->addArgument('icmp');
break;
@@ -99,6 +98,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// ##############
case 'con':
case 'tcp':
+ $output->writeln('Using protocol "tcp"
');
$command->setCommand('tshark');
$command->addArgumentRaw('-R "tcp.flags.syn==1 && tcp.flags.ack==0"');
break;
@@ -111,19 +111,25 @@ public function execute(InputInterface $input, OutputInterface $output) {
// HTTP
// ##############
case 'http':
+ $output->writeln('Using protocol "http"
');
$command->setCommand('tshark');
+ $command->addArgumentRaw('tcp port 80 or tcp port 443 -2 -V -R "http.request" -Tfields -e ip.dst -e http.request.method -e http.request.full_uri');
+ break;
- if ($fullOutput) {
- $command->addArgumentRaw('tcp port 80 or tcp port 443 -2 -V -R "http.request || http.response"');
- } else {
- $command->addArgumentRaw('tcp port 80 or tcp port 443 -2 -V -R "http.request" -Tfields -e ip.dst -e http.request.method -e http.request.full_uri');
- }
+ // ##############
+ // HTTP (full)
+ // ##############
+ case 'http-full':
+ $output->writeln('Using protocol "http" (full mode)
');
+ $command->setCommand('tshark');
+ $command->addArgumentRaw('tcp port 80 or tcp port 443 -2 -V -R "http.request || http.response"');
break;
// ##############
// SOLR
// ##############
case 'solr':
+ $output->writeln('Using protocol "solr"
');
$command->setCommand('tcpdump');
$command->addArgumentRaw('-nl -s0 -w- port 8983');
@@ -136,6 +142,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// ELASTICSEARCH
// ##############
case 'elasticsearch':
+ $output->writeln('Using protocol "elasticsearch"
');
$command->setCommand('tcpdump');
$command->addArgumentRaw('-A -nn -s 0 \'tcp dst port 9200 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)\'');
break;
@@ -145,6 +152,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// ##############
case 'memcache':
case 'memcached':
+ $output->writeln('Using protocol "memcache"
');
$command->setCommand('tcpdump');
$command->addArgumentRaw('-s 65535 -A -ttt port 11211| cut -c 9- | grep -i \'^get\|set\'');
break;
@@ -153,6 +161,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// REDIS
// ##############
case 'redis':
+ $output->writeln('Using protocol "redis"
');
$command->setCommand('tcpdump');
$command->addArgumentRaw('-s 65535 tcp port 6379');
break;
@@ -162,6 +171,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// ##############
case 'smtp':
case 'mail':
+ $output->writeln('Using protocol "smtp"
');
$command->setCommand('tshark');
$command->addArgumentRaw('tcp -f "port 25" -R "smtp"');
break;
@@ -170,6 +180,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// MYSQL
// ##############
case 'mysql':
+ $output->writeln('Using protocol "mysql"
');
$command->setCommand('tshark');
$command->addArgumentRaw('tcp -d tcp.port==3306,mysql -T fields -e mysql.query "port 3306"');
break;
@@ -178,6 +189,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// DNS
// ##############
case 'dns':
+ $output->writeln('Using protocol "dns"
');
$command->setCommand('tshark');
$command->addArgumentRaw('-nn -e ip.src -e dns.qry.name -E separator=" " -T fields port 53');
break;
@@ -186,31 +198,80 @@ public function execute(InputInterface $input, OutputInterface $output) {
// HELP
// ##############
default:
- $output->writeln('Protocol not supported:');
- $output->writeln(' OSI layer 7: http, solr, elasticsearch, memcache, redis, smtp, mysql, dns');
- $output->writeln(' OSI layer 4: tcp');
- $output->writeln(' OSI layer 3: icmp');
- $output->writeln(' OSI layer 2: arp');
+ $output->writeln('Protocol not supported:');
+ $output->writeln(' OSI layer 7: http, solr, elasticsearch, memcache, redis, smtp, mysql, dns');
+ $output->writeln(' OSI layer 4: tcp');
+ $output->writeln(' OSI layer 3: icmp');
+ $output->writeln(' OSI layer 2: arp');
return 1;
break;
}
switch ($command->getCommand()) {
case 'tshark':
+ $output->writeln('Using sniffer "tshark"
');
$command->addArgumentTemplate('-i %s', $dockerInterface);
break;
case 'tcpdump':
+ $output->writeln('Using sniffer "tcpdump"
');
$command->addArgumentTemplate('-i %s', $dockerInterface);
break;
case 'ngrep':
+ $output->writeln('Using sniffer "ngrep"
');
$command->addArgumentTemplate('-d %s', $dockerInterface);
break;
}
+ $this->setTerminalTitle('sniffer', $protocol, '(' . $command->getCommand() .')');
+
$command->executeInteractive();
return 0;
}
+
+
+ /**
+ * Get protocol
+ *
+ * @return string
+ */
+ protected function getProtocol() {
+ $ret = null;
+
+ if(!$this->input->getArgument('protocol')) {
+ $protocolList = array(
+ 'http' => 'HTTP (requests only)',
+ 'http-full' => 'HTTP (full)',
+ 'solr' => 'Solr',
+ 'elasticsearch' => 'Elasticsearch',
+ 'memcache' => 'Memcache',
+ 'redis' => 'Redis',
+ 'smtp' => 'SMTP',
+ 'mysql' => 'MySQL queries',
+ 'dns' => 'DNS',
+ 'tcp' => 'TCP',
+ 'icmp' => 'ICMP',
+ 'arp' => 'ARP',
+ );
+
+ try {
+ $question = new ChoiceQuestion('Please choose network protocol for sniffing', $protocolList);
+ $question->setMaxAttempts(1);
+
+ $questionDialog = new QuestionHelper();
+
+ $ret = $questionDialog->ask($this->input, $this->output, $question);
+ } catch(\InvalidArgumentException $e) {
+ // Invalid server context, just stop here
+ throw new \CliTools\Exception\StopException(1);
+ }
+ } else {
+ $ret = $this->input->getArgument('protocol');
+ }
+
+
+ return $ret;
+ }
}
diff --git a/src/app/CliTools/Console/Command/Docker/UpCommand.php b/src/app/CliTools/Console/Command/Docker/UpCommand.php
index 893ab59..b558adb 100644
--- a/src/app/CliTools/Console/Command/Docker/UpCommand.php
+++ b/src/app/CliTools/Console/Command/Docker/UpCommand.php
@@ -22,7 +22,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
class UpCommand extends AbstractCommand {
@@ -30,7 +30,8 @@ class UpCommand extends AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('docker:up')
+ $this
+ ->setName('docker:up')
->setDescription('Start docker container (with fast switching)');
}
@@ -44,9 +45,15 @@ protected function configure() {
*/
public function execute(InputInterface $input, OutputInterface $output) {
- $dockerPath = \CliTools\Utility\DockerUtility::searchDockerDirectoryRecursive();
+ $dockerPath = \CliTools\Utility\DockerUtility::searchDockerDirectoryRecursive();
$lastDockerPath = $this->getApplication()->getSettingsService()->get('docker.up.last');
+ if (!empty($dockerPath)) {
+ $dockerPath = dirname($dockerPath);
+ }
+
+ $output->writeln('Starting docker containers
');
+
// Stop last docker instance
if ($dockerPath && $lastDockerPath) {
// Only stop if instance is another one
@@ -56,6 +63,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
}
// Start current docker containers
+ $this->output->writeln('Start docker containers in "' . $dockerPath . '"
');
$command = new CommandBuilder(null, 'up -d');
$ret = $this->executeDockerCompose($command);
@@ -76,7 +84,7 @@ protected function stopContainersFromPrevRun($path) {
$currentPath = getcwd();
try {
- $this->output->writeln('Trying to stop last running docker container in "' . $path . '"');
+ $this->output->writeln('Trying to stop last running docker container in "' . $path . '"
');
// Jump into last docker dir
\CliTools\Utility\PhpUtility::chdir($path);
diff --git a/src/app/CliTools/Console/Command/Log/ApacheCommand.php b/src/app/CliTools/Console/Command/Log/ApacheCommand.php
index cf4afa8..55aba20 100644
--- a/src/app/CliTools/Console/Command/Log/ApacheCommand.php
+++ b/src/app/CliTools/Console/Command/Log/ApacheCommand.php
@@ -30,7 +30,8 @@ class ApacheCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('log:apache')
+ $this
+ ->setName('log:apache')
->setAliases(array('apache:log'))
->setDescription('Show up apache log')
->addArgument(
@@ -55,6 +56,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
$grep = $input->getArgument('grep');
}
+ $output->writeln('Starting apache log tail
');
// Show log
$logList = array(
diff --git a/src/app/CliTools/Console/Command/Log/DebugCommand.php b/src/app/CliTools/Console/Command/Log/DebugCommand.php
index 20f4aeb..67ed98a 100644
--- a/src/app/CliTools/Console/Command/Log/DebugCommand.php
+++ b/src/app/CliTools/Console/Command/Log/DebugCommand.php
@@ -30,7 +30,8 @@ class DebugCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('log:debug')
+ $this
+ ->setName('log:debug')
->setAliases(array('debug'))
->setDescription('Show up debugging log')
->addArgument(
@@ -55,6 +56,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
$grep = $input->getArgument('grep');
}
+ $output->writeln('Starting debug log tail
');
// Show log
$logList = array(
diff --git a/src/app/CliTools/Console/Command/Log/MailCommand.php b/src/app/CliTools/Console/Command/Log/MailCommand.php
index 31b699f..778c8d8 100644
--- a/src/app/CliTools/Console/Command/Log/MailCommand.php
+++ b/src/app/CliTools/Console/Command/Log/MailCommand.php
@@ -30,7 +30,8 @@ class MailCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('log:mail')
+ $this
+ ->setName('log:mail')
->setDescription('Show up mail log')
->addArgument(
'grep',
@@ -54,6 +55,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
$grep = $input->getArgument('grep');
}
+ $output->writeln('Starting mail log tail
');
// Show log
$logList = array(
diff --git a/src/app/CliTools/Console/Command/Log/PhpCommand.php b/src/app/CliTools/Console/Command/Log/PhpCommand.php
index 48536c3..425d233 100644
--- a/src/app/CliTools/Console/Command/Log/PhpCommand.php
+++ b/src/app/CliTools/Console/Command/Log/PhpCommand.php
@@ -30,7 +30,8 @@ class PhpCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('log:php')
+ $this
+ ->setName('log:php')
->setAliases(array('php:log'))
->setDescription('Show up php log')
->addArgument(
@@ -55,6 +56,8 @@ public function execute(InputInterface $input, OutputInterface $output) {
$grep = $input->getArgument('grep');
}
+ $output->writeln('Starting php log tail
');
+
// Show log
$logList = array(
'/var/log/php-fpm/dev.error.log',
diff --git a/src/app/CliTools/Console/Command/Mysql/AbstractCommand.php b/src/app/CliTools/Console/Command/Mysql/AbstractCommand.php
new file mode 100644
index 0000000..3389d8b
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Mysql/AbstractCommand.php
@@ -0,0 +1,100 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+use CliTools\Database\DatabaseConnection;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+abstract class AbstractCommand extends \CliTools\Console\Command\AbstractCommand {
+
+ /**
+ * Configure command
+ */
+ protected function configure() {
+ $this
+ ->addOption(
+ 'host',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'MySQL host'
+ )
+ ->addOption(
+ 'port',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'MySQL port'
+ )
+ ->addOption(
+ 'user',
+ 'u',
+ InputOption::VALUE_REQUIRED,
+ 'MySQL user'
+ )
+ ->addOption(
+ 'password',
+ 'p',
+ InputOption::VALUE_REQUIRED,
+ 'MySQL host'
+ );
+ }
+
+ /**
+ * Initializes the command just after the input has been validated.
+ *
+ * This is mainly useful when a lot of commands extends one main command
+ * where some things need to be initialized based on the input arguments and options.
+ *
+ * @param InputInterface $input An InputInterface instance
+ * @param OutputInterface $output An OutputInterface instance
+ */
+ protected function initialize(InputInterface $input, OutputInterface $output) {
+ parent::initialize($input, $output);
+
+ $dsn = null;
+ $user = null;
+ $password = null;
+
+ if ($this->input->hasOption('host') && $this->input->getOption('host')) {
+ $host = $this->input->getOption('host');
+ $port = 3306;
+
+ if ($this->input->getOption('port')) {
+ $port = $this->input->getOption('port');
+ }
+
+ $dsn = 'mysql:host=' . urlencode($host) . ';port=' . (int)$port;
+ }
+
+ if ($this->input->hasOption('user') && $this->input->getOption('user')) {
+ $user = $this->input->getOption('user');
+ }
+
+ if ($this->input->hasOption('password') && $this->input->getOption('password')) {
+ $password = $this->input->getOption('password');
+ }
+
+ if ($user !== null || $password !== null) {
+ DatabaseConnection::setDsn($dsn, $user, $password);
+ }
+ }
+}
diff --git a/src/app/CliTools/Console/Command/Mysql/BackupCommand.php b/src/app/CliTools/Console/Command/Mysql/BackupCommand.php
index 033581e..91bae94 100644
--- a/src/app/CliTools/Console/Command/Mysql/BackupCommand.php
+++ b/src/app/CliTools/Console/Command/Mysql/BackupCommand.php
@@ -21,31 +21,36 @@
*/
use CliTools\Database\DatabaseConnection;
-use CliTools\Console\Builder\CommandBuilder;
-use CliTools\Console\Builder\CommandBuilderInterface;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilderInterface;
+use CliTools\Utility\FilterUtility;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-class BackupCommand extends \CliTools\Console\Command\AbstractCommand {
+class BackupCommand extends AbstractCommand {
/**
* Configure command
*/
protected function configure() {
- $this->setName('mysql:backup')
- ->setDescription('Backup database')
- ->addArgument(
+ parent::configure();
+
+ $this
+ ->setName('mysql:backup')
+ ->setDescription('Backup database')
+ ->addArgument(
'db',
InputArgument::REQUIRED,
'Database name'
- )
- ->addArgument(
+ )
+ ->addArgument(
'file',
InputArgument::REQUIRED,
'File (mysql dump)'
- )->addOption(
+ )
+ ->addOption(
'filter',
'f',
InputOption::VALUE_REQUIRED,
@@ -67,11 +72,13 @@ public function execute(InputInterface $input, OutputInterface $output) {
$filter = $input->getOption('filter');
if (!DatabaseConnection::databaseExists($database)) {
- $output->writeln('Database "' . $database . '" does not exists');
+ $output->writeln('Database "' . $database . '" does not exists');
return 1;
}
+ $output->writeln('Dumping database "' . $database . '" into file "' . $dumpFile . '"
');
+
$fileExt = pathinfo($dumpFile, PATHINFO_EXTENSION);
// Inserting
@@ -82,20 +89,22 @@ public function execute(InputInterface $input, OutputInterface $output) {
switch ($fileExt) {
case 'bz':
+ case 'bz2':
case 'bzip2':
- $output->writeln('Using BZIP2 compression');
+ $output->writeln('Using BZIP2 compression
');
$commandCompressor = new CommandBuilder('bzip2');
break;
case 'gz':
case 'gzip':
- $output->writeln('Using GZIP compression');
+ $output->writeln('Using GZIP compression
');
$commandCompressor = new CommandBuilder('gzip');
break;
case 'lzma':
+ case 'lz':
case 'xz':
- $output->writeln('Using LZMA compression');
+ $output->writeln('Using LZMA compression
');
$commandCompressor = new CommandBuilder('xz');
$commandCompressor->addArgument('--compress')
->addArgument('--stdout');
@@ -104,6 +113,15 @@ public function execute(InputInterface $input, OutputInterface $output) {
$command = new CommandBuilder('mysqldump','--user=%s %s --single-transaction', array(DatabaseConnection::getDbUsername(), $database));
+ // Set server connection details
+ if ($input->getOption('host')) {
+ $command->addArgumentTemplate('-h %s', $input->getOption('host'));
+ }
+
+ if ($input->getOption('port')) {
+ $command->addArgumentTemplate('-P %s', $input->getOption('port'));
+ }
+
if (!empty($filter)) {
$command = $this->addFilterArguments($command, $database, $filter);
}
@@ -112,13 +130,13 @@ public function execute(InputInterface $input, OutputInterface $output) {
$command->addPipeCommand($commandCompressor);
$commandCompressor->setOutputRedirectToFile($dumpFile);
} else {
- $output->writeln('Using no compression');
+ $output->writeln('Using no compression
');
$command->setOutputRedirectToFile($dumpFile);
}
$command->executeInteractive();
- $output->writeln('Database "' . $database . '" stored to "' . $dumpFile . '"');
+ $output->writeln('Database "' . $database . '" stored to "' . $dumpFile . '"
');
}
/**
@@ -140,18 +158,11 @@ protected function addFilterArguments(CommandBuilderInterface $commandDump, $dat
throw new \RuntimeException('MySQL dump filters "' . $filter . '" not available"');
}
+ $this->output->writeln('Using filter "' . $filter . '"');
+
// Get filtered tables
- $tableList = DatabaseConnection::tableList($database);
-
- $tableListFiltered = array();
- foreach ($tableList as $table) {
- foreach ($filterList as $filter) {
- if (preg_match($filter, $table)) {
- continue 2;
- }
- }
- $tableListFiltered[] = $table;
- }
+ $tableList = DatabaseConnection::tableList($database);
+ $ignoredTableList = FilterUtility::mysqlIgnoredTableFilter($tableList, $filterList, $database);
// Dump only structure
$commandStructure = clone $command;
@@ -159,12 +170,14 @@ protected function addFilterArguments(CommandBuilderInterface $commandDump, $dat
// Dump only data (only filtered tables)
$commandData = clone $command;
- $commandData
- ->addArgument('--no-create-info')
- ->addArgumentList($tableListFiltered);
+ $commandData->addArgument('--no-create-info');
+
+ if (!empty($ignoredTableList)) {
+ $commandData->addArgumentTemplateMultiple('--ignore-table=%s', $ignoredTableList);
+ }
// Combine both commands to one
- $command = new \CliTools\Console\Builder\OutputCombineCommandBuilder();
+ $command = new \CliTools\Shell\CommandBuilder\OutputCombineCommandBuilder();
$command
->addCommandForCombinedOutput($commandStructure)
->addCommandForCombinedOutput($commandData);
diff --git a/src/app/CliTools/Console/Command/Mysql/ClearCommand.php b/src/app/CliTools/Console/Command/Mysql/ClearCommand.php
index bda8cff..c4db1c2 100644
--- a/src/app/CliTools/Console/Command/Mysql/ClearCommand.php
+++ b/src/app/CliTools/Console/Command/Mysql/ClearCommand.php
@@ -25,13 +25,16 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-class ClearCommand extends \CliTools\Console\Command\AbstractCommand {
+class ClearCommand extends AbstractCommand {
/**
* Configure command
*/
protected function configure() {
- $this->setName('mysql:clear')
+ parent::configure();
+
+ $this
+ ->setName('mysql:clear')
->setAliases(array('mysql:create'))
->setDescription('Clear (recreate) database')
->addArgument(
@@ -52,15 +55,19 @@ protected function configure() {
public function execute(InputInterface $input, OutputInterface $output) {
$database = $input->getArgument('db');
- $output->writeln('Dropping Database "' . $database . '"...');
- $query = 'DROP DATABASE IF EXISTS ' . DatabaseConnection::sanitizeSqlDatabase($database);
- DatabaseConnection::exec($query);
+ $output->writeln('Clearing database "' . $database . '"
');
+
+ if (DatabaseConnection::databaseExists($database)) {
+ $output->writeln('Dropping database
');
+ $query = 'DROP DATABASE ' . DatabaseConnection::sanitizeSqlDatabase($database);
+ DatabaseConnection::exec($query);
+ }
- $output->writeln('Creating Database "' . $database . '"...');
+ $output->writeln('Creating database
');
$query = 'CREATE DATABASE ' . DatabaseConnection::sanitizeSqlDatabase($database);
DatabaseConnection::exec($query);
- $output->writeln('Database "' . $database . '" dropped and recreated');
+ $output->writeln('Database "' . $database . '" recreated
');
return 0;
}
diff --git a/src/app/CliTools/Console/Command/Mysql/ConnectionsCommand.php b/src/app/CliTools/Console/Command/Mysql/ConnectionsCommand.php
index af13c23..318f241 100644
--- a/src/app/CliTools/Console/Command/Mysql/ConnectionsCommand.php
+++ b/src/app/CliTools/Console/Command/Mysql/ConnectionsCommand.php
@@ -25,13 +25,16 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-class ConnectionsCommand extends \CliTools\Console\Command\AbstractCommand {
+class ConnectionsCommand extends AbstractCommand {
/**
* Configure command
*/
protected function configure() {
- $this->setName('mysql:connections')
+ parent::configure();
+
+ $this
+ ->setName('mysql:connections')
->setDescription('List current connections');
}
diff --git a/src/app/CliTools/Console/Command/Mysql/ConvertCommand.php b/src/app/CliTools/Console/Command/Mysql/ConvertCommand.php
new file mode 100644
index 0000000..9594872
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Mysql/ConvertCommand.php
@@ -0,0 +1,139 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+use CliTools\Database\DatabaseConnection;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ConvertCommand extends AbstractCommand {
+
+ /**
+ * Configure command
+ */
+ protected function configure() {
+ parent::configure();
+
+ $this
+ ->setName('mysql:convert')
+ ->setDescription('Convert charset/collation of a database')
+ ->addArgument(
+ 'database',
+ InputArgument::REQUIRED,
+ 'Database name'
+ )
+ ->addOption(
+ 'charset',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Charset (default: utf8)'
+ )
+ ->addOption(
+ 'collation',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Collation (default: utf8_general_ci)'
+ )
+ ->addOption(
+ 'stdout',
+ null,
+ InputOption::VALUE_NONE,
+ 'Only print sql statements, do not execute it'
+ );
+ }
+
+ /**
+ * Execute command
+ *
+ * @param InputInterface $input Input instance
+ * @param OutputInterface $output Output instance
+ *
+ * @return int|null|void
+ */
+ public function execute(InputInterface $input, OutputInterface $output) {
+ $charset = 'utf8';
+ $collation = 'utf8_general_ci';
+ $stdout = false;
+
+ $database = $input->getArgument('database');
+
+ if ($input->getOption('charset')) {
+ $charset = (string)$input->getOption('charset');
+ }
+
+ if ($input->getOption('collation')) {
+ $collation = (string)$input->getOption('collation');
+ }
+
+ if ($input->getOption('stdout')) {
+ $stdout = true;
+ }
+
+ // ##################
+ // Alter database
+ // ##################
+
+ $query = 'ALTER DATABASE %s CHARACTER SET %s COLLATE %s';
+ $query = sprintf($query,
+ DatabaseConnection::sanitizeSqlDatabase($database),
+ DatabaseConnection::quote($charset),
+ DatabaseConnection::quote($collation)
+ );
+
+ if (!$stdout) {
+ // Execute
+ $output->writeln('Converting database ' . $database . '
');
+ DatabaseConnection::exec($query);
+ } else {
+ // Show only
+ $output->writeln($query . ';');
+ }
+
+ // ##################
+ // Alter tables
+ // ##################
+ $tableList = DatabaseConnection::tableList($database);
+
+ foreach ($tableList as $table) {
+ // Build statement
+ $query = 'ALTER TABLE %s.%s CONVERT TO CHARACTER SET %s COLLATE %s';
+ $query = sprintf($query,
+ DatabaseConnection::sanitizeSqlDatabase($database),
+ DatabaseConnection::sanitizeSqlTable($table),
+ DatabaseConnection::quote($charset),
+ DatabaseConnection::quote($collation)
+ );
+
+ if (!$stdout) {
+ // Execute
+ $output->writeln('Converting table ' . $table . '
');
+ DatabaseConnection::exec($query);
+ } else {
+ // Show only
+ $output->writeln($query . ';');
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/src/app/CliTools/Console/Command/Mysql/DebugCommand.php b/src/app/CliTools/Console/Command/Mysql/DebugCommand.php
index edb162a..e72f217 100644
--- a/src/app/CliTools/Console/Command/Mysql/DebugCommand.php
+++ b/src/app/CliTools/Console/Command/Mysql/DebugCommand.php
@@ -25,13 +25,14 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-class DebugCommand extends \CliTools\Console\Command\AbstractCommand {
+class DebugCommand extends AbstractCommand {
/**
* Configure command
*/
protected function configure() {
- $this->setName('mysql:debug')
+ $this
+ ->setName('mysql:debug')
->setAliases(array('mysql:querylog'))
->setDescription('Debug mysql connections')
->addArgument(
@@ -55,11 +56,13 @@ public function execute(InputInterface $input, OutputInterface $output) {
$debugLogLocation = $this->getApplication()->getConfigValue('db', 'debug_log_dir');
$debugLogDir = dirname($debugLogLocation);
+ $output->writeln('Starting MySQL general query log
');
+
// Create directory if not exists
if (!is_dir($debugLogDir)) {
if (!mkdir($debugLogDir, 0777, true)) {
- $output->writeln('Could not create "' . $debugLogDir . '" directory');
- exit(1);
+ $output->writeln('Could not create "' . $debugLogDir . '" directory');
+ throw new \CliTools\Exception\StopException(1);
}
}
@@ -77,14 +80,14 @@ public function execute(InputInterface $input, OutputInterface $output) {
if (!empty($logFileRow['Value'])) {
// Enable general log
- $output->writeln('Enabling general log');
+ $output->writeln('Enabling general log
');
$query = 'SET GLOBAL general_log = \'ON\'';
DatabaseConnection::exec($query);
// Setup teardown cleanup
$tearDownFunc = function () use ($output) {
// Disable general log
- $output->writeln('Disabling general log');
+ $output->writeln('Disabling general log
');
$query = 'SET GLOBAL general_log = \'OFF\'';
DatabaseConnection::exec($query);
};
@@ -109,7 +112,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
return 0;
} else {
- $output->writeln('MySQL general_log_file not set');
+ $output->writeln('MySQL general_log_file not set');
return 1;
}
diff --git a/src/app/CliTools/Console/Command/Mysql/DropCommand.php b/src/app/CliTools/Console/Command/Mysql/DropCommand.php
index d7f6af8..e2a7015 100644
--- a/src/app/CliTools/Console/Command/Mysql/DropCommand.php
+++ b/src/app/CliTools/Console/Command/Mysql/DropCommand.php
@@ -25,13 +25,16 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-class DropCommand extends \CliTools\Console\Command\AbstractCommand {
+class DropCommand extends AbstractCommand {
/**
* Configure command
*/
protected function configure() {
- $this->setName('mysql:drop')
+ parent::configure();
+
+ $this
+ ->setName('mysql:drop')
->setDescription('Drop database')
->addArgument(
'db',
@@ -51,11 +54,11 @@ protected function configure() {
public function execute(InputInterface $input, OutputInterface $output) {
$database = $input->getArgument('db');
- $output->writeln('Dropping Database "' . $database . '"...');
+ $output->writeln('Dropping Database "' . $database . '"...
');
$query = 'DROP DATABASE IF EXISTS ' . DatabaseConnection::sanitizeSqlDatabase($database);
DatabaseConnection::exec($query);
- $output->writeln('Database "' . $database . '" dropped');
+ $output->writeln('Database dropped
');
return 0;
}
diff --git a/src/app/CliTools/Console/Command/Mysql/ListCommand.php b/src/app/CliTools/Console/Command/Mysql/ListCommand.php
index f04351f..3971b0d 100644
--- a/src/app/CliTools/Console/Command/Mysql/ListCommand.php
+++ b/src/app/CliTools/Console/Command/Mysql/ListCommand.php
@@ -28,13 +28,16 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class ListCommand extends \CliTools\Console\Command\AbstractCommand {
+class ListCommand extends AbstractCommand {
/**
* Configure command
*/
protected function configure() {
- $this->setName('mysql:list')
+ parent::configure();
+
+ $this
+ ->setName('mysql:list')
->setDescription('List all databases')
->addOption(
'sort-name', null,
@@ -72,10 +75,7 @@ protected function configure() {
public function execute(InputInterface $input, OutputInterface $output) {
// Get list of databases
- $query = 'SELECT SCHEMA_NAME
- FROM information_schema.SCHEMATA';
- $databaseList = DatabaseConnection::getCol($query);
-
+ $databaseList = DatabaseConnection::databaseList();
if (!empty($databaseList)) {
// ########################
@@ -84,11 +84,6 @@ public function execute(InputInterface $input, OutputInterface $output) {
$databaseRowList = array();
foreach ($databaseList as $database) {
- // Skip internal mysql databases
- if (in_array(strtolower($database), array('mysql', 'information_schema', 'performance_schema'))) {
- continue;
- }
-
// Get all tables
$query = 'SELECT COUNT(*) AS count
FROM information_schema.tables
@@ -227,7 +222,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
$table->render();
} else {
- $output->writeln('No databases found');
+ $output->writeln('No databases found');
}
return 0;
diff --git a/src/app/CliTools/Console/Command/Mysql/RestartCommand.php b/src/app/CliTools/Console/Command/Mysql/RestartCommand.php
index bc2afd8..fa012bc 100644
--- a/src/app/CliTools/Console/Command/Mysql/RestartCommand.php
+++ b/src/app/CliTools/Console/Command/Mysql/RestartCommand.php
@@ -22,15 +22,16 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
-class RestartCommand extends \CliTools\Console\Command\AbstractCommand {
+class RestartCommand extends AbstractCommand {
/**
* Configure command
*/
protected function configure() {
- $this->setName('mysql:restart')
+ $this
+ ->setName('mysql:restart')
->setDescription('Restart MySQL');
}
diff --git a/src/app/CliTools/Console/Command/Mysql/RestoreCommand.php b/src/app/CliTools/Console/Command/Mysql/RestoreCommand.php
index ce79bfe..6758226 100644
--- a/src/app/CliTools/Console/Command/Mysql/RestoreCommand.php
+++ b/src/app/CliTools/Console/Command/Mysql/RestoreCommand.php
@@ -21,18 +21,22 @@
*/
use CliTools\Database\DatabaseConnection;
+use CliTools\Utility\PhpUtility;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
-class RestoreCommand extends \CliTools\Console\Command\AbstractCommand {
+class RestoreCommand extends AbstractCommand {
/**
* Configure command
*/
protected function configure() {
- $this->setName('mysql:restore')
+ parent::configure();
+
+ $this
+ ->setName('mysql:restore')
->setDescription('Restore database')
->addArgument(
'db',
@@ -59,73 +63,78 @@ public function execute(InputInterface $input, OutputInterface $output) {
$dumpFile = $input->getArgument('file');
if (!is_file($dumpFile) || !is_readable($dumpFile)) {
- $output->writeln('File is not readable');
+ $output->writeln('File is not readable');
return 1;
}
- // Get mime type from file
- $finfo = finfo_open(FILEINFO_MIME_TYPE);
- $dumpFileType = finfo_file($finfo, $dumpFile);
- finfo_close($finfo);
-
- if ($dumpFileType === 'application/octet-stream') {
- $finfo = finfo_open();
- $dumpFileInfo = finfo_file($finfo, $dumpFile);
- finfo_close($finfo);
+ $dumpFileType = PhpUtility::getMimeType($dumpFile);
- if (strpos($dumpFileInfo, 'LZMA compressed data') !== false) {
- $dumpFileType = 'application/x-lzma';
- }
- }
+ $output->writeln('Restoring dump "' . $dumpFile . '" into database "' . $database . '"
');
if (DatabaseConnection::databaseExists($database)) {
// Dropping
- $output->writeln('Dropping Database "' . $database . '"...');
+ $output->writeln('Dropping database
');
$query = 'DROP DATABASE IF EXISTS ' . DatabaseConnection::sanitizeSqlDatabase($database);
DatabaseConnection::exec($query);
}
// Creating
- $output->writeln('Creating Database "' . $database . '"...');
+ $output->writeln('Creating database
');
$query = 'CREATE DATABASE ' . DatabaseConnection::sanitizeSqlDatabase($database);
DatabaseConnection::exec($query);
// Inserting
- $output->writeln('Restoring dump into Database "' . $database . '"...');
putenv('USER=' . DatabaseConnection::getDbUsername());
putenv('MYSQL_PWD=' . DatabaseConnection::getDbPassword());
$commandMysql = new CommandBuilder('mysql','--user=%s %s --one-database', array(DatabaseConnection::getDbUsername(), $database));
+ // Set server connection details
+ if ($input->getOption('host')) {
+ $commandMysql->addArgumentTemplate('-h %s', $input->getOption('host'));
+ }
+
+ if ($input->getOption('port')) {
+ $commandMysql->addArgumentTemplate('-P %s', $input->getOption('port'));
+ }
+
$commandFile = new CommandBuilder();
$commandFile->addArgument($dumpFile);
$commandFile->addPipeCommand($commandMysql);
switch ($dumpFileType) {
case 'application/x-bzip2':
+ $output->writeln('Using BZIP2 decompression
');
$commandFile->setCommand('bzcat');
break;
case 'application/gzip':
+ case 'application/x-gzip':
+ $output->writeln('Using GZIP decompression
');
$commandFile->setCommand('gzcat');
break;
case 'application/x-lzma':
case 'application/x-xz':
+ $output->writeln('Using LZMA decompression
');
$commandFile->setCommand('xzcat');
break;
default:
+ $output->writeln('Using plaintext (no decompression)
');
$commandFile->setCommand('cat');
break;
}
+ $output->writeln('Reading dump
');
$commandFile->executeInteractive();
- $output->writeln('Database "' . $database . '" restored');
+ $output->writeln('Database "' . $database . '" restored
');
return 0;
}
+
+
}
diff --git a/src/app/CliTools/Console/Command/Mysql/SlowLogCommand.php b/src/app/CliTools/Console/Command/Mysql/SlowLogCommand.php
index 400b26b..7b09cd1 100644
--- a/src/app/CliTools/Console/Command/Mysql/SlowLogCommand.php
+++ b/src/app/CliTools/Console/Command/Mysql/SlowLogCommand.php
@@ -26,31 +26,32 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-class SlowLogCommand extends \CliTools\Console\Command\AbstractCommand {
+class SlowLogCommand extends AbstractCommand {
/**
* Configure command
*/
protected function configure() {
- $this->setName('mysql:slowlog')
- ->setDescription('Enable and show slow query log')
- ->addArgument(
- 'grep',
- InputArgument::OPTIONAL,
- 'Grep'
- )
+ $this
+ ->setName('mysql:slowlog')
+ ->setDescription('Enable and show slow query log')
+ ->addArgument(
+ 'grep',
+ InputArgument::OPTIONAL,
+ 'Grep'
+ )
->addOption(
'time',
't',
InputOption::VALUE_REQUIRED,
'Slow query time (default 1 second)'
)
- ->addOption(
- 'no-index',
- 'i',
- InputOption::VALUE_NONE,
- 'Enable log queries without indexes log'
- );
+ ->addOption(
+ 'no-index',
+ 'i',
+ InputOption::VALUE_NONE,
+ 'Enable log queries without indexes log'
+ );
}
/**
@@ -80,11 +81,13 @@ public function execute(InputInterface $input, OutputInterface $output) {
$debugLogLocation = $this->getApplication()->getConfigValue('db', 'debug_log_dir');
$debugLogDir = dirname($debugLogLocation);
+ $output->writeln('Starting MySQL slow query log
');
+
// Create directory if not exists
if (!is_dir($debugLogDir)) {
if (!mkdir($debugLogDir, 0777, true)) {
- $output->writeln('Could not create "' . $debugLogDir . '" directory');
- exit(1);
+ $output->writeln('Could not create "' . $debugLogDir . '" directory');
+ throw new \CliTools\Exception\StopException(1);
}
}
@@ -100,22 +103,22 @@ public function execute(InputInterface $input, OutputInterface $output) {
if (!empty($logFileRow['Value'])) {
// Enable slow log
- $output->writeln('Enabling slow log');
+ $output->writeln('Enabling slow log
');
$query = 'SET GLOBAL slow_query_log = \'ON\'';
DatabaseConnection::exec($query);
// Enable slow log
- $output->writeln('Set long_query_time to ' . (int)abs($slowLogQueryTime) . ' seconds');
+ $output->writeln('Set long_query_time to ' . (int)abs($slowLogQueryTime) . ' seconds
');
$query = 'SET GLOBAL long_query_time = ' . (int)abs($slowLogQueryTime);
DatabaseConnection::exec($query);
// Enable log queries without indexes log
if ($logNonIndexedQueries) {
- $output->writeln('Enabling logging of queries without using indexes');
+ $output->writeln('Enabling logging of queries without using indexes
');
$query = 'SET GLOBAL log_queries_not_using_indexes = \'ON\'';
DatabaseConnection::exec($query);
} else {
- $output->writeln('Disabling logging of queries without using indexes');
+ $output->writeln('Disabling logging of queries without using indexes
');
$query = 'SET GLOBAL log_queries_not_using_indexes = \'OFF\'';
DatabaseConnection::exec($query);
}
@@ -123,7 +126,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// Setup teardown cleanup
$tearDownFunc = function () use ($output, $logNonIndexedQueries) {
// Disable general log
- $output->writeln('Disable slow log');
+ $output->writeln('Disable slow log
');
$query = 'SET GLOBAL slow_query_log = \'OFF\'';
DatabaseConnection::exec($query);
@@ -154,7 +157,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
return 0;
} else {
- $output->writeln('MySQL general_log_file not set');
+ $output->writeln('MySQL general_log_file not set');
return 1;
}
diff --git a/src/app/CliTools/Console/Command/Php/ComposerCommand.php b/src/app/CliTools/Console/Command/Php/ComposerCommand.php
new file mode 100644
index 0000000..652370f
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Php/ComposerCommand.php
@@ -0,0 +1,77 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+use CliTools\Utility\UnixUtility;
+use CliTools\Utility\PhpUtility;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ComposerCommand extends \CliTools\Console\Command\AbstractCommand implements \CliTools\Console\Filter\AnyParameterFilterInterface {
+
+ /**
+ * Configure command
+ */
+ protected function configure() {
+ $this
+ ->setName('php:composer')
+ ->setDescription('Search composer.json updir and start composer');
+ }
+
+ /**
+ * Execute command
+ *
+ * @param InputInterface $input Input instance
+ * @param OutputInterface $output Output instance
+ *
+ * @return int|null|void
+ */
+ public function execute(InputInterface $input, OutputInterface $output) {
+ $composerCmd = $this->getApplication()->getConfigValue('bin', 'composer');
+
+ $paramList = $this->getFullParameterList();
+ $composerJsonPath = UnixUtility::findFileInDirectortyTree('composer.json');
+
+ if (!empty($composerJsonPath)) {
+ $path = dirname($composerJsonPath);
+ $this->output->writeln('Found composer.json directory: ' . $path . '');
+
+ // Switch to directory of docker-compose.yml
+ PhpUtility::chdir($path);
+
+ $command = new CommandBuilder();
+ $command->parse($composerCmd);
+
+ if (!empty($paramList)) {
+ $command->setArgumentList($paramList);
+ }
+
+ $command->executeInteractive();
+ } else {
+ $this->output->writeln('No composer.json found in tree');
+
+ return 1;
+ }
+
+ return 0;
+ }
+}
diff --git a/src/app/CliTools/Console/Command/Php/RestartCommand.php b/src/app/CliTools/Console/Command/Php/RestartCommand.php
index 7b6b18b..86fca58 100644
--- a/src/app/CliTools/Console/Command/Php/RestartCommand.php
+++ b/src/app/CliTools/Console/Command/Php/RestartCommand.php
@@ -22,7 +22,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
class RestartCommand extends \CliTools\Console\Command\AbstractCommand {
@@ -30,7 +30,8 @@ class RestartCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('php:restart')
+ $this
+ ->setName('php:restart')
->setDescription('Restart PHP FPM');
}
diff --git a/src/app/CliTools/Console/Command/Php/TraceCommand.php b/src/app/CliTools/Console/Command/Php/TraceCommand.php
index d4477e1..e5b0b92 100644
--- a/src/app/CliTools/Console/Command/Php/TraceCommand.php
+++ b/src/app/CliTools/Console/Command/Php/TraceCommand.php
@@ -33,7 +33,8 @@ class TraceCommand extends \CliTools\Console\Command\AbstractTraceCommand {
* Configure command
*/
protected function configure() {
- $this->setName('php:trace')
+ $this
+ ->setName('php:trace')
->setDescription('Debug PHP processes with strace');
parent::configure();
}
diff --git a/src/app/CliTools/Console/Command/Samba/RestartCommand.php b/src/app/CliTools/Console/Command/Samba/RestartCommand.php
index a5c62c3..349be43 100644
--- a/src/app/CliTools/Console/Command/Samba/RestartCommand.php
+++ b/src/app/CliTools/Console/Command/Samba/RestartCommand.php
@@ -22,7 +22,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
class RestartCommand extends \CliTools\Console\Command\AbstractCommand {
@@ -30,7 +30,8 @@ class RestartCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('samba:restart')
+ $this
+ ->setName('samba:restart')
->setDescription('Restart Samba SMB daemon');
}
diff --git a/src/app/CliTools/Console/Command/Sync/AbstractCommand.php b/src/app/CliTools/Console/Command/Sync/AbstractCommand.php
new file mode 100644
index 0000000..8d07784
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Sync/AbstractCommand.php
@@ -0,0 +1,1106 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+use CliTools\Utility\PhpUtility;
+use CliTools\Utility\UnixUtility;
+use CliTools\Utility\ConsoleUtility;
+use CliTools\Utility\FilterUtility;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\RemoteCommandBuilder;
+use CliTools\Shell\CommandBuilder\SelfCommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilderInterface;
+use CliTools\Shell\CommandBuilder\OutputCombineCommandBuilder;
+use CliTools\Reader\ConfigReader;
+use CliTools\Database\DatabaseConnection;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Yaml\Yaml;
+use Symfony\Component\Console\Helper\QuestionHelper;
+use Symfony\Component\Console\Question\ChoiceQuestion;
+
+abstract class AbstractCommand extends \CliTools\Console\Command\AbstractCommand {
+
+ const CONFIG_FILE = 'clisync.yml';
+ const GLOBAL_KEY = 'GLOBAL';
+
+ /**
+ * Config area
+ *
+ * @var string
+ */
+ protected $confArea;
+
+ /**
+ * Project working path
+ *
+ * @var string|boolean|null
+ */
+ protected $workingPath;
+
+ /**
+ * Project configuration file path
+ *
+ * @var string|boolean|null
+ */
+ protected $confFilePath;
+
+ /**
+ * Temporary storage dir
+ *
+ * @var string|null
+ */
+ protected $tempDir;
+
+ /**
+ * Configuration
+ *
+ * @var ConfigReader
+ */
+ protected $config = array();
+
+ /**
+ * Context configuration
+ *
+ * @var ConfigReader
+ */
+ protected $contextConfig = array();
+
+ /**
+ * Configure command
+ */
+ protected function configure() {
+ $this
+ ->setDescription('Sync files and database from server')
+ ->addArgument(
+ 'context',
+ InputArgument::OPTIONAL,
+ 'Configuration name for server'
+ )
+ ->addOption(
+ 'mysql',
+ null,
+ InputOption::VALUE_NONE,
+ 'Run only mysql'
+ )
+ ->addOption(
+ 'rsync',
+ null,
+ InputOption::VALUE_NONE,
+ 'Run only rsync'
+ )
+ ->addOption(
+ 'config',
+ null,
+ InputOption::VALUE_NONE,
+ 'Show generated config'
+ );
+ }
+
+ /**
+ * Initializes the command just after the input has been validated.
+ *
+ * This is mainly useful when a lot of commands extends one main command
+ * where some things need to be initialized based on the input arguments and options.
+ *
+ * @param InputInterface $input An InputInterface instance
+ * @param OutputInterface $output An OutputInterface instance
+ * @throws \RuntimeException
+ */
+ protected function initialize(InputInterface $input, OutputInterface $output) {
+ parent::initialize($input, $output);
+
+ $this->initializeConfiguration();
+ }
+
+ /**
+ * Init configuration
+ */
+ protected function initializeConfiguration() {
+ // Search for configuration in path
+ $this->findConfigurationInPath();
+
+ // Read configuration
+ $this->readConfiguration();
+ }
+
+ /**
+ * Validate configuration
+ *
+ * @return boolean
+ */
+ protected function validateConfiguration() {
+ $ret = true;
+
+ // Rsync (optional)
+ if ($this->contextConfig->exists('rsync')) {
+ if (!$this->validateConfigurationRsync()) {
+ $ret = false;
+ }
+ }
+
+ // MySQL (optional)
+ if ($this->contextConfig->exists('mysql.database')) {
+ if (!$this->validateConfigurationMysql()) {
+ $ret = false;
+ }
+ } else {
+ // Clear mysql if any options set
+ $this->contextConfig->clear('mysql');
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Find configuration file in current path
+ */
+ protected function findConfigurationInPath() {
+ $confFileList = array(
+ self::CONFIG_FILE,
+ '.' . self::CONFIG_FILE,
+ );
+
+ // Find configuration file
+ $this->confFilePath = UnixUtility::findFileInDirectortyTree($confFileList);
+ if (empty($this->confFilePath)) {
+ $this->output->writeln('No ' . self::CONFIG_FILE . ' found in tree');
+ throw new \CliTools\Exception\StopException(1);
+ }
+
+ $this->workingPath = dirname($this->confFilePath);
+
+ $this->output->writeln('Found ' . self::CONFIG_FILE . ' directory: ' . $this->workingPath . '');
+ }
+
+ /**
+ * Read and validate configuration
+ */
+ protected function readConfiguration() {
+ $this->config = new ConfigReader();
+
+ if (empty($this->confArea)) {
+ throw new \RuntimeException('Config area not set, cannot continue');
+ }
+
+ if (!file_exists($this->confFilePath)) {
+ throw new \RuntimeException('Config file "' . $this->confFilePath . '" not found');
+ }
+
+ $conf = Yaml::parse(PhpUtility::fileGetContents($this->confFilePath));
+
+ // Switch to area configuration
+ if (!empty($conf)) {
+ $this->config->setData($conf);
+ } else {
+ throw new \RuntimeException('Could not parse "' . $this->confFilePath . '"');
+ }
+ }
+
+ /**
+ * Get context list from current configuration
+ *
+ * @return array|null
+ */
+ protected function getContextListFromConfiguration() {
+ return $this->config->getArray($this->confArea);
+ }
+
+ /**
+ * Get command list from current configuration
+ *
+ * @param string $section Section name for commands (startup, final)
+ * @return array
+ */
+ protected function getCommandList($section) {
+ $ret = array();
+
+ if ($this->contextConfig->exists('command.' . $section)) {
+ $ret = $this->contextConfig->get('command.' . $section);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Build context configuration
+ *
+ * @param $context
+ */
+ protected function buildContextConfiguration($context) {
+ $this->contextConfig = new ConfigReader();
+
+ // Fetch global conf
+ $globalConf = array();
+ if ($this->config->exists(self::GLOBAL_KEY)) {
+ $globalConf = $this->config->get(self::GLOBAL_KEY);
+ }
+
+ // Fetch area conf
+ $areaConf = $this->config->get($this->confArea);
+
+ // Fetch area global conf
+ $areaGlobalConf = array();
+ if ($this->config->exists($this->confArea . '.' . self::GLOBAL_KEY)) {
+ $areaGlobalConf = $this->config->get($this->confArea . '.' . self::GLOBAL_KEY);
+ }
+
+ // Fetch context conf
+ if (empty($areaConf[$context])) {
+ $this->output->writeln('No context "' . $context . '" found');
+ throw new \CliTools\Exception\StopException(1);
+ }
+ $contextConf = $areaConf[$context];
+
+
+ $arrayFilterRecursive = function($input, $callback) use (&$arrayFilterRecursive) {
+ $ret = array();
+ foreach ($input as $key => $value) {
+ if (is_array($value)) {
+ $value = $arrayFilterRecursive($value, $callback);
+ } else {
+ if (strlen($value)==0) {
+ $value = null;
+ }
+ }
+
+ if ($value !== null && $value !== false && $value !== true) {
+ $ret[$key] = $value;
+ }
+ }
+
+ return $ret;
+ };
+
+ // Merge
+ $globalConf = $arrayFilterRecursive( $globalConf, 'strlen' );
+ $areaGlobalConf = $arrayFilterRecursive( $areaGlobalConf, 'strlen' );
+ $contextConf = $arrayFilterRecursive( $contextConf, 'strlen' );
+
+ $conf = array_replace_recursive($globalConf, $areaGlobalConf, $contextConf);
+
+ // Set configuration
+ $this->contextConfig->setData($conf);
+ }
+
+ /**
+ * Execute command
+ *
+ * @param InputInterface $input Input instance
+ * @param OutputInterface $output Output instance
+ *
+ * @return int|null|void
+ * @throws \Exception
+ */
+ public function execute(InputInterface $input, OutputInterface $output) {
+ try {
+ // Get context selection
+ $this->initContext();
+
+ if ($this->input->getOption('config')) {
+ // only show configuration
+ $this->showContextConfig();
+ } else {
+ // Create temp directory and check environment
+ $this->startup();
+
+ // Run playbook
+ $this->runCommands('startup');
+ $this->runMain();
+ $this->runCommands('finalize');
+ }
+
+ } catch (\Exception $e) {
+ $this->cleanup();
+ throw $e;
+ }
+
+ $this->cleanup();
+ }
+
+ /**
+ * Init context
+ */
+ protected function initContext() {
+ $context = $this->getContextFromUser();
+ $this->buildContextConfiguration($context);
+
+ // Validate configuration
+ if (!$this->validateConfiguration()) {
+ $this->output->writeln('Configuration could not be validated');
+ throw new \CliTools\Exception\StopException(1);
+ }
+ }
+
+ /**
+ * Get context from user
+ */
+ protected function getContextFromUser() {
+ $ret = null;
+
+ if (!$this->input->getArgument('context')) {
+ // ########################
+ // Ask user for server context
+ // ########################
+
+ $serverList = $this->config->getList($this->confArea);
+ $serverList = array_diff($serverList, array(self::GLOBAL_KEY));
+
+ if (empty($serverList)) {
+ throw new \RuntimeException('No valid servers found in configuration');
+ }
+
+ $serverOptionList = array();
+
+ foreach ($serverList as $context) {
+ $line = array();
+
+ // hostname
+ $optPath = $this->confArea . '.' . $context . '.ssh.hostname';
+ if ($this->config->exists($optPath)) {
+ $line[] = 'host:' . $this->config->get($optPath);
+ }
+
+ // rsync path
+ $optPath = $this->confArea . '.' . $context . '.rsync.path';
+ if ($this->config->exists($optPath)) {
+ $line[] = 'rsync:' . $this->config->get($optPath);
+ }
+
+ // mysql database list
+ $optPath = $this->confArea . '.' . $context . '.mysql.database';
+ if ($this->config->exists($optPath)) {
+ $dbList = $this->config->getArray($optPath);
+ $foreignDbList = array();
+
+ foreach ($dbList as $databaseConf) {
+ if (strpos($databaseConf, ':') !== false) {
+ // local and foreign database in one string
+ $databaseConf = explode(':', $databaseConf, 2);
+ $foreignDbList[] = $databaseConf[1];
+ } else {
+ // database equal
+ $foreignDbList[] = $databaseConf;
+ }
+ }
+
+ if (!empty($foreignDbList)) {
+ $line[] .= 'mysql:' . implode(', ', $foreignDbList);
+ }
+ }
+
+ if (!empty($line)) {
+ $line = implode(' ', $line);
+ } else {
+ // fallback
+ $line = $context;
+ }
+
+ $serverOptionList[$context] = $line;
+ }
+
+ try {
+ $question = new ChoiceQuestion('Please choose server context for synchronization', $serverOptionList);
+ $question->setMaxAttempts(1);
+
+ $questionDialog = new QuestionHelper();
+
+ $ret = $questionDialog->ask($this->input, $this->output, $question);
+ } catch(\InvalidArgumentException $e) {
+ // Invalid server context, just stop here
+ throw new \CliTools\Exception\StopException(1);
+ }
+ } else {
+ $ret = $this->input->getArgument('context');
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Show context configuration
+ */
+ protected function showContextConfig() {
+ print_r($this->contextConfig->get());
+ }
+
+ /**
+ * Validate configuration (rsync)
+ *
+ * @return boolean
+ */
+ protected function validateConfigurationRsync() {
+ $ret = true;
+
+ // Check if rsync target exists
+ if (!$this->getRsyncPathFromConfig()) {
+ $this->output->writeln('No rsync path configuration found');
+ $ret = false;
+ } else {
+ $this->output->writeln('Using rsync path "' . $this->getRsyncPathFromConfig() . '"');
+ }
+
+ // Check if there are any rsync directories
+ if (!$this->contextConfig->exists('rsync.directory')) {
+ $this->output->writeln('No rsync directory configuration found, filesync disabled');
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Validate configuration (mysql)
+ *
+ * @return boolean
+ */
+ protected function validateConfigurationMysql() {
+ $ret = true;
+
+ // Check if one database is configured
+ if (!$this->contextConfig->exists('mysql.database')) {
+ $this->output->writeln('No mysql database configuration found');
+ $ret = false;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Startup task
+ */
+ protected function startup() {
+ $this->tempDir = '/tmp/.clisync-'.getmypid();
+ $this->clearTempDir();
+ PhpUtility::mkdir($this->tempDir, 0777, true);
+ PhpUtility::mkdir($this->tempDir . '/mysql/', 0777, true);
+
+ $this->checkIfDockerExists();
+ }
+
+ /**
+ * Cleanup task
+ */
+ protected function cleanup() {
+ $this->clearTempDir();
+ }
+
+ /**
+ * Clear temp. storage directory if exists
+ */
+ protected function clearTempDir() {
+ // Remove storage dir
+ if (!empty($this->tempDir) && is_dir($this->tempDir)) {
+ $command = new CommandBuilder('rm', '-rf');
+ $command->addArgumentSeparator()
+ ->addArgument($this->tempDir);
+ $command->executeInteractive();
+ }
+ }
+
+ /**
+ * Check if docker exists
+ *
+ * @throws \CliTools\Exception\StopException
+ */
+ protected function checkIfDockerExists() {
+ $dockerPath = \CliTools\Utility\DockerUtility::searchDockerDirectoryRecursive();
+
+ if (!empty($dockerPath)) {
+ $this->output->writeln('Running docker containers:');
+
+ // Docker instance found
+ $docker = new CommandBuilder('docker', 'ps');
+ $docker->executeInteractive();
+
+ $answer = ConsoleUtility::questionYesNo('Are these running containers the right ones?', 'no');
+
+ if (!$answer) {
+ throw new \CliTools\Exception\StopException(1);
+ }
+ }
+ }
+
+ /**
+ * Run defined commands
+ */
+ protected function runCommands($area) {
+ $commandList = $this->getCommandList($area);
+
+ if (!empty($commandList)) {
+ $this->output->writeln(' ---- Starting ' . strtoupper($area) . ' commands ---- ');
+
+ foreach ($commandList as $commandRow) {
+
+ if (is_string($commandRow)) {
+ // Simple, local task
+ $command = new CommandBuilder();
+ $command->parse($commandRow);
+ } elseif(is_array($commandRow)) {
+ // Complex task
+ $command = $this->buildComplexTask($commandRow);
+ }
+
+ if ($command) {
+ $command->executeInteractive();
+ }
+ }
+ }
+ }
+
+ /**
+ * Build complex task
+ *
+ * @param array $task Task configuration
+ *
+ * @return CommandBuilder|CommandBuilderInterface
+ */
+ protected function buildComplexTask(array $task) {
+ if (empty($task['type'])) {
+ $task['type'] = 'local';
+ }
+
+ if (empty($task['command'])) {
+ throw new \RuntimeException('Task command is empty');
+ }
+
+ // Process task type
+ switch ($task['type']) {
+ case 'remote':
+ // Remote command
+ $command = new RemoteCommandBuilder();
+ $command->parse($task['command']);
+ $command = $this->wrapRemoteCommand($command);
+ break;
+
+ case 'local':
+ // Local command
+ $command = new CommandBuilder();
+ $command->parse($task['command']);
+ break;
+
+ default:
+ throw new \RuntimeException('Unknown task type');
+ break;
+ }
+
+ return $command;
+ }
+
+ /**
+ * Create rsync command for sync
+ *
+ * @param string $source Source directory
+ * @param string $target Target directory
+ * @param string $confKey List of files (patterns)
+ *
+ * @return CommandBuilder
+ */
+ protected function createRsyncCommandWithConfiguration($source, $target, $confKey) {
+ $options = array();
+
+ // #############
+ // Filelist
+ // #############
+ $fileList = array();
+ if ($this->contextConfig->exists($confKey . '.directory')) {
+ $fileList = $this->contextConfig->get($confKey . '.directory');
+ }
+
+ // #############
+ // Excludes
+ // #############
+ $excludeList = array();
+ if ($this->contextConfig->exists($confKey . '.exclude')) {
+ $excludeList = $this->contextConfig->get($confKey . '.exclude');
+ }
+
+ // #############
+ // Max size
+ // #############
+ if ($this->contextConfig->exists($confKey . '.conf.maxSize')) {
+ $options['max-size'] = array(
+ 'template' => '--max-size=%s',
+ 'params' => array(
+ $this->contextConfig->get($confKey . '.conf.maxSize')
+ ),
+ );
+ }
+
+ // #############
+ // Min size
+ // #############
+ if ($this->contextConfig->exists($confKey . '.conf.minSize')) {
+ $options['min-size'] = array(
+ 'template' => '--min-size=%s',
+ 'params' => array(
+ $this->contextConfig->get($confKey . '.conf.minSize')
+ ),
+ );
+ }
+
+ return $this->createRsyncCommand($source, $target, $fileList, $excludeList, $options);
+ }
+
+ /**
+ * Create rsync command for sync
+ *
+ * @param string $source Source directory
+ * @param string $target Target directory
+ * @param array|null $filelist List of files (patterns)
+ * @param array|null $exclude List of excludes (patterns)
+ * @param array|null $options Custom rsync options
+ *
+ * @return CommandBuilder
+ */
+ protected function createRsyncCommand($source, $target, array $filelist = null, array $exclude = null, array $options = null) {
+ $this->output->writeln('Rsync from ' . $source . ' to ' . $target . '');
+
+ $command = new CommandBuilder('rsync', '-rlptD --delete-after --progress --human-readable');
+
+ // Add file list (external file with --files-from option)
+ if (!empty($filelist)) {
+ $this->rsyncAddFileList($command, $filelist);
+ }
+
+ // Add exclude (external file with --exclude-from option)
+ if (!empty($exclude)) {
+ $this->rsyncAddExcludeList($command, $exclude);
+ }
+
+ if (!empty($options)) {
+ foreach ($options as $optionValue) {
+ if (is_array($optionValue)) {
+ $command->addArgumentTemplateList($optionValue['template'], $optionValue['params']);
+ } else {
+ $command->addArgument($optionValue);
+ }
+
+ }
+ }
+
+ // Paths should have leading / to prevent sync issues
+ $source = rtrim($source, '/') . '/';
+ $target = rtrim($target, '/') . '/';
+
+ // Set source and target
+ $command->addArgument($source)
+ ->addArgument($target);
+
+ return $command;
+ }
+
+ /**
+ * Get rsync path from configuration
+ *
+ * @return boolean|string
+ */
+ protected function getRsyncPathFromConfig() {
+ $ret = false;
+ if ($this->contextConfig->exists('rsync.path')) {
+ // Use path from rsync
+ $ret = $this->contextConfig->get('rsync.path');
+ } elseif($this->contextConfig->exists('ssh.hostname') && $this->contextConfig->exists('ssh.path')) {
+ // Build path from ssh configuration
+ $ret = $this->contextConfig->get('ssh.hostname') . ':' . $this->contextConfig->get('ssh.path');
+ }
+
+ return $ret;
+ }
+
+
+ /**
+ * Get rsync working path (with target if set in config)
+ *
+ * @return boolean|string
+ */
+ protected function getRsyncWorkingPath() {
+ $ret = $this->workingPath;
+
+ // remove right /
+ $ret = rtrim($ret, '/');
+
+ if ($this->contextConfig->exists('rsync.workdir')) {
+ $ret .= '/' . $this->contextConfig->get('rsync.workdir');
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Add file (pattern) list to rsync command
+ *
+ * @param CommandBuilder $command Rsync Command
+ * @param array $list List of files
+ */
+ protected function rsyncAddFileList(CommandBuilder $command, array $list) {
+ $rsyncFilter = $this->tempDir . '/.rsync-filelist';
+
+ PhpUtility::filePutContents($rsyncFilter, implode("\n", $list));
+
+ $command->addArgumentTemplate('--files-from=%s', $rsyncFilter);
+
+ // cleanup rsync file
+ $command->getExecutor()->addFinisherCallback(function () use ($rsyncFilter) {
+ unlink($rsyncFilter);
+ });
+
+ }
+
+ /**
+ * Add exclude (pattern) list to rsync command
+ *
+ * @param CommandBuilder $command Rsync Command
+ * @param array $list List of excludes
+ */
+ protected function rsyncAddExcludeList(CommandBuilder $command, $list) {
+ $rsyncFilter = $this->tempDir . '/.rsync-exclude';
+
+ PhpUtility::filePutContents($rsyncFilter, implode("\n", $list));
+
+ $command->addArgumentTemplate('--exclude-from=%s', $rsyncFilter);
+
+ // cleanup rsync file
+ $command->getExecutor()->addFinisherCallback(function () use ($rsyncFilter) {
+ unlink($rsyncFilter);
+ });
+ }
+
+ /**
+ * Create mysql backup command
+ *
+ * @param string $database Database name
+ * @param string $dumpFile MySQL dump file
+ *
+ * @return SelfCommandBuilder
+ */
+ protected function createMysqlRestoreCommand($database, $dumpFile) {
+ $command = new SelfCommandBuilder();
+ $command->addArgumentTemplate('mysql:restore %s %s', $database, $dumpFile);
+ return $command;
+ }
+
+ /**
+ * Create mysql backup command
+ *
+ * @param string $database Database name
+ * @param string $dumpFile MySQL dump file
+ * @param null|string $filter Filter name
+ *
+ * @return SelfCommandBuilder
+ */
+ protected function createMysqlBackupCommand($database, $dumpFile, $filter = null) {
+ $command = new SelfCommandBuilder();
+ $command->addArgumentTemplate('mysql:backup %s %s', $database, $dumpFile);
+
+ if ($filter !== null) {
+ $command->addArgumentTemplate('--filter=%s', $filter);
+ }
+
+ return $command;
+ }
+
+ /**
+ * Wrap command with ssh if needed
+ *
+ * @param CommandBuilderInterface $command
+ * @return CommandBuilderInterface
+ */
+ protected function wrapRemoteCommand(CommandBuilderInterface $command) {
+ // Wrap in ssh if needed
+ if ($this->contextConfig->exists('ssh.hostname')) {
+ $sshCommand = new CommandBuilder('ssh', '-o BatchMode=yes');
+ $sshCommand->addArgument($this->contextConfig->get('ssh.hostname'))
+ ->append($command, true);
+
+ $command = $sshCommand;
+ }
+
+ return $command;
+ }
+
+ /**
+ * Create new mysql command
+ *
+ * @param null|string $database Database name
+ *
+ * @return RemoteCommandBuilder
+ */
+ protected function createRemoteMySqlCommand($database = null) {
+ $command = new RemoteCommandBuilder('mysql');
+ $command
+ // batch mode
+ ->addArgument('-B')
+ // skip column names
+ ->addArgument('-N');
+
+ // Add username
+ if ($this->contextConfig->exists('mysql.username')) {
+ $command->addArgumentTemplate('-u%s', $this->contextConfig->get('mysql.username'));
+ }
+
+ // Add password
+ if ($this->contextConfig->exists('mysql.password')) {
+ $command->addArgumentTemplate('-p%s', $this->contextConfig->get('mysql.password'));
+ }
+
+ // Add hostname
+ if ($this->contextConfig->exists('mysql.hostname')) {
+ $command->addArgumentTemplate('-h%s', $this->contextConfig->get('mysql.hostname'));
+ }
+
+ if ($database !== null) {
+ $command->addArgument($database);
+ }
+
+ return $command;
+ }
+
+
+ /**
+ * Create new mysql command
+ *
+ * @param null|string $database Database name
+ *
+ * @return RemoteCommandBuilder
+ */
+ protected function createLocalMySqlCommand($database = null) {
+ $command = new RemoteCommandBuilder('mysql');
+ $command
+ // batch mode
+ ->addArgument('-B')
+ // skip column names
+ ->addArgument('-N');
+
+ // Add username
+ if (DatabaseConnection::getDbUsername()) {
+ $command->addArgumentTemplate('-u%s', DatabaseConnection::getDbUsername());
+ }
+
+ // Add password
+ if (DatabaseConnection::getDbPassword()) {
+ $command->addArgumentTemplate('-p%s', DatabaseConnection::getDbPassword());
+ }
+
+ // Add hostname
+ if (DatabaseConnection::getDbHostname()) {
+ $command->addArgumentTemplate('-h%s', DatabaseConnection::getDbHostname());
+ }
+
+ // Add hostname
+ if (DatabaseConnection::getDbPort()) {
+ $command->addArgumentTemplate('-P%s', DatabaseConnection::getDbPort());
+ }
+
+ if ($database !== null) {
+ $command->addArgument($database);
+ }
+
+ return $command;
+ }
+
+ /**
+ * Create new mysqldump command
+ *
+ * @param null|string $database Database name
+ *
+ * @return RemoteCommandBuilder
+ */
+ protected function createRemoteMySqlDumpCommand($database = null) {
+ $command = new RemoteCommandBuilder('mysqldump');
+
+ // Add username
+ if ($this->contextConfig->exists('mysql.username')) {
+ $command->addArgumentTemplate('-u%s', $this->contextConfig->get('mysql.username'));
+ }
+
+ // Add password
+ if ($this->contextConfig->exists('mysql.password')) {
+ $command->addArgumentTemplate('-p%s', $this->contextConfig->get('mysql.password'));
+ }
+
+ // Add hostname
+ if ($this->contextConfig->exists('mysql.hostname')) {
+ $command->addArgumentTemplate('-h%s', $this->contextConfig->get('mysql.hostname'));
+ }
+
+ // Add custom options
+ if ($this->contextConfig->exists('mysql.mysqldump.option')) {
+ $command->addArgumentRaw($this->contextConfig->get('mysql.mysqldump.option'));
+ }
+
+ // Transfer compression
+ switch($this->contextConfig->get('mysql.compression')) {
+ case 'bzip2':
+ // Add pipe compressor (bzip2 compressed transfer via ssh)
+ $command->addPipeCommand( new CommandBuilder('bzip2', '--compress --stdout') );
+ break;
+
+ case 'gzip':
+ // Add pipe compressor (gzip compressed transfer via ssh)
+ $command->addPipeCommand( new CommandBuilder('gzip', '--stdout') );
+ break;
+ }
+
+ if ($database !== null) {
+ $command->addArgument($database);
+ }
+
+ return $command;
+ }
+
+ /**
+ * Create new mysqldump command
+ *
+ * @param null|string $database Database name
+ *
+ * @return RemoteCommandBuilder
+ */
+ protected function createLocalMySqlDumpCommand($database = null) {
+ $command = new RemoteCommandBuilder('mysqldump');
+
+ // Add username
+ if (DatabaseConnection::getDbUsername()) {
+ $command->addArgumentTemplate('-u%s', DatabaseConnection::getDbUsername());
+ }
+
+ // Add password
+ if (DatabaseConnection::getDbPassword()) {
+ $command->addArgumentTemplate('-p%s', DatabaseConnection::getDbPassword());
+ }
+
+ // Add hostname
+ if (DatabaseConnection::getDbHostname()) {
+ $command->addArgumentTemplate('-h%s', DatabaseConnection::getDbHostname());
+ }
+
+ // Add hostname
+ if (DatabaseConnection::getDbPort()) {
+ $command->addArgumentTemplate('-P%s', DatabaseConnection::getDbPort());
+ }
+
+ // Add custom options
+ if ($this->contextConfig->exists('mysql.mysqldump.option')) {
+ $command->addArgumentRaw($this->contextConfig->get('mysql.mysqldump.option'));
+ }
+
+ if ($database !== null) {
+ $command->addArgument($database);
+ }
+
+ // Transfer compression
+ switch($this->contextConfig->get('mysql.compression')) {
+ case 'bzip2':
+ // Add pipe compressor (bzip2 compressed transfer via ssh)
+ $command->addPipeCommand( new CommandBuilder('bzip2', '--compress --stdout') );
+ break;
+
+ case 'gzip':
+ // Add pipe compressor (gzip compressed transfer via ssh)
+ $command->addPipeCommand( new CommandBuilder('gzip', '--stdout') );
+ break;
+ }
+
+ return $command;
+ }
+
+
+ /**
+ * Add mysqldump filter to command
+ *
+ * @param CommandBuilderInterface $commandDump Command
+ * @param string $database Database
+ * @param boolean $isRemote Remote filter
+ *
+ * @return CommandBuilderInterface
+ */
+ protected function addMysqlDumpFilterArguments(CommandBuilderInterface $commandDump, $database, $isRemote = true) {
+ $command = $commandDump;
+
+ $filter = $this->contextConfig->get('mysql.filter');
+
+ // get filter
+ if (is_array($filter)) {
+ $filterList = (array)$filter;
+ $filter = 'custom table filter';
+ } else {
+ $filterList = $this->getApplication()->getConfigValue('mysql-backup-filter', $filter);
+ }
+
+ if (empty($filterList)) {
+ throw new \RuntimeException('MySQL dump filters "' . $filter . '" not available"');
+ }
+
+ $this->output->writeln('Using filter "' . $filter . '"
');
+
+ // Get table list (from cloned mysqldump command)
+ if ($isRemote) {
+ $tableListDumper = $this->createRemoteMySqlCommand($database);
+ } else {
+ $tableListDumper = $this->createLocalMySqlCommand($database);
+ }
+
+ $tableListDumper->addArgumentTemplate('-e %s', 'show tables;');
+
+ // wrap with ssh (for remote execution)
+ if ($isRemote) {
+ $tableListDumper = $this->wrapRemoteCommand($tableListDumper);
+ }
+
+ $tableList = $tableListDumper->execute()->getOutput();
+
+ // Filter table list
+ $ignoredTableList = FilterUtility::mysqlIgnoredTableFilter($tableList, $filterList, $database);
+
+ // Dump only structure
+ $commandStructure = clone $command;
+ $commandStructure
+ ->addArgument('--no-data')
+ ->clearPipes();
+
+ // Dump only data (only filtered tables)
+ $commandData = clone $command;
+ $commandData
+ ->addArgument('--no-create-info')
+ ->clearPipes();
+
+ if (!empty($ignoredTableList)) {
+ $commandData->addArgumentTemplateMultiple('--ignore-table=%s', $ignoredTableList);
+ }
+
+ $commandPipeList = $command->getPipeList();
+
+ // Combine both commands to one
+ $command = new OutputCombineCommandBuilder();
+ $command
+ ->addCommandForCombinedOutput($commandStructure)
+ ->addCommandForCombinedOutput($commandData);
+
+ // Read compression pipe
+ if (!empty($commandPipeList)) {
+ $command->setPipeList($commandPipeList);
+ }
+
+ return $command;
+ }
+
+}
diff --git a/src/app/CliTools/Console/Command/Sync/AbstractRemoteSyncCommand.php b/src/app/CliTools/Console/Command/Sync/AbstractRemoteSyncCommand.php
new file mode 100644
index 0000000..f26efa2
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Sync/AbstractRemoteSyncCommand.php
@@ -0,0 +1,50 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+abstract class AbstractRemoteSyncCommand extends AbstractCommand {
+
+ /**
+ * Validate configuration
+ *
+ * @return boolean
+ */
+ protected function validateConfiguration() {
+ $ret = parent::validateConfiguration();
+
+ $output = $this->output;
+
+ // ##################
+ // SSH (optional)
+ // ##################
+
+ if ($this->config->exists('ssh')) {
+ // Check if one database is configured
+ if (!$this->config->exists('ssh.hostname')) {
+ $output->writeln('No ssh hostname configuration found');
+ $ret = false;
+ }
+ }
+
+ return $ret;
+ }
+
+}
diff --git a/src/app/CliTools/Console/Command/Sync/AbstractShareCommand.php b/src/app/CliTools/Console/Command/Sync/AbstractShareCommand.php
new file mode 100644
index 0000000..ddc04cb
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Sync/AbstractShareCommand.php
@@ -0,0 +1,51 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+abstract class AbstractShareCommand extends AbstractCommand {
+
+ const PATH_DUMP = '/dump/';
+ const PATH_DATA = '/data/';
+
+ /**
+ * Configure command
+ */
+ protected function configure() {
+ parent::configure();
+
+ $this->confArea = 'share';
+ }
+
+ /**
+ * Validate configuration
+ *
+ * @return boolean
+ */
+ protected function validateConfiguration() {
+ $ret = parent::validateConfiguration();
+
+ // Rsync required for share
+ $ret = $ret && $this->validateConfigurationRsync();
+
+ return $ret;
+ }
+
+}
diff --git a/src/app/CliTools/Console/Command/Sync/BackupCommand.php b/src/app/CliTools/Console/Command/Sync/BackupCommand.php
new file mode 100644
index 0000000..5c261a2
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Sync/BackupCommand.php
@@ -0,0 +1,131 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+use CliTools\Shell\CommandBuilder\OutputCombineCommandBuilder;
+
+class BackupCommand extends AbstractShareCommand {
+
+ /**
+ * Configure command
+ */
+ protected function configure() {
+ parent::configure();
+
+ $this
+ ->setName('sync:backup')
+ ->setDescription('Backup files and database from share');
+ }
+
+ /**
+ * Startup task
+ */
+ protected function startup() {
+ $this->output->writeln('Starting share backup
');
+ parent::startup();
+ }
+
+ /**
+ * Backup task
+ */
+ protected function runMain() {
+ // ##################
+ // Option specific runners
+ // ##################
+ $runRsync = true;
+ $runMysql = true;
+
+ if ($this->input->getOption('mysql') || $this->input->getOption('rsync')) {
+ // don't run rsync if not specifiecd
+ $runRsync = $this->input->getOption('rsync');
+
+ // don't run mysql if not specifiecd
+ $runMysql = $this->input->getOption('mysql');
+ }
+
+ // ##################
+ // Backup dirs
+ // ##################
+ if ($runRsync && $this->contextConfig->exists('rsync.directory')) {
+ $this->runTaskRsync();
+ }
+
+ // ##################
+ // Backup databases
+ // ##################
+ if ($runMysql && $this->contextConfig->exists('mysql.database')) {
+ $this->runTaskMysql();
+ }
+ }
+
+ /**
+ * Sync files with rsync
+ */
+ protected function runTaskRsync() {
+ $source = $this->getRsyncWorkingPath();
+ $target = $this->getRsyncPathFromConfig() . self::PATH_DATA;
+
+ $command = $this->createRsyncCommandWithConfiguration($source, $target, 'rsync');
+ $command->executeInteractive();
+ }
+
+ /**
+ * Sync database
+ */
+ protected function runTaskMysql() {
+ // ##################
+ // Sync databases
+ // ##################
+ foreach ($this->contextConfig->getArray('mysql.database') as $database) {
+ // make sure we don't have any leading whitespaces
+ $database = trim($database);
+
+ // dump database
+ $dumpFile = $this->tempDir . '/mysql/' . $database . '.dump';
+
+ // ##########
+ // Dump from server
+ // ##########
+ $this->output->writeln('Dumping database "' . $database . '"
');
+
+ $mysqldump = $this->createLocalMySqlDumpCommand($database);
+
+ if ($this->contextConfig->exists('mysql.filter')) {
+ $mysqldump = $this->addMysqlDumpFilterArguments($mysqldump, $database, false);
+ }
+
+ $command = new OutputCombineCommandBuilder();
+ $command->addCommandForCombinedOutput($mysqldump);
+
+ $command
+ ->setOutputRedirectToFile($dumpFile)
+ ->executeInteractive();
+ }
+
+ // ##################
+ // Backup mysql dump
+ // ##################
+ $source = $this->tempDir;
+ $target = $this->getRsyncPathFromConfig() . self::PATH_DUMP;
+ $command = $this->createRsyncCommand($source, $target);
+ $command->executeInteractive();
+ }
+}
diff --git a/src/app/CliTools/Console/Command/Sync/DeployCommand.php b/src/app/CliTools/Console/Command/Sync/DeployCommand.php
new file mode 100644
index 0000000..12fff68
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Sync/DeployCommand.php
@@ -0,0 +1,103 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+use CliTools\Database\DatabaseConnection;
+
+class DeployCommand extends AbstractRemoteSyncCommand {
+
+ /**
+ * Configure command
+ */
+ protected function configure() {
+ parent::configure();
+
+ $this->confArea = 'deploy';
+
+ $this
+ ->setName('sync:deploy')
+ ->setDescription('Deploy files and database to server');
+ }
+
+ /**
+ * Startup task
+ */
+ protected function startup() {
+ $this->output->writeln('Starting server deployment
');
+ parent::startup();
+ }
+
+ /**
+ * Backup task
+ */
+ protected function runMain() {
+ // ##################
+ // Option specific runners
+ // ##################
+ $runRsync = true;
+ $runMysql = true;
+
+ if ($this->input->getOption('mysql') || $this->input->getOption('rsync')) {
+ // don't run rsync if not specifiecd
+ $runRsync = $this->input->getOption('rsync');
+
+ // don't run mysql if not specifiecd
+ $runMysql = $this->input->getOption('mysql');
+ }
+
+ // ##################
+ // Run tasks
+ // ##################
+
+ // Check database connection
+ if ($runMysql && $this->contextConfig->exists('mysql')) {
+ DatabaseConnection::ping();
+ }
+
+ // Sync files with rsync to local storage
+ if ($runRsync && $this->contextConfig->exists('rsync')) {
+ $this->output->writeln('Starting FILE deployment
');
+ $this->runTaskRsync();
+ }
+
+ // Sync database to local server
+ if ($runMysql && $this->contextConfig->exists('mysql')) {
+ $this->output->writeln('Starting MYSQL deployment
');
+ $this->output->writeln('TODO - not implemented');
+ }
+ }
+
+ /**
+ * Sync files with rsync
+ */
+ protected function runTaskRsync() {
+ // ##################
+ // Deploy dirs
+ // ##################
+ $source = $this->getRsyncWorkingPath();
+ $target = $this->getRsyncPathFromConfig();
+
+ $command = $this->createRsyncCommandWithConfiguration($source, $target, 'rsync');
+
+ $command->executeInteractive();
+ }
+
+}
diff --git a/src/app/CliTools/Console/Command/Sync/InitCommand.php b/src/app/CliTools/Console/Command/Sync/InitCommand.php
new file mode 100644
index 0000000..31cbf99
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Sync/InitCommand.php
@@ -0,0 +1,75 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+use CliTools\Utility\PhpUtility;
+use CliTools\Shell\CommandBuilder\EditorCommandBuilder;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class InitCommand extends \CliTools\Console\Command\AbstractCommand {
+
+ /**
+ * Configure command
+ */
+ protected function configure() {
+ $this
+ ->setName('sync:init')
+ ->setDescription('Create example clisync.yml');
+ }
+
+ /**
+ * Execute command
+ *
+ * @param InputInterface $input Input instance
+ * @param OutputInterface $output Output instance
+ *
+ * @return int|null|void
+ * @throws \Exception
+ */
+ public function execute(InputInterface $input, OutputInterface $output) {
+ $cliSyncFilePath = getcwd() . '/' . AbstractCommand::CONFIG_FILE;
+
+ if (file_exists($cliSyncFilePath)) {
+ $this->output->writeln('Configuration file ' . AbstractCommand::CONFIG_FILE . ' already exists');
+ return 1;
+ }
+
+ // fetch example
+ $content = PhpUtility::fileGetContents(CLITOOLS_ROOT_FS . '/conf/clisync.yml');
+
+ // store in current working dir
+ PhpUtility::filePutContents($cliSyncFilePath, $content);
+
+ // Start editor with file (if $EDITOR is set)
+ try {
+ $editor = new EditorCommandBuilder();
+ $editor
+ ->addArgument($cliSyncFilePath)
+ ->executeInteractive();
+ } catch (\Exception $e) {
+ $this->output->writeln('' . $e->getMessage() . '');
+ }
+
+ $this->output->writeln('Successfully created ' . AbstractCommand::CONFIG_FILE . ' ');
+ }
+
+}
diff --git a/src/app/CliTools/Console/Command/Sync/RestoreCommand.php b/src/app/CliTools/Console/Command/Sync/RestoreCommand.php
new file mode 100644
index 0000000..c2699c6
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Sync/RestoreCommand.php
@@ -0,0 +1,113 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+class RestoreCommand extends AbstractShareCommand {
+
+ /**
+ * Configure command
+ */
+ protected function configure() {
+ parent::configure();
+
+ $this
+ ->setName('sync:restore')
+ ->setDescription('Restore files and database from share');
+ }
+
+ /**
+ * Startup task
+ */
+ protected function startup() {
+ $this->output->writeln('
Starting share restore
');
+ parent::startup();
+ }
+
+ /**
+ * Restore task
+ */
+ protected function runMain() {
+ // ##################
+ // Option specific runners
+ // ##################
+ $runRsync = true;
+ $runMysql = true;
+
+ if ($this->input->getOption('mysql') || $this->input->getOption('rsync')) {
+ // don't run rsync if not specifiecd
+ $runRsync = $this->input->getOption('rsync');
+
+ // don't run mysql if not specifiecd
+ $runMysql = $this->input->getOption('mysql');
+ }
+
+ // ##################
+ // Restore dirs
+ // ##################
+ if ($runRsync && $this->contextConfig->exists('rsync.directory')) {
+ $this->runTaskRsync();
+ }
+
+ // ##################
+ // Restore mysql dump
+ // ##################
+ if ($runMysql) {
+ $this->runTaskMysql();
+ }
+ }
+
+ /**
+ * Sync files with rsync
+ */
+ protected function runTaskRsync() {
+ $source = $this->getRsyncPathFromConfig() . self::PATH_DATA;
+ $target = $this->getRsyncWorkingPath();
+
+ $command = $this->createRsyncCommandWithConfiguration($source, $target, 'rsync');
+ $command->executeInteractive();
+ }
+
+ /**
+ * Sync files with mysql
+ */
+ protected function runTaskMysql() {
+ $source = $this->getRsyncPathFromConfig() . self::PATH_DUMP;
+ $target = $this->tempDir;
+ $command = $this->createRsyncCommand($source, $target);
+ $command->executeInteractive();
+
+ $iterator = new \DirectoryIterator($this->tempDir . '/mysql');
+ foreach ($iterator as $item) {
+ // skip dot
+ if ($item->isDot()) {
+ continue;
+ }
+
+ list($database) = explode('.', $item->getFilename(), 2);
+
+ if (!empty($database)) {
+ $this->output->writeln('Restoring database ' . $database . '
');
+
+ $this->createMysqlRestoreCommand($database, $item->getPathname())->executeInteractive();
+ }
+ }
+ }
+}
diff --git a/src/app/CliTools/Console/Command/Sync/ServerCommand.php b/src/app/CliTools/Console/Command/Sync/ServerCommand.php
new file mode 100644
index 0000000..27a0988
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Sync/ServerCommand.php
@@ -0,0 +1,176 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+use CliTools\Database\DatabaseConnection;
+
+class ServerCommand extends AbstractRemoteSyncCommand {
+
+ /**
+ * Configure command
+ */
+ protected function configure() {
+ parent::configure();
+
+ $this->confArea = 'sync';
+
+ $this
+ ->setName('sync:server')
+ ->setDescription('Sync files and database from server');
+ }
+
+ /**
+ * Startup task
+ */
+ protected function startup() {
+ $this->output->writeln('Starting server synchronization
');
+ parent::startup();
+ }
+
+ /**
+ * Validate configuration
+ *
+ * @return boolean
+ */
+ protected function validateConfiguration() {
+ $ret = parent::validateConfiguration();
+
+ $output = $this->output;
+
+ // ##################
+ // SSH (optional)
+ // ##################
+
+ if ($this->contextConfig->exists('ssh')) {
+ // Check if one database is configured
+ if (!$this->contextConfig->exists('ssh.hostname')) {
+ $output->writeln('No ssh hostname configuration found');
+ $ret = false;
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Backup task
+ */
+ protected function runMain() {
+ // ##################
+ // Option specific runners
+ // ##################
+ $runRsync = true;
+ $runMysql = true;
+
+ if ($this->input->getOption('mysql') || $this->input->getOption('rsync')) {
+ // don't run rsync if not specifiecd
+ $runRsync = $this->input->getOption('rsync');
+
+ // don't run mysql if not specifiecd
+ $runMysql = $this->input->getOption('mysql');
+ }
+
+ // ##################
+ // Run tasks
+ // ##################
+
+ // Check database connection
+ if ($runMysql && $this->contextConfig->exists('mysql')) {
+ DatabaseConnection::ping();
+ }
+
+ // Sync files with rsync to local storage
+ if ($runRsync && $this->contextConfig->exists('rsync')) {
+ $this->output->writeln('Starting FILE sync
');
+ $this->runTaskRsync();
+ }
+
+ // Sync database to local server
+ if ($runMysql && $this->contextConfig->exists('mysql')) {
+ $this->output->writeln('Starting MYSQL sync
');
+ $this->runTaskDatabase();
+ }
+ }
+
+ /**
+ * Sync files with rsync
+ */
+ protected function runTaskRsync() {
+ // ##################
+ // Restore dirs
+ // ##################
+ $source = $this->getRsyncPathFromConfig();
+ $target = $this->getRsyncWorkingPath();
+
+ $command = $this->createRsyncCommandWithConfiguration($source, $target, 'rsync');
+ $command->executeInteractive();
+ }
+
+ /**
+ * Sync database
+ */
+ protected function runTaskDatabase() {
+ // ##################
+ // Sync databases
+ // ##################
+ foreach ($this->contextConfig->getArray('mysql.database') as $databaseConf) {
+ if (strpos($databaseConf, ':') !== false) {
+ // local and foreign database in one string
+ list($localDatabase, $foreignDatabase) = explode(':', $databaseConf, 2);
+ } else {
+ // database equal
+ $localDatabase = $databaseConf;
+ $foreignDatabase = $databaseConf;
+ }
+
+ // make sure we don't have any leading whitespaces
+ $localDatabase = trim($localDatabase);
+ $foreignDatabase = trim($foreignDatabase);
+
+ $dumpFile = $this->tempDir . '/' . $localDatabase . '.sql.dump';
+
+ // ##########
+ // Dump from server
+ // ##########
+ $this->output->writeln('Fetching foreign database "' . $foreignDatabase . '"
');
+
+ $mysqldump = $this->createRemoteMySqlDumpCommand($foreignDatabase);
+
+ if ($this->contextConfig->exists('mysql.filter')) {
+ $mysqldump = $this->addMysqlDumpFilterArguments($mysqldump, $foreignDatabase, $this->contextConfig->get('mysql.filter'));
+ }
+
+ $command = $this->wrapRemoteCommand($mysqldump);
+ $command->setOutputRedirectToFile($dumpFile);
+
+ $command->executeInteractive();
+
+ // ##########
+ // Restore local
+ // ##########
+ $this->output->writeln('Restoring database "' . $localDatabase . '"
');
+
+ $this->createMysqlRestoreCommand($localDatabase, $dumpFile)->executeInteractive();
+ }
+ }
+
+
+}
diff --git a/src/app/CliTools/Console/Command/System/BannerCommand.php b/src/app/CliTools/Console/Command/System/BannerCommand.php
index bdf498c..93f7027 100644
--- a/src/app/CliTools/Console/Command/System/BannerCommand.php
+++ b/src/app/CliTools/Console/Command/System/BannerCommand.php
@@ -31,7 +31,8 @@ class BannerCommand extends \CliTools\Console\Command\AbstractCommand implements
* Configure command
*/
protected function configure() {
- $this->setName('system:banner')
+ $this
+ ->setName('system:banner')
->setDescription('Banner generator for /etc/issue');
}
diff --git a/src/app/CliTools/Console/Command/System/CrontaskCommand.php b/src/app/CliTools/Console/Command/System/CrontaskCommand.php
index f6e0865..df3671c 100644
--- a/src/app/CliTools/Console/Command/System/CrontaskCommand.php
+++ b/src/app/CliTools/Console/Command/System/CrontaskCommand.php
@@ -21,7 +21,7 @@
*/
use CliTools\Utility\UnixUtility;
-use CliTools\Console\Builder\SelfCommandBuilder;
+use CliTools\Shell\CommandBuilder\SelfCommandBuilder;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -38,7 +38,8 @@ class CrontaskCommand extends \CliTools\Console\Command\AbstractCommand implemen
* Configure command
*/
protected function configure() {
- $this->setName('system:crontask')
+ $this
+ ->setName('system:crontask')
->setDescription('System cron task');
}
@@ -73,6 +74,8 @@ protected function setupBanner() {
file_put_contents('/etc/issue', $outputIssue);
file_put_contents('/etc/motd', $output);
+
+ UnixUtility::reloadTtyBanner('tty1');
}
/**
diff --git a/src/app/CliTools/Console/Command/System/EnvCommand.php b/src/app/CliTools/Console/Command/System/EnvCommand.php
index cc988dd..af76e5b 100644
--- a/src/app/CliTools/Console/Command/System/EnvCommand.php
+++ b/src/app/CliTools/Console/Command/System/EnvCommand.php
@@ -30,7 +30,8 @@ class EnvCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('system:env')
+ $this
+ ->setName('system:env')
->setDescription('List environment variables');
}
diff --git a/src/app/CliTools/Console/Command/System/OpenFilesCommand.php b/src/app/CliTools/Console/Command/System/OpenFilesCommand.php
index 999f82e..5979602 100644
--- a/src/app/CliTools/Console/Command/System/OpenFilesCommand.php
+++ b/src/app/CliTools/Console/Command/System/OpenFilesCommand.php
@@ -21,7 +21,7 @@
*/
use CliTools\Utility\FormatUtility;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
@@ -33,7 +33,8 @@ class OpenFilesCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('system:openfiles')
+ $this
+ ->setName('system:openfiles')
->setDescription('List swap usage');
}
diff --git a/src/app/CliTools/Console/Command/System/RebootCommand.php b/src/app/CliTools/Console/Command/System/RebootCommand.php
index 06c1c1b..36bbc0c 100644
--- a/src/app/CliTools/Console/Command/System/RebootCommand.php
+++ b/src/app/CliTools/Console/Command/System/RebootCommand.php
@@ -22,7 +22,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
class RebootCommand extends \CliTools\Console\Command\AbstractCommand {
@@ -30,7 +30,8 @@ class RebootCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('system:reboot')
+ $this
+ ->setName('system:reboot')
->setAliases(array('reboot'))
->setDescription('Reboot system');
}
diff --git a/src/app/CliTools/Console/Command/System/ShutdownCommand.php b/src/app/CliTools/Console/Command/System/ShutdownCommand.php
index 88938ae..317ca11 100644
--- a/src/app/CliTools/Console/Command/System/ShutdownCommand.php
+++ b/src/app/CliTools/Console/Command/System/ShutdownCommand.php
@@ -22,7 +22,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
class ShutdownCommand extends \CliTools\Console\Command\AbstractCommand {
@@ -30,7 +30,8 @@ class ShutdownCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('system:shutdown')
+ $this
+ ->setName('system:shutdown')
->setAliases(array('shutdown'))
->setDescription('Shutdown system');
}
diff --git a/src/app/CliTools/Console/Command/System/StartupCommand.php b/src/app/CliTools/Console/Command/System/StartupCommand.php
index cd96e97..f6dcaaa 100644
--- a/src/app/CliTools/Console/Command/System/StartupCommand.php
+++ b/src/app/CliTools/Console/Command/System/StartupCommand.php
@@ -20,9 +20,10 @@
* along with this program. If not, see .
*/
+use CliTools\Utility\UnixUtility;
use CliTools\Database\DatabaseConnection;
-use CliTools\Console\Builder\CommandBuilder;
-use CliTools\Console\Builder\SelfCommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\SelfCommandBuilder;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -32,7 +33,8 @@ class StartupCommand extends \CliTools\Console\Command\AbstractCommand implement
* Configure command
*/
protected function configure() {
- $this->setName('system:startup')
+ $this
+ ->setName('system:startup')
->setDescription('System startup task');
}
@@ -64,6 +66,8 @@ protected function setupBanner() {
file_put_contents('/etc/issue', $outputIssue);
file_put_contents('/etc/motd', $output);
+
+ UnixUtility::reloadTtyBanner('tty1');
}
/**
diff --git a/src/app/CliTools/Console/Command/System/SwapCommand.php b/src/app/CliTools/Console/Command/System/SwapCommand.php
index baf24ad..d0db100 100644
--- a/src/app/CliTools/Console/Command/System/SwapCommand.php
+++ b/src/app/CliTools/Console/Command/System/SwapCommand.php
@@ -33,7 +33,8 @@ class SwapCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('system:swap')
+ $this
+ ->setName('system:swap')
->setDescription('List swap usage');
}
diff --git a/src/app/CliTools/Console/Command/System/UpdateCommand.php b/src/app/CliTools/Console/Command/System/UpdateCommand.php
index 71b0637..c39bcb3 100644
--- a/src/app/CliTools/Console/Command/System/UpdateCommand.php
+++ b/src/app/CliTools/Console/Command/System/UpdateCommand.php
@@ -21,8 +21,8 @@
*/
use CliTools\Service\SelfUpdateService;
-use CliTools\Console\Builder\CommandBuilder;
-use CliTools\Console\Builder\SelfCommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\SelfCommandBuilder;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -32,7 +32,8 @@ class UpdateCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('system:update')
+ $this
+ ->setName('system:update')
->setAliases(array('update'))
->setDescription('Update system');
}
@@ -86,7 +87,7 @@ protected function userUpdate(InputInterface $input, OutputInterface $output) {
$command = new CommandBuilder('git', 'pull');
$command->executeInteractive();
- $command = new \CliTools\Console\Builder\SelfCommandBuilder();
+ $command = new \CliTools\Shell\CommandBuilder\SelfCommandBuilder();
$command->addArgument('user:rebuildsshconfig');
$command->executeInteractive();
} catch (\RuntimeException $e) {
diff --git a/src/app/CliTools/Console/Command/System/VersionCommand.php b/src/app/CliTools/Console/Command/System/VersionCommand.php
index a41a181..c0205b4 100644
--- a/src/app/CliTools/Console/Command/System/VersionCommand.php
+++ b/src/app/CliTools/Console/Command/System/VersionCommand.php
@@ -21,7 +21,7 @@
*/
use CliTools\Database\DatabaseConnection;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
use CliTools\Utility\UnixUtility;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
@@ -33,7 +33,8 @@ class VersionCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('system:version')
+ $this
+ ->setName('system:version')
->setDescription('List common version');
}
diff --git a/src/app/CliTools/Console/Command/TYPO3/BeUserCommand.php b/src/app/CliTools/Console/Command/TYPO3/BeUserCommand.php
index f1aa756..631c29b 100644
--- a/src/app/CliTools/Console/Command/TYPO3/BeUserCommand.php
+++ b/src/app/CliTools/Console/Command/TYPO3/BeUserCommand.php
@@ -33,7 +33,8 @@ class BeUserCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('typo3:beuser')
+ $this
+ ->setName('typo3:beuser')
->setDescription('Add backend admin user to database')
->addArgument(
'database',
@@ -74,6 +75,8 @@ public function execute(InputInterface $input, OutputInterface $output) {
$username = $input->getArgument('user');
$password = $input->getArgument('password');
+ $output->writeln('Injecting TYPO3 backend user
');
+
// Set default user if not specified
if (empty($username)) {
$username = 'dev';
@@ -81,18 +84,18 @@ public function execute(InputInterface $input, OutputInterface $output) {
// check username
if (!preg_match('/^[-_a-zA-Z0-9\.]+$/', $username)) {
- $output->writeln('Invalid username');
+ $output->writeln('Invalid username');
return 1;
}
- $output->writeln('Using user: "' . htmlspecialchars($username) . '"');
+ $output->writeln('Using user: "' . htmlspecialchars($username) . '"
');
// Set default password if not specified
if (empty($password)) {
$password = 'dev';
}
- $output->writeln('Using pass: "' . htmlspecialchars($password) . '"');
+ $output->writeln('Using pass: "' . htmlspecialchars($password) . '"
');
// ##################
// Salting
@@ -101,11 +104,11 @@ public function execute(InputInterface $input, OutputInterface $output) {
if ($input->getOption('plain')) {
// Standard md5
$password = Typo3Utility::generatePassword($password, Typo3Utility::PASSWORD_TYPE_MD5);
- $this->output->writeln('Generating plain (non salted) md5 password');
+ $this->output->writeln('Generating plain (non salted) md5 password
');
} else {
// Salted md5
$password = Typo3Utility::generatePassword($password, Typo3Utility::PASSWORD_TYPE_MD5_SALTED);
- $this->output->writeln('Generating salted md5 password');
+ $this->output->writeln('Generating salted md5 password
');
}
// ##############
@@ -117,20 +120,12 @@ public function execute(InputInterface $input, OutputInterface $output) {
// All databases
// ##############
- // Get list of databases
- $query = 'SELECT SCHEMA_NAME
- FROM information_schema.SCHEMATA';
- $databaseList = DatabaseConnection::getCol($query);
+ $databaseList = DatabaseConnection::databaseList();
$dbFound = false;
foreach ($databaseList as $dbName) {
- // Skip internal mysql databases
- if (in_array(strtolower($dbName), array('mysql', 'information_schema', 'performance_schema'))) {
- continue;
- }
-
// Check if database is TYPO3 instance
- $query = 'SELECT COUNT(*) as count
+ $query = 'SELECT COUNT(*) as count
FROM information_schema.tables
WHERE table_schema = ' . DatabaseConnection::quote($dbName) . '
AND table_name = \'be_users\'';
@@ -143,7 +138,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
}
if (!$dbFound) {
- $output->writeln('No valid TYPO3 database found');
+ $output->writeln('No valid TYPO3 database found');
}
} else {
// ##############
@@ -206,13 +201,13 @@ protected function setTypo3UserForDatabase($database, $username, $password) {
try {
// Get uid from current dev user (if already existing)
$query = 'SELECT uid
- FROM `' . DatabaseConnection::sanitizeSqlDatabase($database) . '`.be_users
+ FROM ' . DatabaseConnection::sanitizeSqlDatabase($database) . '.be_users
WHERE username = ' . DatabaseConnection::quote($username) . '
AND deleted = 0';
$beUserId = DatabaseConnection::getOne($query);
// Insert or update user in TYPO3 database
- $query = 'INSERT INTO `' . DatabaseConnection::sanitizeSqlDatabase($database) . '`.be_users
+ $query = 'INSERT INTO ' . DatabaseConnection::sanitizeSqlDatabase($database) . '.be_users
(uid, tstamp, crdate, realName, username, password, TSconfig, admin, disable, starttime, endtime)
VALUES(
' . DatabaseConnection::quote($beUserId) . ',
@@ -236,12 +231,12 @@ protected function setTypo3UserForDatabase($database, $username, $password) {
DatabaseConnection::exec($query);
if ($beUserId) {
- $this->output->writeln('User successfully updated to "' . $database . '"');
+ $this->output->writeln('User successfully updated to "' . $database . '"
');
} else {
- $this->output->writeln('User successfully added to "' . $database . '"');
+ $this->output->writeln('User successfully added to "' . $database . '"
');
}
} catch (\Exception $e) {
- $this->output->writeln('User adding failed');
+ $this->output->writeln('User adding failed');
}
}
}
diff --git a/src/app/CliTools/Console/Command/TYPO3/CleanupCommand.php b/src/app/CliTools/Console/Command/TYPO3/CleanupCommand.php
index 056ee58..a8bbdb6 100644
--- a/src/app/CliTools/Console/Command/TYPO3/CleanupCommand.php
+++ b/src/app/CliTools/Console/Command/TYPO3/CleanupCommand.php
@@ -31,7 +31,8 @@ class CleanupCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('typo3:cleanup')
+ $this
+ ->setName('typo3:cleanup')
->setDescription('Cleanup caches, logs and indexed search')
->addArgument(
'db',
@@ -54,6 +55,8 @@ public function execute(InputInterface $input, OutputInterface $output) {
// ##################
$dbName = $input->getArgument('db');
+ $output->writeln('Cleanup TYPO3 database
');
+
// ##############
// Loop through databases
// ##############
@@ -104,10 +107,7 @@ protected function cleanupTypo3Database($database) {
$cleanupTableList = array();
// Check if database is TYPO3 instance
- $query = 'SELECT table_name
- FROM information_schema.tables
- WHERE table_schema = ' . DatabaseConnection::quote($database);
- $tableList = DatabaseConnection::getCol($query);
+ $tableList = DatabaseConnection::tableList($database);
foreach ($tableList as $table) {
$clearTable = false;
@@ -165,19 +165,19 @@ protected function cleanupTypo3Database($database) {
}
}
- $this->output->writeln('Starting cleanup of database ' . $database . '...');
+ $this->output->writeln('Starting cleanup of database "' . $database . '"
');
- DatabaseConnection::exec('USE `' . $database . '`');
+ DatabaseConnection::switchDatabase(DatabaseConnection::sanitizeSqlDatabase($database));
foreach ($cleanupTableList as $table) {
- $query = 'TRUNCATE `' . $table . '`';
+ $query = 'TRUNCATE ' . DatabaseConnection::sanitizeSqlTable($table);
DatabaseConnection::exec($query);
if ($this->output->isVerbose()) {
- $this->output->writeln(' -> Truncating table ' . $table . '');
+ $this->output->writeln('Truncating table ' . $table . '
');
}
}
- $this->output->writeln(' -> finished');
+ $this->output->writeln('finished
');
}
}
diff --git a/src/app/CliTools/Console/Command/TYPO3/ClearCacheCommand.php b/src/app/CliTools/Console/Command/TYPO3/ClearCacheCommand.php
index b7ec986..f5c161c 100644
--- a/src/app/CliTools/Console/Command/TYPO3/ClearCacheCommand.php
+++ b/src/app/CliTools/Console/Command/TYPO3/ClearCacheCommand.php
@@ -21,7 +21,7 @@
*/
use CliTools\Utility\Typo3Utility;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -32,7 +32,8 @@ class ClearCacheCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('typo3:clearcache')
+ $this
+ ->setName('typo3:clearcache')
->setDescription('Clear cache on all (or one specific) TYPO3 instances')
->addArgument(
'path',
@@ -56,6 +57,8 @@ public function execute(InputInterface $input, OutputInterface $output) {
$basePath = $this->getApplication()->getConfigValue('config', 'www_base_path', '/var/www/');
$maxDepth = 3;
+ $output->writeln('Clear TYPO3 cache
');
+
if ($input->getArgument('path')) {
$basePath = $input->getArgument('path');
}
@@ -67,7 +70,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// Check if coreapi is installed
if (!is_dir($dirPath . '/typo3conf/ext/coreapi/')) {
- $output->writeln('EXT:coreapi is missing on ' . $dirPath . ', skipping');
+ $output->writeln('EXT:coreapi is missing on ' . $dirPath . ', skipping
');
continue;
}
@@ -77,14 +80,14 @@ public function execute(InputInterface $input, OutputInterface $output) {
'cache:clearallcaches'
);
- $output->writeln('Running clearcache command on ' . $dirPath . '');
+ $output->writeln('Running clearcache command on ' . $dirPath . '
');
try {
$command = new CommandBuilder('php');
$command->setArgumentList($params)
->executeInteractive();
} catch (\Exception $e) {
- $output->writeln(' Failed with exception: ' . $e->getMessage() . '');
+ $output->writeln(' Failed with exception: ' . $e->getMessage() . '');
}
}
diff --git a/src/app/CliTools/Console/Command/TYPO3/DomainCommand.php b/src/app/CliTools/Console/Command/TYPO3/DomainCommand.php
index 63b8a80..321423e 100644
--- a/src/app/CliTools/Console/Command/TYPO3/DomainCommand.php
+++ b/src/app/CliTools/Console/Command/TYPO3/DomainCommand.php
@@ -21,6 +21,7 @@
*/
use CliTools\Database\DatabaseConnection;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -31,12 +32,37 @@ class DomainCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('typo3:domain')
+ $this
+ ->setName('typo3:domain')
->setDescription('Add common development domains to database')
->addArgument(
'db',
InputArgument::OPTIONAL,
'Database name'
+ )
+ ->addOption(
+ 'baseurl',
+ null,
+ InputOption::VALUE_NONE,
+ 'Also set config.baseURL setting'
+ )
+ ->addOption(
+ 'list',
+ null,
+ InputOption::VALUE_NONE,
+ 'List only databases'
+ )
+ ->addOption(
+ 'remove',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Remove domain (with wildcard support)'
+ )
+ ->addOption(
+ 'duplicate',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Add duplication domains (will duplicate all domains in system, eg. for vagrant share)'
);
}
@@ -54,6 +80,8 @@ public function execute(InputInterface $input, OutputInterface $output) {
// ##################
$dbName = $input->getArgument('db');
+ $output->writeln('Updating TYPO3 domain entries
');
+
// ##############
// Loop through databases
// ##############
@@ -62,134 +90,168 @@ public function execute(InputInterface $input, OutputInterface $output) {
// ##############
// All databases
// ##############
-
- // Get list of databases
- $query = 'SELECT SCHEMA_NAME
- FROM information_schema.SCHEMATA';
- $databaseList = DatabaseConnection::getCol($query);
+ $databaseList = DatabaseConnection::databaseList();
foreach ($databaseList as $dbName) {
- // Skip internal mysql databases
- if (in_array(strtolower($dbName), array('mysql', 'information_schema', 'performance_schema'))) {
- continue;
- }
-
// Check if database is TYPO3 instance
- $query = 'SELECT COUNT(*) as count
+ $query = 'SELECT COUNT(*) as count
FROM information_schema.tables
WHERE table_schema = ' . DatabaseConnection::quote($dbName) . '
AND table_name = \'sys_domain\'';
$isTypo3Database = DatabaseConnection::getOne($query);
if ($isTypo3Database) {
- $this->setupDevelopmentDomainsForDatabase($dbName);
+ $this->runTaskForDomain($dbName);
}
}
} else {
// ##############
// One databases
// ##############
- $this->setupDevelopmentDomainsForDatabase($dbName);
+ $this->runTaskForDomain($dbName);
}
-
- return 0;
}
/**
- * Set development domains for TYPO3 database
- *
- * @param string $database Database
- *
- * @return void
+ * Run tasks for one domain
*/
- protected function setupDevelopmentDomainsForDatabase($database) {
-
- // ##################
- // Build domain
- // ##################
- $domain = null;
+ protected function runTaskForDomain($dbName) {
+ DatabaseConnection::switchDatabase($dbName);
- if (preg_match('/^([^_]+)_([^_]+).*/i', $database, $matches)) {
- $domain = $matches[2] . '.' . $matches[1];
+ if ($this->input->getOption('list')) {
+ // Show domain list (and skip all other tasks)
+ $this->showDomainList($dbName);
} else {
- return false;
- }
+ // Remove domains (eg. for cleanup)
+ if ($this->input->getOption('remove')) {
+ $this->removeDomains($this->input->getOption('remove'));
+ }
- // ##################
- // Check if multi site
- // ##################
- $isMultiSite = false;
+ // Set development domains
+ $this->manipulateDomains();
- $query = 'SELECT uid
- FROM ' . DatabaseConnection::sanitizeSqlDatabase($database) . '.pages
- WHERE is_siteroot = 1
- AND deleted = 0';
- $rootPageSiteList = DatabaseConnection::getCol($query);
+ // Add sharing domains
+ if ($this->input->getOption('baseurl')) {
+ $this->updateBaseUrlConfig();
+ }
+
+ // Add sharing domains
+ if ($this->input->getOption('duplicate')) {
+ $this->addDuplicateDomains($this->input->getOption('duplicate'));
+ }
- if (count($rootPageSiteList) >= 2) {
- $isMultiSite = true;
+ // Show domain list
+ $this->showDomainList($dbName);
}
+ }
- // ##################
- // Disable all other domains
- // ##################
- $query = 'UPDATE ' . DatabaseConnection::sanitizeSqlDatabase($database) . '.sys_domain
- SET hidden = 1';
+ /**
+ * Remove domains
+ */
+ protected function removeDomains($pattern) {
+ $pattern = str_replace('*', '%', $pattern);
+
+ $query = 'DELETE FROM sys_domain WHERE domainName LIKE %s';
+ $query = sprintf($query, DatabaseConnection::quote($pattern));
DatabaseConnection::exec($query);
+ }
+ /**
+ * Update baseURL config
+ */
+ protected function updateBaseUrlConfig() {
+ $query = 'SELECT st.uid as template_id,
+ st.config as template_config,
+ (SELECT sd.domainName
+ FROM sys_domain sd
+ WHERE sd.pid = st.pid
+ ORDER BY sd.forced DESC,
+ sd.sorting ASC
+ LIMIT 1) as domain_name
+ FROM sys_template st
+ WHERE st.root = 1
+ AND st.deleted = 0
+ HAVING domain_name IS NOT NULL';
+ $templateIdList = DatabaseConnection::getAll($query);
- // Get development domains from config
- $tldList = (array)$this->getApplication()->getConfigValue('config', 'domain_dev', array());
+ foreach ($templateIdList as $row) {
+ $templateId = $row['template_id'];
+ $domainName = $row['domain_name'];
+ $templateConf = $row['template_config'];
- foreach ($tldList as $tld) {
- $fullDomain = $domain . '.' . $tld;
+ // Remove old baseURL entries (no duplciates)
+ $templateConf = preg_replace('/^config.baseURL = .*$/m', '', $templateConf);
+ $templateConf = trim($templateConf);
- // ##############
- // Loop through root pages
- // ##############
- foreach ($rootPageSiteList as $rootPageUid) {
- $rootPageDomain = $fullDomain;
+ // Add new baseURL
+ $templateConf .= "\n" . 'config.baseURL = http://' . $domainName .'/';
- // Add rootpage id to domain if TYPO3 instance is multi page
- // eg. 123.dev.foobar.dev
- if ($isMultiSite) {
- $rootPageDomain = $rootPageUid . '.' . $rootPageDomain;
- }
+ $query = 'UPDATE sys_template SET config = %s WHERE uid = %s';
+ $query = sprintf($query, DatabaseConnection::quote($templateConf), (int)$templateId);
+ DatabaseConnection::exec($query);
+ }
+ }
- // Check if we have already an entry
- $query = 'SELECT uid
- FROM ' . DatabaseConnection::sanitizeSqlDatabase($database) . '.sys_domain
- WHERE pid = ' . (int)$rootPageUid . '
- AND domainName = ' . DatabaseConnection::quote($rootPageDomain);
- $sysDomainId = DatabaseConnection::getOne($query);
-
- // Add/Update domain
- $query = 'INSERT INTO ' . DatabaseConnection::sanitizeSqlDatabase($database) . '.sys_domain
- (uid, pid, tstamp, crdate, cruser_id, hidden, domainName, sorting, forced)
- VALUES (
- ' . (int)$sysDomainId . ',
- ' . (int)$rootPageUid . ',
- ' . time() . ',
- ' . time() . ',
- 1,
- 0,
- ' . DatabaseConnection::quote($rootPageDomain) . ',
- 1,
- 1
- ) ON DUPLICATE KEY UPDATE
- pid = VALUES(pid),
- hidden = VALUES(hidden),
- domainName = VALUES(domainName),
- sorting = VALUES(sorting),
- forced = VALUES(forced)';
- DatabaseConnection::exec($query);
-
- if ($sysDomainId) {
- $this->output->writeln('Domain "' . $rootPageDomain . '" updated to "' . $database . '"');
- } else {
- $this->output->writeln('Domain "' . $rootPageDomain . '" added to "' . $database . '"');
- }
- }
+ /**
+ * Add share domains (eg. for vagrantshare)
+ *
+ * @param string $suffix Domain suffix
+ */
+ protected function addDuplicateDomains($suffix) {
+ $devDomain = '.' . $this->getApplication()->getConfigValue('config', 'domain_dev');
+
+ $query = 'SELECT * FROM sys_domain';
+ $domainList = DatabaseConnection::getAll($query);
+
+ foreach ($domainList as $domain) {
+ unset($domain['uid']);
+
+ $domainName = $domain['domainName'];
+
+ // remove development suffix
+ $domainName = preg_replace('/' . preg_quote($devDomain). '$/', '', $domainName);
+
+ // add share domain
+ $domainName .= '.' . ltrim($suffix, '.');
+
+ $domain['domainName'] = $domainName;
+
+ DatabaseConnection::insert('sys_domain', $domain);
+ }
+ }
+
+ /**
+ * Show list of domains
+ *
+ * @param string $dbName Domain name
+ */
+ protected function showDomainList($dbName) {
+ $query = 'SELECT domainName FROM sys_domain ORDER BY domainName ASC';
+ $domainList = DatabaseConnection::getCol($query);
+
+ $this->output->writeln('Domain list of "' . $dbName . '":
');
+
+ foreach ($domainList as $domain) {
+ $this->output->writeln(' ' . $domain . '
');
}
+ $this->output->writeln('');
+ }
+
+ /**
+ * Set development domains for TYPO3 database
+ *
+ * @return void
+ */
+ protected function manipulateDomains() {
+ $devDomain = '.' . $this->getApplication()->getConfigValue('config', 'domain_dev');
+ $domainLength = strlen($devDomain);
+
+ // ##################
+ // Fix domains
+ // ##################
+ $query = 'UPDATE sys_domain
+ SET domainName = CONCAT(domainName, ' . DatabaseConnection::quote($devDomain) . ')
+ WHERE RIGHT(domainName, ' . $domainLength . ') <> ' . DatabaseConnection::quote($devDomain);
+ DatabaseConnection::exec($query);
}
}
diff --git a/src/app/CliTools/Console/Command/TYPO3/InstallerCommand.php b/src/app/CliTools/Console/Command/TYPO3/InstallerCommand.php
index 3827dd7..c827bfd 100644
--- a/src/app/CliTools/Console/Command/TYPO3/InstallerCommand.php
+++ b/src/app/CliTools/Console/Command/TYPO3/InstallerCommand.php
@@ -32,7 +32,8 @@ class InstallerCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('typo3:installer')
+ $this
+ ->setName('typo3:installer')
->setDescription('Enable installer on all (or one specific) TYPO3 instances')
->addArgument(
'path',
diff --git a/src/app/CliTools/Console/Command/TYPO3/ListCommand.php b/src/app/CliTools/Console/Command/TYPO3/ListCommand.php
index d77a071..9b141aa 100644
--- a/src/app/CliTools/Console/Command/TYPO3/ListCommand.php
+++ b/src/app/CliTools/Console/Command/TYPO3/ListCommand.php
@@ -32,7 +32,8 @@ class ListCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('typo3:list')
+ $this
+ ->setName('typo3:list')
->setDescription('List all TYPO3 instances')
->addArgument(
'path',
diff --git a/src/app/CliTools/Console/Command/TYPO3/SchedulerCommand.php b/src/app/CliTools/Console/Command/TYPO3/SchedulerCommand.php
index ceca306..83f5d66 100644
--- a/src/app/CliTools/Console/Command/TYPO3/SchedulerCommand.php
+++ b/src/app/CliTools/Console/Command/TYPO3/SchedulerCommand.php
@@ -21,7 +21,7 @@
*/
use CliTools\Utility\Typo3Utility;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -32,13 +32,14 @@ class SchedulerCommand extends \CliTools\Console\Command\AbstractCommand {
* Configure command
*/
protected function configure() {
- $this->setName('typo3:scheduler')
+ $this
+ ->setName('typo3:scheduler')
->setDescription('Run scheduler on all (or one specific) TYPO3 instances')
->addArgument(
'path',
InputArgument::OPTIONAL,
'Path to TYPO3 instance'
- );
+);
}
/**
diff --git a/src/app/CliTools/Console/Command/User/RebuildSshConfigCommand.php b/src/app/CliTools/Console/Command/User/RebuildSshConfigCommand.php
index 8e9fb84..8adcf21 100644
--- a/src/app/CliTools/Console/Command/User/RebuildSshConfigCommand.php
+++ b/src/app/CliTools/Console/Command/User/RebuildSshConfigCommand.php
@@ -29,7 +29,8 @@ class RebuildSshConfigCommand extends \CliTools\Console\Command\AbstractCommand
* Configure command
*/
protected function configure() {
- $this->setName('user:rebuildsshconfig')
+ $this
+ ->setName('user:rebuildsshconfig')
->setDescription('Rebuild SSH Config for current user');
}
@@ -45,7 +46,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
$userHome = getenv('HOME');
$userName = getenv('USER');
- $output->writeln('Rebuilding ~/.ssh/config ...');
+ $output->writeln('Rebuilding ~/.ssh/config ...
');
$targetConfFile = $userHome . '/.ssh/config';
$userConfFile = $userHome . '/.ssh/config.user';
@@ -77,13 +78,13 @@ public function execute(InputInterface $input, OutputInterface $output) {
$confContent[] = '# from: ' . $defaultUserFile;
$confContent[] = file_get_contents($defaultUserFile);
- $output->writeln('Using user defaults from ' . $defaultUserFile . '');
+ $output->writeln('Using user defaults from ' . $defaultUserFile . '
');
} elseif (file_exists($defaultFile)) {
// System default
$confContent[] = '# from: ' . $defaultFile;
$confContent[] = file_get_contents($defaultFile);
- $output->writeln('Using system defaults from ' . $defaultFile . '');
+ $output->writeln('Using system defaults from ' . $defaultFile . '
');
} else {
// No default found, provide at least good defaults
$confContent[] = '# from: no config';
@@ -95,7 +96,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
$confContent[] = ' ServerAliveInterval 60';
$confContent[] = ' ForwardAgent no';
- $output->writeln('No defaults found, setting internal defaults');
+ $output->writeln('No defaults found, setting internal defaults
');
}
$confContent[] = '';
@@ -131,7 +132,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
$confContent[] = file_get_contents($filePath);
$confContent[] = '';
- $output->writeln('Using ' . $filePath . '');
+ $output->writeln('Using ' . $filePath . '
');
}
}
@@ -146,7 +147,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
if (file_exists($userConfFile)) {
$confContent[] = file_get_contents($userConfFile);
- $output->writeln('Using ' . $userConfFile . '');
+ $output->writeln('Using ' . $userConfFile . '
');
}
$confContent[] = '';
@@ -166,7 +167,7 @@ public function execute(InputInterface $input, OutputInterface $output) {
// Write file
file_put_contents($targetConfFile, $confContent);
- $output->writeln('Finished rebuilding ssh configuration');
+ $output->writeln('Finished rebuilding ssh configuration
');
return 0;
}
diff --git a/src/app/CliTools/Console/Command/Vagrant/ShareCommand.php b/src/app/CliTools/Console/Command/Vagrant/ShareCommand.php
new file mode 100644
index 0000000..2c97fa3
--- /dev/null
+++ b/src/app/CliTools/Console/Command/Vagrant/ShareCommand.php
@@ -0,0 +1,187 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+use CliTools\Shell\CommandBuilder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\SelfCommandBuilder;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ShareCommand extends \CliTools\Console\Command\AbstractCommand {
+
+ /**
+ * Configure command
+ */
+ protected function configure() {
+ $this
+ ->setName('vagrant:share')
+ ->setDescription('Start share for vagrant')
+ ->addArgument(
+ 'name',
+ InputArgument::OPTIONAL,
+ 'Specific name for the share'
+ )
+ ->addOption(
+ 'http',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Local HTTP port to forward to'
+ )
+ ->addOption(
+ 'https',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Local HTTPS port to forward to'
+ )
+ ->addOption(
+ 'name',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Specific name for the share'
+ )
+ ->addOption(
+ 'ssh',
+ null,
+ InputOption::VALUE_NONE,
+ 'Allow \'vagrant connect --ssh\' access'
+ )
+ ->addOption(
+ 'ssh-no-password',
+ null,
+ InputOption::VALUE_NONE,
+ 'Key won\'t be encrypted with --ssh'
+ )
+ ->addOption(
+ 'ssh-port',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'Specific port for SSH when using --ssh'
+ )
+ ->addOption(
+ '--ssh-once',
+ null,
+ InputOption::VALUE_NONE,
+ 'Allow \'vagrant connect --ssh\' only one time'
+ );
+ }
+
+ /**
+ * Execute command
+ *
+ * @param InputInterface $input Input instance
+ * @param OutputInterface $output Output instance
+ *
+ * @return int|null|void
+ */
+ public function execute(InputInterface $input, OutputInterface $output) {
+
+ $runningCallback = function($process, $status) {
+ static $domainFound = false;
+ if ($domainFound) {
+ return;
+ }
+
+ $pid = $status['pid'];
+
+ exec('pgrep -P ' . (int)$pid . ' | xargs ps -o command=', $output);
+
+ if (!empty($output)) {
+ foreach ($output as $line) {
+ if (preg_match('/register\.vagrantshare\.com/', $line)) {
+
+ if (preg_match('/-name ([^\s]+)/', $line, $matches)) {
+ $domainName = $matches[1];
+
+ $typo3Domain = new SelfCommandBuilder();
+ $typo3Domain
+ ->addArgument('typo3:domain')
+ ->addArgumentTemplate('--remove=%s', '*.vagrantshare.com')
+ ->addArgumentTemplate('--duplicate=%s', $domainName . '.vagrantshare.com')
+ ->execute();
+
+ $domainFound = true;
+ }
+ }
+ }
+ }
+ };
+
+ $cleanupCallback = function() {
+ $typo3Domain = new SelfCommandBuilder();
+ $typo3Domain
+ ->addArgument('typo3:domain')
+ ->addArgumentTemplate('--remove=%s', '*.vagrantshare.com')
+ ->execute();
+ };
+ $this->getApplication()->registerTearDown($cleanupCallback);
+
+ $opts = array(
+ 'runningCallback' => $runningCallback,
+ );
+
+ $vagrant = new CommandBuilder('vagrant', 'share');
+
+ // Share name
+ if ($input->getOption('name')) {
+ $vagrant->addArgumentTemplate('--name %s', $input->getOption('name'));
+ } elseif ($input->getArgument('name')) {
+ $vagrant->addArgumentTemplate('--name %s', $input->getArgument('name'));
+ }
+
+
+ // HTTP port
+ if ($input->getOption('http')) {
+ $vagrant->addArgumentTemplate('--http %s', $input->getOption('http'));
+ } else {
+ $vagrant->addArgumentTemplate('--http %s', 80);
+ }
+
+ // HTTPS port
+ if ($input->getOption('https')) {
+ $vagrant->addArgumentTemplate('--http %s', $input->getOption('https'));
+ } else {
+ $vagrant->addArgumentTemplate('--https %s', 443);
+ }
+
+
+ // SSH stuff
+ if ($input->getOption('ssh')) {
+ $vagrant->addArgument('--ssh');
+ }
+
+ if ($input->getOption('ssh-no-password')) {
+ $vagrant->addArgument('--ssh-no-password');
+ }
+
+ if ($input->getOption('ssh-port')) {
+ $vagrant->addArgumentTemplate('--ssh-port %s', $input->getOption('ssh-port'));
+ }
+
+ if ($input->getOption('ssh-once')) {
+ $vagrant->addArgument('--ssh-once');
+ }
+
+
+ $vagrant->executeInteractive($opts);
+ }
+}
diff --git a/src/app/CliTools/Console/Formatter/OutputFormatterStyle.php b/src/app/CliTools/Console/Formatter/OutputFormatterStyle.php
new file mode 100644
index 0000000..17024a2
--- /dev/null
+++ b/src/app/CliTools/Console/Formatter/OutputFormatterStyle.php
@@ -0,0 +1,164 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+class OutputFormatterStyle extends \Symfony\Component\Console\Formatter\OutputFormatterStyle {
+
+ protected static $availableForegroundColors = array(
+ 'black' => array('set' => 30, 'unset' => 39),
+ 'red' => array('set' => 31, 'unset' => 39),
+ 'green' => array('set' => 32, 'unset' => 39),
+ 'yellow' => array('set' => 33, 'unset' => 39),
+ 'blue' => array('set' => 34, 'unset' => 39),
+ 'magenta' => array('set' => 35, 'unset' => 39),
+ 'cyan' => array('set' => 36, 'unset' => 39),
+ 'white' => array('set' => 37, 'unset' => 39),
+ );
+ protected static $availableBackgroundColors = array(
+ 'black' => array('set' => 40, 'unset' => 49),
+ 'red' => array('set' => 41, 'unset' => 49),
+ 'green' => array('set' => 42, 'unset' => 49),
+ 'yellow' => array('set' => 43, 'unset' => 49),
+ 'blue' => array('set' => 44, 'unset' => 49),
+ 'magenta' => array('set' => 45, 'unset' => 49),
+ 'cyan' => array('set' => 46, 'unset' => 49),
+ 'white' => array('set' => 47, 'unset' => 49),
+ );
+ protected static $availableOptions = array(
+ 'bold' => array('set' => 1, 'unset' => 22),
+ 'underscore' => array('set' => 4, 'unset' => 24),
+ 'blink' => array('set' => 5, 'unset' => 25),
+ 'reverse' => array('set' => 7, 'unset' => 27),
+ 'conceal' => array('set' => 8, 'unset' => 28),
+ );
+
+ /**
+ * Padding
+ *
+ * @var null|integer
+ */
+ protected $padding;
+
+ /**
+ * Padding
+ *
+ * @var null|integer
+ */
+ protected $paddingOutside;
+
+ /**
+ * Wrap
+ *
+ * @var null|string
+ */
+ protected $wrap;
+
+ /**
+ * Application
+ *
+ * @var \CliTools\Console\Application
+ */
+ protected $application;
+
+ /**
+ * Set padding
+ *
+ * @param integer|string $padding Padding
+ */
+ public function setPadding($padding) {
+ $this->padding = $padding;
+ }
+
+ /**
+ * Set padding
+ *
+ * @param integer|string $padding Padding
+ */
+ public function setPaddingOutside($padding) {
+ $this->paddingOutside = $padding;
+ }
+
+ /**
+ * Set application
+ *
+ * @param \CliTools\Console\Application $app Application
+ */
+ public function setApplication(\CliTools\Console\Application $app) {
+ $this->application = $app;
+ }
+
+ /**
+ * Set wrap
+ *
+ * @param string $wrap Wrap value
+ */
+ public function setWrap($wrap) {
+ $this->wrap = $wrap;
+ }
+
+ /**
+ * Applies the style to a given text.
+ *
+ * @param string $text The text to style
+ *
+ * @return string
+ */
+ public function apply($text) {
+
+ $ret = $text;
+
+ // ##################
+ // Padding
+ // ##################
+
+ if (!empty($this->padding)) {
+ $ret = $this->padding . $ret;
+ }
+
+
+ // ##################
+ // Wrap
+ // ##################
+
+ if (!empty($this->wrap)) {
+ list($width) = $this->application->getTerminalDimensions();
+
+ $length = strlen($text);
+ $wrapLength = (int)($width - $length - 2)/2 * 0.5;
+
+ if ($wrapLength >= 1) {
+ $ret = str_repeat($this->wrap, $wrapLength) . ' '. $ret . ' ' . str_repeat($this->wrap, $wrapLength);
+ }
+ }
+
+ $ret = parent::apply($ret);
+
+ // ##################
+ // Padding
+ // ##################
+
+ if (!empty($this->paddingOutside)) {
+ $ret = $this->paddingOutside . $ret;
+ }
+
+ return $ret;
+ }
+}
diff --git a/src/app/CliTools/Database/DatabaseConnection.php b/src/app/CliTools/Database/DatabaseConnection.php
index 883a21e..fb2a178 100644
--- a/src/app/CliTools/Database/DatabaseConnection.php
+++ b/src/app/CliTools/Database/DatabaseConnection.php
@@ -76,6 +76,15 @@ public static function setDsn($dsn, $username = null, $password = null) {
self::$connection = null;
}
+ /**
+ * Get Db DSN
+ *
+ * @return string
+ */
+ public static function getDsn() {
+ return self::$dbDsn;
+ }
+
/**
* Get Db Username
*
@@ -94,6 +103,25 @@ public static function getDbPassword() {
return self::$dbPassword;
}
+ /**
+ * Get Db Hostname
+ *
+ * @return string
+ */
+ public static function getDbHostname() {
+ return self::parseDsnValue('host');
+ }
+
+ /**
+ * Get Db Port
+ *
+ * @return string
+ */
+ public static function getDbPort() {
+ return self::parseDsnValue('port');
+ }
+
+
/**
* Get connection
*
@@ -121,6 +149,24 @@ public static function getConnection() {
return self::$connection;
}
+
+ /**
+ * Ping server
+ *
+ * @return bool
+ */
+ public static function ping() {
+ ConsoleUtility::verboseWriteln('DB::PING', null);
+ try {
+ self::getConnection()->query('SELECT 1');
+ } catch (\PDOException $e) {
+ ConsoleUtility::verboseWriteln('DB::QUERY::EXCEPTION', $e);
+ throw $e;
+ }
+
+ return true;
+ }
+
/**
* Execute SELECT query
*
@@ -142,6 +188,17 @@ public static function query($query) {
return $ret;
}
+ /**
+ * Switch database
+ *
+ * @param string $database Database
+ *
+ * @throws \PDOException
+ */
+ public static function switchDatabase($database) {
+ self::exec('USE ' . self::sanitizeSqlDatabase($database));
+ }
+
/**
* Execute INSERT/DELETE/UPDATE query
*
@@ -163,6 +220,29 @@ public static function exec($query) {
return $ret;
}
+
+ /**
+ * Generate and execute INSERT query
+ *
+ * @param string $table Table name
+ * @param array $values Values
+ *
+ * @return int
+ * @throws \PDOException
+ */
+ public static function insert($table, $values) {
+ $fieldList = array_keys($values);
+
+ $valueList = array();
+ foreach ($values as $value) {
+ $valueList[] = self::quote($value);
+ }
+
+ $query = 'INSERT INTO %s (%s) VALUES (%s)';
+ $query = sprintf($query, $table, implode(',',$fieldList), implode(',',$valueList));
+ self::exec($query);
+ }
+
/**
* Quote
*
@@ -358,6 +438,22 @@ public static function databaseExists($database) {
return ($ret === 1 );
}
+ /**
+ * Return list of databases
+ *
+ * @return array
+ */
+ public static function databaseList() {
+ // Get list of databases
+ $query = 'SELECT SCHEMA_NAME FROM information_schema.SCHEMATA';
+ $ret = DatabaseConnection::getCol($query);
+
+ // Filter mysql specific databases
+ $ret = array_diff($ret, array('mysql', 'information_schema', 'performance_schema'));
+
+ return $ret;
+ }
+
/**
* Return list of tables of one database
*
@@ -372,6 +468,22 @@ public static function tableList($database) {
return $ret;
}
+
+ /**
+ * Check if table exists in database
+ *
+ * @param string $database Database name
+ * @param string $table Table name
+ * @return boolean
+ */
+ public static function tableExists($database, $table) {
+ $query = 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s';
+ $query = sprintf($query, self::quote($database), self::quote($table) );
+ $ret = (bool)self::getOne($query);
+
+ return $ret;
+ }
+
/**
* Begin transaction
*/
@@ -487,7 +599,7 @@ public static function sanitizeSqlField($field) {
* @return string
*/
public static function sanitizeSqlTable($table) {
- return preg_replace('/[^_a-zA-Z0-9]/', '', $table);
+ return '`' . preg_replace('/[^_a-zA-Z0-9]/', '', $table) . '`';
}
/**
@@ -498,6 +610,24 @@ public static function sanitizeSqlTable($table) {
* @return string
*/
public static function sanitizeSqlDatabase($database) {
- return preg_replace('/[^_a-zA-Z0-9]/', '', $database);
+ return '`' . preg_replace('/[^_a-zA-Z0-9]/', '', $database) . '`';
+ }
+
+ /**
+ * Parse DSN and return value
+ *
+ * @param string $key DSN Key
+ * @param string|null $default Default value
+ * @return string|null
+ */
+ protected static function parseDsnValue($key, $default = NULL) {
+ $ret = $default;
+
+ $pattern = sprintf('~%s=([^;]*)(?:;|$)~', preg_quote($key, '~'));
+ if (preg_match($pattern, self::$dbDsn, $matches)) {
+ $ret = $matches[1];
+ }
+
+ return $ret;
}
}
diff --git a/src/app/CliTools/Exception/CommandExecutionException.php b/src/app/CliTools/Exception/CommandExecutionException.php
index 0896fda..9fa52d5 100644
--- a/src/app/CliTools/Exception/CommandExecutionException.php
+++ b/src/app/CliTools/Exception/CommandExecutionException.php
@@ -20,8 +20,7 @@
* along with this program. If not, see .
*/
-use CliTools\Console\Builder\CommandBuilder;
-use CliTools\Console\Builder\CommandBuilderInterface;
+use CliTools\Shell\CommandBuilder\CommandBuilderInterface;
class CommandExecutionException extends \RuntimeException {
diff --git a/src/app/bootstrap.php b/src/app/CliTools/Exception/StopException.php
similarity index 73%
rename from src/app/bootstrap.php
rename to src/app/CliTools/Exception/StopException.php
index 8c73e30..0d565b8 100644
--- a/src/app/bootstrap.php
+++ b/src/app/CliTools/Exception/StopException.php
@@ -1,4 +1,7 @@
@@ -17,14 +20,6 @@
* along with this program. If not, see .
*/
-error_reporting(E_ALL);
-
-// ####################################
-// Autoload
-// ####################################
+class StopException extends \RuntimeException {
-$loader = new Symfony\Component\ClassLoader\UniversalClassLoader();
-$loader->registerNamespaces(array(
- 'CliTools' => __DIR__
- ));
-$loader->register();
+}
diff --git a/src/app/CliTools/Reader/ConfigReader.php b/src/app/CliTools/Reader/ConfigReader.php
new file mode 100644
index 0000000..fa4ce16
--- /dev/null
+++ b/src/app/CliTools/Reader/ConfigReader.php
@@ -0,0 +1,199 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+class ConfigReader implements \ArrayAccess {
+
+ /**
+ * Data storage
+ *
+ * @var array
+ */
+ protected $data = array();
+
+ /**
+ * Constructor
+ *
+ * @param array $data Data configuration
+ */
+ public function __construct(array $data = null) {
+ if ($data !== null) {
+ $this->setData($data);
+ }
+ }
+
+ /**
+ * Set configuration data
+ *
+ * @param array $data Data configuration
+ */
+ public function setData(array $data) {
+ $this->data = $data;
+ }
+
+ /**
+ * Get value from specific node (dotted array notation)
+ *
+ * @param string|null $path Path to node (eg. foo.bar.baz)
+ * @return mixed|null
+ */
+ public function get($path = null) {
+ return $this->getNode($path);
+ }
+
+ /**
+ * Get array value from specific node (dotted array notation)
+ *
+ * @param string|null $path Path to node (eg. foo.bar.baz)
+ * @return array|null
+ */
+ public function getArray($path = null) {
+ $ret = $this->getNode($path);
+
+ if (!is_array($ret)) {
+ $ret = array();
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Get list of keys from specific node (dotted array notation)
+ *
+ * @param string|null $path Path to node (eg. foo.bar.baz)
+ * @return array|null
+ */
+ public function getList($path = null) {
+ $ret = $this->getNode($path);
+
+ if (is_array($ret)) {
+ $ret = array_keys($ret);
+ } else {
+ $ret = array();
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Set value to specific node (dotted array notation)
+ *
+ * @param string $path Path to node (eg. foo.bar.baz)
+ * @param mixed $value Value to set
+ */
+ public function set($path, $value) {
+ $node =& $this->getNode($path);
+ $node = $value;
+ }
+
+ /**
+ * Clear value at specific node (dotted array notation)
+ *
+ * @param null|string $path Path to node (eg. foo.bar.baz)
+ */
+ public function clear($path = null) {
+ $node =& $this->getNode($path);
+ $node = null;
+ }
+
+ /**
+ * Check if specific node exists
+ *
+ * @param null|string $path Path to node (eg. foo.bar.baz)
+ * @return bool
+ */
+ public function exists($path = null) {
+ return ($this->getNode($path) !== null);
+ }
+
+ /**
+ * Get node by reference
+ *
+ * @param string|null $path Path to node (eg. foo.bar.baz)
+ * @return mixed|null
+ */
+ protected function &getNode($path) {
+ $data = &$this->data;
+
+ if ($path !== null) {
+ $pathList = explode('.', $path);
+ foreach ($pathList as $node) {
+ if (isset($data[$node])) {
+ $data = &$data[$node];
+ } else {
+ unset($data);
+ $data = null;
+ break;
+ }
+ }
+ }
+
+ if ($data !== null) {
+ $ret = &$data;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Array accessor: Set value to offset
+ *
+ * @param string $offset Array key
+ * @param mixed $value Value
+ */
+ public function offsetSet($offset, $value) {
+ if ($offset === null) {
+ $this->data[] = $value;
+ } else {
+ $this->data[$offset] = $value;
+ }
+ }
+
+ /**
+ * Array accessor: Check if offset exists
+ *
+ * @param string $offset Array key
+ * @return boolean
+ */
+ public function offsetExists($offset) {
+ return isset($this->data[$offset]);
+ }
+
+ /**
+ * Array accessor: Unset offset
+ *
+ * @param string $offset Array key
+ */
+ public function offsetUnset($offset) {
+ unset($this->data[$offset]);
+ }
+
+ /**
+ * Array accessor: Get value at offset
+ *
+ * @param string $offset Array key
+ * @return mixed
+ */
+ public function offsetGet($offset) {
+ return isset($this->data[$offset]) ? $this->data[$offset] : null;
+ }
+
+}
diff --git a/src/app/CliTools/Service/SelfUpdateService.php b/src/app/CliTools/Service/SelfUpdateService.php
index c3d89e9..89a34c4 100644
--- a/src/app/CliTools/Service/SelfUpdateService.php
+++ b/src/app/CliTools/Service/SelfUpdateService.php
@@ -2,7 +2,7 @@
namespace CliTools\Service;
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
/*
* CliTools Command
@@ -23,6 +23,12 @@
*/
class SelfUpdateService {
+ /**
+ * Github repo url
+ *
+ * @var null|string
+ */
+ protected $githubRepo;
/**
* Update url
@@ -31,6 +37,27 @@ class SelfUpdateService {
*/
protected $updateUrl;
+ /**
+ * Version
+ *
+ * @var null|string
+ */
+ protected $updateVersion;
+
+ /**
+ * Changelog
+ *
+ * @var null|string
+ */
+ protected $updateChangelog;
+
+ /**
+ * Update github url
+ *
+ * @var null|string
+ */
+ protected $githubReleaseUrl;
+
/**
* Path to current clitools command
*
@@ -62,6 +89,13 @@ class SelfUpdateService {
*/
protected $application;
+ /**
+ * If pre releases should be used
+ *
+ * @var bool
+ */
+ protected $updateAllowPreRelease = false;
+
/**
* Constructor
*
@@ -75,6 +109,27 @@ public function __construct($app, $output) {
$this->collectInformations();
}
+ /**
+ * Enable prerelease versions (beta)
+ *
+ * @return $this
+ */
+ public function enablePreVersions() {
+ $this->updateAllowPreRelease = true;
+ return $this;
+ }
+
+ /**
+ * Enable update from old server
+ *
+ * @return $this
+ */
+ public function enableUpdateFallback() {
+ $this->updateUrl = $this->application->getConfigValue('config', 'update_fallback_url', null);
+ $this->updateVersion = 'fallback';
+ return $this;
+ }
+
/**
* Check if super user rights are required
*
@@ -92,29 +147,100 @@ public function isElevationNeeded() {
/**
* Update clitools command
+ *
+ * @param boolean $force Force update
*/
- public function update() {
- $this->updateUrl = $this->application->getConfigValue('config', 'self_update_url', null);
+ public function update($force = false) {
+
+ // Only ask for github if update url is not set
+ if (!$this->updateUrl) {
+ if (!empty($this->githubReleaseUrl)) {
+ $this->fetchLatestReleaseFromGithub();
+ } else {
+ throw new \RuntimeException('GitHub Release URL not set');
+ }
+ }
- if (empty($this->updateUrl)) {
- throw new \RuntimeException('Self-Update url is not set');
+ if ($this->checkIfUpdateNeeded($force)) {
+ // Update needed
+ $this->doUpdate();
+ }
+ }
+
+ /**
+ * Check if update is needed
+ *
+ * @param boolean $force Force update
+ *
+ * @return bool
+ */
+ protected function checkIfUpdateNeeded($force) {
+ $ret = false;
+
+ $this->output->write('Checking version... ');
+
+ // Check if version is equal
+ if ($this->updateVersion !== CLITOOLS_COMMAND_VERSION) {
+ $this->output->write('new version "' . $this->updateVersion . '" found');
+ $ret = true;
+ } else {
+ $this->output->write('already up to date');
}
- $this->output->writeln('Update URL: ' . $this->updateUrl . '');
+ // Check if update is forced
+ if ($force) {
+ $this->output->write(' [forced]');
+ $ret = true;
+ }
- $this->output->writeln('Download new clitools command version...');
- $this->downloadUpdate();
+ $this->output->writeln('');
+
+ return $ret;
+ }
+
+ /**
+ * Do update
+ */
+ protected function doUpdate() {
+ if (empty($this->updateUrl)) {
+ throw new \RuntimeException('Self-Update url is not found');
+ }
try {
- $versionString = $this->testUpdate();
+ // ##############
+ // Download
+ // ##############
+
+ $this->output->writeln('Update URL: ' . $this->updateUrl . '');
+
+ $this->output->write('Downloading.');
+ $this->downloadUpdate();
+ $this->output->writeln(' done');
+
+ // ##############
+ // Test
+ // ##############
+ $this->testUpdate();
- $this->output->writeln('Deploy update...');
+ // ##############
+ // Deploy
+ // ##############
+ $this->output->writeln('Deploying update... ');
$this->deployUpdate();
+ // ##############
+ // Summary
+ // ##############
+
+ // Version
$this->output->writeln('');
- $this->output->writeln('Updated to:');
- $this->output->writeln(' ' . $versionString);
+ $this->output->writeln('Updated from Version ' . CLITOOLS_COMMAND_VERSION . ' to ' . $this->updateVersion . '');
$this->output->writeln('');
+
+ // Changelog
+ if (!empty($this->updateChangelog)) {
+ $this->showChangelog();
+ }
} catch (\Exception $e) {
$this->output->writeln('Update failed');
}
@@ -122,6 +248,81 @@ public function update() {
$this->cleanup();
}
+ /**
+ * Fetch latest release from github api
+ */
+ protected function fetchLatestReleaseFromGithub() {
+ $this->output->write('Getting informations from GitHub... ');
+
+ $releaseList = \CliTools\Utility\PhpUtility::curlFetch($this->githubReleaseUrl);
+ $releaseList = json_decode($releaseList, true);
+
+ if (!empty($releaseList)) {
+ foreach ($releaseList as $release) {
+ // Check release
+ if (!empty($release['draft'])) {
+ // no valid release
+ continue;
+ }
+
+ // Check for pre release
+ if (!$this->updateAllowPreRelease && !empty($release['prerelease'])) {
+ // no pre release allowed
+ continue;
+ }
+
+ // Check for required tag_name
+ if (empty($release['tag_name'])) {
+ // no valid release (requires version tag)
+ continue;
+ }
+
+ // Get basic informations
+ $this->updateVersion = trim($release['tag_name']);
+ $this->updateChangelog = $release['body'];
+
+ foreach ($release['assets'] as $asset) {
+ if ($asset['name'] === 'clitools.phar') {
+ $this->updateUrl = $asset['browser_download_url'];
+ }
+ }
+
+ if (!empty($this->updateVersion) && !empty($this->updateUrl)) {
+ // valid version found
+ break;
+ }
+ }
+ }
+
+ if (!empty($this->updateUrl)) {
+ $this->output->writeln('done');
+ } else {
+ $this->output->writeln('failed');
+ throw new \RuntimeException('Could not fetch new version - maybe GitHub API is down or other error occurred');
+ }
+ }
+
+ /**
+ * Show changelog
+ */
+ protected function showChangelog() {
+
+ $message = $this->updateChangelog;
+
+ // Pad lines
+ $message = explode("\n", $message);
+ $message = array_map(function($line) {
+ return ' ' . $line;
+ }, $message);
+ $message = implode("\n", $message);
+
+ $message = preg_replace('/`([^`]+)`/', '\1', $message);
+
+ $this->output->writeln('Changelog:');
+ $this->output->writeln($message);
+ $this->output->writeln('');
+ }
+
/**
* Get current file informations=
*/
@@ -146,29 +347,35 @@ protected function collectInformations() {
$this->cliToolsCommandPerms['perms'] = fileperms($this->cliToolsCommandPath);
$this->cliToolsCommandPerms['owner'] = (int)fileowner($this->cliToolsCommandPath);
$this->cliToolsCommandPerms['group'] = (int)filegroup($this->cliToolsCommandPath);
+
+ // ##################
+ // Set github defaults
+ // ##################
+ $this->githubRepo = $this->application->getConfigValue('config', 'github_repo', null);
+ $this->githubReleaseUrl = 'https://api.github.com/repos/' . $this->githubRepo . '/releases';
}
/**
* Download file
*/
protected function downloadUpdate() {
- $curlHandle = curl_init();
- curl_setopt($curlHandle, CURLOPT_URL, $this->updateUrl);
- curl_setopt($curlHandle, CURLOPT_VERBOSE, 0);
- curl_setopt($curlHandle, CURLOPT_HEADER, 0);
- curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, 1);
- curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, 2);
- curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, 1);
-
- $curlData = curl_exec($curlHandle);
- if (curl_errno($curlHandle) || empty($curlData)) {
- throw new \RuntimeException('Could not download update: ' . curl_error($curlHandle));
- }
- curl_close($curlHandle);
+ $output = $this->output;
+
+ // Progress counter
+ $progress = function($downloadTotal, $downoadProgress) use ($output) {
+ static $counter = 0;
+
+ if($counter % 30 === 0) {
+ $output->write('.');
+ }
+
+ $counter++;
+ };
+
+ $data = \CliTools\Utility\PhpUtility::curlFetch($this->updateUrl, $progress);
$tmpFile = tempnam(sys_get_temp_dir(), 'ct');
- file_put_contents($tmpFile, $curlData);
+ file_put_contents($tmpFile, $data);
$this->cliToolsUpdatePath = $tmpFile;
}
@@ -211,7 +418,7 @@ protected function deployUpdate() {
}
/**
- * Test update and show version
+ * Test update and try to get version
*
* @return string
*/
@@ -228,5 +435,9 @@ protected function testUpdate() {
* Cleanup
*/
protected function cleanup() {
+ // Remove old update file if set and exists
+ if ($this->cliToolsUpdatePath && file_exists($this->cliToolsUpdatePath)) {
+ unlink($this->cliToolsUpdatePath);
+ }
}
}
diff --git a/src/app/CliTools/Console/Builder/AbstractCommandBuilder.php b/src/app/CliTools/Shell/CommandBuilder/AbstractCommandBuilder.php
similarity index 77%
rename from src/app/CliTools/Console/Builder/AbstractCommandBuilder.php
rename to src/app/CliTools/Shell/CommandBuilder/AbstractCommandBuilder.php
index 106fefe..45781b9 100644
--- a/src/app/CliTools/Console/Builder/AbstractCommandBuilder.php
+++ b/src/app/CliTools/Shell/CommandBuilder/AbstractCommandBuilder.php
@@ -1,6 +1,6 @@
/dev/null';
+
+ /**
+ * Redirect STDERR to STDOUT
+ */
const OUTPUT_REDIRECT_ALL_STDOUT = ' 2>&1';
- const OUTPUT_REDIRECT_NO_STDERR = ' 2> /dev/null';
+
+ /**
+ * Redirect STDERR to /dev/null (no error output)
+ */
+ const OUTPUT_REDIRECT_NO_STDERR = ' 2> /dev/null';
// ##########################################
// Attributs
@@ -55,7 +66,7 @@ class AbstractCommandBuilder implements CommandBuilderInterface {
*
* @var null|string
*/
- protected $outputRedirect = null;
+ protected $outputRedirect;
/**
* Command pipe
@@ -174,7 +185,8 @@ public function addArgumentSeparator() {
* @return $this
*/
public function setArgumentList(array $args) {
- $this->argumentList = $args;
+ $this->clearArguments();
+ $this->appendArgumentsToList($args);
return $this;
}
@@ -209,10 +221,10 @@ public function addArgument($arg) {
}
/**
- * Set argument with template
+ * Add argument with template
*
- * @param string $arg Argument sprintf
- * @param string $params Argument parameters
+ * @param string $arg Argument sprintf
+ * @param string $params... Argument parameters
*
* @return $this
*/
@@ -223,6 +235,22 @@ public function addArgumentTemplate($arg, $params) {
return $this->addArgumentTemplateList($arg, $funcArgs);
}
+ /**
+ * Add argument with template multiple times
+ *
+ * @param string $arg Argument sprintf
+ * @param array $paramList Argument parameters
+ *
+ * @return $this
+ */
+ public function addArgumentTemplateMultiple($arg, $paramList) {
+ foreach ($paramList as $param) {
+ $this->addArgumentTemplate($arg, $param);
+ }
+
+ return $this;
+ }
+
/**
* Set argument with template
*
@@ -232,6 +260,8 @@ public function addArgumentTemplate($arg, $params) {
* @return $this
*/
public function addArgumentTemplateList($arg, array $params) {
+ $this->validateArgumentValue($arg);
+
$params = array_map('escapeshellarg', $params);
$this->argumentList[] = vsprintf($arg, $params);
return $this;
@@ -245,12 +275,45 @@ public function addArgumentTemplateList($arg, array $params) {
* @return $this
*/
public function addArgumentList(array $arg, $escape = true) {
+ $this->appendArgumentsToList($arg, $escape);
+ return $this;
+ }
+
+ /**
+ * Append one argument to list
+ *
+ * @param array $arg Arguments
+ * @param boolean $escape Enable argument escaping
+ *
+ * @return $this
+ */
+ protected function appendArgumentToList($arg, $escape = true) {
+ $this->validateArgumentValue($arg);
+
if ($escape) {
- $arg = array_map('escapeshellarg', $arg);
+ $arg = escapeshellarg($arg);
}
- $this->argumentList = array_merge($this->argumentList, $arg);
- return $this;
+ $this->argumentList[] = $arg;
+ }
+
+ /**
+ * Append multiple arguments to list
+ *
+ * @param array $args Arguments
+ * @param boolean $escape Enable argument escaping
+ *
+ * @return $this
+ */
+ protected function appendArgumentsToList($args, $escape = true) {
+ // Validate each argument value
+ array_walk($args, array($this, 'validateArgumentValue'));
+
+ if ($escape) {
+ $args = array_map('escapeshellarg', $args);
+ }
+
+ $this->argumentList = array_merge($this->argumentList, $args);
}
/**
@@ -294,6 +357,16 @@ public function setOutputRedirectToFile($filename) {
return $this;
}
+ /**
+ * Clear output redirect
+ *
+ * @return $this
+ */
+ public function clearOutputRedirect() {
+ $this->outputRedirect = null;
+ return $this;
+ }
+
/**
* Parse command and attributs from exec line
*
@@ -303,10 +376,20 @@ public function setOutputRedirectToFile($filename) {
* @return $this
*/
public function parse($str) {
- list($command, $attributs) = explode(' ', $str, 2);
+ $parsedCmd = explode(' ', $str, 2);
- $this->setCommand($command);
- $this->setArgumentList(array($attributs), false);
+ // Check required command
+ if (empty($parsedCmd[0])) {
+ throw new \RuntimeException('Command is empty');
+ }
+
+ // Set command (first value)
+ $this->setCommand($parsedCmd[0]);
+
+ // Set arguments (second values)
+ if (!empty($parsedCmd[1])) {
+ $this->addArgumentRaw($parsedCmd[1]);
+ }
return $this;
}
@@ -378,6 +461,15 @@ public function setPipeList(array $pipeList) {
return $this;
}
+ /**
+ * Clear pipe list
+ *
+ * @return $this
+ */
+ public function clearPipes() {
+ $this->pipeList = array();
+ return $this;
+ }
/**
* Add pipe command
@@ -400,7 +492,7 @@ public function build() {
$ret = array();
if (!$this->isExecuteable()) {
- throw new \RuntimeException('Command "' . $this->getCommand() . '" is not executable or available');
+ throw new \RuntimeException('Command "' . $this->getCommand() . '" is not executable or available, please install it');
}
// Add command
@@ -460,10 +552,23 @@ public function execute() {
/**
* Execute command
*
+ * @param array $opts Option array
* @return Executor
*/
- public function executeInteractive() {
- return $this->getExecutor()->execInteractive();
+ public function executeInteractive(array $opts = null) {
+ return $this->getExecutor()->execInteractive($opts);
+ }
+
+ /**
+ * Validate argument value
+ *
+ * @param mixed $value Value
+ * @throws \RuntimeException
+ */
+ protected function validateArgumentValue($value) {
+ if (strlen($value) === 0) {
+ throw new \RuntimeException('Argument value cannot be empty');
+ }
}
// ##########################################
diff --git a/src/app/CliTools/Console/Builder/CommandBuilder.php b/src/app/CliTools/Shell/CommandBuilder/CommandBuilder.php
similarity index 95%
rename from src/app/CliTools/Console/Builder/CommandBuilder.php
rename to src/app/CliTools/Shell/CommandBuilder/CommandBuilder.php
index bdac5c2..2757325 100644
--- a/src/app/CliTools/Console/Builder/CommandBuilder.php
+++ b/src/app/CliTools/Shell/CommandBuilder/CommandBuilder.php
@@ -1,6 +1,6 @@
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+class EditorCommandBuilder extends CommandBuilder {
+
+ /**
+ * Initalized command
+ *
+ * @throws \RuntimeException
+ */
+ protected function initialize() {
+ parent::initialize();
+
+ $editorCmd = getenv('EDITOR');
+
+ if (empty($editorCmd)) {
+ throw new \RuntimeException('No $EDITOR environment variable set');
+ }
+
+ $this->parse($editorCmd);
+ }
+}
diff --git a/src/app/CliTools/Console/Builder/FullSelfCommandBuilder.php b/src/app/CliTools/Shell/CommandBuilder/FullSelfCommandBuilder.php
similarity index 97%
rename from src/app/CliTools/Console/Builder/FullSelfCommandBuilder.php
rename to src/app/CliTools/Shell/CommandBuilder/FullSelfCommandBuilder.php
index 3471975..5b1e384 100644
--- a/src/app/CliTools/Console/Builder/FullSelfCommandBuilder.php
+++ b/src/app/CliTools/Shell/CommandBuilder/FullSelfCommandBuilder.php
@@ -1,6 +1,6 @@
setCommand(array_shift($arguments));
} elseif (!empty($_SERVER['_'])) {
+ // Plain PHP version
if ($_SERVER['argv'][0] !== $_SERVER['_']) {
$this->setCommand($_SERVER['_']);
$this->addArgument(reset($arguments));
diff --git a/src/app/CliTools/Console/Shell/Executor.php b/src/app/CliTools/Shell/Executor.php
similarity index 73%
rename from src/app/CliTools/Console/Shell/Executor.php
rename to src/app/CliTools/Shell/Executor.php
index 93d0903..f57fcbf 100644
--- a/src/app/CliTools/Console/Shell/Executor.php
+++ b/src/app/CliTools/Shell/Executor.php
@@ -21,8 +21,7 @@
*/
use CliTools\Exception\CommandExecutionException;
-use CliTools\Console\Builder\CommandBuilder;
-use CliTools\Console\Builder\CommandBuilderInterface;
+use CliTools\Shell\CommandBuilder\CommandBuilderInterface;
use CliTools\Utility\ConsoleUtility;
class Executor {
@@ -64,6 +63,13 @@ class Executor {
*/
protected $strictMode = true;
+ /**
+ * Finisher callback list
+ *
+ * @var array
+ */
+ protected $finishers = array();
+
// ##########################################
// Methods
// ##########################################
@@ -152,6 +158,15 @@ public function setStrictMode($strictMode) {
return $this;
}
+ /**
+ * Clear state
+ */
+ public function clear() {
+ $this->output = null;
+ $this->returnCode = null;
+ $this->finishers = array();
+ }
+
/**
* Execute command
@@ -166,6 +181,8 @@ public function execute() {
exec($this->command->build(), $this->output, $this->returnCode);
+ $this->runFinishers();
+
if ($this->strictMode && $this->returnCode !== 0) {
throw $this->generateException('Process ' . $this->command->getCommand() . ' did not finished successfully');
}
@@ -176,10 +193,11 @@ public function execute() {
/**
* Execute interactive
*
+ * @param array $opts Option array
* @return $this
* @throws \Exception
*/
- public function execInteractive() {
+ public function execInteractive(array $opts = null) {
$this->checkCommand();
ConsoleUtility::verboseWriteln('EXEC::INTERACTIVE', $this->command->build());
@@ -193,9 +211,34 @@ public function execInteractive() {
$process = proc_open($this->command->build(), $descriptorSpec, $pipes);
if (is_resource($process)) {
- $this->returnCode = proc_close($process);
+ if (!empty($opts['startupCallback']) && is_callable($opts['startupCallback'])) {
+ $opts['startupCallback']($process);
+ }
+
+ do {
+ if (is_resource($process)) {
+ $status = proc_get_status($process);
+ if (!empty($status) && !empty($opts['runningCallback']) && is_callable($opts['runningCallback'])) {
+ $opts['runningCallback']($process, $status);
+ }
+ } else {
+ break;
+ }
+ usleep(100 * 1000);
+ } while (!empty($status) && is_array($status) && $status['running'] === true);
+
+ if (is_resource($process)) {
+ proc_close($process);
+ }
+
+ $this->returnCode = $status['exitcode'];
- if ($this->strictMode && $this->returnCode !== 0) {
+ $this->runFinishers();
+
+ if ($status['signaled'] === true && $status['exitcode'] === -1) {
+ // user may hit CTRL+C
+ ConsoleUtility::getOutput()->writeln('Processed stopped by signal');
+ } elseif ($this->strictMode && $this->returnCode !== 0) {
throw $this->generateException('Process ' . $this->command->getCommand() . ' did not finished successfully');
}
} else {
@@ -243,4 +286,24 @@ protected function generateException($msg) {
return $e;
}
+
+ /**
+ * Add finisher callback (will run after command execution)
+ *
+ * @param callable $callback
+ */
+ public function addFinisherCallback(callable $callback) {
+ $this->finishers[] = $callback;
+ }
+
+ /**
+ * Run finisher commands
+ */
+ public function runFinishers() {
+ foreach ($this->finishers as $call) {
+ if (is_callable($call)) {
+ $call($this);
+ }
+ }
+ }
}
diff --git a/src/app/CliTools/Utility/CommandExecutionUtility.php b/src/app/CliTools/Utility/CommandExecutionUtility.php
deleted file mode 100644
index 74e5b4b..0000000
--- a/src/app/CliTools/Utility/CommandExecutionUtility.php
+++ /dev/null
@@ -1,169 +0,0 @@
-
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-use CliTools\Exception\CommandExecutionException;
-
-/**
- * Class CommandExecutionUtility
- *
- * @package CliTools\Utility
- * @deprecated
- */
-class CommandExecutionUtility {
-
- /**
- * Build raw command
- *
- * @param string $command Command
- * @param string|null $parameterTemplate Parameter Template
- * @param array|null $parameter Parameter List
- *
- * @return string
- */
- public static function buildCommand($command, $parameterTemplate = null, $parameter = null) {
- // Escape command
- $execCommand = escapeshellcmd($command);
-
- // Escape args
- if ($parameter !== null && is_array($parameter) && count($parameter) >= 1) {
- // dynamic paramter
- $parameter = array_map('escapeshellarg', $parameter);
-
- // Just add parameter if template is empty
- if ($parameterTemplate === null) {
- $parameterTemplate = str_repeat('%s ', count($parameter));
- }
-
- $execCommand .= ' ' . vsprintf($parameterTemplate, $parameter);
- } elseif ($parameterTemplate !== null && $parameter === null) {
- // only template specified, use as static parameter
- $execCommand .= ' ' . $parameterTemplate;
- }
-
- return $execCommand;
- }
-
-
- /**
- * Build argument list as string
- *
- * @param array $parameter Parameter List
- *
- * @return string
- */
- public static function buildArgumentString(array $parameter) {
- $parameter = array_map('escapeshellarg', $parameter);
- $ret = implode(' ', $parameter);
- return $ret;
- }
-
- /**
- * Exec raw command
- *
- * @param string $command Command
- * @param string $output Output
- *
- * @return integer
- * @throws CommandExecutionException
- */
- public static function execRaw($command, &$output = null) {
- ConsoleUtility::verboseWriteln('EXEC::RAW', $command);
-
- exec($command, $output, $execStatus);
-
- if ($execStatus !== 0) {
- $e = new CommandExecutionException('Process ' . $command . ' did not finished successfully');
- $e->setReturnCode($execStatus);
- throw $e;
- }
-
- return $execStatus;
- }
-
- /**
- * Exec command
- *
- * @param string $command Command
- * @param string $output Output
- * @param string|null $parameterTemplate Parameter Template
- * @param array|null $parameter Parameter List
- *
- * @return integer
- * @throws CommandExecutionException
- */
- public static function exec($command, &$output, $parameterTemplate, $parameter = null) {
- $execCommand = self::buildCommand($command, $parameterTemplate, $parameter);
-
- ConsoleUtility::verboseWriteln('EXEC::EXEC', $execCommand);
-
- exec($execCommand, $output, $execStatus);
-
- if ($execStatus !== 0) {
- $e = new CommandExecutionException('Process ' . $execCommand . ' did not finished successfully');
- $e->setReturnCode($execStatus);
- throw $e;
- }
-
- return $execStatus;
- }
-
- /**
- * Execute command (via passthru)
- *
- * @param string $command Command
- * @param string|null $parameterTemplate Parameter Template
- * @param array|null $parameter Parameter List
- *
- * @return integer
- * @throws CommandExecutionException
- */
- public static function execInteractive($command, $parameterTemplate = null, $parameter = null) {
- $execCommand = self::buildCommand($command, $parameterTemplate, $parameter);
-
- ConsoleUtility::verboseWriteln('EXEC::INTERACTIVE', $execCommand);
-
- $descriptorSpec = array(
- 0 => array('file', 'php://stdin', 'r'), // stdin is a file that the child will read from
- 1 => array('file', 'php://stdout', 'w'), // stdout is a file that the child will write to
- 2 => array('file', 'php://stderr', 'w') // stderr is a file that the child will write to
- );
-
- $process = proc_open($execCommand, $descriptorSpec, $pipes);
-
- if (is_resource($process)) {
- $execStatus = proc_close($process);
- $execStatus = pcntl_wexitstatus($execStatus);
-
- if ($execStatus !== 0) {
- $e = new CommandExecutionException('Process ' . $execCommand . ' did not finished successfully [return code: ' . $execStatus . ']');
- $e->setReturnCode($execStatus);
- throw $e;
- }
- } else {
- $e = new CommandExecutionException('Process ' . $execCommand . ' could not be started');
- $e->setReturnCode(-1);
- throw $e;
- }
-
- return $execStatus;
- }
-}
diff --git a/src/app/CliTools/Utility/ConsoleUtility.php b/src/app/CliTools/Utility/ConsoleUtility.php
index 1c4dc93..1dad09e 100644
--- a/src/app/CliTools/Utility/ConsoleUtility.php
+++ b/src/app/CliTools/Utility/ConsoleUtility.php
@@ -20,6 +20,8 @@
* along with this program. If not, see .
*/
+use Symfony\Component\Console\Question\Question;
+use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -108,4 +110,32 @@ public static function verboseWriteln($area, $line) {
self::$output->writeln($line);
}
}
+
+ /**
+ * Ask question with yes/no detection
+ *
+ * @param string $question Question
+ * @param string $default Default
+ *
+ * @return bool
+ */
+ public static function questionYesNo($message, $default) {
+ $ret = false;
+
+ while (1) {
+ $question = new Question(' >>> ' . $message . ' [yes/no] ', $default);
+ $questionDialog = new QuestionHelper();
+ $answer = $questionDialog->ask(self::$input, self::$output, $question);
+
+ if (stripos($answer, 'n') === 0) {
+ $ret = false;
+ break;
+ } elseif (stripos($answer, 'y') === 0) {
+ $ret = true;
+ break;
+ }
+ }
+
+ return $ret;
+ }
}
diff --git a/src/app/CliTools/Utility/DockerUtility.php b/src/app/CliTools/Utility/DockerUtility.php
index 57d22f0..980f7e7 100644
--- a/src/app/CliTools/Utility/DockerUtility.php
+++ b/src/app/CliTools/Utility/DockerUtility.php
@@ -20,7 +20,7 @@
* along with this program. If not, see .
*/
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
use CliTools\Console\Shell\Executor;
class DockerUtility {
@@ -73,27 +73,7 @@ public static function getDockerConfiguration($container) {
* @return bool|string
*/
public static function searchDockerDirectoryRecursive($path = null) {
- $ret = false;
-
- // Set path to current path (if not specified)
- if ($path === null) {
- $path = getcwd();
- }
-
- if (!empty($path) && $path !== '/') {
- // Check if current path is docker directory
- if (self::isDockerDirectory($path)) {
- // Docker found
- $ret = $path;
- } else {
- // go up in directory
- $path .= '/../';
- $path = realpath($path);
- $ret = self::searchDockerDirectoryRecursive($path);
- }
- }
-
- return $ret;
+ return UnixUtility::findFileInDirectortyTree('docker-compose.yml', $path);
}
/**
@@ -109,8 +89,7 @@ public static function isDockerDirectory($path = null) {
}
$dockerFileList = array(
- 'docker-compose.yml',
- 'fig.yml',
+ 'docker-compose.yml'
);
foreach ($dockerFileList as $dockerFile) {
diff --git a/src/app/CliTools/Utility/FilterUtility.php b/src/app/CliTools/Utility/FilterUtility.php
new file mode 100644
index 0000000..09d169e
--- /dev/null
+++ b/src/app/CliTools/Utility/FilterUtility.php
@@ -0,0 +1,76 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+class FilterUtility {
+
+ /**
+ * Filter mysql table list by filter
+ *
+ * @param array $tables List of tables
+ * @param array $filters List of filters
+ *
+ * @return array
+ */
+ public static function mysqlTableFilter(array $tables, array $filters) {
+ $ret = array();
+
+ foreach ($tables as $table) {
+ foreach ($filters as $filter) {
+ if (preg_match($filter, $table)) {
+ continue 2;
+ }
+ }
+ $ret[] = $table;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Filter mysql table list by filter
+ *
+ * @param array $tables List of tables
+ * @param array $filters List of filters
+ * @param string|null $database Database
+ *
+ * @return array
+ */
+ public static function mysqlIgnoredTableFilter(array $tables, array $filters, $database = null) {
+ $ret = array();
+
+ foreach ($tables as $table) {
+ foreach ($filters as $filter) {
+ if (preg_match($filter, $table)) {
+
+ if ($database !== null) {
+ $ret[] = $database . '.' . $table;
+ } else {
+ $ret[] = $table;
+ }
+ continue 2;
+ }
+ }
+ }
+
+ return $ret;
+ }
+}
diff --git a/src/app/CliTools/Utility/PhpUtility.php b/src/app/CliTools/Utility/PhpUtility.php
index 97ede9b..e5656c8 100644
--- a/src/app/CliTools/Utility/PhpUtility.php
+++ b/src/app/CliTools/Utility/PhpUtility.php
@@ -22,6 +22,47 @@
class PhpUtility {
+ /**
+ * Get content of file
+ *
+ * @param string $file Filename
+ * @return string
+ */
+ public static function fileGetContents($file) {
+ if (!is_file($file) || !is_readable($file)) {
+ throw new \RuntimeException('Could not read "' . $file . '"');
+ }
+
+ return file_get_contents($file);
+ }
+
+ /**
+ * Get content of file (array)
+ *
+ * @param string $file Filename
+ * @return array
+ */
+ public static function fileGetContentsArray($file) {
+ $content = self::fileGetContents($file);
+ $content = str_replace("/r/n", "/n", $content);
+
+ $ret = explode("/n", $content);
+
+ return $ret;
+ }
+
+ /**
+ * Get content of file
+ *
+ * @param string $file Filename
+ * @param string $content Content
+ */
+ public static function filePutContents($file, $content) {
+ if (file_put_contents($file, $content) === false) {
+ throw new \RuntimeException('Could not write "' . $file . '"');
+ }
+ }
+
/**
* Change current working directory
*
@@ -29,11 +70,32 @@ class PhpUtility {
* @throws \RuntimeException
*/
public static function chdir($path) {
- if (!chdir($path)) {
+ if (!is_dir($path) || !chdir($path)) {
throw new \RuntimeException('Could not change working directory to "' . $path . '"');
}
}
+ /**
+ * Create new directory
+ *
+ * @param string $path Directory
+ * @param integer $mode Perms
+ * @param boolean $recursive Creation of nested directories
+ * @param resource $context Context
+ * @throws \RuntimeException
+ */
+ public static function mkdir($path, $mode = 0777, $recursive = false, $context = null) {
+ if ($context !== null) {
+ $res = mkdir($path, $mode, $recursive, $context);
+ } else {
+ $res = mkdir($path, $mode, $recursive);
+ }
+
+ if (!$res) {
+ throw new \RuntimeException('Could not create directory "' . $path . '"');
+ }
+ }
+
/**
* Remove file
*
@@ -45,4 +107,64 @@ public static function unlink($path) {
throw new \RuntimeException('Could not change working directory to "' . $path . '"');
}
}
+
+ /**
+ * Fetch content from url using curl
+ *
+ * @param string $url Url
+ * @param callable $progress Progress callback
+ *
+ * @return mixed
+ */
+ public static function curlFetch($url, callable $progress = null) {
+ $curlHandle = curl_init();
+ curl_setopt($curlHandle, CURLOPT_URL, $url);
+ curl_setopt($curlHandle, CURLOPT_VERBOSE, 0);
+ curl_setopt($curlHandle, CURLOPT_HEADER, 0);
+ curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, 1);
+ curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, 2);
+ curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, 1);
+ curl_setopt($curlHandle, CURLOPT_USERAGENT, 'CliTools ' . CLITOOLS_COMMAND_VERSION . '(https://github.com/mblaschke/vagrant-clitools)');
+
+ if($progress) {
+ curl_setopt($curlHandle, CURLOPT_NOPROGRESS, false);
+ curl_setopt($curlHandle, CURLOPT_PROGRESSFUNCTION, $progress);
+ }
+
+ $ret = curl_exec($curlHandle);
+ if (curl_errno($curlHandle) || empty($ret)) {
+ throw new \RuntimeException('Could not fetch url "' . $url . '", error: ' . curl_error($curlHandle));
+ }
+ curl_close($curlHandle);
+
+ return $ret;
+ }
+
+
+ /**
+ * Get MIME type for file
+ *
+ * @param string $file Path to file
+ *
+ * @return string
+ */
+ public static function getMimeType($file) {
+ // Get mime type from file
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+ $ret = finfo_file($finfo, $file);
+ finfo_close($finfo);
+
+ if ($ret === 'application/octet-stream') {
+ $finfo = finfo_open();
+ $dumpFileInfo = finfo_file($finfo, $file);
+ finfo_close($finfo);
+
+ if (strpos($dumpFileInfo, 'LZMA compressed data') !== false) {
+ $ret = 'application/x-lzma';
+ }
+ }
+
+ return $ret;
+ }
}
diff --git a/src/app/CliTools/Utility/UnixUtility.php b/src/app/CliTools/Utility/UnixUtility.php
index 1abd1fa..135f059 100644
--- a/src/app/CliTools/Utility/UnixUtility.php
+++ b/src/app/CliTools/Utility/UnixUtility.php
@@ -20,7 +20,7 @@
* along with this program. If not, see .
*/
-use CliTools\Console\Builder\CommandBuilder;
+use CliTools\Shell\CommandBuilder\CommandBuilder;
abstract class UnixUtility {
@@ -49,13 +49,13 @@ public static function lsbSystemDescription() {
/**
* Get CPU Count
*
- * @return string
+ * @return integer
*/
public static function cpuCount() {
$command = new CommandBuilder('nproc');
$ret = $command->execute()->getOutputString();
- $ret = trim($ret);
+ $ret = (int)trim($ret);
return $ret;
}
@@ -63,7 +63,7 @@ public static function cpuCount() {
/**
* Get Memory Count
*
- * @return string
+ * @return integer
*/
public static function memorySize() {
$command = new CommandBuilder('cat', '/proc/meminfo');
@@ -112,7 +112,7 @@ public static function dockerVersion() {
/**
* Get mount info list
*
- * @return string
+ * @return array
*/
public static function mountInfoList() {
$command = new CommandBuilder('df', '-a --type=ext3 --type=ext4 --type vmhgfs --type vboxsf --portability');
@@ -205,12 +205,12 @@ public static function defaultGateway() {
* @param string $message Message
*/
public static function sendWallMessage($message) {
- $commandWall = new CommandBuilder('wall');
- $commandWall->setOutputRedirect(CommandBuilder::OUTPUT_REDIRECT_NULL);
+ $wall = new CommandBuilder('wall');
+ $wall->setOutputRedirect(CommandBuilder::OUTPUT_REDIRECT_NULL);
$command = new CommandBuilder('echo');
$command->addArgument($message)
- ->addPipeCommand($commandWall);
+ ->addPipeCommand($wall);
$command->execute();
}
@@ -256,4 +256,73 @@ public static function checkExecutable($command) {
return false;
}
+
+ /**
+ * Search directory upwards for a file
+ *
+ * @param string|array $file Filename
+ * @param string $path Path
+ * @return boolean|string
+ */
+ public static function findFileInDirectortyTree($file, $path = null) {
+ $ret = false;
+
+ $fileList = (array)$file;
+
+ // Set path to current path (if not specified)
+ if ($path === null) {
+ $path = getcwd();
+ }
+
+ if (!empty($path) && $path !== '/') {
+ // Check if file exists in path
+ foreach ($fileList as $file) {
+ if (file_exists($path . '/' . $file)) {
+ // File found
+ $ret = $path . '/' . $file;
+ break;
+ }
+ }
+
+ if ($ret === false) {
+ // go up in directory
+ $path .= '/../';
+ $path = realpath($path);
+ $ret = self::findFileInDirectortyTree($fileList, $path);
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Reload tty
+ */
+ public static function reloadTtyBanner($ttyName) {
+ // Check if we can reload tty
+ try {
+ $who = new CommandBuilder('who');
+ $who->addPipeCommand( new CommandBuilder('grep', '%s', array($ttyName)));
+ $who->execute();
+
+ // if there is no exception -> there is a logged in user
+ } catch (\Exception $e) {
+ // if there is an exception -> there is NO logged in user
+
+ try {
+ $ps = new CommandBuilder('ps', 'h -o pid,comm,args -C getty');
+ $ps->addPipeCommand( new CommandBuilder('grep', '%s', array($ttyName)));
+ $output = $ps->execute()->getOutput();
+
+ if (!empty($output)) {
+ $outputLine = trim(reset($output));
+ $outputLineParts = preg_split('/[\s]+/', $outputLine);
+ list($pid) = $outputLineParts;
+
+ posix_kill($pid, SIGHUP);
+ }
+
+ } catch (\Exception $e) {}
+ }
+ }
}
diff --git a/src/command.php b/src/command.php
index a70c438..9e191f4 100644
--- a/src/command.php
+++ b/src/command.php
@@ -19,11 +19,11 @@
* along with this program. If not, see .
*/
-define('CLITOOLS_COMMAND_VERSION', '1.9.0');
+error_reporting(E_ALL);
+define('CLITOOLS_COMMAND_VERSION', '2.0.0');
define('CLITOOLS_ROOT_FS', __DIR__);
require __DIR__ . '/vendor/autoload.php';
-require __DIR__ . '/app/bootstrap.php';
$app = new CliTools\Console\Application('CliTools :: Development Console Utility', CLITOOLS_COMMAND_VERSION);
diff --git a/src/composer.json b/src/composer.json
index 4d2d5ce..c2e61bb 100644
--- a/src/composer.json
+++ b/src/composer.json
@@ -1,10 +1,16 @@
{
+ "name": "Clitools",
"repositories": [
{ "type": "vcs", "url": "https://github.com/jamiebicknell/Growl-GNTP.git" }
],
"require": {
"symfony/console": "2.*",
- "symfony/class-loader": "2.*",
- "jamiebicknell/Growl-GNTP": "dev-master"
+ "jamiebicknell/Growl-GNTP": "dev-master",
+ "symfony/yaml": "^2.6"
+ },
+ "autoload": {
+ "psr-0": {
+ "CliTools": "./app"
+ }
}
}
diff --git a/src/composer.lock b/src/composer.lock
index fd5c2cf..6487db0 100644
--- a/src/composer.lock
+++ b/src/composer.lock
@@ -1,10 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
- "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "4585abd5cf3956b5044b2156049a1fe9",
+ "hash": "7b023eac532864cff6d957dd51e5999c",
"packages": [
{
"name": "jamiebicknell/Growl-GNTP",
@@ -47,36 +47,42 @@
"time": "2014-06-21 19:14:27"
},
{
- "name": "symfony/class-loader",
- "version": "v2.6.6",
- "target-dir": "Symfony/Component/ClassLoader",
+ "name": "symfony/console",
+ "version": "v2.7.1",
"source": {
"type": "git",
- "url": "https://github.com/symfony/ClassLoader.git",
- "reference": "861765b3e5f32979de5bd19ad2577cbb830a29d5"
+ "url": "https://github.com/symfony/Console.git",
+ "reference": "564398bc1f33faf92fc2ec86859983d30eb81806"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/ClassLoader/zipball/861765b3e5f32979de5bd19ad2577cbb830a29d5",
- "reference": "861765b3e5f32979de5bd19ad2577cbb830a29d5",
+ "url": "https://api.github.com/repos/symfony/Console/zipball/564398bc1f33faf92fc2ec86859983d30eb81806",
+ "reference": "564398bc1f33faf92fc2ec86859983d30eb81806",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": ">=5.3.9"
},
"require-dev": {
- "symfony/finder": "~2.0,>=2.0.5",
- "symfony/phpunit-bridge": "~2.7"
+ "psr/log": "~1.0",
+ "symfony/event-dispatcher": "~2.1",
+ "symfony/phpunit-bridge": "~2.7",
+ "symfony/process": "~2.1"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/process": ""
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.6-dev"
+ "dev-master": "2.7-dev"
}
},
"autoload": {
- "psr-0": {
- "Symfony\\Component\\ClassLoader\\": ""
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -84,57 +90,48 @@
"MIT"
],
"authors": [
- {
- "name": "Symfony Community",
- "homepage": "http://symfony.com/contributors"
- },
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony ClassLoader Component",
- "homepage": "http://symfony.com",
- "time": "2015-03-27 10:19:51"
+ "description": "Symfony Console Component",
+ "homepage": "https://symfony.com",
+ "time": "2015-06-10 15:30:22"
},
{
- "name": "symfony/console",
- "version": "v2.6.6",
- "target-dir": "Symfony/Component/Console",
+ "name": "symfony/yaml",
+ "version": "v2.7.1",
"source": {
"type": "git",
- "url": "https://github.com/symfony/Console.git",
- "reference": "5b91dc4ed5eb08553f57f6df04c4730a73992667"
+ "url": "https://github.com/symfony/Yaml.git",
+ "reference": "9808e75c609a14f6db02f70fccf4ca4aab53c160"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Console/zipball/5b91dc4ed5eb08553f57f6df04c4730a73992667",
- "reference": "5b91dc4ed5eb08553f57f6df04c4730a73992667",
+ "url": "https://api.github.com/repos/symfony/Yaml/zipball/9808e75c609a14f6db02f70fccf4ca4aab53c160",
+ "reference": "9808e75c609a14f6db02f70fccf4ca4aab53c160",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": ">=5.3.9"
},
"require-dev": {
- "psr/log": "~1.0",
- "symfony/event-dispatcher": "~2.1",
- "symfony/phpunit-bridge": "~2.7",
- "symfony/process": "~2.1"
- },
- "suggest": {
- "psr/log": "For using the console logger",
- "symfony/event-dispatcher": "",
- "symfony/process": ""
+ "symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.6-dev"
+ "dev-master": "2.7-dev"
}
},
"autoload": {
- "psr-0": {
- "Symfony\\Component\\Console\\": ""
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -142,18 +139,18 @@
"MIT"
],
"authors": [
- {
- "name": "Symfony Community",
- "homepage": "http://symfony.com/contributors"
- },
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony Console Component",
- "homepage": "http://symfony.com",
- "time": "2015-03-30 15:54:10"
+ "description": "Symfony Yaml Component",
+ "homepage": "https://symfony.com",
+ "time": "2015-06-10 15:30:22"
}
],
"packages-dev": [],
diff --git a/src/conf/.gitkeep b/src/conf/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/config.ini b/src/config.ini
index 4409b59..dd808c0 100644
--- a/src/config.ini
+++ b/src/config.ini
@@ -1,8 +1,9 @@
[config]
-ssh_conf_path = "/opt/conf/ssh"
-www_base_path = "/var/www"
-domain_dev[] = "dev"
-self_update_url = "https://www.achenar.net/clicommand/clitools.phar"
+ssh_conf_path = "/opt/conf/ssh"
+www_base_path = "/var/www"
+domain_dev = "vm"
+github_repo = "mblaschke/clitools"
+update_fallback_url = "https://www.achenar.net/clicommand/clitools.phar"
[db]
dsn = "mysql:host=localhost"
@@ -10,6 +11,9 @@ username = "root"
password = ""
debug_log_dir = "/tmp/"
+[bin]
+composer = "composer"
+
[syscheck]
enabled = 1
wall = 1
@@ -34,9 +38,17 @@ typo3[] = "/^sys_log$/i"
typo3[] = "/^sys_history$/i"
typo3[] = "/^tx_extbase_cache.*/i"
+
[commands]
; load following classes
class[] = "CliTools\Console\Command\Common\SelfUpdateCommand"
+class[] = "CliTools\Console\Command\Common\MakeCommand"
+
+class[] = "CliTools\Console\Command\Sync\InitCommand"
+class[] = "CliTools\Console\Command\Sync\ServerCommand"
+class[] = "CliTools\Console\Command\Sync\BackupCommand"
+class[] = "CliTools\Console\Command\Sync\RestoreCommand"
+class[] = "CliTools\Console\Command\Sync\DeployCommand"
class[] = "CliTools\Console\Command\TYPO3\BeUserCommand"
class[] = "CliTools\Console\Command\TYPO3\InstallerCommand"
@@ -63,9 +75,11 @@ class[] = "CliTools\Console\Command\Mysql\RestartCommand"
class[] = "CliTools\Console\Command\Mysql\DebugCommand"
class[] = "CliTools\Console\Command\Mysql\SlowLogCommand"
class[] = "CliTools\Console\Command\Mysql\DropCommand"
+class[] = "CliTools\Console\Command\Mysql\ConvertCommand"
class[] = "CliTools\Console\Command\Php\TraceCommand"
class[] = "CliTools\Console\Command\Php\RestartCommand"
+class[] = "CliTools\Console\Command\Php\ComposerCommand"
class[] = "CliTools\Console\Command\Samba\RestartCommand"
@@ -93,6 +107,8 @@ class[] = "CliTools\Console\Command\System\CrontaskCommand"
class[] = "CliTools\Console\Command\User\RebuildSshConfigCommand"
+class[] = "CliTools\Console\Command\Vagrant\ShareCommand"
+
exclude[] = "CliTools\Console\Command\*\RestartCommand"
; exclude this class (example)