├── README.md ├── api.stag.php ├── autoload_stag.php ├── bin └── stag ├── command.php ├── commands ├── command.clear_cache.php ├── command.deploy.php ├── command.help.php ├── command.install.php ├── command.pull_content.php └── command.set_permissions.php ├── default.yaml ├── tasks.stag.php ├── tests └── stag_task.help_test.php ├── utilities └── ssh.php └── vendor └── ftp ├── composer.json ├── example.php ├── license.md ├── readme.md └── src └── Ftp.php /README.md: -------------------------------------------------------------------------------- 1 | ![STAG: A Statamic CLI](http://assets.thisbythem.com.s3.amazonaws.com/blog/jw-stag.jpg "STAG: A Statamic CLI") 2 | 3 | ## What is it? 4 | It's the command line interface for Statamic. It can do all sorts of 5 | wonderful things for you like: 6 | 7 | - Deploy your code to a server (via git, rsync or ftp) 8 | - Pull your content down from a server (via git, rsync or ftp) 9 | - Clear your site cache -- locally or remotely 10 | - Set file permissions -- locally or remotely 11 | 12 | ## Who is it for? 13 | You're a good looking Statamic developer who loves the command line. You 14 | believe that `stag clear_cache` is much better than using Finder to 15 | clear those directories manually. 16 | 17 | ## DISCLAIMER 18 | Stag is in beta. It has been used on OSX to talk to an Ubuntu server. 19 | 20 | It will be manipulating files and setting permissions on things, so if 21 | you aren't comfortable with the command line, or your site isn't under 22 | some sort of version control, then stag might not be for you... yet. Testers welcome! 23 | 24 | ## Installing 25 | Download the repo, unzip and drop the stag directory into your 26 | \_add-ons. _(You'll need to rename stag-master to stag)_ 27 | 28 | ### Using stag 29 | You need to add the stag bin directory to your $PATH. In your 30 | .bash_profile (or your prefrerred shell's config) drop in this line: 31 | 32 | ``` 33 | export PATH=$PATH:_add-ons/stag/bin 34 | ``` 35 | 36 | Reload your config file: 37 | 38 | ``` 39 | source ~/.bash_profile 40 | ``` 41 | 42 | Type `stag` in your Statamic root directory and if you are greeted by a 43 | couple of bucks, Congratulations! Stag is installed! 44 | 45 | ### Getting Started 46 | To list all the commands, type: `stag help` 47 | 48 | For any command that will perform tasks on your server, you'll need to 49 | have setup [passwordless ssh 50 | access](http://www.thegeekstuff.com/2008/11/3-steps-to-perform-ssh-login-without-password-using-ssh-keygen-ssh-copy-id/). 51 | It's relatively easy-to-do and makes things much more secure. If that's 52 | ready to go, you'll need to configure stag to talk to your server. Copy 53 | the default config to `_config/add-ons/stag.yaml` 54 | 55 | [Server Configuration](https://github.com/thisbythem/stag/wiki/Server-Configuration) 56 | 57 | You can read more in-depth about each command here: 58 | [Wiki](https://github.com/thisbythem/stag/wiki) 59 | 60 | ### Support 61 | Again, stag is in beta, so if you encounter something not working 62 | properly, please [create an 63 | issue](https://github.com/thisbythem/stag/issues/new) and we'll to take 64 | a look. 65 | 66 | ### Contribute 67 | If you have an idea of something you'd like stag to do, suggest it here: 68 | [support@thisbythem.com](mailto:support@thisbythem.com?Subject=Stag Ideas) 69 | or feel free to fork, hack and pull request. Happy Hacking! 70 | -------------------------------------------------------------------------------- /api.stag.php: -------------------------------------------------------------------------------- 1 | tasks->run($method, $opts); 9 | } 10 | 11 | public function displayFeedback($msg) { 12 | $this->tasks->displayFeedback($msg); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /autoload_stag.php: -------------------------------------------------------------------------------- 1 | register(); 37 | } 38 | 39 | require_once APP_PATH . '/vendor/Spyc/Spyc.php'; 40 | require_once APP_PATH . '/vendor/Lex/Parser.php'; 41 | require_once APP_PATH . '/core/exceptions.php'; 42 | require_once APP_PATH . '/core/functions.php'; 43 | 44 | // register the Statamic autoloader 45 | spl_autoload_register("autoload_statamic"); 46 | 47 | $stag = new API_stag(); 48 | 49 | $file = array_shift($argv); 50 | $action = array_shift($argv); 51 | $opts = $argv; 52 | $stag->run($action, $opts); 53 | exit(0); 54 | 55 | ?> 56 | 57 | -------------------------------------------------------------------------------- /command.php: -------------------------------------------------------------------------------- 1 | config = $config; 15 | $this->commands = $this->getCommands(); 16 | } 17 | 18 | protected function displayFeedback($msg) { 19 | Addon::getApi('stag')->displayFeedback($msg); 20 | } 21 | 22 | private function getCommands() { 23 | if (empty($this->commands)) { 24 | // Make this dynamic 25 | $this->commands = array( 26 | 'clear_cache' => 'ClearCache', 27 | 'deploy' => 'Deploy', 28 | 'help' => 'Help', 29 | 'pull_content' => 'PullContent', 30 | 'set_permissions' => 'SetPermissions' 31 | ); 32 | } 33 | 34 | return $this->commands; 35 | } 36 | 37 | protected function getSshConnection() { 38 | $this->extractConfigForEnv(); 39 | 40 | $ssh = new SSH($this->user, $this->host); 41 | $ssh->setForwardAgent($this->forward_agent); 42 | $ssh->setWebroot($this->webroot); 43 | 44 | if ($this->port) { 45 | $ssh->setPort($this->port); 46 | } 47 | 48 | return $ssh; 49 | } 50 | 51 | protected function extractConfigForEnv() { 52 | $env_config = $this->config['servers'][$this->env]; 53 | 54 | if ($this->env == null) { 55 | $this->handleNoEnv(); 56 | } 57 | 58 | if ($env_config == null) { 59 | $this->handleNoConfigForEnv(); 60 | } 61 | 62 | $this->webroot = $env_config['webroot']; 63 | $this->user = $env_config['user']; 64 | $this->password = $env_config['password']; 65 | $this->ftp_password = $env_config['ftp_password']; 66 | $this->host = $env_config['host']; 67 | $this->port = $env_config['port']; 68 | $this->forward_agent = $env_config['forward_agent']; 69 | $this->strategy = $env_config['strategy']; 70 | $this->deploy = $env_config['deploy']; 71 | $this->repo_url = $env_config['repo_url']; 72 | $this->pull_content = $env_config['pull_content']; 73 | } 74 | 75 | private function handleNoConfigForEnv() { 76 | $output = <<env. Please check your spelling (or 78 | config) and try again. 79 | EOF; 80 | $this->displayFeedback($output); 81 | exit(1); 82 | } 83 | 84 | protected function handleNoEnv() { 85 | $output = <<displayFeedback($output); 89 | exit(1); 90 | } 91 | 92 | protected function handleNotDeployed() { 93 | if (!$this->hasBeenDeployed()) { 94 | $output = <<webroot 96 | 97 | Please check your configuration or try deploying first? 98 | EOF; 99 | $this->displayFeedback($output); 100 | exit(1); 101 | } 102 | } 103 | 104 | protected function hasBeenDeployed() { 105 | $ssh = $this->getSshConnection(); 106 | $webroot_exists = $ssh->homeExec("[ -d $this->webroot ] && echo 'found'"); 107 | return (trim($webroot_exists) === 'found') ? true : false; 108 | } 109 | 110 | protected function hasNotBeenDeployed() { 111 | return !$this->hasBeenDeployed(); 112 | } 113 | } 114 | 115 | -------------------------------------------------------------------------------- /commands/command.clear_cache.php: -------------------------------------------------------------------------------- 1 | env = array_shift($options); 9 | $this->directories = $this->config['clear_cache']['directories']; 10 | 11 | if ($this->env == null) { 12 | $this->clearLocalCache(); 13 | } else { 14 | $this->clearRemoteServerCache(); 15 | } 16 | } 17 | 18 | public static function helpDetail() { 19 | return << 21 | 22 | Clearing cache so you don't have to: This will clear Statamic's _cache 23 | directory of it's contents. 24 | 25 | If you pass in an environment name, this will SSH into that server and clear 26 | the _cache diretory there. You can configure what directories it clears in your 27 | configuration YAML. 28 | EOF; 29 | } 30 | 31 | public static function helpSummary() { 32 | return <<directories as $dir) { 42 | $sub_dir = "$cache_dir/$dir"; 43 | if (Folder::exists($sub_dir)) { 44 | $output = shell_exec("rm -rf $sub_dir/*"); 45 | if (strpos($output, 'denied')) { 46 | $output = "ERROR:\n$output"; 47 | } else { 48 | $output = "_cache/$dir has been cleared."; 49 | } 50 | $this->displayFeedback($output); 51 | } 52 | } 53 | } 54 | 55 | private function clearRemoteServerCache() { 56 | $this->handleNotDeployed(); 57 | 58 | $ssh = $this->getSshConnection(); 59 | $cache_dir = "_cache"; 60 | 61 | $this->displayFeedback("Connecting to $this->env:"); 62 | 63 | foreach ($this->directories as $dir) { 64 | $cmd = $ssh->exec("rm -rf $cache_dir/$dir/*"); 65 | 66 | if (strpos($cmd, 'Permission denied')) { 67 | $output = <<displayFeedback($output); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /commands/command.deploy.php: -------------------------------------------------------------------------------- 1 | 18 | 19 | This command will not run if you haven't configured your server settings and 20 | deployment strategy in your stag YAML. Please refer to http://github.com/stag 21 | for detailed instructions on how to do so. If you have configured it, please 22 | double check your environment name. 23 | EOF; 24 | } 25 | 26 | public function run($options = array()) { 27 | $this->env = array_shift($options); 28 | 29 | $this->displayFeedback("Checking configuration:"); 30 | // Handle incorrect arguments 31 | $this->handleNoEnvironment(); 32 | $this->handleInvalidEnvironment(); 33 | 34 | $this->extractConfigForEnv($this->env); 35 | 36 | // Check if we have a valid strategy 37 | $this->handleInvalidStrategy(); 38 | 39 | if ($this->hasBeenDeployed()) { 40 | $this->deployed = true; 41 | 42 | // Do we need to pull content first? 43 | if ($this->shouldPullContentFirst()) { 44 | $this->displayFeedback("Pulling content from server:"); 45 | $class_name = $this->commands['pull_content']; 46 | $puller = new $class_name($this->config); 47 | $puller->run(array($this->env)); 48 | } 49 | } 50 | 51 | // Deploy the site 52 | $this->displayFeedback("Deploying to $this->env:"); 53 | $strategy_method_name = 'deployWith' . ucwords($this->strategy); 54 | $this->{$strategy_method_name}(); 55 | 56 | // Clear Cache? 57 | if ($this->shouldClearCacheAfter()) { 58 | $this->displayFeedback("Clearing cache:"); 59 | $class_name = $this->commands['clear_cache']; 60 | $cleaner = new $class_name($this->config); 61 | $cleaner->run(array($this->env)); 62 | } 63 | 64 | // Set Perms? 65 | if ($this->shouldSetPermissionsAfter()) { 66 | $this->displayFeedback("Setting permissions:"); 67 | $class_name = $this->commands['set_permissions']; 68 | $perms = new $class_name($this->config); 69 | $perms->run(array($this->env)); 70 | } 71 | } 72 | 73 | private function shouldPullContentFirst() { 74 | return $this->config['servers'][$this->env]['deploy']['pull_content_before']; 75 | } 76 | 77 | private function shouldClearCacheAfter() { 78 | return $this->config['servers'][$this->env]['deploy']['clear_cache_after']; 79 | } 80 | 81 | private function shouldSetPermissionsAfter() { 82 | return $this->config['servers'][$this->env]['deploy']['set_permissions_after']; 83 | } 84 | 85 | private function shouldUpdateSubmodules() { 86 | return $this->config['servers'][$this->env]['deploy']['update_submodules']; 87 | } 88 | 89 | private function handleNoEnvironment() { 90 | if ($this->env == null) { 91 | $output = << 93 | 94 | I need an environment name to deploy to. 95 | EOF; 96 | $this->displayFeedback($output); 97 | exit(1); 98 | } 99 | } 100 | 101 | private function handleInvalidEnvironment() { 102 | if (!array_key_exists($this->env, $this->config['servers'])) { 103 | $output = <<env. Please check your settings and 105 | try again. 106 | EOF; 107 | $this->displayFeedback($output); 108 | exit(1); 109 | } 110 | } 111 | 112 | private function handleInvalidStrategy() { 113 | if (!method_exists($this, 'deployWith' . ucwords($this->strategy))) { 114 | $output = <<strategy. But I'm happy to learn! 116 | EOF; 117 | $this->displayFeedback($output); 118 | exit(1); 119 | } 120 | } 121 | 122 | private function deployWithGit() { 123 | $ssh = $this->getSshConnection(); 124 | if ($this->deployed) { 125 | $output = $ssh->exec('git pull'); 126 | 127 | if ($this->shouldUpdateSubmodules()) { 128 | $output .= $ssh->exec('git submodule init; git submodule update'); 129 | } 130 | } else { 131 | $path_pieces = explode('/', $this->webroot); 132 | $dir_name = array_pop($path_pieces); 133 | $path = implode('/', $path_pieces); 134 | $repo = $this->repo_url; 135 | if (!$repo) { 136 | $this->displayFeedback('I need a git repo to clone.'); 137 | exit(1); 138 | } 139 | $output = $ssh->homeExec("cd $path; git clone $repo $dir_name"); 140 | } 141 | 142 | $this->displayFeedback($output); 143 | } 144 | 145 | private function deployWithRsync() { 146 | $this->displayFeedback("Deploying with rsync to $this->env"); 147 | $ignore_files = $this->getIgnoreFiles(); 148 | 149 | if ($this->hasNotBeenDeployed()) { 150 | $ssh = $this->getSshConnection(); 151 | $ssh->homeExec("mkdir -p $this->webroot/{_cache,_logs}"); 152 | } 153 | 154 | $cmd = 'rsync -e '; 155 | 156 | if ($this->port !== null) { 157 | $cmd .= "'ssh -p $this->port' "; 158 | } 159 | 160 | $cmd .= "-avl --stats --progress "; 161 | 162 | foreach ($ignore_files as $ignore) { 163 | $cmd .= "--exclude $ignore "; 164 | 165 | } 166 | 167 | $cmd .= BASE_PATH . " $this->user@$this->host:$this->webroot;"; 168 | 169 | $output = shell_exec($cmd); 170 | $this->displayFeedback($output); 171 | } 172 | 173 | private function deployWithFtp() { 174 | $password = ($this->ftp_password) ? $this->ftp_password : $this->password; 175 | 176 | try { 177 | $ftp = new Ftp("ftp://$this->user:$password@$this->host"); 178 | 179 | if (!$ftp->isDir($this->webroot)) { 180 | $ftp->mkdir($this->webroot); 181 | $ftp->mkdir("$this->webroot/_logs"); 182 | $ftp->mkdir("$this->webroot/_cache"); 183 | } 184 | 185 | $this->putAll($ftp, BASE_PATH, $this->webroot); 186 | $this->displayFeedback("Site has been deployed."); 187 | $ftp->close(); 188 | } catch (Exception $e) { 189 | $this->displayFeedback("Something has gone wrong:"); 190 | $this->displayFeedback(var_dump($e)); 191 | exit(1); 192 | } 193 | } 194 | 195 | private function putAll($ftp, $src_dir, $dst_dir) { 196 | $ignore_files = $this->getIgnoreFiles(); 197 | 198 | $d = dir($src_dir); 199 | while($file = $d->read()) { 200 | if (!in_array($file, $ignore_files)) { 201 | if (is_dir($src_dir."/".$file)) { 202 | if (!$ftp->isDir($dst_dir."/".$file)) { 203 | $ftp->mkdir($dst_dir."/".$file); 204 | $this->displayFeedback("Creating $dst_dir/$file"); 205 | } 206 | $this->putAll($ftp, $src_dir."/".$file, $dst_dir."/".$file); 207 | } else { 208 | $dest_file = "$dst_dir/$file"; 209 | $upload = $ftp->put($dest_file, $src_dir."/".$file, FTP_BINARY); 210 | $this->displayFeedback("Uploading $dest_file"); 211 | } 212 | } 213 | } 214 | } 215 | 216 | private function getIgnoreFiles() { 217 | $system_ignore_files = array('.', '..'); 218 | $ignore_files = $this->config['servers'][$this->env]['deploy']['ignore_files']; 219 | 220 | if (!empty($ignore_files)) { 221 | $ignore_files = array_merge($system_ignore_files, $ignore_files); 222 | } 223 | return $ignore_files; 224 | } 225 | 226 | } 227 | -------------------------------------------------------------------------------- /commands/command.help.php: -------------------------------------------------------------------------------- 1 | displayGeneralHelp(); 8 | } else { 9 | $command = array_shift($options); 10 | $this->displayCommandHelp($command); 11 | } 12 | return true; 13 | } 14 | 15 | public static function helpDetail() { 16 | return << 31 | 32 | -= CURRENTLY INSTALLED COMMANDS =- 33 | EOF; 34 | $this->displayFeedback($output); 35 | 36 | foreach ($this->commands as $name => $className) { 37 | $this->displayFeedback($className::helpSummary()); 38 | } 39 | exit(0); 40 | } 41 | 42 | protected function displayCommandHelp($command) { 43 | if (array_key_exists($command, $this->commands)) { 44 | $klass = $this->commands[$command]; 45 | if (method_exists($klass, 'helpDetail')) { 46 | $this->displayFeedback($klass::helpDetail()); 47 | } else { 48 | $this->displayFeedback("Sorry! No help found for $command"); 49 | } 50 | exit(0); 51 | } 52 | $this->displayNotFound($command); 53 | } 54 | 55 | private function displayNotFound($cmd) { 56 | $this->displayFeedback("Sorry! I don't know anything about $cmd."); 57 | exit(1); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /commands/command.install.php: -------------------------------------------------------------------------------- 1 | displayFeedback('Coming Soon!'); 7 | } 8 | 9 | public static function helpDetail() { 10 | return << 14 | 15 | Ideally, I will search Github for the supplied add-on name. If I find it, I'll 16 | install it in the add-ons directory. I have yet to be implemented, sit tight. 17 | EOF; 18 | } 19 | 20 | public static function helpSummary() { 21 | return << 16 | 17 | This command will pull down the content changes from the server. You 18 | can configure it to use rsync, ftp or git. 19 | EOF; 20 | } 21 | 22 | public function run($options = array()) { 23 | $this->env = array_shift($options); 24 | 25 | // Handle incorrect arguments 26 | $this->handleNoEnvironment(); 27 | 28 | $this->extractConfigForEnv($this->env); 29 | 30 | // Check if we have a valid strategy 31 | $this->handleInvalidStrategy(); 32 | 33 | // Check that it's been deployed 34 | $this->handleNotDeployed(); 35 | 36 | // Pull that content 37 | $strategy_method_name = 'pullContentWith' . ucwords($this->strategy); 38 | $this->{$strategy_method_name}(); 39 | } 40 | 41 | private function handleNoEnvironment() { 42 | if ($this->env == null) { 43 | $output = << 45 | 46 | I need a server name in order to pull content from it. 47 | EOF; 48 | $this->displayFeedback($output); 49 | exit(1); 50 | } 51 | } 52 | 53 | private function handleInvalidStrategy() { 54 | if (!method_exists($this, 'pullContentWith' . ucwords($this->strategy))) { 55 | $output = <<strategy. Care to show me how? 57 | EOF; 58 | $this->displayFeedback($output); 59 | exit(1); 60 | } 61 | } 62 | 63 | private function pullContentWithGit() { 64 | $this->displayFeedback("Checking for changes on $this->env"); 65 | $ssh = $this->getSshConnection(); 66 | 67 | $output = $ssh->exec("cd $this->webroot; git status"); 68 | $has_git_changes = strpos($output, "committed"); 69 | $has_git_changes += strpos($output, "Untracked"); 70 | 71 | if (!$has_git_changes) { 72 | $this->displayFeedback("No changes on $this->env."); 73 | return; 74 | } 75 | 76 | $commit_message = $this->config['servers'][$this->env]['pull_content']['commit_message']; 77 | $this->displayFeedback("Committing and pushing up content."); 78 | $output = $ssh->exec("git add --all"); 79 | $output .= $ssh->exec("git commit -am \"$commit_message\""); 80 | $output .= $ssh->exec("git pull; git push;"); 81 | $this->displayFeedback($output); 82 | 83 | $this->displayFeedback("Updates pushed."); 84 | } 85 | 86 | private function pullContentWithRsync() { 87 | $content_dirs = $this->getContentDirectories(); 88 | 89 | $this->displayFeedback("Pulling content from $this->env"); 90 | 91 | foreach ($content_dirs as $dir) { 92 | $cmd = 'rsync -e '; 93 | 94 | if ($this->port !== null) { 95 | $cmd .= "'ssh -p $this->port' "; 96 | } 97 | 98 | $cmd .= "-avl --stats --progress $this->user@$this->host:$this->webroot/$dir/ $dir;"; 99 | 100 | $output = shell_exec($cmd); 101 | $this->displayFeedback($output); 102 | } 103 | 104 | $this->displayFeedback("All content pulled."); 105 | } 106 | 107 | private function pullContentWithFtp() { 108 | $this->displayFeedback("Pulling content from $this->env"); 109 | $dirs = $this->getContentDirectories(); 110 | $password = ($this->ftp_password) ? $this->ftp_password : $this->password; 111 | 112 | try { 113 | $ftp = new Ftp("ftp://$this->user:$password@$this->host"); 114 | foreach ($dirs as $dir) { 115 | $ftp->chdir("$this->webroot/$dir"); 116 | $files = $ftp->nlist('*'); 117 | $this->getAll($ftp, $dir, $files); 118 | } 119 | $this->displayFeedback("Content has been pulled."); 120 | $ftp->close(); 121 | } catch (Exception $e) { 122 | $this->displayFeedback("Something has gone wrong:"); 123 | $this->displayFeedback(var_dump($e)); 124 | exit(1); 125 | } 126 | } 127 | 128 | private function getAll($ftp, $dir, $files) { 129 | $system_ignore_files = array('.', '..'); 130 | 131 | foreach ($files as $file) { 132 | if (!in_array($file, $system_ignore_files)) { 133 | if ($ftp->isDir($file)) { 134 | $files = $ftp->nlist($file); 135 | $this->getAll($ftp, $dir, $files); 136 | } else { 137 | $ftp->get("$dir/$file", $file, FTP_BINARY); 138 | $this->displayFeedback("Downloading: $file"); 139 | } 140 | } 141 | } 142 | } 143 | 144 | private function getContentDirectories() { 145 | $content_dirs = array(Config::getContentRoot()); 146 | $config_dirs = $this->pull_content['content_directories']; 147 | 148 | if (!empty($config_dirs)) { 149 | $content_dirs = array_unique(array_merge($content_dirs, $config_dirs)); 150 | } 151 | 152 | return $content_dirs; 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /commands/command.set_permissions.php: -------------------------------------------------------------------------------- 1 | env = array_shift($options); 11 | $config = $this->config['set_permissions']; 12 | 13 | $this->permissions = $config['site_permissions']; 14 | $this->writable_permissions = $config['writable_permissions']; 15 | $this->writable_directories = $config['writable_directories']; 16 | 17 | if ($this->env == null) { 18 | $this->setPermissionsLocal(); 19 | } else { 20 | $this->setPermissionsOnServer(); 21 | } 22 | } 23 | 24 | public static function helpDetail() { 25 | return << 27 | 28 | Making it easy to set permissions. If you run the command without an 29 | environment, it'll set your local site's permissions. 30 | EOF; 31 | } 32 | 33 | public static function helpSummary() { 34 | return <<displayFeedback($output); 43 | 44 | if ($output == '') { 45 | $this->displayFeedback("Site permissions set"); 46 | } else { 47 | $this->displayFeedback($output); 48 | } 49 | 50 | foreach ($this->writable_directories as $dir) { 51 | $cmd = "chmod -R $this->writable_permissions $dir"; 52 | $output = shell_exec($cmd); 53 | 54 | if ($output == '') { 55 | $this->displayFeedback("$dir permissions set"); 56 | } else { 57 | $this->displayFeedback($output); 58 | } 59 | } 60 | } 61 | 62 | private function setPermissionsOnServer() { 63 | $this->handleNotDeployed(); 64 | 65 | $ssh = $this->getSshConnection(); 66 | $this->displayFeedback("Connecting to $this->env:"); 67 | $output = $ssh->exec("chmod -R $this->permissions ."); 68 | 69 | if ($output == '') { 70 | $this->displayFeedback("Site permissions set"); 71 | } else { 72 | $this->displayFeedback($output); 73 | } 74 | 75 | foreach ($this->writable_directories as $dir) { 76 | $output = $ssh->exec("chmod -R $this->writable_permissions $dir"); 77 | 78 | if ($output == '') { 79 | $this->displayFeedback("$dir permissions set"); 80 | } else { 81 | $this->displayFeedback($output); 82 | } 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /default.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # ______ _________ _ ______ 3 | # {__} .' ____ \ | _ _ | / \ .' ___ | {__} 4 | # \/_____! | (___ \_| |_/ | | \_| / _ \ / .' \_| !_____\/ 5 | # \----| _.____`. | | / ___ \ | | ____ |----/ 6 | # /| |\ | \____) | _| |_ _/ / \ \_ \ `.___] | /| |\ 7 | # \______.' |_____| |____| |____| `._____.' 8 | # 9 | # Stag can do all sorts of wonderful things but you need to configure a few 10 | # things to make it happen. 11 | # 12 | # DISCLAIMER: stag is still in beta mode. If you're not comfortable on the 13 | # command line or ssh'n into your server. If you don't know how to do that, 14 | # stag might not be for you just yet. 15 | # 16 | # SO PROCEED AT YOUR OWN RISK! 17 | # 18 | # That being said, I am using it in a production environment successfully. So 19 | # if you still want to move forward, let's get started. 20 | # 21 | # Copy this whole file and dump it into _config/add-ons/stag.yaml 22 | # 23 | # *= SERVERS =* 24 | # 25 | # stag can talk to your servers, no matter how many you've got. For most 26 | # commands, you will need ssh access. The deployment and pull content commands 27 | # are configured here as well as they might be different for each server. 28 | # 29 | # This is specific to each server, so there are no defaults. 30 | servers: 31 | # Uncomment the following and fill your server's info 32 | # production: # The name of the server to use with stag 33 | # host: example.com # The host to ssh into 34 | # webroot: /var/www/example.com/public # The webroot of your site 35 | # user: deploy # SSH username 36 | # password: password # DEPRECATED: Please use ftp_password 37 | # ftp_password: passwod # Your FTP password; Only if you're using ftp 38 | # port: 22 # SSH port; Don't need it if it's the default 39 | # forward_agent: true # Recommended if you're using git for deployments 40 | # strategy: git # You can set this to git, rsync or ftp 41 | # repo_url: git@github.com:example.git # If using git as your strategy, set the ssh repo url 42 | # 43 | # === DEPLOY === 44 | # You can have deploy pull_content, clear cache, and set permissions during 45 | # the deployment task. Each strategy will have a few different options. Use 46 | # the following configurations as guides for your preferred strategy. 47 | # 48 | # == GIT == 49 | # deploy: 50 | # pull_content_before: true 51 | # clear_cache_after: true 52 | # set_permissions_after: false 53 | # update_submodules: false 54 | # 55 | # == RSYNC == 56 | # deploy: 57 | # pull_content_before: true 58 | # clear_cache_after: true 59 | # set_permissions_after: false 60 | # ignore_files: 61 | # - .git 62 | # - _cache 63 | # - _logs 64 | # 65 | # == FTP == 66 | # deploy: 67 | # pull_content_before: true 68 | # clear_cache_after: true 69 | # set_permissions_after: true 70 | # ignore_files: 71 | # - .git 72 | # - _cache 73 | # - _logs 74 | # 75 | # === PULL CONTENT === 76 | # This will pull your content down from the server. You can choose between 77 | # git, ftp or rsync. 78 | # 79 | # == GIT == 80 | # pull_content: 81 | # commit_message: "Content update from production" 82 | # 83 | # == RSYNC == 84 | # pull_content: 85 | # content_directories: 86 | # - _content 87 | # - assets 88 | # 89 | # == FTP == 90 | # pull_content: 91 | # content_directories: 92 | # - _content 93 | # - assets 94 | # 95 | # *= CLEAR CACHE =* 96 | # When in doubt clear the cache out. 97 | # stag will clear the directories in the _cache folders that you specify. The 98 | # only reason you'd need to change the defaults is if you're storing other 99 | # things in the cache that need clearing. If you have a server configured, 100 | # it'll clear cache there too! 101 | clear_cache: 102 | directories: 103 | - _app 104 | - _add-ons 105 | 106 | # *= SET PERMISSIONS =* 107 | # stag will set permissions for you. If you have a server configured, it'll 108 | # even ssh in and try and update permissions there too! The defaults might do 109 | # ya fine, but by all means, change away my sysadmin friend. 110 | set_permissions: 111 | site_permissions: 755 # This will be run first on the whole site directory 112 | writable_permissions: 775 # Will be run after to set the writable directories 113 | writable_directories: # A list of the writable directories; Statamic defaults 114 | - _cache 115 | - _content 116 | - _logs 117 | - _config/users 118 | -------------------------------------------------------------------------------- /tasks.stag.php: -------------------------------------------------------------------------------- 1 | handleNoCommandGiven(); 12 | } 13 | 14 | $filename = __DIR__ . "/commands/command.$method.php"; 15 | 16 | if (File::exists($filename)) { 17 | $klass = $this->classify($method); 18 | $obj = new $klass($this->config); 19 | $obj->run($options); 20 | } else { 21 | $this->handleCommandNotFound($method); 22 | } 23 | } 24 | 25 | public function displayFeedback($message) { 26 | if ($this->show_command_line_output) { 27 | fwrite(STDOUT, "$message\n\n"); 28 | } 29 | } 30 | 31 | private function classify($str) { 32 | return ucwords(Helper::camelCase($str)); 33 | } 34 | 35 | private function handleNoCommandGiven() { 36 | $output = << 46 | Try stag help to list all commands. 47 | EOF; 48 | $this->displayFeedback($output); 49 | exit(1); 50 | } 51 | 52 | private function handleCommandNotFound($cmd) { 53 | $output = <<displayFeedback($output); 57 | exit(1); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/stag_task.help_test.php: -------------------------------------------------------------------------------- 1 | assertEquals(false, true); 7 | } 8 | 9 | } 10 | 11 | -------------------------------------------------------------------------------- /utilities/ssh.php: -------------------------------------------------------------------------------- 1 | user = $user; 13 | $this->host = $host; 14 | } 15 | 16 | public function setForwardAgent($forward_agent) { 17 | $this->forward_agent = $forward_agent; 18 | } 19 | 20 | public function setWebroot($webroot) { 21 | $this->webroot = $webroot; 22 | } 23 | 24 | public function setPort($port) { 25 | $this->port = $port; 26 | } 27 | 28 | public function exec($cmd) { 29 | $ssh = $this->connectionString(); 30 | return shell_exec("$ssh 'cd $this->webroot; $cmd'"); 31 | } 32 | 33 | public function homeExec($cmd) { 34 | $ssh = $this->connectionString(); 35 | return shell_exec("$ssh '$cmd'"); 36 | } 37 | 38 | protected function connectionString() { 39 | $conn = array('ssh'); 40 | 41 | if ($this->forward_agent) { 42 | $conn[] = '-A'; 43 | } 44 | 45 | if ($this->port) { 46 | $conn[] = "-p $this->port"; 47 | } 48 | 49 | $conn[] = "-o ConnectTimeout=$this->timeout"; 50 | 51 | $conn[] = "$this->user@$this->host"; 52 | 53 | return implode(' ', $conn); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /vendor/ftp/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dg/ftp-php", 3 | "description": "Easy-to-use library for accessing FTP servers", 4 | "keywords": ["ftp"], 5 | "homepage": "https://github.com/dg/ftp-php", 6 | "license": ["BSD-3-Clause"], 7 | "authors": [ 8 | { 9 | "name": "David Grudl", 10 | "homepage": "http://davidgrudl.com" 11 | } 12 | ], 13 | "autoload": { 14 | "classmap": ["src/"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vendor/ftp/example.php: -------------------------------------------------------------------------------- 1 | connect('ftp.ed.ac.uk'); 11 | 12 | // Login with username and password 13 | $ftp->login('anonymous', 'example@example.com'); 14 | 15 | // Download file 'README' to local temporary file 16 | $temp = tmpfile(); 17 | $ftp->fget($temp, 'README', Ftp::ASCII); 18 | 19 | // echo file 20 | echo '
';
21 | 	fseek($temp, 0);
22 | 	fpassthru($temp);
23 | 
24 | } catch (FtpException $e) {
25 | 	echo 'Error: ', $e->getMessage();
26 | }
27 | 


--------------------------------------------------------------------------------
/vendor/ftp/license.md:
--------------------------------------------------------------------------------
 1 | Copyright (c) 2008, Copyright (c) 2008 David Grudl
 2 | All rights reserved.
 3 | 
 4 | Redistribution and use in source and binary forms, with or without modification,
 5 | are permitted provided that the following conditions are met:
 6 | 
 7 |     * Redistributions of source code must retain the above copyright notice,
 8 |       this list of conditions and the following disclaimer.
 9 | 
10 |     * Redistributions in binary form must reproduce the above copyright notice,
11 |       this list of conditions and the following disclaimer in the documentation
12 |       and/or other materials provided with the distribution.
13 | 
14 |     * Neither the name of David Grudl nor the names of its
15 |       contributors may be used to endorse or promote products derived from this
16 |       software without specific prior written permission.
17 | 
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | 


--------------------------------------------------------------------------------
/vendor/ftp/readme.md:
--------------------------------------------------------------------------------
 1 | FTP for PHP
 2 | ===========
 3 | 
 4 | [![Downloads this Month](https://img.shields.io/packagist/dm/dg/ftp-php.svg)](https://packagist.org/packages/dg/ftp-php)
 5 | 
 6 | FTP for PHP is a very small and easy-to-use library for accessing FTP servers.
 7 | 
 8 | It requires PHP 5.0 or newer and is licensed under the New BSD License.
 9 | You can obtain the latest version from our [GitHub repository](http://github.com/dg/ftp-php)
10 | or install it via Composer:
11 | 
12 | 	php composer.phar require dg/ftp-php
13 | 
14 | 
15 | Usage
16 | -----
17 | 
18 | Opens an FTP connection to the specified host:
19 | 
20 | 	$ftp = new Ftp;
21 | 	$ftp->connect($host);
22 | 
23 | Login with username and password
24 | 
25 | 	$ftp->login($username, $password);
26 | 
27 | Upload the file
28 | 
29 | 	$ftp->put($destination_file, $source_file, FTP_BINARY);
30 | 
31 | Close the FTP stream
32 | 
33 | 	$ftp->close();
34 | 	// or simply unset($ftp);
35 | 
36 | Ftp throws exception if operation failed. So you can simply do following:
37 | 
38 | 	try {
39 | 		$ftp = new Ftp;
40 | 		$ftp->connect($host);
41 | 		$ftp->login($username, $password);
42 | 		$ftp->put($destination_file, $source_file, FTP_BINARY);
43 | 
44 | 	} catch (FtpException $e) {
45 | 		echo 'Error: ', $e->getMessage();
46 | 	}
47 | 
48 | On the other hand, if you'd like the possible exception quietly catch, call methods with the prefix 'try':
49 | 
50 | 	$ftp->tryDelete($destination_file);
51 | 
52 | When the connection is accidentally interrupted, you can re-establish it using method $ftp->reconnect().
53 | 
54 | 
55 | Changelog
56 | ---------
57 | v1.1 (6/2014)
58 | - added support for passive mode
59 | 
60 | v1.0 (8/2012)
61 | - initial release
62 | 
63 | 
64 | -----
65 | (c) David Grudl, 2008, 2014 (http://davidgrudl.com)
66 | 


--------------------------------------------------------------------------------
/vendor/ftp/src/Ftp.php:
--------------------------------------------------------------------------------
  1 |  'ssl_connect',
 29 | 		'getoption' => 'get_option',
 30 | 		'setoption' => 'set_option',
 31 | 		'nbcontinue' => 'nb_continue',
 32 | 		'nbfget' => 'nb_fget',
 33 | 		'nbfput' => 'nb_fput',
 34 | 		'nbget' => 'nb_get',
 35 | 		'nbput' => 'nb_put',
 36 | 	);
 37 | 
 38 | 	/** @var resource */
 39 | 	private $resource;
 40 | 
 41 | 	/** @var array */
 42 | 	private $state;
 43 | 
 44 | 	/** @var string */
 45 | 	private $errorMsg;
 46 | 
 47 | 
 48 | 	/**
 49 | 	 * @param  string  URL ftp://...
 50 | 	 * @param  bool
 51 | 	 */
 52 | 	public function __construct($url = NULL, $passiveMode = TRUE)
 53 | 	{
 54 | 		if (!extension_loaded('ftp')) {
 55 | 			throw new Exception('PHP extension FTP is not loaded.');
 56 | 		}
 57 | 		if ($url) {
 58 | 			$parts = parse_url($url);
 59 | 			if (!isset($parts['scheme']) || ($parts['scheme'] !== 'ftp' && $parts['scheme'] !== 'sftp')) {
 60 | 				throw new InvalidArgumentException('Invalid URL.');
 61 | 			}
 62 | 			$func = $parts['scheme'] === 'ftp' ? 'connect' : 'ssl_connect';
 63 | 			$this->$func($parts['host'], empty($parts['port']) ? NULL : (int) $parts['port']);
 64 | 			$this->login(urldecode($parts['user']), urldecode($parts['pass']));
 65 | 			$this->pasv((bool) $passiveMode);
 66 | 			if (isset($parts['path'])) {
 67 | 				$this->chdir($parts['path']);
 68 | 			}
 69 | 		}
 70 | 	}
 71 | 
 72 | 
 73 | 	/**
 74 | 	 * Magic method (do not call directly).
 75 | 	 * @param  string  method name
 76 | 	 * @param  array   arguments
 77 | 	 * @return mixed
 78 | 	 * @throws Exception
 79 | 	 * @throws FtpException
 80 | 	 */
 81 | 	public function __call($name, $args)
 82 | 	{
 83 | 		$name = strtolower($name);
 84 | 		$silent = strncmp($name, 'try', 3) === 0;
 85 | 		$func = $silent ? substr($name, 3) : $name;
 86 | 		$func = 'ftp_' . (isset(self::$aliases[$func]) ? self::$aliases[$func] : $func);
 87 | 
 88 | 		if (!function_exists($func)) {
 89 | 			throw new Exception("Call to undefined method Ftp::$name().");
 90 | 		}
 91 | 
 92 | 		$this->errorMsg = NULL;
 93 | 		set_error_handler(array($this, '_errorHandler'));
 94 | 
 95 | 		if ($func === 'ftp_connect' || $func === 'ftp_ssl_connect') {
 96 | 			$this->state = array($name => $args);
 97 | 			$this->resource = call_user_func_array($func, $args);
 98 | 			$res = NULL;
 99 | 
100 | 		} elseif (!is_resource($this->resource)) {
101 | 			restore_error_handler();
102 | 			throw new FtpException("Not connected to FTP server. Call connect() or ssl_connect() first.");
103 | 
104 | 		} else {
105 | 			if ($func === 'ftp_login' || $func === 'ftp_pasv') {
106 | 				$this->state[$name] = $args;
107 | 			}
108 | 
109 | 			array_unshift($args, $this->resource);
110 | 			$res = call_user_func_array($func, $args);
111 | 
112 | 			if ($func === 'ftp_chdir' || $func === 'ftp_cdup') {
113 | 				$this->state['chdir'] = array(ftp_pwd($this->resource));
114 | 			}
115 | 		}
116 | 
117 | 		restore_error_handler();
118 | 		if (!$silent && $this->errorMsg !== NULL) {
119 | 			if (ini_get('html_errors')) {
120 | 				$this->errorMsg = html_entity_decode(strip_tags($this->errorMsg));
121 | 			}
122 | 
123 | 			if (($a = strpos($this->errorMsg, ': ')) !== FALSE) {
124 | 				$this->errorMsg = substr($this->errorMsg, $a + 2);
125 | 			}
126 | 
127 | 			throw new FtpException($this->errorMsg);
128 | 		}
129 | 
130 | 		return $res;
131 | 	}
132 | 
133 | 
134 | 	/**
135 | 	 * Internal error handler. Do not call directly.
136 | 	 */
137 | 	public function _errorHandler($code, $message)
138 | 	{
139 | 		$this->errorMsg = $message;
140 | 	}
141 | 
142 | 
143 | 	/**
144 | 	 * Reconnects to FTP server.
145 | 	 * @return void
146 | 	 */
147 | 	public function reconnect()
148 | 	{
149 | 		@ftp_close($this->resource); // intentionally @
150 | 		foreach ($this->state as $name => $args) {
151 | 			call_user_func_array(array($this, $name), $args);
152 | 		}
153 | 	}
154 | 
155 | 
156 | 	/**
157 | 	 * Checks if file or directory exists.
158 | 	 * @param  string
159 | 	 * @return bool
160 | 	 */
161 | 	public function fileExists($file)
162 | 	{
163 | 		return (bool) $this->nlist($file);
164 | 	}
165 | 
166 | 
167 | 	/**
168 | 	 * Checks if directory exists.
169 | 	 * @param  string
170 | 	 * @return bool
171 | 	 */
172 | 	public function isDir($dir)
173 | 	{
174 | 		$current = $this->pwd();
175 | 		try {
176 | 			$this->chdir($dir);
177 | 		} catch (FtpException $e) {
178 | 		}
179 | 		$this->chdir($current);
180 | 		return empty($e);
181 | 	}
182 | 
183 | 
184 | 	/**
185 | 	 * Recursive creates directories.
186 | 	 * @param  string
187 | 	 * @return void
188 | 	 */
189 | 	public function mkDirRecursive($dir)
190 | 	{
191 | 		$parts = explode('/', $dir);
192 | 		$path = '';
193 | 		while (!empty($parts)) {
194 | 			$path .= array_shift($parts);
195 | 			try {
196 | 				if ($path !== '') $this->mkdir($path);
197 | 			} catch (FtpException $e) {
198 | 				if (!$this->isDir($path)) {
199 | 					throw new FtpException("Cannot create directory '$path'.");
200 | 				}
201 | 			}
202 | 			$path .= '/';
203 | 		}
204 | 	}
205 | 
206 | 
207 | 	/**
208 | 	 * Recursive deletes path.
209 | 	 * @param  string
210 | 	 * @return void
211 | 	 */
212 | 	public function deleteRecursive($path)
213 | 	{
214 | 		if (!$this->tryDelete($path)) {
215 | 			foreach ((array) $this->nlist($path) as $file) {
216 | 				if ($file !== '.' && $file !== '..') {
217 | 					$this->deleteRecursive(strpos($file, '/') === FALSE ? "$path/$file" : $file);
218 | 				}
219 | 			}
220 | 			$this->rmdir($path);
221 | 		}
222 | 	}
223 | 
224 | }
225 | 
226 | 
227 | 
228 | class FtpException extends Exception
229 | {
230 | }
231 | 


--------------------------------------------------------------------------------