Merge branch 'release-2.140.100' 2.140.100
authorChris Koeritz <fred@gruntose.com>
Thu, 16 Nov 2017 14:44:06 +0000 (09:44 -0500)
committerChris Koeritz <fred@gruntose.com>
Thu, 16 Nov 2017 14:44:06 +0000 (09:44 -0500)
merging in from development branch to create 2.140.100

29 files changed:
documentation/feisty_meow_command_reference.txt [new file with mode: 0644]
production/feisty_meow_config.ini
production/sites/cakelampvm.com/docs/manual/cakelampvm_guide_v002.html
production/sites/cakelampvm.com/goog_maps_helper_mod/GoogleMapHelper.php [new file with mode: 0644]
production/sites/cakelampvm.com/goog_maps_helper_mod/compare_with_install.sh [new file with mode: 0644]
production/sites/cakelampvm.com/hello.txt [new file with mode: 0644]
scripts/archival/snarf_feisty_meow.pl
scripts/core/common.alias
scripts/core/functions.sh
scripts/core/launch_feisty_meow.sh
scripts/core/reconfigure_feisty_meow.sh
scripts/core/variables.sh
scripts/rev_control/puffer.sh [new file with mode: 0644]
scripts/rev_control/version_control.sh
scripts/site_avenger/avcoreup.sh
scripts/site_avenger/config/default.app
scripts/site_avenger/powerup.sh
scripts/site_avenger/revamp_cakelampvm.sh
scripts/site_avenger/shared_site_mgr.sh
scripts/site_avenger/sitepush.sh
scripts/site_avenger/siteup.sh
scripts/site_avenger/standup.sh
scripts/site_avenger/teardown.sh
scripts/system/add_apache_site.sh
scripts/system/add_domain.sh
scripts/system/common_sysadmin.sh [new file with mode: 0644]
scripts/system/remove_apache_site.sh [new file with mode: 0644]
scripts/system/remove_domain.sh [new file with mode: 0644]
scripts/tty/terminal_titler.sh

diff --git a/documentation/feisty_meow_command_reference.txt b/documentation/feisty_meow_command_reference.txt
new file mode 100644 (file)
index 0000000..5cbd70e
--- /dev/null
@@ -0,0 +1,158 @@
+
+Welcome, adventurous script user.
+
+These are the handiest commands available in the Feisty Meow scripts.
+Note that each script is expected to be self-documenting.  Try running it
+with a "--help" flag (or with no parameters in some cases) to print the
+built-in docs.  At worst, you may have to read the script (documentation
+fail--please let us know).
+
+setup and loading commands
+==========================
+
++ read "readme.txt" in the top of the feisty meow codebase, or
++ read it online at: https://feistymeow.org/feisty_meow/readme.txt
+
+revision control commands
+=========================
+
+all revision control commands bring up the editor in the EDITOR environment
+variable when creating commit messages.  you need to actually save and quit
+from that editor when you're done writing your commit message.
+
+  here's a guide to writing good commit messages:
+  + https://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message
+
+========
+the first suite of commands takes a list of directory names as parameters and
+then operates on those names.
+========
+
+  rgetem:
+  does a simple update (or pull) of the repository paths provided on the
+  command line.  this will only get things from the main origin that the
+  repository is hooked up with, so it is super quick compared to the next
+  couple commands.
+
+  rpuffer:
+  update the repositories provided on the command line by "puffing them out",
+  which means that the upstream repositories that feed the local one will be
+  synched up with it.  this is quite important to do when a git repository has
+  multiple branches, since unmerged changes upstream can really snarl up your
+  checkin.  this is basically a heavyweight version of rgetem.
+
+  rcheckin:
+  checks in the list of repositories passed on the command line.  in git
+  parlance, this adds all modified or untracked files, then commits all
+  changes in the repository, and finally pushes up the changes to the remote
+  online repository.  before doing the checkin, this will do a full "rpuffer"
+  update on the repository to ensure that there are no unmerged upstream
+  changes that could cause problems later.
+
+========
+the next suite of commands uses the REPOSITORY_LIST environment variable as
+the set of revision controlled folders to operate on.  the feisty meow scripts
+automatically add the feisty meow top-level (the apex) to this list to ensure
+that updates are received when available.
+========
+
+  getem:
+  update all repositories in the REPOSITORY_LIST from their upstream remote
+  counterparts.  fast.
+
+  puffer:
+  puffs out the REPOSITORY_LIST items to merge upstream changes.
+
+  checkin:
+  checks in all changes in the REPOSITORY_LIST to their remote repositories.
+
+========
+some assorted other revision control commands:
+========
+
+  feisty_branch:
+  shows the current branch that is checked out.
+
+  this command will move your feisty meow codebase to the development branch:
+  pushd $FEISTY_MEOW_APEX; git checkout dev; popd
+
+  and this command will get you back onto the mainline branch:
+  pushd $FEISTY_MEOW_APEX; git checkout master; popd
+
+=============================
+the site avenger script suite
+=============================
+
+the site avenger tools (inherited from the avbash project) are commands for
+managing web sites.  these scripts offer a lot of power to the developer, and
+of course that comes with great responsibility...
+
+the site avenger scripts are configured by "app" files stored in the "config"
+directory (in $FEISTY_MEOW_SCRIPTS/scripts/site_avenger/config).  the scripts
+seek out a config file named after the application, e.g. they look for
+"winterportlibrary.app" if the application name is "winterportlibrary".
+the basic config file "default.app" is used for any application that is unknown
+in the config directory.  any of the variable definitions provided in
+default.app can be overridden to change how the applications, and associated
+web site and domain, are configured.  see "mapsdemo.app" for an example of
+overriding the domain name for the mapsdemo application.
+
+  revamp_cakelampvm:
+  establishes permissions and ownership to make the virtual machine and its
+  services behave properly.  if something goes wonky, try running this script.
+  this script is also the main vehicle for delivering configuration changes
+  to the cakelampvm.  we are trying really hard to never release a version 2
+  of the vm, since we can patch it as needed using the revamp script.  let's
+  see how well that works out...
+
+  standup:
+  brings up an application or web site from scratch (potentially) by creating
+  an appropriate domain name, writing a basic apache site config file, pulling
+  the application from a git repository, and "powering up" the application via
+  composer.  this is most powerful and effective on php sites, but can also be
+  used for other types of websites.  note that this, and all of the scripts
+  here, are heavily biased for site avenger based development at saco designs.
+  to make these scripts truly your own, write configuration files (see above)
+  that define the proper folders and repository for your applications.
+
+  teardown:
+  takes down a site previously brought up by the standup command.  this just
+  eliminates the domain and the apache site though; the code is left in place
+  to prevent disaster.
+  
+  powerup:
+  similar to standup, but just gets the application source out and powers it
+  up with composer.
+(note: automatic database configuration and inflation is in the pipeline but
+is not ready yet.  that will go into powerup.)
+  
+  avcoreup:
+  updates the avcore portion of a site avenger application.  this command can
+  accept an application name within which to update, or it can auto-pick the
+  applicatin for you from the available checked out ones in ~/apps (the default
+  storage folder for all site avenger style sites).
+
+  siteup:
+  updates the entire checked out repository for a site avenger application.
+  supports app name on the command line, or auto-picks the app.
+
+  sitepush:
+  checks in the source code and other site assets for a site avenger app.
+  supports passing an app name on the command line, or auto-picks the app.
+
+
+  satis-refresh:
+  updates satis for a site(?).
+note: this is the one site avenger command that hasn't been "feisty meowicized" yet.
+
+lower level scripts used by site avenger scripts:
+
+  add_domain / remove_domain: (from system script collection)
+  adds a DNS domain to the bind9 configuration.
+  (the domain tools, for example, are
+    very sensitive to edits within the chunks of code they have written.  if you
+  need to edit bind config files, be sure to do it way above or way below the
+  auto-generated domains.)
+
+  add_apache_site / remove_apache_site:
+
index 8e74eaac095ce53353605b888f84319a28bec05b..7abba84985ba34a03701cc1c71eb7d042579530e 100644 (file)
@@ -3,7 +3,7 @@
 # specifies the version of the code that is being constructed here.
 major=2
 minor=140
-revision=99
+revision=100
 build=420
 
 # specifies the remainder of the version record info.
index 610b4564f960ec684afd245a279c08a7bdffbbbc..010635031446c218a7c9610bd20f074ee0a52ac3 100644 (file)
@@ -9,17 +9,54 @@
       Configuration and Usage</h1>
     <h2 style=" text-align: center;">By Chris Koeritz</h2>
     <h3 style="   text-align: center;"> Vintage: cakelampvm v002 &nbsp;&nbsp;
-      Updated: 2017-11-10</h3>
+      Updated: 2017-11-16</h3>
+    <p>The cakelampvm project provides a virtualbox VM that acts as an "internet
+      in a bottle".&nbsp; The virtual machine provides DNS services (<a title="dns server"
+        href="http://www.bind9.net/">bind9</a>), a Web server (<a title="patchy"
+        href="https://httpd.apache.org/">Apache2</a>), a full <a title="ubuntu means compassion and humanity"
+        href="https://www.ubuntu.com/">Ubuntu</a> <a title="it's pronounced leenoox"
+        href="https://www.linuxfoundation.org/">Linux</a> desktop environment,
+      the <a title="flux is change" href="http://fluxbox.org/">Fluxbox</a> <a
+        title="a better windows" href="https://www.x.org/">X window manager</a>,
+      and a suite of tools called the <a title="feisty meow® concerns ltd. website"
+        href="https://feistymeow.org/">Feisty Meow® codebase</a>
+      <meta http-equiv="content-type" content="text/html; charset=utf-8">
+      .&nbsp; Together, these services provide you with a very flexible and
+      powerful testbed for web development, especially suited for <a title="it's cake"
+        href="https://cakephp.org/">CakePHP</a>.</p>
+    <p>todo: arrange gritty details to back.</p>
+    <p>TOC GOES HERE.</p>
     <h6> </h6>
-    <h2>Basic info for the guest VM</h2>
+    <h2><span style="text-decoration: underline;">G</span>uest VM Configuration</h2>
     <ul>
-      <li>hostname: cakelampvm.com</li>
+      <li>hostname: <a title="the vm's website, when configured properly" href="https://cakelampvm.com/">cakelampvm.com</a></li>
       <li>local IP address: 10.28.42.20</li>
-      <li>services: DNS (bind9), apache2, fluxbox X windowing system, gnome
-        display manager</li>
+      <li>services: DNS (bind9), apache2, fluxbox X windowing system, <a title="not just in the garden"
+          href="https://www.gnome.org/">gnome display manager</a></li>
       <li>main user: developer (password distributed separately)</li>
       <li>mysql root password: (password distributed separately)</li>
     </ul>
+    <h2>Powering up with the Feisty Meow® scripts</h2>
+    <p>[First, let me drop the registered trademark symbol from here on
+      in.&nbsp; I hope its presence above has been sufficiently clear for legal
+      purposes, but now it will just get in the way.&nbsp; Also, capitalization
+      really bores me, and it's the feisty meow codebase anyhow, so that's how
+      it will be written henceforth.]</p>
+    <p>The feisty meow scripts are a cohesive bash scripting environment for
+      getting a variety of tasks done.&nbsp; The scripts recently incorporated
+      the 'avbash' collection from Saco Designs, which provides tools for
+      bringing up CakePHP web sites and managing the collection of repositories
+      for those sites.&nbsp; Each website is considered an "application", and
+      the application name itself (e.g. "winterportlibrary") can often provide
+      all the details for "powering up" the site.&nbsp; The feisty meow team has
+      added additional scripts for managing DNS domains and Apache websites that
+      provide the capability to "stand up" an entire website around an
+      application, with accompanying domain.</p>
+    <p>The scripts for now are documented separately within the Feisty Meow
+      codebase.&nbsp; The Feisty Meow readme file provides some valuable
+      information on configuring the codebase.&nbsp; If you have the cakelampvm,
+      then this has already been done for you on the vm in the developer account.&nbsp;
+      The script documentation is available in the UHHHHHH page of something..</p>
     <h2>How to set up virtualbox for your host PC</h2>
     <ol>
       <li>Download and install virtualbox:
@@ -56,7 +93,8 @@
         specific vm).</li>
       <li> Click on the "Network" tab.</li>
       <li> Choose the "Host-only Networks" tab from within "Network".</li>
-      <li> Click the plus icon to add a new host-only network.</li>
+      <li> Click the plus icon to add a new host-only network, or if there is
+        already a Host-only network, then edit it.</li>
       <li>Set the "Adapter" parameters:<br>
         IPv4 Address: 10.28.42.1<br>
         IPv4 Network Mask: 255.255.255.0<br>
         These are my settings, with IPv6 left disabled:<br>
         <img alt="nat net config" src="images/nat_network_config.png"></li>
     </ol>
+    <h2>Start up the VM</h2>
+    <p>Using the virtualbox interface, you should now be able to start your
+      virtual machine.&nbsp; Virtualbox will complain if it detects any
+      remaining configuration problems in the VM, but it should start
+      normally.&nbsp; The Linux boot sequence will show many lines of text,
+      before bringing up a black console window with a login dialog.</p>
+    <p>You can log in directly on the VM console with the developer account, but
+      it is generally more useful to connect to the cakelampvm over ssh.&nbsp;
+      If the networking has been established properly, you should be able to do
+      this with:</p>
+    <pre>ssh developer@cakelampvm.com&nbsp; (or equivalent with your ssh client)</pre>
+    <p>And then provide the password to log in.</p>
+    <p>A feature called "X forwarding" is enabled, so if you start graphical
+      applications on the VM, you can display them from an appropriately
+      configured host.&nbsp; (If you're running Linux as the host for the VM,
+      you can definitely run remote windows.&nbsp; Windows may not support
+      that.)</p>
+    <p>#### check this!!!</p>
     <h2>Using the guest VM's DNS services</h2>
     <p>The cakelampvm has been set up to provide a DNS server which will answer
       name requests for all of the sites that the VM hosts.</p>
     <pre>ping cakelampvm.com</pre>
     <pre>ping defaultcake.cakelampvm.com</pre>
     <pre>ping mapsdemo.cakelampvm.com</pre>
+    <h3>Setting up DNS on Windows</h3>
+    <p>The ipconfig tool will provide helpful information about your current
+      networking and DNS configuration:</p>
+    <pre>ipconfig --all</pre>
+    <p>The DNS configuration on Windows is somewhat byzantine.&nbsp; The pipe
+      characters ('|') below are used to separate the menus or tabs or dialogs
+      to traverse.&nbsp; Follow this path to get to the DNS config:</p>
+    <pre>Control Panel | Network &amp; Sharing | click WiFI or Ethernet link near top right | click Adapter Settings on left | click on specific network device to modify | select Properties</pre>
+    <p><br>
+    </p>
+    <p><br>
+    </p>
+    <p><br>
+    </p>
     <h2>Editing files on the guest VM from the host</h2>
     <p>On the host computer, look for the guest vm as a networked computer
       called cakelampvm.&nbsp; This should provide some network shares using
diff --git a/production/sites/cakelampvm.com/goog_maps_helper_mod/GoogleMapHelper.php b/production/sites/cakelampvm.com/goog_maps_helper_mod/GoogleMapHelper.php
new file mode 100644 (file)
index 0000000..074707f
--- /dev/null
@@ -0,0 +1,1728 @@
+<?php
+namespace Geo\View\Helper;
+
+use Cake\Core\Configure;
+use Cake\Core\Exception\Exception;
+use Cake\Routing\Router;
+use Cake\Utility\Hash;
+use Cake\View\Helper;
+use Cake\View\View;
+use Geo\View\Helper\JsBaseEngineTrait;
+
+/**
+ * This is a CakePHP helper that helps users to integrate GoogleMap v3
+ * into their application by only writing PHP code. This helper depends on jQuery.
+ *
+ * Capable of resetting itself (full or partly) for multiple maps on a single view.
+ *
+ * CodeAPI: http://code.google.com/intl/de-DE/apis/maps/documentation/javascript/basics.html
+ * Icons/Images: http://gmapicons.googlepages.com/home
+ *
+ * @author Rajib Ahmed
+ * @author Mark Scherer
+ * @link http://www.dereuromark.de/2010/12/21/googlemapsv3-cakephp-helper/
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ * @property \Cake\View\Helper\HtmlHelper $Html
+ */
+class GoogleMapHelper extends Helper {
+
+       use JsBaseEngineTrait;
+
+       const API = 'maps.google.com/maps/api/js';
+
+       const STATIC_API = 'maps.google.com/maps/api/staticmap';
+
+       /**
+        * @var int
+        */
+       public static $mapCount = 0;
+
+       /**
+        * @var int
+        */
+       public static $markerCount = 0;
+
+       /**
+        * @var int
+        */
+       public static $iconCount = 0;
+
+       /**
+        * @var int
+        */
+       public static $infoWindowCount = 0;
+
+       /**
+        * @var int
+        */
+       public static $infoContentCount = 0;
+
+       const TYPE_ROADMAP = 'R';
+
+       const TYPE_HYBRID = 'H';
+
+       const TYPE_SATELLITE = 'S';
+
+       const TYPE_TERRAIN = 'T';
+
+       /**
+        * @var array
+        */
+       public $types = [
+               self::TYPE_ROADMAP => 'ROADMAP',
+               self::TYPE_HYBRID => 'HYBRID',
+               self::TYPE_SATELLITE => 'SATELLITE',
+               self::TYPE_TERRAIN => 'TERRAIN'
+       ];
+
+       const TRAVEL_MODE_DRIVING = 'D';
+
+       const TRAVEL_MODE_BICYCLING = 'B';
+
+       const TRAVEL_MODE_TRANSIT = 'T';
+
+       const TRAVEL_MODE_WALKING = 'W';
+
+       /**
+        * @var array
+        */
+       public $travelModes = [
+               self::TRAVEL_MODE_DRIVING => 'DRIVING',
+               self::TRAVEL_MODE_BICYCLING => 'BICYCLING',
+               self::TRAVEL_MODE_TRANSIT => 'TRANSIT',
+               self::TRAVEL_MODE_WALKING => 'WALKING'
+       ];
+
+       /**
+        * Needed helpers
+        *
+        * @var array
+        */
+       public $helpers = ['Html'];
+
+       /**
+        * Google maker config instance variable
+        *
+        * @var array
+        */
+       public $markers = [];
+
+       /**
+        * @var array
+        */
+       public $infoWindows = [];
+
+       /**
+        * @var array
+        */
+       public $infoContents = [];
+
+       /**
+        * @var array
+        */
+       public $icons = [];
+
+       /**
+        * @var array
+        */
+       public $matching = [];
+
+       /**
+        * @var string
+        */
+       public $map = '';
+
+       /**
+        * @var array
+        */
+       protected $_mapIds = []; // Remember already used ones (valid xhtml contains ids not more than once)
+
+       /**
+        * Default settings
+        *
+        * @var array
+        */
+       protected $_defaultConfig = [
+               'zoom' => null, // global, both map and staticMap
+               'lat' => null, // global, both map and staticMap
+               'lng' => null, // global, both map and staticMap
+               'api' => '3',
+               'type' => self::TYPE_ROADMAP,
+               'map' => [
+                       'api' => null,
+                       'zoom' => null,
+                       'lat' => null,
+                       'lng' => null,
+                       'type' => null,
+                       'streetViewControl' => false,
+                       'navigationControl' => true,
+                       'mapTypeControl' => true,
+                       'scaleControl' => true,
+                       'scrollwheel' => false,
+                       'keyboardShortcuts' => true,
+                       'typeOptions' => [],
+                       'navOptions' => [],
+                       'scaleOptions' => [],
+                       'defaultLat' => 51, // only last fallback, use Configure::write('Google.lat', ...); to define own one
+                       'defaultLng' => 11, // only last fallback, use Configure::write('Google.lng', ...); to define own one
+                       'defaultZoom' => 5,
+               ],
+               'staticMap' => [
+                       'size' => '300x300',
+                       'format' => 'png',
+                       'mobile' => false,
+                       //'shadow' => true // for icons
+               ],
+               'geolocate' => false,
+               'language' => null,
+               'region' => null,
+               'showMarker' => true,
+               //'showInfoWindow' => true,
+               'infoWindow' => [
+                       'content' => '',
+                       'useMultiple' => false, // Using single infowindow object for all
+                       'maxWidth' => 300,
+                       'lat' => null,
+                       'lng' => null,
+                       'pixelOffset' => 0,
+                       'zIndex' => 200,
+                       'disableAutoPan' => false
+               ],
+               'marker' => [
+                       //'autoCenter' => true,
+                       'animation' => null, // BOUNCE or DROP  https://developers.google.com/maps/documentation/javascript/3.exp/reference#Animation
+                       'icon' => null, // => default (red marker) //http://google-maps-icons.googlecode.com/files/home.png
+                       'title' => null,
+                       'shadow' => null,
+                       'shape' => null,
+                       'zIndex' => null,
+                       'draggable' => false,
+                       'cursor' => null,
+                       'directions' => false, // add form with directions
+                       'open' => false, // New in 1.5
+               ],
+               'div' => [
+                       'id' => 'map_canvas',
+                       'width' => '100%',
+                       'height' => '400px',
+                       'class' => 'map',
+                       'escape' => true
+               ],
+               'event' => [
+               ],
+               'animation' => [
+                       //TODO
+               ],
+               'polyline' => [
+                       'color' => '#FF0000',
+                       'opacity' => 1.0,
+                       'weight' => 2,
+               ],
+               'directions' => [
+                       'travelMode' => self::TRAVEL_MODE_DRIVING,
+                       'unitSystem' => 'METRIC',
+                       'directionsDiv' => null,
+               ],
+               'callbacks' => [
+                       'geolocate' => null //TODO
+               ],
+               'plugins' => [
+                       'keydragzoom' => false, // http://google-maps-utility-library-v3.googlecode.com/svn/tags/keydragzoom/
+                       'markermanager' => false, // http://google-maps-utility-library-v3.googlecode.com/svn/tags/markermanager/
+                       'markercluster' => false, // http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclusterer/
+               ],
+               'autoCenter' => false, // try to fit all markers in (careful, all zooms values are omitted)
+               'autoScript' => false, // let the helper include the necessary js script links
+               'block' => true, // for scripts
+               'localImages' => false,
+               'https' => null, // auto detect
+               'key' => null,
+       ];
+
+       /**
+        * @var array
+        */
+       protected $_runtimeConfig = [];
+
+       /**
+        * @var bool
+        */
+       protected $_apiIncluded = false;
+
+       /**
+        * @var bool
+        */
+       protected $_gearsIncluded = false;
+
+       /**
+        * @var bool
+        */
+       protected $_located = false;
+
+       /**
+        * @param \Cake\View\View|null $View
+        * @param array $config
+        */
+       public function __construct(View $View, array $config = []) {
+               parent::__construct($View, $config);
+       }
+
+       /**
+        * @param array $config
+        * @return void
+        */
+       public function initialize(array $config) {
+               parent::initialize($config);
+
+               $defaultConfig = Hash::merge($this->_defaultConfig, (array)Configure::read('GoogleMap'));
+               $config = Hash::merge($defaultConfig, $config);
+
+               if (isset($config['api']) && !isset($config['map']['api'])) {
+                       $config['map']['api'] = $config['api'];
+               }
+               if (isset($config['zoom']) && !isset($config['map']['zoom'])) {
+                       $config['map']['zoom'] = $config['zoom'];
+               }
+               if (isset($config['lat']) && !isset($config['map']['lat'])) {
+                       $config['map']['lat'] = $config['lat'];
+               }
+               if (isset($config['lng']) && !isset($config['map']['lng'])) {
+                       $config['map']['lng'] = $config['lng'];
+               }
+               if (isset($config['type']) && !isset($config['map']['type'])) {
+                       $config['map']['type'] = $config['type'];
+               }
+               if (isset($config['size'])) {
+                       $config['div']['width'] = $config['size']['width'];
+                       $config['div']['height'] = $config['size']['height'];
+               }
+               if (isset($config['staticSize'])) {
+                       $config['staticMap']['size'] = $config['staticSize'];
+               }
+               // the following are convenience defaults - if not available the map lat/lng/zoom defaults will be used
+               if (isset($config['staticZoom'])) {
+                       $config['staticMap']['zoom'] = $config['staticZoom'];
+               }
+               if (isset($config['staticLat'])) {
+                       $config['staticMap']['lat'] = $config['staticLat'];
+               }
+               if (isset($config['staticLng'])) {
+                       $config['staticMap']['lng'] = $config['staticLng'];
+               }
+               if (isset($config['localImages'])) {
+                       if ($config['localImages'] === true) {
+                               $config['localImages'] = Router::url('/img/google_map/', true);
+                       }
+               }
+
+               // BC
+               if (!empty($config['inline'])) {
+                       trigger_error('Deprecated inline option, use block instead.', E_USER_DEPRECATED);
+                       $config['block'] = null;
+               }
+
+               $this->_config = $config;
+               $this->_runtimeConfig = $this->_config;
+       }
+
+       /**
+        * JS maps.google API url.
+        *
+        * Options read via configs
+        * - key
+        * - api
+        * - language (iso2: en, de, ja, ...)
+        *
+        * You can adds more after the URL like "&key=value&..." via
+        * - query string array: additional query strings (e.g. callback for deferred execution - not supported yet by this helper)
+        *
+        * @param array $query
+        * @return string Full URL
+        */
+       public function apiUrl(array $query = []) {
+               $url = $this->_protocol() . static::API;
+
+               if ($this->_runtimeConfig['map']['api']) {
+                        $query['v'] = $this->_runtimeConfig['map']['api'];
+               }
+               if ($this->_runtimeConfig['key']) {
+                       $query['key'] = $this->_runtimeConfig['key'];
+               }
+
+               if ($this->_runtimeConfig['language']) {
+                       $query['language'] = $this->_runtimeConfig['language'];
+               }
+
+               if ($query) {
+                       $query = http_build_query($query);
+
+                       $url .= '?' . $query;
+               }
+
+               return $url;
+       }
+
+       /**
+        * @deprecated
+        * @return string
+        */
+       public function gearsUrl() {
+               $this->_gearsIncluded = true;
+               $url = $this->_protocol() . 'code.google.com/apis/gears/gears_init.js';
+               return $url;
+       }
+
+       /**
+        * @return string currentMapObject
+        */
+       public function name() {
+               return 'map' . static::$mapCount;
+       }
+
+       /**
+        * @return string currentContainerId
+        */
+       public function id() {
+               return $this->_runtimeConfig['div']['id'];
+       }
+
+       /**
+        * Make it possible to include multiple maps per page
+        * resets markers, infoWindows etc
+        *
+        * @param bool $full true=optionsAsWell
+        * @return void
+        */
+       public function reset($full = true) {
+               static::$markerCount = static::$infoWindowCount = 0;
+               $this->markers = $this->infoWindows = [];
+               if ($full) {
+                       $this->_runtimeConfig = $this->_config;
+               }
+       }
+
+       /**
+        * Set the controls of current map
+        *
+        * Control options
+        * - zoom, scale, overview: TRUE/FALSE
+        *
+        * - map: FALSE, small, large
+        * - type: FALSE, normal, menu, hierarchical
+        * TIP: faster/shorter by using only the first character (e.g. "H" for "hierarchical")
+        *
+        * @param array $options
+        * @return void
+        */
+       public function setControls(array $options = []) {
+               if (isset($options['streetView'])) {
+                       $this->_runtimeConfig['map']['streetViewControl'] = $options['streetView'];
+               }
+               if (isset($options['zoom'])) {
+                       $this->_runtimeConfig['map']['scaleControl'] = $options['zoom'];
+               }
+               if (isset($options['scrollwheel'])) {
+                       $this->_runtimeConfig['map']['scrollwheel'] = $options['scrollwheel'];
+               }
+               if (isset($options['keyboardShortcuts'])) {
+                       $this->_runtimeConfig['map']['keyboardShortcuts'] = $options['keyboardShortcuts'];
+               }
+               if (isset($options['type'])) {
+                       $this->_runtimeConfig['map']['type'] = $options['type'];
+               }
+       }
+
+       /**
+        * This the initialization point of the script
+        * Returns the div container you can echo on the website
+        *
+        * @param array $options associative array of settings are passed
+        * @return string divContainer
+        */
+       public function map(array $options = []) {
+               $this->reset();
+               $this->_runtimeConfig = Hash::merge($this->_runtimeConfig, $options);
+               $this->_runtimeConfig['map'] = $options + $this->_runtimeConfig['map'];
+
+               if (!isset($this->_runtimeConfig['map']['lat']) || !isset($this->_runtimeConfig['map']['lng'])) {
+                       $this->_runtimeConfig['map']['lat'] = $this->_runtimeConfig['map']['defaultLat'];
+                       $this->_runtimeConfig['map']['lng'] = $this->_runtimeConfig['map']['defaultLng'];
+               }
+               if (!isset($this->_runtimeConfig['map']['zoom'])) {
+                       $this->_runtimeConfig['map']['zoom'] = $this->_runtimeConfig['map']['defaultZoom'];
+               }
+
+               $result = '';
+
+               // autoinclude js?
+               if ($this->_runtimeConfig['autoScript'] && !$this->_apiIncluded) {
+                       $res = $this->Html->script($this->apiUrl(), ['block' => $this->_runtimeConfig['block']]);
+                       $this->_apiIncluded = true;
+
+                       if (!$this->_runtimeConfig['block']) {
+                               $result .= $res . PHP_EOL;
+                       }
+                       // usually already included
+                       //http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js
+               }
+               // still not very common: http://code.google.com/intl/de-DE/apis/maps/documentation/javascript/basics.html
+               if (false && !empty($this->_runtimeConfig['autoScript']) && !$this->_gearsIncluded) {
+                       $res = $this->Html->script($this->gearsUrl(), ['block' => $this->_runtimeConfig['block']]);
+                       if (!$this->_runtimeConfig['block']) {
+                               $result .= $res . PHP_EOL;
+                       }
+               }
+
+               $map = "
+                       var initialLocation = " . $this->_initialLocation() . ";
+                       var browserSupportFlag = new Boolean();
+                       var myOptions = " . $this->_mapOptions() . ";
+
+                       // deprecated
+                       gMarkers" . static::$mapCount . " = new Array();
+                       gInfoWindows" . static::$mapCount . " = new Array();
+                       gWindowContents" . static::$mapCount . " = new Array();
+               ";
+
+               #rename "map_canvas" to "map_canvas1", ... if multiple maps on one page
+               while (in_array($this->_runtimeConfig['div']['id'], $this->_mapIds)) {
+                       $this->_runtimeConfig['div']['id'] .= '-1'; //TODO: improve
+               }
+               $this->_mapIds[] = $this->_runtimeConfig['div']['id'];
+
+               $map .= "
+                       var " . $this->name() . ' = new google.maps.Map(document.getElementById("' . $this->_runtimeConfig['div']['id'] . "\"), myOptions);
+                       ";
+               $this->map = $map;
+
+               $this->_runtimeConfig['div']['style'] = '';
+               if (is_numeric($this->_runtimeConfig['div']['width'])) {
+                       $this->_runtimeConfig['div']['width'] .= 'px';
+               }
+               if (is_numeric($this->_runtimeConfig['div']['height'])) {
+                       $this->_runtimeConfig['div']['height'] .= 'px';
+               }
+
+               $this->_runtimeConfig['div']['style'] .= 'width: ' . $this->_runtimeConfig['div']['width'] . ';';
+               $this->_runtimeConfig['div']['style'] .= 'height: ' . $this->_runtimeConfig['div']['height'] . ';';
+               unset($this->_runtimeConfig['div']['width']);
+               unset($this->_runtimeConfig['div']['height']);
+
+               $defaultText = isset($this->_runtimeConfig['content']) ? $this->_runtimeConfig['content'] : __('Map cannot be displayed!');
+               $result .= $this->Html->tag('div', $defaultText, $this->_runtimeConfig['div']);
+
+               return $result;
+       }
+
+       /**
+        * Generate a new LatLng object with the current lat and lng.
+        *
+        * @return string
+        */
+       protected function _initialLocation() {
+               if ($this->_runtimeConfig['map']['lat'] && $this->_runtimeConfig['map']['lng']) {
+                       return 'new google.maps.LatLng(' . $this->_runtimeConfig['map']['lat'] . ', ' . $this->_runtimeConfig['map']['lng'] . ')';
+               }
+               $this->_runtimeConfig['autoCenter'] = true;
+               return 'false';
+       }
+
+       /**
+        * Add a marker to the map.
+        *
+        * Options:
+        * - lat and lng or address (to geocode on demand, not recommended, though)
+        * - title, content, icon, directions, maxWidth, open (optional)
+        *
+        * Note, that you can only set one marker to "open" for single window mode.
+        * If you declare multiple ones, the last one will be the one shown as open.
+        *
+        * @param array $options
+        * @return mixed Integer marker count or boolean false on failure
+        * @throws \Cake\Core\Exception\Exception
+        */
+       public function addMarker($options) {
+               $defaults = $this->_runtimeConfig['marker'];
+               if (isset($options['icon']) && is_array($options['icon'])) {
+                       $defaults = $options['icon'] + $defaults;
+                       unset($options['icon']);
+               }
+               $options += $defaults;
+
+               $params = [];
+               $params['map'] = $this->name();
+
+               if (isset($options['title'])) {
+                       $params['title'] = json_encode($options['title']);
+               }
+               if (isset($options['icon'])) {
+                       $params['icon'] = $options['icon'];
+                       if (is_int($params['icon'])) {
+                               $params['icon'] = 'gIcons' . static::$mapCount . '[' . $params['icon'] . ']';
+                       } else {
+                               $params['icon'] = json_encode($params['icon']);
+                       }
+               }
+               if (isset($options['shadow'])) {
+                       $params['shadow'] = $options['shadow'];
+                       if (is_int($params['shadow'])) {
+                               $params['shadow'] = 'gIcons' . static::$mapCount . '[' . $params['shadow'] . ']';
+                       } else {
+                               $params['shadow'] = json_encode($params['shadow']);
+                       }
+               }
+               if (isset($options['shape'])) {
+                       $params['shape'] = $options['shape'];
+               }
+               if (isset($options['zIndex'])) {
+                       $params['zIndex'] = $options['zIndex'];
+               }
+               if (isset($options['animation'])) {
+                       $params['animation'] = 'google.maps.Animation.' . strtoupper($options['animation']);
+               }
+
+               // geocode if necessary
+               if (!isset($options['lat']) || !isset($options['lng'])) {
+                       $this->map .= "
+var geocoder = new google.maps.Geocoder();
+
+function geocodeAddress(address) {
+       geocoder.geocode({'address': address}, function(results, status) {
+               if (status == google.maps.GeocoderStatus.OK) {
+
+                       x" . static::$markerCount . " = new google.maps.Marker({
+                               position: results[0].geometry.location,
+                               " . $this->_toObjectParams($params, false, false) . "
+                       });
+                       gMarkers" . static::$mapCount . " .push(
+                               x" . static::$markerCount . "
+                       );
+                       return results[0].geometry.location;
+               } else {
+                       //alert('Geocoding was not successful for the following reason: ' + status);
+                       return null;
+               }
+       });
+}";
+                       if (!isset($options['address'])) {
+                               throw new Exception('Either use lat/lng or address to add a marker');
+                       }
+                       $position = 'geocodeAddress("' . h($options['address']) . '")';
+               } else {
+                       $position = 'new google.maps.LatLng(' . $options['lat'] . ',' . $options['lng'] . ')';
+               }
+
+               $marker = "
+                       var x" . static::$markerCount . " = new google.maps.Marker({
+                               position: " . $position . ",
+                               " . $this->_toObjectParams($params, false, false) . "
+                       });
+                       gMarkers" . static::$mapCount . " .push(
+                               x" . static::$markerCount . "
+                       );
+               ";
+               $this->map .= $marker;
+
+               if (!empty($options['directions'])) {
+                       $options['content'] .= $this->_directions($options['directions'], $options);
+               }
+
+               // Fill popup windows
+               if (!empty($options['content']) && $this->_runtimeConfig['infoWindow']['useMultiple']) {
+                       $x = $this->addInfoWindow(['content' => $options['content']]);
+                       $this->addEvent(static::$markerCount, $x, $options['open']);
+
+               } elseif (!empty($options['content'])) {
+                       if (!isset($this->_runtimeConfig['marker']['infoWindow'])) {
+                               $this->_runtimeConfig['marker']['infoWindow'] = $this->addInfoWindow();
+                       }
+
+                       $x = $this->addInfoContent($options['content']);
+                       $event = "
+                       gInfoWindows" . static::$mapCount . '[' . $this->_runtimeConfig['marker']['infoWindow'] . ']. setContent(gWindowContents' . static::$mapCount . '[' . $x . "]);
+                       gInfoWindows" . static::$mapCount . '[' . $this->_runtimeConfig['marker']['infoWindow'] . '].open(' . $this->name() . ', gMarkers' . static::$mapCount . '[' . $x . "]);
+                       ";
+                       $this->addCustomEvent(static::$markerCount, $event);
+
+                       if (!empty($options['open'])) {
+                               $this->addCustom($event);
+                       }
+               }
+
+               // Custom matching event?
+               if (isset($options['id'])) {
+                       $this->matching[$options['id']] = static::$markerCount;
+               }
+
+               return static::$markerCount++;
+       }
+
+       /**
+        * Build directions form (type get) for directions inside infoWindows
+        *
+        * Options for directions (if array)
+        * - label
+        * - submit
+        * - escape: defaults to true
+        *
+        * @param mixed $directions
+        * - bool TRUE for autoDirections (using lat/lng)
+        * @param array $markerOptions
+        * - options array of marker for autoDirections etc (optional)
+        * @return string HTML
+        */
+       protected function _directions($directions, array $markerOptions = []) {
+               $options = [
+                       'from' => null,
+                       'to' => null,
+                       'label' => __('Enter your address'),
+                       'submit' => __('Get directions'),
+                       'escape' => true,
+                       'zoom' => null, // auto
+               ];
+               if ($directions === true) {
+                       $options['to'] = $markerOptions['lat'] . ',' . $markerOptions['lng'];
+               } elseif (is_array($directions)) {
+                       $options = $directions + $options;
+               }
+               if (empty($options['to']) && empty($options['from'])) {
+                       return '';
+               }
+               $form = '<form action="http://maps.google.com/maps" method="get" target="_blank">';
+               $form .= $options['escape'] ? h($options['label']) : $options['label'];
+               if (!empty($options['from'])) {
+                       $form .= '<input type="hidden" name="saddr" value="' . $options['from'] . '" />';
+               } else {
+                       $form .= '<input type="text" name="saddr" />';
+               }
+               if (!empty($options['to'])) {
+                       $form .= '<input type="hidden" name="daddr" value="' . $options['to'] . '" />';
+               } else {
+                       $form .= '<input type="text" name="daddr" />';
+               }
+               if (isset($options['zoom'])) {
+                       $form .= '<input type="hidden" name="z" value="' . $options['zoom'] . '" />';
+               }
+               $form .= '<input type="submit" value="' . $options['submit'] . '" />';
+               $form .= '</form>';
+
+               return '<div class="directions">' . $form . '</div>';
+       }
+
+       /**
+        * @param string $content
+        * @return int Current marker counter
+        */
+       public function addInfoContent($content) {
+               $this->infoContents[static::$markerCount] = $this->escapeString($content);
+               $event = "
+                       gWindowContents" . static::$mapCount . '.push(' . $this->escapeString($content) . ");
+                       ";
+               $this->addCustom($event);
+
+               //TODO: own count?
+               return static::$markerCount;
+       }
+
+       /**
+        * @var array
+        */
+       public $setIcons = [
+               'color' => 'http://www.google.com/mapfiles/marker%s.png',
+               'alpha' => 'http://www.google.com/mapfiles/marker%s%s.png',
+               'numeric' => 'http://google-maps-icons.googlecode.com/files/%s%s.png',
+               'special' => 'http://google-maps-icons.googlecode.com/files/%s.png'
+       ];
+
+       /**
+        * Get a custom icon set
+        *
+        * @param string $color Color: green, red, purple, ... or some special ones like "home", ...
+        * @param string|null $char Char: A...Z or 0...20/100 (defaults to none)
+        * @param string $size Size: s, m, l (defaults to medium)
+        * NOTE: for special ones only first parameter counts!
+        * @return array Array(icon, shadow, shape, ...)
+        */
+       public function iconSet($color, $char = null, $size = 'm') {
+               $colors = ['red', 'green', 'yellow', 'blue', 'purple', 'white', 'black'];
+               if (!in_array($color, $colors)) {
+                       $color = 'red';
+               }
+
+               if (!empty($this->_runtimeConfig['localImages'])) {
+                       $this->setIcons['color'] = $this->_runtimeConfig['localImages'] . 'marker%s.png';
+                       $this->setIcons['alpha'] = $this->_runtimeConfig['localImages'] . 'marker%s%s.png';
+                       $this->setIcons['numeric'] = $this->_runtimeConfig['localImages'] . '%s%s.png';
+                       $this->setIcons['special'] = $this->_runtimeConfig['localImages'] . '%s.png';
+               }
+
+               if (!empty($char)) {
+                       if ($color === 'red') {
+                               $color = '';
+                       } else {
+                               $color = '_' . $color;
+                       }
+                       $url = sprintf($this->setIcons['alpha'], $color, $char);
+               } else {
+                       if ($color === 'red') {
+                               $color = '';
+                       } else {
+                               $color = '_' . $color;
+                       }
+                       $url = sprintf($this->setIcons['color'], $color);
+               }
+
+               /*
+        var iconImage = new google.maps.MarkerImage('images/' + images[0] + ' .png',
+            new google.maps.Size(iconData[images[0]].width, iconData[images[0]].height),
+            new google.maps.Point(0,0),
+            new google.maps.Point(0, 32)
+        );
+
+        var iconShadow = new google.maps.MarkerImage('images/' + images[1] + ' .png',
+            new google.maps.Size(iconData[images[1]].width, iconData[images[1]].height),
+            new google.maps.Point(0,0),
+            new google.maps.Point(0, 32)
+        );
+
+        var iconShape = {
+            coord: [1, 1, 1, 32, 32, 32, 32, 1],
+            type: 'poly'
+        };
+        */
+
+               $shadow = 'http://www.google.com/mapfiles/shadow50.png';
+               $res = [
+                       'url' => $url,
+                       'icon' => $this->icon($url, ['size' => ['width' => 20, 'height' => 34]]),
+                       'shadow' => $this->icon($shadow, ['size' => ['width' => 37, 'height' => 34], 'shadow' => ['width' => 10, 'height' => 34]])
+               ];
+               return $res;
+       }
+
+       /**
+        * Generate icon array.
+        *
+        * custom icon: http://thydzik.com/thydzikGoogleMap/markerlink.php?text=?&color=FFFFFF
+        * custom icons: http://code.google.com/p/google-maps-icons/wiki/NumericIcons#Lettered_Balloons_from_A_to_Z,_in_10_Colors
+        * custom shadows: http://www.cycloloco.com/shadowmaker/shadowmaker.htm
+        *
+        * @param string $image Image Url (http://...)
+        * @param string|null $shadow ShadowImage Url (http://...)
+        * @param array $imageOptions Image options
+        * @param array $shadowOptions Shadow image options
+        * @return array Resulting array
+        */
+       public function addIcon($image, $shadow = null, array $imageOptions = [], array $shadowOptions = []) {
+               $res = ['url' => $image];
+               $res['icon'] = $this->icon($image, $imageOptions);
+               if ($shadow) {
+                       $last = $this->_iconRemember[$res['icon']];
+                       if (!isset($shadowOptions['anchor'])) {
+                               $shadowOptions['anchor'] = [];
+                       }
+                       $shadowOptions['anchor'] = $last['options']['anchor'] + $shadowOptions['anchor'];
+
+                       $res['shadow'] = $this->icon($shadow, $shadowOptions);
+               }
+               return $res;
+       }
+
+       /**
+        * @var array
+        */
+       protected $_iconRemember = [];
+
+       /**
+        * Generate icon object
+        *
+        * @param string $url (required)
+        * @param array $options (optional):
+        * - size: array(width=>x, height=>y)
+        * - origin: array(width=>x, height=>y)
+        * - anchor: array(width=>x, height=>y)
+        * @return int Icon count
+        */
+       public function icon($url, array $options = []) {
+               // The shadow image is larger in the horizontal dimension
+               // while the position and offset are the same as for the main image.
+               if (empty($options['size'])) {
+                       if (substr($url, 0, 1) === '/') {
+                               // patch local paths to use the document root.  otherwise getimagesize fails filesystem lookup.
+                               // paths with http or other protocol in front will be handled more simply in 'else' below.
+                               $canonicalPath = realpath(WWW_ROOT . $url);
+                               if (! $canonicalPath) {
+                                       // failed to resolve the path, so just fall back to the url provided.
+                                       $canonicalPath = "$url";
+                               }
+                               $data = getimagesize($canonicalPath);
+                       } else {
+                               $data = getimagesize($url);
+                       }
+                       if ($data) {
+                               $options['size']['width'] = $data[0];
+                               $options['size']['height'] = $data[1];
+                       } else {
+                               $options['size']['width'] = $options['size']['height'] = 0;
+                       }
+               }
+               if (empty($options['anchor'])) {
+                       $options['anchor']['width'] = (int)($options['size']['width'] / 2);
+                       $options['anchor']['height'] = $options['size']['height'];
+               }
+               if (empty($options['origin'])) {
+                       $options['origin']['width'] = $options['origin']['height'] = 0;
+               }
+               if (isset($options['shadow'])) {
+                       $options['anchor'] = $options['shadow'];
+               }
+
+               $icon = 'new google.maps.MarkerImage("' . $url . '",
+       new google.maps.Size(' . $options['size']['width'] . ', ' . $options['size']['height'] . '),
+       new google.maps.Point(' . $options['origin']['width'] . ', ' . $options['origin']['height'] . '),
+       new google.maps.Point(' . $options['anchor']['width'] . ', ' . $options['anchor']['height'] . ')
+)';
+               $this->icons[static::$iconCount] = $icon;
+               $this->_iconRemember[static::$iconCount] = ['url' => $url, 'options' => $options, 'id' => static::$iconCount];
+               return static::$iconCount++;
+       }
+
+       /**
+        * Creates a new InfoWindow.
+        *
+        * @param array $options
+        * - lat, lng, content, maxWidth, pixelOffset, zIndex
+        * @return int windowCount
+        */
+       public function addInfoWindow(array $options = []) {
+               $defaults = $this->_runtimeConfig['infoWindow'];
+               $options += $defaults;
+
+               if (!empty($options['lat']) && !empty($options['lng'])) {
+                       $position = 'new google.maps.LatLng(' . $options['lat'] . ', ' . $options['lng'] . ')';
+               } else {
+                       $position = ' ' . $this->name() . ' .getCenter()';
+               }
+
+               $windows = "
+                       gInfoWindows" . static::$mapCount . ".push(new google.maps.InfoWindow({
+                                       position: {$position},
+                                       content: " . $this->escapeString($options['content']) . ",
+                                       maxWidth: {$options['maxWidth']},
+                                       pixelOffset: {$options['pixelOffset']}
+                                       /*zIndex: {$options['zIndex']},*/
+                       }));
+                       ";
+               $this->map .= $windows;
+               return static::$infoWindowCount++;
+       }
+
+       /**
+        * Add event to open marker on click.
+        *
+        * @param int $marker
+        * @param int $infoWindow
+        * @param bool $open Also open it right away.
+        * @return void
+        */
+       public function addEvent($marker, $infoWindow, $open = false) {
+               $this->map .= "
+                       google.maps.event.addListener(gMarkers" . static::$mapCount . "[{$marker}], 'click', function() {
+                               gInfoWindows" . static::$mapCount . "[$infoWindow].open(" . $this->name() . ", this);
+                       });
+               ";
+               if ($open) {
+                       $event = 'gInfoWindows' . static::$mapCount . "[$infoWindow].open(" . $this->name() .
+                               ', gMarkers' . static::$mapCount . '[' . $marker . ']);';
+                       $this->addCustom($event);
+               }
+       }
+
+       /**
+        * Add a custom event for a marker on click.
+        *
+        * @param int $marker
+        * @param string $event (js)
+        * @return void
+        */
+       public function addCustomEvent($marker, $event) {
+               $this->map .= "
+                       google.maps.event.addListener(gMarkers" . static::$mapCount . "[{$marker}], 'click', function() {
+                               $event
+                       });
+               ";
+       }
+
+       /**
+        * Add custom JS.
+        *
+        * @param string $js Custom JS
+        * @return void
+        */
+       public function addCustom($js) {
+               $this->map .= $js;
+       }
+
+       /**
+        * Add directions to the map.
+        *
+        * @param array|string $from Location as array(fixed lat/lng pair) or string (to be geocoded at runtime)
+        * @param array|string $to Location as array(fixed lat/lng pair) or string (to be geocoded at runtime)
+        * @param array $options
+        * - directionsDiv: Div to place directions in text form
+        * - travelMode: TravelMode,
+        * - transitOptions: TransitOptions,
+        * - unitSystem: UnitSystem (IMPERIAL, METRIC, AUTO),
+        * - waypoints[]: DirectionsWaypoint,
+        * - optimizeWaypoints: Boolean,
+        * - provideRouteAlternatives: Boolean,
+        * - avoidHighways: Boolean,
+        * - avoidTolls: Boolean
+        * - region: String
+        * @see https://developers.google.com/maps/documentation/javascript/3.exp/reference#DirectionsRequest
+        * @return void
+        */
+       public function addDirections($from, $to, array $options = []) {
+               $id = 'd' . static::$markerCount++;
+               $defaults = $this->_runtimeConfig['directions'];
+               $options += $defaults;
+               $travelMode = $this->travelModes[$options['travelMode']];
+
+               $directions = "
+                       var {$id}Service = new google.maps.DirectionsService();
+                       var {$id}Display;
+                       {$id}Display = new google.maps.DirectionsRenderer();
+                       {$id}Display. setMap(" . $this->name() . ");
+                       ";
+
+               if (!empty($options['directionsDiv'])) {
+                       $directions .= "{$id}Display. setPanel(document.getElementById('" . $options['directionsDiv'] . "'));";
+               }
+
+               if (is_array($from)) {
+                       $from = 'new google.maps.LatLng(' . (float)$from['lat'] . ', ' . (float)$from['lng'] . ')';
+               } else {
+                       $from = '"' . h($from) . '"';
+               }
+               if (is_array($to)) {
+                       $to = 'new google.maps.LatLng(' . (float)$to['lat'] . ', ' . (float)$to['lng'] . ')';
+               } else {
+                       $to = '"' . h($to) . '"';
+               }
+
+               $directions .= "
+                       var request = {
+                               origin: $from,
+                               destination: $to,
+                               unitSystem: google.maps.UnitSystem." . $options['unitSystem'] . ",
+                               travelMode: google.maps.TravelMode. $travelMode
+                       };
+                       {$id}Service.route(request, function(result, status) {
+                               if (status == google.maps.DirectionsStatus.OK) {
+                                       {$id}Display. setDirections(result);
+                               }
+                       });
+               ";
+               $this->map .= $directions;
+       }
+
+       /**
+        * Add a polyline
+        *
+        * This method adds a line between 2 points
+        *
+        * @param array|string $from Location as array(fixed lat/lng pair) or string (to be geocoded at runtime)
+        * @param array|string $to Location as array(fixed lat/lng pair) or string (to be geocoded at runtime)
+        * @param array $options
+        * - color (#FFFFFF ... #000000)
+        * - opacity (0.1 ... 1, defaults to 1)
+        * - weight in pixels (defaults to 2)
+        * @see https://developers.google.com/maps/documentation/javascript/3.exp/reference#Polyline
+        * @return void
+        */
+       public function addPolyline($from, $to, array $options = []) {
+               if (is_array($from)) {
+                       $from = 'new google.maps.LatLng(' . (float)$from['lat'] . ', ' . (float)$from['lng'] . ')';
+               } else {
+                       throw new Exception('not implemented yet, use array of lat/lng');
+                       //$from = '\'' . h($from) . '\'';
+               }
+               if (is_array($to)) {
+                       $to = 'new google.maps.LatLng(' . (float)$to['lat'] . ', ' . (float)$to['lng'] . ')';
+               } else {
+                       throw new Exception('not implemented yet, use array of lat/lng');
+                       //$to = '\'' . h($to) . '\'';
+               }
+
+               $defaults = $this->_runtimeConfig['polyline'];
+               $options += $defaults;
+
+               $id = 'p' . static::$markerCount++;
+
+               $polyline = "var start = $from;";
+               $polyline .= "var end = $to;";
+               $polyline .= "
+                               var poly = [
+                                       start,
+                                       end
+                               ];
+                               var {$id}Polyline = new google.maps.Polyline({
+                                       path: poly,
+                                       strokeColor: '" . $options['color'] . "',
+                                       strokeOpacity: " . $options['opacity'] . ",
+                                       strokeWeight: " . $options['weight'] . "
+                               });
+                               {$id}Polyline.setMap(" . $this->name() . ");
+                       ";
+               $this->map .= $polyline;
+       }
+
+       /**
+        * @param string $content (html/text)
+        * @param int $index infoWindowCount
+        * @return void
+        */
+       public function setContentInfoWindow($content, $index) {
+               $this->map .= "
+                       gInfoWindows" . static::$mapCount . "[$index]. setContent(" . $this->escapeString($content) . ');';
+       }
+
+       /**
+        * Json encode string
+        *
+        * @param mixed $content
+        * @return string JSON
+        */
+       public function escapeString($content) {
+               return json_encode($content);
+       }
+
+       /**
+        * This method returns the javascript for the current map container.
+        * Including script tags.
+        * Just echo it below the map container. New: Alternativly, use finalize() directly.
+        *
+        * @return string
+        */
+       public function script() {
+               $script = '<script>
+               ' . $this->finalize(true) . '
+</script>';
+               return $script;
+       }
+
+       /**
+        * Finalize the map and write the javascript to the buffer.
+        * Make sure that your view does also output the buffer at some place!
+        *
+        * @param bool $return If the output should be returned instead
+        * @return null|string Javascript if $return is true
+        */
+       public function finalize($return = false) {
+               $script = $this->_arrayToObject('matching', $this->matching, false, true) . PHP_EOL;
+               $script .= $this->_arrayToObject('gIcons' . static::$mapCount, $this->icons, false, false) . '
+
+       jQuery(document).ready(function() {
+               ';
+
+               $script .= $this->map;
+               if ($this->_runtimeConfig['geolocate']) {
+                       $script .= $this->_geolocate();
+               }
+
+               if ($this->_runtimeConfig['showMarker'] && !empty($this->markers) && is_array($this->markers)) {
+                       $script .= implode($this->markers, ' ');
+               }
+
+               if ($this->_runtimeConfig['autoCenter']) {
+                       $script .= $this->_autoCenter();
+               }
+               $script .= '
+
+       });';
+               static::$mapCount++;
+               if ($return) {
+                       return $script;
+               }
+               $this->Html->scriptBlock($script, ['block' => true]);
+       }
+
+       /**
+        * Set a custom geolocate callback
+        *
+        * @param string|bool $js Custom JS
+        * false: no callback at all
+        * @return void
+        */
+       public function geolocateCallback($js) {
+               if ($js === false) {
+                       $this->_runtimeConfig['callbacks']['geolocate'] = false;
+                       return;
+               }
+               $this->_runtimeConfig['callbacks']['geolocate'] = $js;
+       }
+
+       /**
+        * Experimental - works in cutting edge browsers like chrome10
+        *
+        * @return string
+        */
+       protected function _geolocate() {
+               return '
+       // Try W3C Geolocation (Preferred)
+       if (navigator.geolocation) {
+               browserSupportFlag = true;
+               navigator.geolocation.getCurrentPosition(function(position) {
+                       geolocationCallback(position.coords.latitude, position.coords.longitude);
+               }, function() {
+                       handleNoGeolocation(browserSupportFlag);
+               });
+               // Try Google Gears Geolocation
+       } else if (google.gears) {
+               browserSupportFlag = true;
+               var geo = google.gears.factory.create("beta.geolocation");
+               geo.getCurrentPosition(function(position) {
+                       geolocationCallback(position.latitude, position.longitude);
+               }, function() {
+                       handleNoGeoLocation(browserSupportFlag);
+               });
+               // Browser doesn\'t support Geolocation
+       } else {
+               browserSupportFlag = false;
+               handleNoGeolocation(browserSupportFlag);
+       }
+
+       function geolocationCallback(lat, lng) {
+               ' . $this->_geolocationCallback() . '
+       }
+
+       function handleNoGeolocation(errorFlag) {
+       if (errorFlag == true) {
+               //alert("Geolocation service failed.");
+       } else {
+               //alert("Your browser doesn\'t support geolocation. We\'ve placed you in Siberia.");
+       }
+       //' . $this->name() . ' . setCenter(initialLocation);
+       }
+       ';
+       }
+
+       /**
+        * @return string
+        */
+       protected function _geolocationCallback() {
+               if (($js = $this->_runtimeConfig['callbacks']['geolocate']) === false) {
+                       return '';
+               }
+               if ($js === null) {
+                       $js = 'initialLocation = new google.maps.LatLng(lat, lng);
+               ' . $this->name() . ' . setCenter(initialLocation);
+';
+               }
+               return $js;
+       }
+
+       /**
+        * Auto center map
+        * careful: with only one marker this can result in too high zoom values!
+        *
+        * @return string autoCenterCommands
+        */
+       protected function _autoCenter() {
+               return '
+               var bounds = new google.maps.LatLngBounds();
+               $.each(gMarkers' . static::$mapCount . ',function (index, marker) { bounds.extend(marker.position);});
+               ' . $this->name() . ' .fitBounds(bounds);
+               ';
+       }
+
+       /**
+        * @return string JSON like js string
+        */
+       protected function _mapOptions() {
+               $options = $this->_runtimeConfig['map'] + $this->_runtimeConfig;
+
+               $mapOptions = array_intersect_key($options, [
+                       'streetViewControl' => null,
+                       'navigationControl' => null,
+                       'mapTypeControl' => null,
+                       'scaleControl' => null,
+                       'scrollwheel' => null,
+                       'zoom' => null,
+                       'keyboardShortcuts' => null,
+                       'styles' => null,
+               ]);
+               $res = [];
+               foreach ($mapOptions as $key => $mapOption) {
+                       $res[] = $key . ': ' . $this->value($mapOption);
+               }
+               if (empty($options['autoCenter'])) {
+                       $res[] = 'center: initialLocation';
+               }
+               if (!empty($options['navOptions'])) {
+                       $res[] = 'navigationControlOptions: ' . $this->_controlOptions('nav', $options['navOptions']);
+               }
+               if (!empty($options['typeOptions'])) {
+                       $res[] = 'mapTypeControlOptions: ' . $this->_controlOptions('type', $options['typeOptions']);
+               }
+               if (!empty($options['scaleOptions'])) {
+                       $res[] = 'scaleControlOptions: ' . $this->_controlOptions('scale', $options['scaleOptions']);
+               }
+
+               if (array_key_exists($options['type'], $this->types)) {
+                       $type = $this->types[$options['type']];
+               } else {
+                       $type = $options['type'];
+               }
+               $res[] = 'mapTypeId: google.maps.MapTypeId.' . $type;
+
+               return '{' . implode(', ', $res) . '}';
+       }
+
+       /**
+        * @param string $type
+        * @param array $options
+        * @return string JSON like js string
+        */
+       protected function _controlOptions($type, $options) {
+               $mapping = [
+                       'nav' => 'NavigationControlStyle',
+                       'type' => 'MapTypeControlStyle',
+                       'scale' => ''
+               ];
+               $res = [];
+               if (!empty($options['style']) && ($m = $mapping[$type])) {
+                       $res[] = 'style: google.maps.' . $m . '.' . $options['style'];
+               }
+               if (!empty($options['pos'])) {
+                       $res[] = 'position: google.maps.ControlPosition.' . $options['pos'];
+               }
+
+               return '{' . implode(', ', $res) . '}';
+       }
+
+       /**
+        * Returns a maps.google link
+        *
+        * @param string $title  Link title
+        * @param array $mapOptions
+        * @param array $linkOptions
+        * @return string HTML link
+        */
+       public function mapLink($title, $mapOptions = [], $linkOptions = []) {
+               return $this->Html->link($title, $this->mapUrl($mapOptions + ['escape' => false]), $linkOptions);
+       }
+
+       /**
+        * Returns a maps.google url
+        *
+        * Options:
+        * - from: necessary (address or lat,lng)
+        * - to: 1x necessary (address or lat,lng - can be an array of multiple destinations: array('dest1', 'dest2'))
+        * - zoom: optional (defaults to none)
+        * - query: Additional query strings as array
+        * - escape: defaults to true
+        *
+        * @param array $options Options
+        * @return string link: http://...
+        */
+       public function mapUrl(array $options = []) {
+               $url = $this->_protocol() . 'maps.google.com/maps?';
+
+               $urlArray = !empty($options['query']) ? $options['query'] : [];
+               if (!empty($options['from'])) {
+                       $urlArray['saddr'] = $options['from'];
+               }
+
+               if (!empty($options['to']) && is_array($options['to'])) {
+                       $to = array_shift($options['to']);
+                       foreach ($options['to'] as $key => $value) {
+                               $to .= '+to:' . $value;
+                       }
+                       $urlArray['daddr'] = $to;
+               } elseif (!empty($options['to'])) {
+                       $urlArray['daddr'] = $options['to'];
+               }
+
+               if (isset($options['zoom']) && $options['zoom'] !== false) {
+                       $urlArray['z'] = (int)$options['zoom'];
+               }
+               //$urlArray[] = 'f=d';
+               //$urlArray[] = 'hl=de';
+               //$urlArray[] = 'ie=UTF8';
+
+               $options += [
+                       'escape' => true,
+               ];
+
+               $query = http_build_query($urlArray);
+               if ($options['escape']) {
+                       $query = h($query);
+               }
+
+               return $url . $query;
+       }
+
+       /**
+        * Creates a plain image map.
+        *
+        * @link http://code.google.com/intl/de-DE/apis/maps/documentation/staticmaps
+        * @param array $options Options
+        * - string $size [necessary: VALxVAL, e.g. 500x400 - max 640x640]
+        * - string $center: x,y or address [necessary, if no markers are given; else tries to take defaults if available] or TRUE/FALSE
+        * - int $zoom [optional; if no markers are given, default value is used; if set to "auto" and ]*
+        * - array $markers [optional, @see staticPaths() method]
+        * - string $type [optional: roadmap/hybrid, ...; default:roadmap]
+        * - string $mobile TRUE/FALSE
+        * - string $visible: $area (x|y|...)
+        * - array $paths [optional, @see staticPaths() method]
+        * - string $language [optional]
+        * @param array $attributes HTML attributes for the image
+        * - title
+        * - alt (defaults to 'Map')
+        * - url (tip: you can pass $this->link(...) and it will create a link to maps.google.com)
+        * @return string imageTag
+        */
+       public function staticMap(array $options = [], array $attributes = []) {
+               $defaultAttributes = ['alt' => __d('tools', 'Map')];
+               $attributes += $defaultAttributes;
+
+               // This was fixed in 3.5.1 to auto-escape URL query strings for security reasons
+               $escape = version_compare(Configure::version(), '3.5.1') < 0 ? true : false;
+               return $this->Html->image($this->staticMapUrl($options + ['escape' => $escape]), $attributes);
+       }
+
+       /**
+        * Create a link to a plain image map
+        *
+        * @param string $title Link title
+        * @param array $mapOptions
+        * @param array $linkOptions
+        * @return string HTML link
+        */
+       public function staticMapLink($title, array $mapOptions = [], array $linkOptions = []) {
+               return $this->Html->link($title, $this->staticMapUrl($mapOptions + ['escape' => false]), $linkOptions);
+       }
+
+       /**
+        * Creates a URL to a plain image map.
+        *
+        * Options:
+        * - escape: defaults to true (Deprecated as of CakePHP 3.5.1 and now has to be always false)
+        *
+        * @param array $options
+        * - see staticMap() for details
+        * @return string urlOfImage: http://...
+        */
+       public function staticMapUrl(array $options = []) {
+               $mapUrl = $this->_protocol() . static::STATIC_API;
+               /*
+               $params = array(
+                       'mobile' => 'false',
+                       'format' => 'png',
+                       //'center' => false
+               );
+
+               if (!empty($options['mobile'])) {
+                       $params['mobile'] = 'true';
+               }
+               */
+
+               $defaults = $this->_config['staticMap'] + $this->_config;
+
+               $mapOptions = $options + $defaults;
+
+               $params = array_intersect_key($mapOptions, [
+                       'mobile' => null,
+                       'format' => null,
+                       'size' => null,
+                       //'zoom' => null,
+                       //'lat' => null,
+                       //'lng' => null,
+                       //'visible' => null,
+                       //'type' => null,
+               ]);
+
+               // add API key to parameters.
+               if ($this->_runtimeConfig['key']) {
+                       $params['key'] = $this->_runtimeConfig['key'];
+               }
+
+               // do we want zoom to auto-correct itself?
+               if (!isset($options['zoom']) && !empty($mapOptions['markers']) || !empty($mapOptions['paths']) || !empty($mapOptions['visible'])) {
+                       $options['zoom'] = 'auto';
+               }
+
+               // a position on the map that is supposed to stay visible at all cost
+               if (!empty($mapOptions['visible'])) {
+                       $params['visible'] = urlencode($mapOptions['visible']);
+               }
+
+               // center and zoom are not necessary if path, visible or markers are given
+               if (!isset($options['center']) || $options['center'] === false) {
+                       // dont use it
+               } elseif ($options['center'] === true && $mapOptions['lat'] !== null && $mapOptions['lng'] !== null) {
+                       $params['center'] = urlencode((string)$mapOptions['lat'] . ',' . (string)$mapOptions['lng']);
+               } elseif (!empty($options['center'])) {
+                       $params['center'] = urlencode($options['center']);
+               } /*else {
+                       // try to read from markers array???
+                       if (isset($options['markers']) && count($options['markers']) == 1) {
+                               //pr ($options['markers']);
+                       }
+               }*/
+
+               if (!isset($options['zoom']) || $options['zoom'] === false) {
+                       // dont use it
+               } else {
+                       if ($options['zoom'] === 'auto') {
+                               if (!empty($options['markers']) && strpos($options['zoom'], '|') !== false) {
+                                       // let google find the best zoom value itself
+                               } else {
+                                       // do something here?
+                               }
+                       } else {
+                               $params['zoom'] = $options['zoom'];
+                       }
+               }
+
+               if (array_key_exists($mapOptions['type'], $this->types)) {
+                       $params['maptype'] = $this->types[$mapOptions['type']];
+               } else {
+                       $params['maptype'] = $mapOptions['type'];
+               }
+               $params['maptype'] = strtolower($params['maptype']);
+
+               // old: {latitude},{longitude},{color}{alpha-character}
+               // new: @see staticMarkers()
+               if (!empty($options['markers'])) {
+                       $params['markers'] = $options['markers'];
+               }
+
+               if (!empty($options['paths'])) {
+                       $params['path'] = $options['paths'];
+               }
+
+               // valXval
+               if (!empty($options['size'])) {
+                       $params['size'] = $options['size'];
+               }
+
+               $pieces = [];
+               foreach ($params as $key => $value) {
+                       if (is_array($value)) {
+                               $value = implode('&' . $key . '=', $value);
+                       } elseif ($value === true) {
+                               $value = 'true';
+                       } elseif ($value === false) {
+                               $value = 'false';
+                       } elseif ($value === null) {
+                               continue;
+                       }
+                       $pieces[] = $key . '=' . $value;
+               }
+
+               $options += [
+                       'escape' => true,
+               ];
+               $query = implode('&', $pieces);
+               if ($options['escape']) {
+                       $query = h($query);
+               }
+
+               return $mapUrl . '?' . $query;
+       }
+
+       /**
+        * Prepare paths for staticMap
+        *
+        * @param array $pos PathElementArrays
+        * - elements: [required] (multiple array(lat=>x, lng=>y) or just a address strings)
+        * - color: red/blue/green (optional, default blue)
+        * - weight: numeric (optional, default: 5)
+        * @return array Array of paths: e.g: color:0x0000FF80|weight:5|37.40303,-122.08334|37.39471,-122.07201|37.40589,-122.06171{|...}
+        */
+       public function staticPaths(array $pos = []) {
+               $defaults = [
+                       'color' => 'blue',
+                       'weight' => 5 // pixel
+               ];
+
+               // not a 2-level array? make it one
+               if (!isset($pos[0])) {
+                       $pos = [$pos];
+               }
+
+               $res = [];
+               foreach ($pos as $p) {
+                       $options = $p + $defaults;
+
+                       $markers = $options['path'];
+                       unset($options['path']);
+
+                       // prepare color
+                       if (!empty($options['color'])) {
+                               $options['color'] = $this->_prepColor($options['color']);
+                       }
+
+                       $path = [];
+                       foreach ($options as $key => $value) {
+                               $path[] = $key . ':' . urlencode($value);
+                       }
+                       foreach ($markers as $key => $pos) {
+                               if (is_array($pos)) {
+                                       // lat/lng?
+                                       $pos = $pos['lat'] . ',' . $pos['lng'];
+                               }
+                               $path[] = $pos;
+                       }
+                       $res[] = implode('|', $path);
+               }
+               return $res;
+       }
+
+       /**
+        * Prepare markers for staticMap
+        *
+        * @param array $pos markerArrays
+        * - lat: xx.xxxxxx (necessary)
+        * - lng: xx.xxxxxx (necessary)
+        * - address: (instead of lat/lng)
+        * - color: red/blue/green (optional, default blue)
+        * - label: a-z or numbers (optional, default: s)
+        * - icon: custom icon (png, gif, jpg - max 64x64 - max 5 different icons per image)
+        * - shadow: TRUE/FALSE
+        * @param array $style (global) (overridden by custom marker styles)
+        * - color
+        * - label
+        * - icon
+        * - shadow
+        * @return array markers: color:green|label:Z|48,11|Berlin
+        *
+        * NEW: size:mid|color:red|label:E|37.400465,-122.073003|37.437328,-122.159928&markers=size:small|color:blue|37.369110,-122.096034
+        * OLD: 40.702147,-74.015794,blueS|40.711614,-74.012318,greenG{|...}
+        */
+       public function staticMarkers(array $pos = [], array $style = []) {
+               $markers = [];
+               $verbose = false;
+
+               $defaults = [
+                       'shadow' => 'true',
+                       'color' => 'blue',
+                       'label' => '',
+                       'address' => '',
+                       'size' => ''
+               ];
+
+               // not a 2-level array? make it one
+               if (!isset($pos[0])) {
+                       $pos = [$pos];
+               }
+
+               // new in staticV2: separate styles! right now just merged
+               foreach ($pos as $p) {
+                       $p += $style + $defaults;
+
+                       // adress or lat/lng?
+                       if (!empty($p['lat']) && !empty($p['lng'])) {
+                               $p['address'] = $p['lat'] . ',' . $p['lng'];
+                       }
+                       $p['address'] = urlencode($p['address']);
+
+                       $values = [];
+
+                       // prepare color
+                       if (!empty($p['color'])) {
+                               $p['color'] = $this->_prepColor($p['color']);
+                               $values[] = 'color:' . $p['color'];
+                       }
+                       // label? A-Z0-9
+                       if (!empty($p['label'])) {
+                               $values[] = 'label:' . strtoupper($p['label']);
+                       }
+                       if (!empty($p['size'])) {
+                               $values[] = 'size:' . $p['size'];
+                       }
+                       if (!empty($p['shadow'])) {
+                               $values[] = 'shadow:' . $p['shadow'];
+                       }
+                       if (!empty($p['icon'])) {
+                               $values[] = 'icon:' . urlencode($p['icon']);
+                       }
+                       $values[] = $p['address'];
+
+                       //TODO: icons
+                       $markers[] = implode('|', $values);
+               }
+
+               //TODO: shortcut? only possible if no custom params!
+               if ($verbose) {
+
+               }
+               // long: markers=styles1|address1&markers=styles2|address2&...
+               // short: markers=styles,address1|address2|address3|...
+
+               return $markers;
+       }
+
+       /**
+        * Ensure that we stay on the appropriate protocol
+        *
+        * @return string protocol base (including ://)
+        */
+       protected function _protocol() {
+               $https = $this->_runtimeConfig['https'];
+               if ($https === null) {
+                       $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on';
+               }
+               return ($https ? 'https' : 'http') . '://';
+       }
+
+       /**
+        * // to 0x
+        * or // added
+        *
+        * @param string $color Color: FFFFFF, #FFFFFF, 0xFFFFFF or blue
+        * @return string Color
+        */
+       protected function _prepColor($color) {
+               if (strpos($color, '#') !== false) {
+                       return str_replace('#', '0x', $color);
+               }
+               if (is_numeric($color)) {
+                       return '0x' . $color;
+               }
+               return $color;
+       }
+
+       /**
+        * @param string $name
+        * @param array $array
+        * @param bool $asString
+        * @param bool $keyAsString
+        * @return string
+        */
+       protected function _arrayToObject($name, $array, $asString = true, $keyAsString = false) {
+               $res = 'var ' . $name . ' = {' . PHP_EOL;
+               $res .= $this->_toObjectParams($array, $asString, $keyAsString);
+               $res .= '};';
+               return $res;
+       }
+
+       /**
+        * @param array $array
+        * @param bool $asString
+        * @param bool $keyAsString
+        * @return string
+        */
+       protected function _toObjectParams($array, $asString = true, $keyAsString = false) {
+               $pieces = [];
+               foreach ($array as $key => $value) {
+                       $e = ($asString && strpos($value, 'new ') !== 0 ? '"' : '');
+                       $ke = ($keyAsString ? '"' : '');
+                       $pieces[] = $ke . $key . $ke . ': ' . $e . $value . $e;
+               }
+               return implode(',' . PHP_EOL, $pieces);
+       }
+
+}
diff --git a/production/sites/cakelampvm.com/goog_maps_helper_mod/compare_with_install.sh b/production/sites/cakelampvm.com/goog_maps_helper_mod/compare_with_install.sh
new file mode 100644 (file)
index 0000000..cd46374
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+
+if [ ! -d "$HOME/apps/mapsdemo/avenger5" ]; then
+  echo Not seeing the mapsdemo checked out man.
+  exit 1
+fi
+
+meld $(find ~/apps/mapsdemo/avenger5/ -iname goog*map*helper.php) ./Goog*Map*Help*p
+
+
diff --git a/production/sites/cakelampvm.com/hello.txt b/production/sites/cakelampvm.com/hello.txt
new file mode 100644 (file)
index 0000000..7bf0d1b
--- /dev/null
@@ -0,0 +1,25 @@
+Welcome to the CakePHP LAMP VM.
+
+Please refer to the built-in documentation available at: http://cakelampvm.com
+
+Some first steps to make this vm your own:
+
+####
+
+1) change your password for the developer account.
+(may eventually be automatically required)
+
+####
+
+2) change your git configuration for user and email.  this is how we've
+configured it so far:
+
+  # git config --global user.email "developer@cakelampvm.com"
+  # git config --global user.name "Developer J. Cakemo"
+
+if you're developing on a real project, you probably don't want the bogus
+email and even more bogus name above attached to your commits.
+Just run the two commands again but with proper values.
+
+####
+
index 7f64b96dc49a4a761ccdf1c9ca2956cb9735c418..e6c54770b2d92693b940f5f267cc06f580b65fd3 100644 (file)
@@ -58,6 +58,7 @@ local($root) = &canonicalize("$FEISTY_MEOW_APEX");
 &backup_hierarchy($snarf_file_base, $number, "$root", "production/assign_bases");
 &backup_hierarchy($snarf_file_base, $number, "$root", "production/check_versions");
 &backup_hierarchy($snarf_file_base, $number, "$root", "production/setup_src");
+&backup_hierarchy($snarf_file_base, $number, "$root", "production/sites");
 
 # now rename the file so only the unpacker can access it.
 &rename_archive($snarf_file);
index 9c94ef167597c8075920465714826f8220784dcd..87e47676a43e7ededa30ff106fa0e83a0eac7577 100644 (file)
@@ -83,7 +83,7 @@ define_yeti_alias lesser='bash $FEISTY_MEOW_SCRIPTS/files/lesser.sh'
 
 # some aliases that are just generally nice to have.
 define_yeti_alias aliases=alias
-define_yeti_alias calc='kcalc'
+define_yeti_alias calc='galculator'
 define_yeti_alias cd..='\cd ..'
 define_yeti_alias cd...='\cd ../..'
 define_yeti_alias cd....='\cd ../../..'
@@ -102,7 +102,10 @@ define_yeti_alias feisty_branch='pushd $FEISTY_MEOW_APEX ; git branch ; popd'
 # some important retreads on aliases that provide a sudo-ized version of other scripts.
 
 define_yeti_alias snarf_linux_config="sudo -E PERLLIB=\$PERLLIB perl \$FEISTY_MEOW_SCRIPTS/archival/snarf_linux_config.pl"
-#no, does its own sudo wrangling.edefine_yeti_alias standup="sudo bash \"$FEISTY_MEOW_SCRIPTS/site_avenger/standup.sh\""
+define_yeti_alias add_domain="sudo bash \$FEISTY_MEOW_SCRIPTS/system/add_domain.sh"
+define_yeti_alias remove_domain="sudo bash \$FEISTY_MEOW_SCRIPTS/system/remove_domain.sh"
+define_yeti_alias add_apache_site="sudo bash \$FEISTY_MEOW_SCRIPTS/system/add_apache_site.sh"
+define_yeti_alias remove_apache_site="sudo bash \$FEISTY_MEOW_SCRIPTS/system/remove_apache_site.sh"
 
 #hmmm: some magma intrusions from the fred customizations...
 define_yeti_alias revamp_cakelampvm="sudo bash \"$FEISTY_MEOW_SCRIPTS/site_avenger/revamp_cakelampvm.sh\""
index b477db18de989195e0ed8c6afefc2357cbde4837..daaabcb16cece5a745d7735421fbecfc1c4691f3 100644 (file)
@@ -165,12 +165,18 @@ if [ -z "$skip_all" ]; then
   function ssh()
   {
     local args=($*)
-    save_terminal_title
     # we remember the old terminal title, then force the TERM variable to a more generic
     # version for the other side (just 'linux'); we don't want the remote side still
     # thinking it's running xterm.
-    export TERM=linux
+    save_terminal_title
+#hmmm: why were we doing this?  it scorches the user's logged in session, leaving it without proper terminal handling.
+#    # we save the value of TERM; we don't want to leave the user's terminal
+#    # brain dead once we come back from this function.
+#    local oldterm="$TERM"
+#    export TERM=linux
     /usr/bin/ssh -X -C "${args[@]}"
+#    # restore the terminal variable also.
+#    TERM="$oldterm"
     restore_terminal_title
   }
 
@@ -470,6 +476,14 @@ if [ -z "$skip_all" ]; then
       echo "but that folder does not exist.  Skipping customization."
       return 1
     fi
+
+    # prevent permission foul-ups.
+#hmmm: save error output here instead of muting it.
+#hmmm: better yet actually, just don't complain on freaking cygwin, since that's where this happens
+    chown -R "$(logname):$(logname)" \
+        "$FEISTY_MEOW_LOADING_DOCK"/* "$FEISTY_MEOW_GENERATED_STORE"/* 2>/dev/null
+    test_or_continue "chowning to $(logname) didn't happen."
+
     regenerate >/dev/null
     pushd "$FEISTY_MEOW_LOADING_DOCK/custom" &>/dev/null
     incongruous_files="$(bash "$FEISTY_MEOW_SCRIPTS/files/list_non_dupes.sh" "$FEISTY_MEOW_SCRIPTS/customize/$custom_user" "$FEISTY_MEOW_LOADING_DOCK/custom")"
@@ -499,6 +513,11 @@ if [ -z "$skip_all" ]; then
     echo
     regenerate
 
+    # prevent permission foul-ups, again.
+    chown -R "$(logname):$(logname)" \
+        "$FEISTY_MEOW_LOADING_DOCK" "$FEISTY_MEOW_GENERATED_STORE" 2>/dev/null
+    test_or_continue "chowning to $(logname) didn't happen."
+
     restore_terminal_title
   }
 
@@ -842,6 +861,69 @@ return 0
 
   ##############
 
+  # given a filename and a string to seek and a number of lines, then this
+  # function will remove the first occurrence of a line in the file that
+  # matches the string, and it will also axe the next N lines as specified.
+  function create_chomped_copy_of_file()
+  {
+    local filename="$1"; shift
+    local seeker="$1"; shift
+    local numlines=$1; shift
+
+#echo into create_chomped_copy...
+#var filename seeker numlines 
+
+    # make a backup first, oy.
+    \cp -f "$filename" "/tmp/$(basename ${filename}).bkup-${RANDOM}" 
+    test_or_die "backing up file: $filename"
+
+    # make a temp file to write to before we move file into place in bind.
+    local new_version="/tmp/$(basename ${filename}).bkup-${RANDOM}" 
+    \rm -f "$new_version"
+    test_or_die "cleaning out new version of file from: $new_version"
+
+    local line
+    local skip_count=0
+    local found_any=
+    while read line; do
+      # don't bother looking at the lines if we're already in skip mode.
+      if [[ $skip_count == 0 ]]; then
+        # find the string they're seeking.
+        if [[ ! "$line" =~ .*${seeker}.* ]]; then
+          # no match.
+          echo "$line" >> "$new_version"
+        else
+          # a match!  start skipping.  we will delete this line and the next N lines.
+          ((skip_count++))
+#echo first skip count is now $skip_count
+          found_any=yes
+        fi
+      else
+        # we're already skipping.  let's keep going until we hit the limit.
+        ((skip_count++))
+#echo ongoing skip count is now $skip_count
+        if (( $skip_count > $numlines )); then
+          echo "Done skipping, and back to writing output file."
+          skip_count=0
+        fi
+      fi
+    done < "$filename"
+
+#echo file we created looks like this:
+#cat "$new_version"
+
+    if [ ! -z "$found_any" ]; then
+      # put the file back into place under the original name.
+      \mv "$new_version" "$filename"
+      test_or_die "moving the new version into place in: $filename"
+    else
+      # cannot always be considered an error, but we can at least gripe.
+      echo "Did not find any matches for seeker '$seeker' in file: $filename"
+    fi
+  }
+
+  ##############
+
   # NOTE: no more function definitions are allowed after this point.
 
   function function_sentinel()
index fc50921c06c3adb2843ef01e9753e77df43f07e5..f793c6d9d412d4ed80afc8663f686e7a8dffd090 100644 (file)
@@ -215,6 +215,23 @@ if [ "$NO_REPAIRS_NEEDED" == "true" ]; then
     unset FEISTY_MEOW_SHOW_LAUNCH_GREETING
   fi
 
+  # only run this hello file if the core feisty meow support haven't been loaded already.  this
+  # hopefully guarantees we show the info at most once in one shell continuum.
+  # this can also be disabled if the NO_HELLO variable has a non-empty value.
+  type CORE_VARIABLES_LOADED &>/dev/null
+  if [ $? -ne 0 -a -z "$NO_HELLO" ]; then
+    # print out a personalized hello file if we find one.
+    if [ -f ~/hello.txt ]; then
+      echo
+      sep 28
+      perl $FEISTY_MEOW_SCRIPTS/*/filedump.pl ~/hello.txt
+      sep 28
+      echo
+    fi
+    # from now on there should be no extra helloing.
+    export NO_HELLO=true
+  fi
+
   # load the last bits we do here.
   source "$FEISTY_MEOW_LOADING_DOCK/fmc_ending_sentinel.sh"
 
index b6421224607bcd18d5fd39ca55e630eff0046113..2224c716e3f0e12ab664b6757919716764867169 100644 (file)
@@ -25,15 +25,15 @@ export FEISTY_MEOW_APEX="$(/bin/pwd)"
 unset CORE_VARIABLES_LOADED
 
 # repetitive bit stolen from variables.  should make a file out of this somehow.
-IS_DOS=$(uname | grep -i ming)
-if [ -z "$IS_DOS" ]; then IS_DOS=$(uname | grep -i cygwin); fi
-# now if we're stuck in DOS, then fix the feisty meow variable name.
-if [ ! -z "$IS_DOS" ]; then
-  FEISTY_MEOW_APEX="$(cmd /c chdir | tr A-Z a-z | sed -e 's/\\/\//g')"
-echo feisty meow dos is: $FEISTY_MEOW_APEX
-  FEISTY_MEOW_APEX="$(dos_to_unix_path "$FEISTY_MEOW_APEX")"
-echo new feisty meow fixed dir is: $FEISTY_MEOW_APEX
-fi
+#IS_DOS=$(uname | grep -i ming)
+#if [ -z "$IS_DOS" ]; then IS_DOS=$(uname | grep -i cygwin); fi
+## now if we're stuck in DOS, then fix the feisty meow variable name.
+#if [ ! -z "$IS_DOS" ]; then
+#  FEISTY_MEOW_APEX="$(cmd /c chdir | tr A-Z a-z | sed -e 's/\\/\//g')"
+#echo feisty meow dos is: $FEISTY_MEOW_APEX
+#  FEISTY_MEOW_APEX="$(dos_to_unix_path "$FEISTY_MEOW_APEX")"
+#echo new feisty meow fixed dir is: $FEISTY_MEOW_APEX
+#fi
 
 popd &>/dev/null
 
index 7c552cbe65c3148fdf8285f72f9f9ccd60066132..b64b167098c89726696ab05b7edb3ebeddf51ea4 100644 (file)
@@ -290,8 +290,8 @@ fi
 
 ##############
 
-# pull in the custom overrides for feisty_meow scripts.  this is done last,
-# because we want to set everything up as expected, then let the user
+# pull in the custom overrides for feisty_meow scripts.  this is done almost
+# last, because we want to set everything up as expected, then let the user
 # override individual variables and definitions.  we also don't guard this
 # to avoid running it again, because we don't know what mix of functions and
 # aliases they want to define in there.
@@ -306,3 +306,37 @@ for i in $FEISTY_MEOW_LOADING_DOCK/custom/*.sh; do
   source "$i"
 done
 
+##############
+
+# a late breaking action is to set the editor, if we can.
+# we will fallback to whatever we can find on the host.
+export EDITOR
+if [ ! -z "$DISPLAY" ]; then
+  # only try to add bluefish, a gui editor, if there is an X display for it.
+  if [ -z "$EDITOR" ]; then
+    EDITOR="$(which bluefish)"
+  fi
+fi
+if [ -z "$EDITOR" ]; then
+  EDITOR="$(which gvim)"
+  if [ ! -z "$EDITOR" ]; then
+    # if we found gvim, then add in the no forking flag.
+    EDITOR+=" --nofork"
+  fi
+fi
+if [ -z "$EDITOR" ]; then
+  EDITOR="$(which vim)"
+fi
+if [ -z "$EDITOR" ]; then
+  EDITOR="$(which vi)"
+fi
+##
+# out of ideas about editors at this point.
+##
+# set the VISUAL variable from EDITOR if we found an editor to use.
+if [ ! -z "$EDITOR" ]; then
+  VISUAL="$EDITOR"
+fi
+
+##############
+
diff --git a/scripts/rev_control/puffer.sh b/scripts/rev_control/puffer.sh
new file mode 100644 (file)
index 0000000..c02ef3b
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+# puffer: "puffs out" all of the folders present in the REPOSITORY_LIST
+# variable, which causes the repo to be merged with its remote versions.
+# this enables a clean check-in; after puffer runs, there will be no secret
+# upstream changes that could mess up the git push (svn and cvs are not
+# supported in this script, since they branch differently than git).
+
+source "$FEISTY_MEOW_SCRIPTS/core/launch_feisty_meow.sh"
+source "$FEISTY_MEOW_SCRIPTS/rev_control/version_control.sh"
+
+##############
+
+echo "puffing out repositories at: $(date)"
+echo
+
+FULL_LIST=" $(dirname $FEISTY_MEOW_APEX) $HOME "
+if [ "$OS" == "Windows_NT" ]; then
+  FULL_LIST+=" c:/ d:/ e:/ "
+fi
+
+puff_out_list $FULL_LIST
+test_or_die "puffing out list: $FULL_LIST"
+
+##############
+
+# regenerate the scripts after puffing out, since this could mean a modified version
+# of feisty meow is present.
+regenerate
+
+##############
+
index 5c343b5d07b2fc357d83c516782dcb8ee96cb45c..75f344cf66ab8485f0e8b980f6b83c8eef924859 100644 (file)
@@ -15,7 +15,7 @@ source "$FEISTY_MEOW_SCRIPTS/tty/terminal_titler.sh"
 export MAX_DEPTH=5
 
 # use our splitter tool for lengthy output if it's available.
-if [ ! -z "$(which splitter)" ]; then
+if [ ! -z "$(which splitter 2>/dev/null)" ]; then
   TO_SPLITTER="$(which splitter)"
   # calculate the number of columsn in the terminal.
   cols=$(get_maxcols)
@@ -96,8 +96,19 @@ function do_checkin()
       # see if there are any changes in the local repository.
       if ! git diff-index --quiet HEAD --; then
         # tell git about all the files and get a check-in comment.
+#hmmm: begins to look like, you guessed it, a reusable bit that all commit actions could enjoy.
         git commit .
-        test_or_die "git commit"
+        retval=$?
+        test_or_continue "git commit"
+        if [ $retval -ne 0 ]; then
+          echo -e -n "Commit failed or was aborted:\nShould we continue with other check-ins? [y/N] "
+          local line
+          read line
+          if [[ "${line:0:1}" != "y" ]]; then
+            echo "Stopping check-in process due to missing commit and user request."
+            exit 1
+          fi
+        fi
       fi
 
       # a new set of steps we have to take to make sure the branch integrity is good.
@@ -107,7 +118,7 @@ function do_checkin()
       # there could already be committed changes that haven't been pushed yet.
 
       # upload any changes to the upstream repo so others can see them.
-      git push origin "$(my_branch_name)" 2>&1 | grep -v "X11 forwarding request failed" | $TO_SPLITTER
+      git push --tags origin "$(my_branch_name)" 2>&1 | grep -v "X11 forwarding request failed" | $TO_SPLITTER
       promote_pipe_return 0
       test_or_die "git push"
 
@@ -179,7 +190,7 @@ function do_report_new
   return 0
 }
 
-# checks in all the folders in a specified list.
+# checks in all the folders in the specified list.
 function checkin_list()
 {
   # make the list of directories unique.
@@ -197,7 +208,7 @@ function checkin_list()
     if [[ $outer =~ /.* ]]; then
       # yep, this path is absolute.  just handle it directly.
       if [ ! -d "$outer" ]; then continue; fi
-      do_checkin $outer
+      do_checkin "$outer"
       test_or_die "running check-in (absolute) on path: $outer"
       sep 28
     else
@@ -205,7 +216,7 @@ function checkin_list()
         # add in the directory component to see if we can find the folder.
         local path="$inner/$outer"
         if [ ! -d "$path" ]; then continue; fi
-        do_checkin $path
+        do_checkin "$path"
         test_or_die "running check-in (relative) on path: $path"
         sep 28
       done
@@ -215,24 +226,62 @@ function checkin_list()
   restore_terminal_title
 }
 
-# takes out the first few carriage returns that are in the input.
-function squash_first_few_crs()
+# does a careful git update on all the folders in the specified list.
+function puff_out_list()
 {
-  i=0
-  while read input_text; do
-    i=$((i+1))
-    if [ $i -le 5 ]; then
-      echo -n "$input_text  "
+  # make the list of directories unique.
+  local list="$(uniquify $*)"
+
+  save_terminal_title
+
+  # turn repo list back into an array.
+  eval "repository_list=( ${REPOSITORY_LIST[*]} )"
+
+  local outer inner
+
+#hmmm: once again, seeing some reusable code in this loop...
+  for outer in "${repository_list[@]}"; do
+    # check the repository first, since it might be an absolute path.
+    if [[ $outer =~ /.* ]]; then
+      # yep, this path is absolute.  just handle it directly.
+      if [ ! -d "$outer" ]; then continue; fi
+      do_careful_git_update "$outer"
+      test_or_die "running puff-out (absolute) on path: $outer"
+      sep 28
     else
-      echo $input_text
+      for inner in $list; do
+        # add in the directory component to see if we can find the folder.
+        local path="$inner/$outer"
+        if [ ! -d "$path" ]; then continue; fi
+        do_careful_git_update "$path"
+        test_or_die "running puff-out (relative) on path: $path"
+        sep 28
+      done
     fi
   done
-  if [ $i -le 3 ]; then
-    # if we're still squashing eols, make sure we don't leave them hanging.
-    echo
-  fi
+
+  restore_terminal_title
 }
 
+#hmmm: to go below.
+### takes out the first few carriage returns that are in the input.
+##function squash_first_few_crs()
+##{
+  ##i=0
+  ##while read input_text; do
+    ##i=$((i+1))
+    ##if [ $i -le 5 ]; then
+      ##echo -n "$input_text  "
+    ##else
+      ##echo $input_text
+    ##fi
+  ##done
+  ##if [ $i -le 3 ]; then
+    ### if we're still squashing eols, make sure we don't leave them hanging.
+    ##echo
+  ##fi
+##}
+
 #hmmm: the below are git specific and should be named that way.
 
 function all_branch_names()
@@ -275,16 +324,19 @@ function check_branch_state()
   local remote_branch=$(git rev-parse "$branch")
   local merge_base=$(git merge-base @ "$branch")
 
+  local to_echo=
   if [ "$local_branch" == "$remote_branch" ]; then
-    echo "okay"
+    to_echo="okay"
   elif [ "$local_branch" == "$merge_base" ]; then
-    echo "needs_pull"
+    to_echo="needs_pull"
   elif [ "$remote_branch" == "$merge_base" ]; then
-    echo "needs_push"
+    to_echo="needs_push"
   else
-    echo "diverged"
+    to_echo="diverged"
   fi
 
+  echo -n "$to_echo"
+
   return $to_return
 }
 
@@ -294,7 +346,7 @@ function show_branch_conditionally()
 {
   local this_branch="$1"; shift
 
-  state=$(check_branch_state "$this_branch")
+  local state=$(check_branch_state "$this_branch")
   if [ "$state" != "okay" ]; then
     echo "=> branch '$this_branch' state is not clean: $state"
   fi
@@ -342,8 +394,6 @@ function do_careful_git_update()
       # we are pretty sure the remote branch does exist.
       git pull --no-ff origin "$bran" | $TO_SPLITTER
       promote_pipe_return 0
-
-      echo "=> branch '$bran' state after pull is: $state"
     fi
     test_or_die "git pull of remote branch: $bran"
   done
index f759494973e4b686b50d8d61807810e89869dfef..2ae511cbb8eb50148b9efd2841ee0bddf088463e 100644 (file)
@@ -18,18 +18,18 @@ source "$WORKDIR/shared_site_mgr.sh"
 
 sep
 
-check_application_dir "$APPLICATION_DIR"
+check_application_dir "$BASE_APPLICATION_PATH"
 
 # find proper webroot where the site will be initialized.
 if [ -z "$app_dirname" ]; then
   # no dir was passed, so guess it.
-  find_app_folder "$APPLICATION_DIR"
+  find_app_folder "$BASE_APPLICATION_PATH"
 else
-  test_app_folder "$APPLICATION_DIR" "$app_dirname"
+  test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
 fi
 
 # where we expect to find our checkout folder underneath.
-full_app_dir="$APPLICATION_DIR/$app_dirname"
+full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
 
 # simplistic approach here; just go to the folder and pull the changes.
 
index a7b9e90982241e8b1433fb38c6d3d29ae35e7f95..0235eb5074a7334736f64b69d51b648cf40fc988 100644 (file)
 
 # basic information that is constant for all site avenger sites.
 
-export APPLICATION_DIR="$HOME/apps"
+# the top level of the user's application storage.
+export BASE_APPLICATION_PATH="$HOME/apps"
+# where the code should come from.
 export DEFAULT_REPOSITORY_ROOT="git@github.com:kwentworth"
+# we checkout the git repository to a directory underneath the
+# app storage directory named this:
 export CHECKOUT_DIR_NAME="avenger5"
+# the subfolder that the web browser will look for the site in,
+# underneath the application's specific path.
+export STORAGE_SUFFIX="/public"
+
+####
+
+# constants within our cakelampvm machine.
+
+# in our scheme, the single IP address that all our domains map to.
+export IP_ADDRESS="10.28.42.20"
+# the email address (where first dot is replaced by @) for the administrator of the domain.
+export SERVER_ADMIN="developer.cakelampvm.com"
+# the name of the name server for the new domains (should already be configured).
+export MAIN_NAME_SERVER="ns.cakelampvm.com"
+# the name of the mail server for a new domain (should already be configured).
+export MAIL_SERVER="mail.cakelampvm.com"
+# the distribution name to be listed in info for the new domain or subdomain.
+export DISTRO="ubuntu"
 
 ####
 
index c67bd25a689cf5f7613727c6bdc076165049ac3d..c2ed28d5afaa15f9bb87d552dab1dccab7567528 100644 (file)
@@ -63,18 +63,18 @@ source "$WORKDIR/shared_site_mgr.sh"
 
 sep
 
-check_application_dir "$APPLICATION_DIR"
+check_application_dir "$BASE_APPLICATION_PATH"
 
 # find proper webroot where the site will be initialized.
 if [ -z "$app_dirname" ]; then
   # no dir was passed, so guess it.
-  find_app_folder "$APPLICATION_DIR"
+  find_app_folder "$BASE_APPLICATION_PATH"
 else
-  test_app_folder "$APPLICATION_DIR" "$app_dirname"
+  test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
 fi
 
 # where we expect to find our checkout folder underneath.
-full_app_dir="$APPLICATION_DIR/$app_dirname"
+full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
 
 # use our default values for the repository and theme if they're not provided.
 if [ -z "$repo_name" ]; then
@@ -110,8 +110,8 @@ sep
 #if [ ! -z "$user_name" ]; then
 #  echo "Chowning the apps folder to be owned by: $user_name"
 ##hmmm: have to hope for now for standard group named after user 
-#  chown -R "$user_name:$user_name" "$APPLICATION_DIR"
-#  test_or_die "Chowning $APPLICATION_DIR to be owned by $user_name"
+#  chown -R "$user_name:$user_name" "$BASE_APPLICATION_PATH"
+#  test_or_die "Chowning $BASE_APPLICATION_PATH to be owned by $user_name"
 #fi
 
 sep
index 8e87c652917e7b959276c08643bcc7e081724642..d024cd10922782e072c9570d7dbb535fc4e66a77 100644 (file)
@@ -14,29 +14,89 @@ fi
 export WORKDIR="$( \cd "$(\dirname "$0")" && \pwd )"  # obtain the script's working directory.
 export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
 
+export NO_HELLO=right
 source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
 
 ##############
 
 # fix up the main web storage.
 chown -R www-data:www-data /var/www 
+test_or_die "chown www-data"
 group_perm /var/www 
+test_or_die "group_perm www-data"
 
 ##############
 
 # set up access on some important folders for the developer user.
 chown -R developer:developer /home/developer
+test_or_die "chown developer home"
 harsh_perm /home/developer/.ssh
-chown -R developer:developer /opt/feistymeow.org 
-group_perm /opt/feistymeow.org 
+test_or_die "harsh_perm setting on developer .ssh"
 chown -R developer:developer /etc/apache2 /etc/bind 
+test_or_die "chown apache2 and bind to developer"
 group_perm /etc/apache2 /etc/bind 
+test_or_die "group perms on apache2 and bind"
 
 ##############
 
 # fix perms for fred user.
 chown -R fred:fred /home/fred /home/archives/stuffing 
+test_or_die "chown fred home"
 harsh_perm /home/fred/.ssh
+test_or_die "harsh_perm setting on fred .ssh"
+chown -R fred:fred /opt/feistymeow.org 
+test_or_die "chown feisty meow to fred"
+group_perm /opt/feistymeow.org 
+test_or_die "group perms on feisty meow"
+
+##############
+#
+# some slightly tricky bits start here.  we want to massage the vm into the
+# best possible shape without needing to re-release it.
+#
+##############
+
+# only update hello if they've still got the file there.  we don't want to
+# keep forcing our hellos at people.
+if [ -f "$HOME/hello.txt" ]; then
+  # copy the most recent hello file into place for the user.
+  \cp -f "$FEISTY_MEOW_APEX/production/sites/cakelampvm.com/hello.txt" "$HOME"
+  test_or_continue "copying hello file for user"
+fi
+
+##############
+
+# install a better editor app.
+
+echo "
+The script is about to install the bluefish editor and some dependencies.
+If the app is not already installed, then this process takes only about a
+minute on a slower home DSL internet connection...
+
+"
+
+apt-get install -y bluefish &> "/tmp/install_bluefish-$(logname).log"
+test_or_continue "failed to install bluefish editor.  not good."
 
 ##############
 
+
+#hmmm: todo
+# deploy the site updater here to fix the local cakelampvm.com site...
+
+
+##############
+
+# sequel--tell them they're great and show the hello again also.
+
+regenerate
+
+echo "
+
+
+Thanks for revamping your cakelampvm.  :-)
+"
+
+##############
+
+
index 56e658e2d633201244ee88fdca034b8fa7680ccc..3f73b3a8f033a615bbad7c7d85ec436192b7f97f 100644 (file)
@@ -44,7 +44,7 @@ function locate_config_file()
   local app_dirname="$1"; shift
 
   local configfile="$WORKDIR/config/${app_dirname}.app"
-echo hoping config file would be: $configfile
+  echo "config file?: $configfile"
   if [ ! -f "$configfile" ]; then
     # this is not a good config file.  we can't auto-guess the config.
     echo -e "
index 79156ea2ac76bb02e1ea1c28e2c0748321a4d552..1336a891917b6871f3bb6f3150311203dcf597af 100644 (file)
@@ -19,18 +19,18 @@ source "$WORKDIR/shared_site_mgr.sh"
 
 sep
 
-check_application_dir "$APPLICATION_DIR"
+check_application_dir "$BASE_APPLICATION_PATH"
 
 # find proper webroot where the site will be initialized.
 if [ -z "$app_dirname" ]; then
   # no dir was passed, so guess it.
-  find_app_folder "$APPLICATION_DIR"
+  find_app_folder "$BASE_APPLICATION_PATH"
 else
-  test_app_folder "$APPLICATION_DIR" "$app_dirname"
+  test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
 fi
 
 # where we expect to find our checkout folder underneath.
-full_app_dir="$APPLICATION_DIR/$app_dirname"
+full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
 
 # use our default values for the repository and theme if they're not provided.
 if [ -z "$repo_name" ]; then
index a63744be2ee02355999c770401ca0c74a3932ded..282d0cdc060f93a7d25746a89f32f43b12c078c6 100644 (file)
@@ -19,18 +19,18 @@ source "$WORKDIR/shared_site_mgr.sh"
 
 sep
 
-check_application_dir "$APPLICATION_DIR"
+check_application_dir "$BASE_APPLICATION_PATH"
 
 # find proper webroot where the site will be initialized.
 if [ -z "$app_dirname" ]; then
   # no dir was passed, so guess it.
-  find_app_folder "$APPLICATION_DIR"
+  find_app_folder "$BASE_APPLICATION_PATH"
 else
-  test_app_folder "$APPLICATION_DIR" "$app_dirname"
+  test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
 fi
 
 # where we expect to find our checkout folder underneath.
-full_app_dir="$APPLICATION_DIR/$app_dirname"
+full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
 
 # use our default values for the repository and theme if they're not provided.
 if [ -z "$repo_name" ]; then
index ae64d2b877b60d9daf7d86c1aa904f5678e24f3e..c31a33d8390e0e11487dec439146a08e780d3cdb 100644 (file)
@@ -47,24 +47,22 @@ elif [ -z "$app_dirname" ]; then
   print_instructions
 fi
 
-#we will require sudo later.
-#if [[ $EUID != 0 ]]; then
-#  echo "This script must be run as root or sudo."
-#  exit 1
-#fi
+# force the sudo at the start of the script, rather than waiting halfway
+# through to ask for access.
+sudo bash -c 'echo sudo permissions acquired.'
 
 source "$WORKDIR/shared_site_mgr.sh"
 
 sep
 
-check_application_dir "$APPLICATION_DIR"
+check_application_dir "$BASE_APPLICATION_PATH"
 
 # find proper webroot where the site will be initialized.
 if [ -z "$app_dirname" ]; then
   # no dir was passed, so guess it.
-  find_app_folder "$APPLICATION_DIR"
+  find_app_folder "$BASE_APPLICATION_PATH"
 else
-  test_app_folder "$APPLICATION_DIR" "$app_dirname"
+  test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
 fi
 
 #echo "!! domain being added is: $DOMAIN_NAME"
index b72aa8e0fff5a5e5d1ed7a356262de225604dbb1..3e194083baa03488ab4705dd7edd9b30619b82a0 100644 (file)
@@ -1,12 +1,78 @@
 #!/bin/bash
 
-echo "sorry--this script is not implemented yet."
+# this performs the inverse operation of standup, by relying on the
+# remove_domain and remove_apache_site scripts.
+#
+# Author: Chris Koeritz
 
+export WORKDIR="$( \cd "$(\dirname "$0")" && \pwd )"  # obtain the script's working directory.
+export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
 
+source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
 
-# need the inverse of add_domain and add_apache_site.
-# can use the same machinery as standup, just need to invoke these two new removal methods.
+############################
 
-# the decommissioning of the app is a question though.  we don't want to delete it, i'm pretty sure.
-# so how about that part is to do nothing?
+function print_instructions()
+{
+  echo
+  echo "$(basename $0 .sh) {app name}"
+  echo
+  echo "
+$(basename $0 .sh) will drop a web site out of apache server and out of the
+DNS server, as if it never existed.  The site storage is left untouched; we
+don't know what valuable assets lurk there.
+This script must be run as sudo or root; it makes changes to system files.
+"
+  exit 0
+}
+
+############################
+
+# main body of script.
+
+# check for parameters.
+app_dirname="$1"; shift
+
+if [ "$app_dirname" == "-help" -o "$app_dirname" == "--help" ]; then
+  print_instructions
+elif [ -z "$app_dirname" ]; then
+  print_instructions
+fi
+
+# force the sudo at the start of the script, rather than waiting halfway
+# through to ask for access.
+sudo bash -c 'echo sudo permissions acquired.'
+
+source "$WORKDIR/shared_site_mgr.sh"
+
+sep
+
+check_application_dir "$BASE_APPLICATION_PATH"
+
+# find proper webroot where the site will be initialized.
+if [ -z "$app_dirname" ]; then
+  # no dir was passed, so guess it.
+  find_app_folder "$BASE_APPLICATION_PATH"
+else
+  test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
+fi
+
+sep
+
+sudo bash "$FEISTY_MEOW_SCRIPTS/system/remove_apache_site.sh" "$DOMAIN_NAME"
+test_or_die "dropping apache site for: $DOMAIN_NAME"
+
+sep
+
+#echo "!! domain being removed is: $DOMAIN_NAME"
+
+sudo bash "$FEISTY_MEOW_SCRIPTS/system/remove_domain.sh" "$DOMAIN_NAME"
+test_or_die "dropping domain: $DOMAIN_NAME"
+
+sep
+
+echo "
+Finished tearing down the domain name and apache site for:
+  $DOMAIN_NAME
+"
 
index 57b5d519be85af004ec2f17bcae60036bc25a2c8..a9a379fd748ad3a5901e8176471ed1d108685181 100644 (file)
@@ -7,128 +7,16 @@ export WORKDIR="$( \cd "$(\dirname "$0")" && /bin/pwd )"  # obtain the script's
 export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
 
 source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
+source "$FEISTY_MEOW_SCRIPTS/system/common_sysadmin.sh"
 
 # some convenient defaults for our current usage.
 
-BASE_PATH="$HOME/apps"
-STORAGE_SUFFIX="/public"
-
-# this function writes out the new configuration file for the site.
-function write_apache_config()
-{
-  local appname="$1"; shift
-  local sitename="$1"; shift
-  local site_path="$1"; shift
-
-  local site_config="/etc/apache2/sites-available/${sitename}.conf"
-
-  # check if config file already exists and bail if so.
-  if [ -f "$site_config" ]; then
-    echo "The apache configuration file already exists at:"
-    echo "  $site_config"
-    echo "Since apache configuration files can get very complex, we do not want to"
-    echo "assume that this file is removable.  Calling the site addition done."
-    exit 0
-  fi
-
-  echo "Creating a new apache2 site for $sitename with config file:"
-  echo "  $site_config"
-
-  # if no path, then we default to our standard app storage location.  otherwise, we
-  # put the site where they told us to.
-  if [ -z "$site_path" ]; then
-    # path where site gets checked out, in some arcane manner, and which happens to be
-    # above the path where we put webroot (in the storage suffix, if defined).
-    local path_above="${BASE_PATH}/${appname}"
-    # no slash between appname and suffix, in case suffix is empty.
-    local full_path="${path_above}${STORAGE_SUFFIX}"
-#echo really full path is $full_path
-  else
-    # we'll go with their specification for the site storage.
-    local full_path="$site_path"
-  fi
-
-  echo "
-# set up the user's web folder as an apache user web directory.
-
-# set permissions on the actual app folder.
-<Directory \"$full_path\">
-  Options +ExecCGI +Indexes +FollowSymLinks +Includes +MultiViews 
-  Require all granted
-</Directory>
-
-<VirtualHost *:80>
-    ServerName ${sitename}
-    DocumentRoot ${full_path}
-    ErrorLog \${APACHE_LOG_DIR}/${sitename}-error.log
-    CustomLog \${APACHE_LOG_DIR}/${sitename}-access.log combined
-    Include /etc/apache2/conf-library/basic-options.conf
-    Include /etc/apache2/conf-library/rewrite-enabling.conf
-</VirtualHost>
-" >"$site_config" 
-
-  chown "$(logname):$(logname)" "$site_config"
-  test_or_die "setting ownership on: $site_config"
-}
-
-# turns on the config file we create above for apache.
-function enable_site()
-{
-  local sitename="$1"; shift
-  local site_config="/etc/apache2/sites-available/${sitename}.conf"
-
-  outfile="$TMP/apacheout.$RANDOM"
-  a2ensite "$(basename $site_config)" &>$outfile
-  if [ $? -ne 0 ]; then
-    # an error happened, so we show the command's output at least.
-    cat $outfile
-    echo
-    echo "There was a problem enabling the apache config file in:"
-    echo "  $site_config"
-    echo "Please consult the apache error logs for more details."
-    exit 1
-  fi
-  \rm "$outfile"
-}
-
-# restarts the apache2 service.
-function restart_apache()
-{
-  service apache2 restart
-  if [ $? -ne 0 ]; then
-    echo "There was a problem restarting the apache2 service."
-    echo "Please consult the apache error logs for more details."
-    exit 1
-  fi
-}
-
-# sets up the serverpilot storage location for a user hosted web site.
-function maybe_create_site_storage()
-{
-  local our_app="$1"; shift
-  # make sure the base path for storage of all the apps for this user exists.
-  local full_path="$BASE_PATH/$our_app"
-  if [ ! -d "$full_path" ]; then
-    mkdir -p $full_path
-    test_or_die "The app storage path could not be created.\n  Path in question is: $full_path"
-  fi
-
-  # now give the web server some access to the folder.  this is crucial since the folders
-  # can be hosted in any user folder, and the group permissions will not necessarily be correct already.
-  local chow_path="$full_path"
-  # only the first chmod is recursive; the rest just apply to the specific folder of interest.
-  chmod -R g+rx "$chow_path"
-  # walk backwards up the path and fix perms.
-  while [[ $chow_path != $HOME ]]; do
-echo chow path is now $chow_path
-    chmod g+rx "$chow_path"
-    test_or_die "Failed to add group permissions on the path: $chow_path"
-    # reassert the user's ownership of any directories we might have just created.
-    chown $(logname) "$chow_path"
-    test_or_die "changing ownership to user failed on the path: $chow_path"
-    chow_path="$(dirname "$chow_path")"
-  done
-}
+if [ -z "$BASE_APPLICATION_PATH" ]; then
+  BASE_APPLICATION_PATH="$HOME/apps"
+fi
+if [ -z "$STORAGE_SUFFIX" ]; then
+  STORAGE_SUFFIX="/public"
+fi
 
 # main body of script.
 
@@ -151,7 +39,7 @@ This script needs to know (1) the application name for the new site and
 appropriate name for a file-system compatible folder name.  There is an
 optional third parameter (3) the path for site storage.  If the site path
 is not provided, we'll use this path:
-  $BASE_PATH/{app name}/$STORAGE_SUFFIX"
+  $BASE_APPLICATION_PATH/{app name}/$STORAGE_SUFFIX"
   exit 1
 fi
 
index 80ebb2f37d0563e6d0d9b0d768d1e9b80a0797c3..e12faa7be928ddaa8cba56bc9babc60ac2d5e040 100644 (file)
@@ -1,10 +1,12 @@
 #!/bin/bash
 
-# this set of functions serve the main purpose of adding new domains or subdomains to the bind9 DNS server on the current host.
-# it is currently highly specific to running a bunch of domains on a linux VM, where the VM has one IP address.
-# note that bind 'named' must already be configured.
-# also, it is assumed that if a subdomain is being added, then the containing domain has already been configured and is 
-# configured in a file similar to "blah.com.conf" in /etc/bind.
+# this set of functions serve the main purpose of adding new domains or
+# subdomains to the bind9 DNS server on the current host.  it is currently
+# highly specific to running a bunch of domains on a linux VM, where the VM
+# has one IP address.  note that the bind 'named' must already be configured.
+# also, it is assumed that, if a subdomain is being added, then the containing
+# domain has already been configured and is configured in a file similar to
+# "blah.com.conf" in /etc/bind.
 #
 # Author: Chris Koeritz
 
@@ -12,141 +14,31 @@ export WORKDIR="$( \cd "$(\dirname "$0")" && \pwd )"  # obtain the script's work
 export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
 
 source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
+source "$FEISTY_MEOW_SCRIPTS/system/common_sysadmin.sh"
 
 # some defaults that are convenient for current purposes.
+# existing values will be respected over our defaults.
 
-# hmmm: !!! these would need to be parameterized somehow for this script to become really general.
-
-# in our scheme, the single IP address that all our domains map to.
-IP_ADDRESS="10.28.42.20"
-# the email address (where first dot is replaced by @) for the administrator of the domain.
-SERVER_ADMIN="developer.cakelampvm.com"
-# the name of the name server for the new domains (should already be configured).
-MAIN_NAME_SERVER="ns.cakelampvm.com"
-# the name of the mail server for a new domain (should already be configured).
-MAIL_SERVER="mail.cakelampvm.com"
-# the distribution name to be listed in info for the new domain or subdomain.
-DISTRO="ubuntu"
-
-# creates a totally new domain config file for DNS.
-function write_new_domain_file()
-{
-  local domain_name="$1"; shift
-
-  local domain_file="/etc/bind/${domain_name}.conf"
-
-  echo "adding a totally new domain called $domain_name"
-  echo "using the config file: $domain_file"
-
-  if [ -f $domain_file ]; then
-    echo
-    echo "The domain configuration file already exists at:"
-    echo "  $domain_file"
-    echo "Since we don't want to tear that down if it has specialized configuration"
-    echo "data in it, we will just leave it in place and consider our job done."
-    echo
-    exit 0
-  fi
-
-  echo "
-\$TTL 1W
-@      IN SOA  @       ${SERVER_ADMIN}. (
-               2017100801 ; serial
-               2H ; refresh
-               8M ; retry
-               14D ; expiry
-               6H ) ; minimum
-
-       IN NS           ${MAIN_NAME_SERVER}.
-       IN MX   10      ${MAIL_SERVER}.
-
-${domain_name}.        IN A    ${IP_ADDRESS}
-       IN HINFO        \"linux server\" \"${DISTRO}\"
-" >"$domain_file"
-
-  # our personalized configuration approach wants the real owner to own the file.
-  chown "$(logname):$(logname)" $domain_file
-  test_or_die "setting ownership on: $domain_file"
-}
-
-# hooks up a new config file into bind's list of zones.
-function add_zone_for_new_domain()
-{
-  local domain_name="$1"; shift
-
-  local domain_file="/etc/bind/${domain_name}.conf"
-
-  echo "adding a new domain configured by ${domain_file} into"
-  echo "the named.conf.local configuration file."
-
-  # append the reference to the new conf file in the zone list.
-  echo "
-zone \"${domain_name}\" in {
-       file \"${domain_file}\";
-       type master;
-       allow-query { any; };
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-" >> /etc/bind/named.conf.local
-
-  # keep ownership for the real user.
-  chown "$(logname):$(logname)" /etc/bind/named.conf.local
-  test_or_die "setting ownership on: /etc/bind/named.conf.local"
-
-}
-
-# adds a new subdomain under a containing domain.
-function add_new_subdomain()
-{
-  local new_domain="$1"; shift
-
-  # split up the full domain name into subdomain portion and containing domain.
-  local subdomain="${new_domain%.*.*}"
-  local containing_domain="${new_domain#*.}"
-
-  echo "adding a subdomain $subdomain to containing domain $containing_domain"
-
-  local domain_file="/etc/bind/${containing_domain}.conf"
-  # see if config file already exists; if not, complain.
-  if [ ! -f "$domain_file" ]; then
-    echo "The domain configuration file for $new_domain is missing."
-    echo "It should already be present in: $domain_file"
-    echo "Please add the containing domain before trying to add a subdomain."
-    exit 1
-  fi
-
-  # see if subdomain already present in config.
-  if [ $(grep -q "$new_domain" "$domain_file") ]; then
-    echo "The subdomain $subdomain already seems to exist in the domain"
-    echo "configuration file: $domain_file"
-    echo "Please edit the config file to remove the subdomain before trying"
-    echo "to re-add the subdomain."
-    exit 1
-  fi
-
-  # append the new subdomain into the config file.
-  echo "
-${subdomain}.${containing_domain}.    IN A    ${IP_ADDRESS}
-        IN HINFO \"linux server\" \"${DISTRO}\"
-" >> /etc/bind/${containing_domain}.conf
-
-  # keep ownership for real user.
-  chown "$(logname):$(logname)" "/etc/bind/${containing_domain}.conf"
-  test_or_die "setting ownership on: /etc/bind/${containing_domain}.conf"
-}
-
-function restart_bind()
-{
-  echo restarting DNS server.
-  service bind9 restart
-  if [ $? -ne 0 ]; then
-    echo "The bind service did not restart properly.  Please check the error logs."
-    exit 1
-  fi
-  echo DNS server restarted.
-}
+if [ -z "$IP_ADDRESS" ]; then
+  # in our scheme, the single IP address that all our domains map to.
+  IP_ADDRESS="10.28.42.20"
+fi
+if [ -z "$SERVER_ADMIN" ]; then
+  # the email address (where first dot is replaced by @) for the administrator of the domain.
+  SERVER_ADMIN="developer.cakelampvm.com"
+fi
+if [ -z "$MAIN_NAME_SERVER" ]; then
+  # the name of the name server for the new domains (should already be configured).
+  MAIN_NAME_SERVER="ns.cakelampvm.com"
+fi
+if [ -z "$MAIL_SERVER" ]; then
+  # the name of the mail server for a new domain (should already be configured).
+  MAIL_SERVER="mail.cakelampvm.com"
+fi
+if [ -z "$DISTRO" ]; then
+  # the distribution name to be listed in info for the new domain or subdomain.
+  DISTRO="ubuntu"
+fi
 
 # main body of script.
 
diff --git a/scripts/system/common_sysadmin.sh b/scripts/system/common_sysadmin.sh
new file mode 100644 (file)
index 0000000..223067f
--- /dev/null
@@ -0,0 +1,352 @@
+#!/bin/bash
+
+# this is a library of functions shared by scripts in the system folder.
+#
+# Author: Chris Koeritz
+
+############################################################################
+
+# bind9 methods...
+
+# removes a full domain from the DNS.
+function remove_domain_file()
+{
+  local domain_name="$1"; shift
+
+  local domain_file="/etc/bind/${domain_name}.conf"
+  if [ -f "$domain_file" ]; then
+    # don't destroy, just shuffle.
+    \mv -f "$domain_file" "/tmp/$(basename ${domain_file})-old-${RANDOM}"
+    test_or_die "removing domain file: $domain_file"
+  else
+    echo "Did not see a domain file to remove: $domain_file"
+  fi
+}
+
+# creates a totally new domain config file for DNS.
+function write_new_domain_file()
+{
+  local domain_name="$1"; shift
+
+  local domain_file="/etc/bind/${domain_name}.conf"
+
+  echo "adding a totally new domain called $domain_name"
+  echo "using the config file: $domain_file"
+
+  if [ -f $domain_file ]; then
+    echo
+    echo "The domain configuration file already exists at:"
+    echo "  $domain_file"
+    echo "Since we don't want to tear that down if it has specialized configuration"
+    echo "data in it, we will just leave it in place and consider our job done."
+    echo
+    exit 0
+  fi
+
+  echo "
+\$TTL 1W
+@      IN SOA  @       ${SERVER_ADMIN}. (
+               2017100801 ; serial
+               2H ; refresh
+               8M ; retry
+               14D ; expiry
+               6H ) ; minimum
+
+       IN NS           ${MAIN_NAME_SERVER}.
+       IN MX   10      ${MAIL_SERVER}.
+
+${domain_name}.        IN A    ${IP_ADDRESS}
+       IN HINFO        \"linux server\" \"${DISTRO}\"
+" >"$domain_file"
+
+  # our personalized configuration approach wants the real owner to own the file.
+  chown "$(logname):$(logname)" $domain_file
+  test_or_die "setting ownership on: $domain_file"
+}
+
+# takes a zone back out of the local conf file for bind
+function remove_zone_for_domain()
+{
+  local domain_name="$1"; shift
+
+  local domain_file="/etc/bind/${domain_name}.conf"
+
+  # eat the zone file definition.  this will botch up badly if more text was added
+  # or the zone info shrank.
+  create_chomped_copy_of_file "/etc/bind/named.conf.local" "zone.*${domain_name}" 6
+}
+
+# hooks up a new config file into bind's list of zones.
+function add_zone_for_new_domain()
+{
+  local domain_name="$1"; shift
+
+  local domain_file="/etc/bind/${domain_name}.conf"
+
+  echo "adding a new domain configured by ${domain_file} into"
+  echo "the named.conf.local configuration file."
+
+  # append the reference to the new conf file in the zone list.
+  echo "
+zone \"${domain_name}\" in {
+       file \"${domain_file}\";
+       type master;
+       allow-query { any; };
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+" >> /etc/bind/named.conf.local
+
+  # keep ownership for the real user.
+  chown "$(logname):$(logname)" /etc/bind/named.conf.local
+  test_or_die "setting ownership on: /etc/bind/named.conf.local"
+}
+
+# zaps a subdomain out of the containing domain file.
+function remove_subdomain()
+{
+  local old_domain="$1"; shift
+
+  # split up the full domain name into subdomain portion and containing domain.
+  local subdomain="${old_domain%.*.*}"
+  local containing_domain="${old_domain#*.}"
+
+  echo "removing subdomain $subdomain from containing domain $containing_domain"
+
+  local domain_file="/etc/bind/${containing_domain}.conf"
+  # see if config file already exists; if not, complain.
+  if [ ! -f "$domain_file" ]; then
+    echo "The domain configuration file for $old_domain is missing."
+    echo "It should already be present in: $domain_file"
+    echo "We cannot remove a subdomain if the containing domain isn't there."
+    exit 1
+  fi
+
+  # see if subdomain already present in config.
+  if ! grep -q "$old_domain" "$domain_file"; then
+    echo "The subdomain $subdomain is already missing from the domain"
+    echo "configuration file: $domain_file"
+    echo "Our work is apparently done for removing it."
+    return 0
+  fi
+
+  create_chomped_copy_of_file "$domain_file" "${old_domain}" 2
+}
+
+# adds a new subdomain under a containing domain.
+function add_new_subdomain()
+{
+  local new_domain="$1"; shift
+
+  # split up the full domain name into subdomain portion and containing domain.
+  local subdomain="${new_domain%.*.*}"
+  local containing_domain="${new_domain#*.}"
+
+  echo "adding a subdomain $subdomain to containing domain $containing_domain"
+
+  local domain_file="/etc/bind/${containing_domain}.conf"
+  # see if config file already exists; if not, complain.
+  if [ ! -f "$domain_file" ]; then
+    echo "The domain configuration file for $new_domain is missing."
+    echo "It should already be present in: $domain_file"
+    echo "Please add the containing domain before trying to add a subdomain."
+    exit 1
+  fi
+
+  # see if subdomain already present in config.
+  if grep -q "$new_domain" "$domain_file"; then
+    echo "The subdomain $subdomain already seems to exist in the domain"
+    echo "configuration file: $domain_file"
+    echo "We are considering our work done; if you want to modify the subdomain,"
+    echo "then please call remove_domain on it first."
+    return 0
+  fi
+
+  # append the new subdomain into the config file.
+  echo "${subdomain}.${containing_domain}.    IN A    ${IP_ADDRESS}
+        IN HINFO \"linux server\" \"${DISTRO}\"
+" >> /etc/bind/${containing_domain}.conf
+
+  # keep ownership for real user.
+  chown "$(logname):$(logname)" "/etc/bind/${containing_domain}.conf"
+  test_or_die "setting ownership on: /etc/bind/${containing_domain}.conf"
+}
+
+function restart_bind()
+{
+  echo restarting DNS server.
+  service bind9 restart
+  if [ $? -ne 0 ]; then
+    echo "The bind service did not restart properly.  Please check the error logs."
+    exit 1
+  fi
+  echo DNS server restarted.
+}
+
+############################################################################
+
+# apache2 methods...
+
+# removes a config file for apache given the app name and site name.
+function remove_apache_config()
+{
+  local sitename="$1"; shift
+
+  local site_config="/etc/apache2/sites-available/${sitename}.conf"
+
+  if [ -f "$site_config" ]; then
+    # don't destroy, just shuffle.
+    \mv -f "$site_config" "/tmp/$(basename ${site_config})-old-${RANDOM}"
+    test_or_die "removing site config: $site_config"
+  else
+    echo "Did not see a site config to remove: $site_config"
+  fi
+}
+
+# this function writes out the new configuration file for the site.
+function write_apache_config()
+{
+  local appname="$1"; shift
+  local sitename="$1"; shift
+  local site_path="$1"; shift
+
+  local site_config="/etc/apache2/sites-available/${sitename}.conf"
+
+  # check if config file already exists and bail if so.
+  if [ -f "$site_config" ]; then
+    echo "The apache configuration file already exists at:"
+    echo "  $site_config"
+    echo "Since apache configuration files can get very complex, we do not want to"
+    echo "assume that this file is removable.  Calling the site addition done."
+    exit 0
+  fi
+
+  echo "Creating a new apache2 site for $sitename with config file:"
+  echo "  $site_config"
+
+  # if no path, then we default to our standard app storage location.  otherwise, we
+  # put the site where they told us to.
+  if [ -z "$site_path" ]; then
+    # path where site gets checked out, in some arcane manner, and which happens to be
+    # above the path where we put webroot (in the storage suffix, if defined).
+    local path_above="${BASE_APPLICATION_PATH}/${appname}"
+    # no slash between appname and suffix, in case suffix is empty.
+    local full_path="${path_above}${STORAGE_SUFFIX}"
+#echo really full path is $full_path
+  else
+    # we'll go with their specification for the site storage.
+    local full_path="$site_path"
+  fi
+
+  echo "
+# set up the user's web folder as an apache user web directory.
+
+# set permissions on the actual app folder.
+<Directory \"$full_path\">
+  Options +ExecCGI +Indexes +FollowSymLinks +Includes +MultiViews 
+  Require all granted
+</Directory>
+
+<VirtualHost *:80>
+    ServerName ${sitename}
+    DocumentRoot ${full_path}
+    ErrorLog \${APACHE_LOG_DIR}/${sitename}-error.log
+    CustomLog \${APACHE_LOG_DIR}/${sitename}-access.log combined
+    Include /etc/apache2/conf-library/basic-options.conf
+    Include /etc/apache2/conf-library/rewrite-enabling.conf
+</VirtualHost>
+" >"$site_config" 
+
+  chown "$(logname):$(logname)" "$site_config"
+  test_or_die "setting ownership on: $site_config"
+}
+
+# stops apache from serving up the site.
+function disable_site()
+{
+  local sitename="$1"; shift
+  local site_config="/etc/apache2/sites-available/${sitename}.conf"
+
+  if [ ! -f "$site_config" ]; then
+    echo "The site config did not exist and could not be disabled: $site_config"
+    return 0
+  fi
+
+#hmmm: repeated pattern of hidden output file, very useful.  abstract it...
+  local outfile="$TMP/apacheout.$RANDOM"
+  a2dissite "$(basename $site_config)" &>$outfile
+  if [ $? -ne 0 ]; then
+    # an error happened, so we show the command's output at least.
+    cat $outfile
+    echo
+    echo "There was a problem disabling the apache config file in:"
+    echo "  $site_config"
+    echo "Please consult the apache error logs for more details."
+    exit 1
+  fi
+  \rm "$outfile"
+}
+
+# turns on the config file we create above for apache.
+function enable_site()
+{
+  local sitename="$1"; shift
+  local site_config="/etc/apache2/sites-available/${sitename}.conf"
+
+  local outfile="$TMP/apacheout.$RANDOM"
+  a2ensite "$(basename $site_config)" &>$outfile
+  if [ $? -ne 0 ]; then
+    # an error happened, so we show the command's output at least.
+    cat $outfile
+    echo
+    echo "There was a problem enabling the apache config file in:"
+    echo "  $site_config"
+    echo "Please consult the apache error logs for more details."
+    exit 1
+  fi
+  \rm "$outfile"
+}
+
+# restarts the apache2 service.
+function restart_apache()
+{
+  service apache2 restart
+  if [ $? -ne 0 ]; then
+    echo "There was a problem restarting the apache2 service."
+    echo "Please consult the apache error logs for more details."
+    exit 1
+  fi
+}
+
+# sets up the serverpilot storage location for a user hosted web site.
+function maybe_create_site_storage()
+{
+  local our_app="$1"; shift
+  # make sure the path for storage this app exists for the user.
+  local full_path="$BASE_APPLICATION_PATH/$our_app"
+  if [ ! -d "$full_path" ]; then
+    mkdir -p $full_path
+    test_or_die "The app storage path could not be created.\n  Path in question is: $full_path"
+  fi
+
+  # now give the web server some access to the folder.  this is crucial since the folders
+  # can be hosted in any user folder, and the group permissions will not necessarily be correct already.
+  local chow_path="$full_path"
+  # only the first chmod is recursive; the rest just apply to the specific folder of interest.
+  chmod -R g+rx "$chow_path"
+  # walk backwards up the path and fix perms.
+  while [[ $chow_path != $HOME ]]; do
+#echo chow path is now $chow_path
+    chmod g+rx "$chow_path"
+    test_or_die "Failed to add group permissions on the path: $chow_path"
+    # reassert the user's ownership of any directories we might have just created.
+    chown $(logname) "$chow_path"
+    test_or_die "changing ownership to user failed on the path: $chow_path"
+    chow_path="$(dirname "$chow_path")"
+  done
+}
+
+############################################################################
+
+
diff --git a/scripts/system/remove_apache_site.sh b/scripts/system/remove_apache_site.sh
new file mode 100644 (file)
index 0000000..ef6c8b6
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# uninstalls the apache website for a specified domain.
+
+# auto-find the scripts, since we might want to run this as sudo.
+export WORKDIR="$( \cd "$(\dirname "$0")" && /bin/pwd )"  # obtain the script's working directory.
+export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
+
+source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
+source "$FEISTY_MEOW_SCRIPTS/system/common_sysadmin.sh"
+
+# some convenient defaults for our current usage.
+
+if [ -z "$BASE_APPLICATION_PATH" ]; then
+  BASE_APPLICATION_PATH="$HOME/apps"
+fi
+if [ -z "$STORAGE_SUFFIX" ]; then
+  STORAGE_SUFFIX="/public"
+fi
+
+# main body of script.
+
+if [[ $EUID != 0 ]]; then
+  echo "This script must be run as root or sudo."
+  exit 1
+fi
+
+site="$1"; shift
+
+if [ -z "$site" ]; then
+#hmmm: move to a print_instructions function.
+  echo "
+$(basename $0): {dns name} 
+
+This script needs to know (1) the DNS name for the apache virtual host.
+The script will uninstall that site's configuration files for apache2.
+"
+  exit 1
+fi
+
+disable_site "$site"
+remove_apache_config "$site"
+restart_apache
+
diff --git a/scripts/system/remove_domain.sh b/scripts/system/remove_domain.sh
new file mode 100644 (file)
index 0000000..f734878
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/bash
+
+# performs the inverse function of add_domain by deconfiguring a domain
+# in bind.  the domain needs to have been set up by add_domain, or this will
+# not succeed.
+#
+# Author: Chris Koeritz
+
+export WORKDIR="$( \cd "$(\dirname "$0")" && \pwd )"  # obtain the script's working directory.
+export FEISTY_MEOW_APEX="$( \cd "$WORKDIR/../.." && \pwd )"
+
+source "$FEISTY_MEOW_APEX/scripts/core/launch_feisty_meow.sh"
+source "$FEISTY_MEOW_SCRIPTS/system/common_sysadmin.sh"
+
+# some defaults that are convenient for current purposes.
+# existing values will be respected over our defaults.
+
+#if [ -z "$IP_ADDRESS" ]; then
+#  # in our scheme, the single IP address that all our domains map to.
+#  IP_ADDRESS="10.28.42.20"
+#fi
+#if [ -z "$SERVER_ADMIN" ]; then
+#  # the email address (where first dot is replaced by @) for the administrator of the domain.
+#  SERVER_ADMIN="developer.cakelampvm.com"
+#fi
+#if [ -z "$MAIN_NAME_SERVER" ]; then
+#  # the name of the name server for the new domains (should already be configured).
+#  MAIN_NAME_SERVER="ns.cakelampvm.com"
+#fi
+#if [ -z "$MAIL_SERVER" ]; then
+#  # the name of the mail server for a new domain (should already be configured).
+#  MAIL_SERVER="mail.cakelampvm.com"
+#fi
+#if [ -z "$DISTRO" ]; then
+#  # the distribution name to be listed in info for the new domain or subdomain.
+#  DISTRO="ubuntu"
+#fi
+
+# main body of script.
+
+if [[ $EUID != 0 ]]; then
+  echo "This script must be run as root or sudo."
+  exit 1
+fi
+
+old_domain="$1"; shift
+
+if [ -z "$old_domain" ]; then
+  echo "This script needs a domain name to remove from DNS." 
+  exit 1
+fi
+
+# if domain name has three or more components, then remove a subdomain.
+# otherwise, remove a full domain.
+if [[ $old_domain == *"."*"."* ]]; then
+  # remove a subdomain from the containing domain.
+  remove_subdomain "$old_domain"
+  restart_bind
+else
+  # remove the full domain in DNS.
+  remove_domain_file "$old_domain"
+  remove_zone_for_domain "$old_domain"
+  restart_bind
+fi
+
+
index 8bc6ccd36d3c31b1145b3df0c1c401d58414beba..0203b9ae7fd771bbacbc80b1c2d2319e34462e3f 100644 (file)
@@ -17,7 +17,8 @@ function apply_title_to_terminal()
     title="$(hostname)"
   fi
   
-  if [ "${TERM}" != "dumb" -a -z "$PBS_ENVIRONMENT" -a ! -z "$PS1" ]; then
+  if [ "${TERM}" != "dumb" -a -z "$PBS_ENVIRONMENT" -a \
+        ! -z "$PS1" -a "${TERM}" != "linux" ]; then
     echo -n -e "\033]0;${title}\007"
   else
     # not running interactively, so just echo the title.
@@ -31,28 +32,46 @@ function apply_title_to_terminal()
 function set_terminal_title()
 {
   apply_title_to_terminal $*
+
+#tricky tries to get it to be available when we ask for it in get_terminal_title
+  sync
+#  echo -n
+
+#  # we're enforcing a new title from here on.
+#  unset PRIOR_TERMINAL_TITLE
   save_terminal_title
 }
 
-# reads the current terminal title, if possible, and saves it to our record.
-function save_terminal_title()
+# echoes back the current title on the terminal window, if we can acquire it.
+function get_terminal_title()
 {
+  # this is an important value now; it is checked for in save_terminal_title.
+  local term_title_found="unknown"
   # save the former terminal title if we're running in X with xterm.
   which xprop &>/dev/null
   if [ $? -eq 0 ]; then
     # make sure we're actually using xterm *and* that we have a window ID.
     if [[ "$TERM" =~ .*"xterm".* && ! -z "$WINDOWID" ]]; then
-      local prior_title="$(xprop -id $WINDOWID | perl -nle 'print $1 if /^WM_NAME.+= \"(.*)\"$/')"
-      if [ ! -z "$prior_title" ]; then
-        if [ ! -z "$DEBUG_TERM_TITLE" ]; then
-          echo "saving prior terminal title as '$prior_title'"
-        fi
-        export PRIOR_TERMINAL_TITLE="$prior_title"
-      else
-        if [ ! -z "$DEBUG_TERM_TITLE" ]; then
-          echo "not saving prior terminal title which was empty"
-        fi
-      fi
+      term_title_found="$(xprop -id $WINDOWID | perl -nle 'print $1 if /^WM_NAME.+= \"(.*)\"$/')"
+    fi
+  fi
+  echo -n "$term_title_found"
+}
+
+# reads the current terminal title, if possible, and saves it to our record.
+function save_terminal_title()
+{
+  local title="$(get_terminal_title)"
+  if [ "$title" != "unknown" ]; then
+    # there was a title, so save it.
+    if [ ! -z "$DEBUG_TERM_TITLE" ]; then
+      echo "saving prior terminal title as '$title'"
+    fi
+    export PRIOR_TERMINAL_TITLE="$title"
+  else
+    # the terminal had no title, or we couldn't access it, or there's no terminal.
+    if [ ! -z "$DEBUG_TERM_TITLE" ]; then
+      echo "not saving prior terminal title which was empty"
     fi
   fi
 }
@@ -83,7 +102,7 @@ function label_terminal_with_info()
     fi
     pruned_host=$(echo $HOSTNAME | sed -e 's/^\([^\.]*\)\..*$/\1/')
     date_string=$(date +"%Y %b %e @ %T")
-    user=$USER
+    user=$(logname)
     if [ -z "$user" ]; then
       # try snagging the windoze name.
       user=$USERNAME