really close for simple powerups
[feisty_meow.git] / scripts / site_avenger / shared_site_mgr.sh
1 #!/bin/bash
2
3 # Author: Kevin Wentworth
4 # Author: Chris Koeritz
5
6 # This contains a bunch of reusable functions that help out in managing websites.
7
8 # This script is sourced, and relies on the value of THISDIR, which should
9 # point at the directory where the site management scripts are stored,
10 # especially this one.
11
12 source "$FEISTY_MEOW_SCRIPTS/core/launch_feisty_meow.sh"
13
14 export SSM_LOG_FILE="$TMP/$(logname)-siteavenger-script.log"
15
16 # configure feisty revision control to ignore vendor folders.
17 export NO_CHECKIN_VENDOR=true
18
19 # handles the computation of the base application path and the app dir name.
20 # this expects to be passed the application directory name, but it will attempt to
21 # do something intelligent if no name is passed in.
22 function autoconfigure_paths()
23 {
24   export app_dirname="$1"; shift
25
26   if [ -z "$app_dirname" ]; then
27     echo "$(date_stringer): Guessing application dir from local folder."
28     app_dirname="$(basename $(\pwd))"
29     export BASE_APPLICATION_PATH="$(dirname $(\pwd))"
30 echo "calculated application dir of '$app_dirname' and"
31 echo "a base app path of '$BASE_APPLICATION_PATH'"
32   fi
33
34   # get our configuration loaded, if we know the config file.
35   # if there is none, we will use our default version.
36   export SITE_MANAGEMENT_CONFIG_FILE
37   if [ -z "$SITE_MANAGEMENT_CONFIG_FILE" ]; then
38     SITE_MANAGEMENT_CONFIG_FILE="$THISDIR/config/default.app"
39     echo "$(date_stringer): Site management config file was not set.  Using default:" >> "$SSM_LOG_FILE"
40     echo "$(date_stringer):   $SITE_MANAGEMENT_CONFIG_FILE" >> "$SSM_LOG_FILE"
41   fi
42
43   # load in at least the default version to get us moving.
44   source "$SITE_MANAGEMENT_CONFIG_FILE"
45   exit_on_error "loading site management configuration from: $SITE_MANAGEMENT_CONFIG_FILE"
46
47
48 echo "after site config file sourced, app dirname now '$app_dirname' and"
49 echo "base app path now '$BASE_APPLICATION_PATH'"
50
51 }
52
53 # tests that the main storage folder for apps exists.
54 # the parameter passed in should be the application directory name (app_dirname), without
55 # any additional path components.  the script will attempt to auto-configure the application
56 # base path (above the project folder with app_dirname) and get all the other path variables
57 # established.
58 function check_apps_root()
59 {
60   local temp_app_dirname="$1"; shift
61
62 echo new call to auto conf func...
63   autoconfigure_paths "$temp_app_dirname"
64 echo after call to auto conf func...
65
66   if [ -z "$BASE_APPLICATION_PATH" ]; then
67 echo fix this: we had no base app path, what to do now?
68 exit 1
69   fi
70
71   if [ ! -d "$BASE_APPLICATION_PATH" ]; then
72     echo "$(date_stringer): Creating the apps directory: $BASE_APPLICATION_PATH" >> "$SSM_LOG_FILE"
73     mkdir "$BASE_APPLICATION_PATH"
74     exit_on_error "Making apps directory when not already present"
75   fi
76 }
77
78 #hmmm: extract to core somewhere...
79 # locates a parent directory of a certain name, if possible.  returns success
80 # (as zero) if the directory was found, and failure if there was no parent
81 # named as requested.  sets a global variable PARENT_DIR_FOUND to the full
82 # directory name that matched, including the name being sought (but omitting
83 # any deeper directories than that).
84 function find_named_parent_dir()
85 {
86   local dir_name_sought="$1"; shift
87   # clear any previous global result.
88   unset PARENT_DIR_FOUND
89   # check for degenerate case of parameter count.
90   if [ -z "$dir_name_sought" ]; then
91     echo "
92 find_named_parent_dir: requires a directory name parameter, which will be
93 sought out above the current directory.  the return value indicates whether
94 the requested name was found or not.
95 "
96     return 1
97   fi
98   # signal a failure by default with our return value.
99   local retval=1
100   # loop upwards in dir hierarchy to find the name.
101   while true; do
102     local currdir="$(\pwd)"
103     if [ "$currdir" == "/" ]; then
104       # we climbed out of all subdirs.  this is a failure case.
105       retval=1
106       break
107     fi
108     # get the base part of our name to check on success.
109     local base="$(basename "$currdir")"
110     if [ "$base" == "$dir_name_sought" ]; then
111       # yes, that is the right name.  success case.  save our result.
112       export PARENT_DIR_FOUND="$currdir"
113       retval=0
114       break
115     fi
116     # hop up a directory.
117     pushd .. &>/dev/null
118   done
119
120   # rollback any directories we pushed.
121   while popd &>/dev/null; do true; done
122
123   return $retval
124 }
125
126 # tries to find an appropriate config file for the application.
127 function locate_config_file()
128 {
129   local app_dirname="$1"; shift
130
131   local configfile="$THISDIR/config/${app_dirname}.app"
132   echo "$(date_stringer): config file guessed?: $configfile" >> "$SSM_LOG_FILE"
133   if [ ! -f "$configfile" ]; then
134     # this is not a good config file.  we can't auto-guess the config.
135     echo -e "$(date_stringer): 
136 There is no specific site configuration file in:
137   $configfile
138 We will continue onward using the default and hope that this project follows
139 the standard pattern for cakephp projects." >> "$SSM_LOG_FILE"
140     # we'll pull in the default config file we set earlier; this will
141     # reinitialize some variables based on the app name.
142   else
143     # they gave us a valid config file.  let's try using it.
144     export SITE_MANAGEMENT_CONFIG_FILE="$configfile"
145   fi
146
147   # try to load the config.
148   source "$SITE_MANAGEMENT_CONFIG_FILE"
149   exit_on_error "loading site management configuration from: $SITE_MANAGEMENT_CONFIG_FILE"
150
151   return 0
152 }
153
154 # this function will seek out top-level directories in the target directory passed in.
155 # if there is only one directory, then it is returned (in the app_dirname variable).
156 # otherwise, the user is asked which directory to use.
157 # important: this sets a global variable app_dirname to the application's directory name.
158 function find_app_folder()
159 {
160   local appsdir="$1"; shift
161
162   # throw away any prior value so no confusion arises.
163   unset app_dirname
164   
165   # count number of directories...  if exactly one, then choose it.
166   numdirs=$(count_directories "$appsdir/")
167
168   if [ $numdirs -eq 0 ]; then
169     sep
170     echo "There are no directories in the application directory:"
171     echo "  $appsdir"
172     echo "Please create a directory for the site storage, based on the application"
173     echo "name that you want to work on.  Or you can just pass the directory name"
174     echo "on the command line, e.g.:"
175     echo "  $(basename $0) turtle"
176     sep
177     return 1
178   elif [ $numdirs -eq 1 ]; then
179     # one directory in apps, so we'll pick that one.
180     app_dirname="$(basename $(find "$appsdir" -follow -mindepth 1 -maxdepth 1 -type d) )"
181     exit_on_error "Guessing application folder"
182   else
183     # there's more than one folder in apps...
184
185     # make sure we're allowed to auto-guess the folder name from our current dir.
186     if [ -z "$NO_AUTOMATIC_FOLDER_GUESS" ]; then
187       # if we can find the special checkout directory name above our current PWD, then that
188       # might tell us our project name.
189       if  find_named_parent_dir "$CHECKOUT_DIR_NAME"; then
190         # we can grab a name above the checkout dir name location.  let's try that.
191         app_dirname="$(basename "$(dirname $PARENT_DIR_FOUND)" )"
192       fi
193     else
194       # flag maintenance, to avoid hosing other commands by leaving this set.
195       unset NO_AUTOMATIC_FOLDER_GUESS
196
197       # well, we couldn't guess a directory based on our current location,
198       # so ask the user to choose.
199       # Reference: https://askubuntu.com/questions/1705/how-can-i-create-a-select-menu-in-a-shell-script
200       holdps3="$PS3"
201       PS3='Please pick a folder for site initialization: '
202       options=( $(find "$appsdir" -follow -mindepth 1 -maxdepth 1 -type d -exec basename {} ';') "Quit")
203       select app_dirname in "${options[@]}"; do
204         case $app_dirname in
205           "Quit") echo ; echo "Quitting from the script."; return 1; ;;
206           *) echo ; echo "You picked folder '$app_dirname'" ; break; ;;
207         esac
208       done
209       if [ -z "$app_dirname" ]; then
210         echo "The folder was not provided.  This script needs a directory name"
211         echo "within which to initialize the site."
212         return 1
213       fi
214       PS3="$holdps3"
215     fi
216   fi
217   test_app_folder "$appsdir" "$app_dirname"
218   exit_on_error "Testing application folder: $app_dirname"
219
220   echo "Application folder is: $app_dirname"
221   return 0
222 }
223
224 # ensures that the app directory name is valid and then loads the config
225 # for the app (either via a specific file or using the defaults).
226 function test_app_folder()
227 {
228   local appsdir="$1"; shift
229   local dir="$1"; shift
230
231   local combo="$appsdir/$dir"
232
233   if [ ! -d "$combo" ]; then
234     echo "$(date_stringer): Creating app directory: $combo" >> "$SSM_LOG_FILE"
235     mkdir "$combo"
236     exit_on_error "Making application directory when not already present"
237   fi
238
239 echo yo combo is $combo
240
241   if [ -d "$combo/$CHECKOUT_DIRNAME" ]; then
242     echo "Dropping expectation for intermediary checkout directory name."
243     unset CHECKOUT_DIRNAME
244   fi
245
246   locate_config_file "$dir"
247 }
248
249 # eases some permissions to enable apache to write log files and do other shopkeeping.
250 function fix_site_perms()
251 {
252   local app_dir="$1"; shift
253
254   local site_dir="$app_dir/$CHECKOUT_DIR_NAME"
255
256   if [ -f "$site_dir/bin/cake" ]; then
257     sudo chmod -R a+rx "$site_dir/bin/cake"
258     exit_on_error "Enabling execute bit on cake binary"
259   fi
260
261   if [ -d "$site_dir/logs" ]; then
262     sudo chmod -R g+w "$site_dir/logs"
263     exit_on_error "Enabling group write on site's Logs directory"
264   fi
265
266   if [ -d "$site_dir/tmp" ]; then
267     sudo chmod -R g+w "$site_dir/tmp"
268     exit_on_error "Enabling group write on site's tmp directory"
269   fi
270 }
271
272 # tosses out any cached object data that originated from the database.
273 function clear_orm_cache()
274 {
275   local site_dir="$1"; shift
276
277   if [ -f "$site_dir/bin/cake" ]; then
278     # flush any cached objects from db.
279     "$site_dir/bin/cake" orm_cache clear
280     exit_on_error "Clearing ORM cache"
281   fi
282 }
283
284 # updates the revision control repository passed in.  this expects that the
285 # repo will live in a folder called "checkout_dirname" under the app path,
286 # which is the standard for deployed site avenger sites.  if that directory is
287 # missing, then we assume a checkout of the top-level repository instead.
288 # important: this also sets a global variable called site_store_path to the full
289 # path of the application.
290 function update_repo()
291 {
292   local full_app_dir="$1"; shift
293   local checkout_dirname="$1"; shift
294   local repo_root="$1"; shift
295   local repo_name="$1"; shift
296
297 echo "$(date_stringer): here are parms in update repo:" >> "$SSM_LOG_FILE"
298 echo "$(date_stringer): $(var full_app_dir checkout_dirname repo_root repo_name)" >> "$SSM_LOG_FILE"
299
300   # forget any prior value, since we are going to validate the path.
301   unset site_store_path
302
303   pushd "$full_app_dir" &>/dev/null
304   exit_on_error "Switching to our app dir '$full_app_dir'"
305
306   local complete_path="$full_app_dir"
307 #hmmm: below code problematic for when we want a new git clone to show up!
308   if [ ! -z "$checkout_dirname" -a -d "$full_app_dir/$checkout_dirname" ]; then
309     # make the full path using the non-empty checkout dir name.
310     complete_path+="/$checkout_dirname"
311   else
312     # using the additional path component failed, so we reset that to see if
313     # we can still proceed normally.
314     unset checkout_dirname
315   fi
316
317 echo set complete_path: $complete_path
318
319   # see if the checkout directory exits.  the repo_found variable is set to
320   # non-empty if we find it and it's a valid git repo.
321   repo_found=
322   if [ -d "$complete_path" ]; then
323     # checkout directory exists, so let's check it.
324     pushd "$complete_path" &>/dev/null
325     exit_on_error "Switching to directory for check out: $complete_path"
326
327     # ask for repository name (without .git).
328     if git rev-parse --git-dir > /dev/null 2>&1; then
329       # this is a valid git repo.
330       repo_found=yes
331     fi
332  
333     # we don't consider the state of having the dir exist but the repo be wrong as good.
334     if [ -z "$repo_found" ]; then
335       echo "There is a problem; this folder is not a valid repository:"
336       echo "  $complete_path"
337       echo "This script cannot continue unless the git repository is valid."
338       exit 1
339     fi
340     popd &>/dev/null
341   fi
342
343   if [ ! -z "$repo_found" ]; then
344     # a repository was found, so update the version here and leave.
345     pushd "$complete_path" &>/dev/null
346     exit_on_error "Switching to directory for repo update: $complete_path"
347     echo "Repository $repo_name exists.  Updating it."
348     git pull --tags --all
349     exit_on_error "Recursive checkout on: $complete_path"
350     popd &>/dev/null
351   else
352     # clone the repo since it wasn't found.
353     pushd "$complete_path/.." &>/dev/null
354     exit_on_error "Switching to parent directory prior to new clone: $complete_path/.."
355     echo "Cloning repository $repo_name now."
356     git clone "$repo_root/$repo_name.git" $checkout_dirname
357     exit_on_error "Git clone of repository: $repo_name"
358     popd &>/dev/null
359   fi
360
361 #unused?
362   # construct the full path to where the app will actually live.
363   site_store_path="$complete_path"
364
365   popd &>/dev/null
366 }
367
368 # this function goes to the directory specified and makes it right with
369 # composer install.  this is as opposed to composer update, which could
370 # change the state. 
371 function composer_repuff()
372 {
373   local site_store_path="$1"; shift
374
375   pushd "$site_store_path" &>/dev/null
376   exit_on_error "Switching to our app dir '$site_store_path'"
377
378   echo "Updating site with composer..."
379
380   composer -n install
381   exit_on_error "Composer installation step on '$site_store_path'."
382   echo "Site updated."
383
384 #hmmm: argh global
385   dir="$site_store_path/$CHECKOUT_DIR_NAME/vendor/siteavenger/avcore"
386   if [ -d "$dir" ]; then
387     echo "Running avcore database migrations..."
388     logfile="$TMP/problem-avcore_db_migration-$(date_stringer).log"
389     ./bin/cake migrations migrate -p Avcore &>"$logfile"
390     if [ $? -ne 0 ]; then
391       echo "** FAILED: Database migrations for avcore.  Check log file in: $logfile"
392       # we keep going, because some sites aren't ready for this yet.
393     else
394       \rm "$logfile"
395       echo "Database for avcore migrated."
396     fi
397   fi
398
399   clear_orm_cache
400
401   popd &>/dev/null
402 }
403
404 # this function creates the links needed to make the site function properly given our
405 # various dependencies and infrastructure.
406 function create_site_links()
407 {
408   local site_store_path="$1"; shift
409   local theme_name="$1"; shift
410
411   echo "Creating symbolic links for site assets..."
412
413   # jump into the site path so we can start making relative links.
414   pushd "$site_store_path" &>/dev/null
415   exit_on_error "Switching to our app dir '$site_store_path'"
416
417   pushd webroot &>/dev/null
418
419   # remove all symlinks that might plague us.
420   find . -maxdepth 1 -type l -exec rm -f {} ';'
421   exit_on_error "Cleaning out links in webroot"
422
423   # link in the avcore plugin.
424   make_safe_link "../vendor/siteavenger/avcore/webroot" avcore
425
426   # make the link for our theme as a lower-case version of the theme.
427   themelower=${theme_name,,}
428   make_safe_link "../plugins/$theme_name/webroot" "$themelower"
429
430   # link in any favicon files.
431   if [ -d "../plugins/$theme_name/favicon" ]; then
432     local fave
433     for fave in "../plugins/$theme_name/favicon"/*; do
434       make_safe_link "$fave" .
435     done
436   fi
437
438   # get back out of webroot.
439   popd &>/dev/null
440
441   # hop up a level above where we had been.
442   pushd .. &>/dev/null
443
444   # link 'public' to webroot.
445   if [ -L public ]; then
446     # public is a symlink.
447     \rm public
448     exit_on_error "Removing public directory symlink"
449   elif [ -d public ]; then
450     # public is a folder with default files.
451 #hmmm: is that safe?
452     \rm -rf public
453     exit_on_error "Removing public directory and contents"
454   fi
455
456   # create the main 'public' symlink
457 #hmmm: argh global
458   make_safe_link $CHECKOUT_DIR_NAME/webroot public
459   exit_on_error "Creating link to webroot called 'public'"
460
461 #hmmm: public/$themelower/im will be created automatically by system user with appropriate permissions
462
463   echo Created symbolic links.
464
465   popd &>/dev/null
466   popd &>/dev/null
467 }
468
469 # fetches composer to make sure it's up to date.
470 # (if powerup runs, composer install doesn't update git origin.)
471 function update_composer_repository()
472 {
473   local site_store_path="$1"; shift
474
475   pushd "$site_store_path" &>/dev/null
476
477   if git config remote.composer.url &>/dev/null; then
478     git pull composer
479     echo "Updated the composer repository."
480   else
481     echo "No composer repository was found for updating."
482   fi
483 }
484
485 # fixes the ownership for a site avenger or php application.
486 # this almost certainly will require sudo capability, if there are any ownership problems
487 # that need to be resolved.
488 function fix_appdir_ownership()
489 {
490   local appsdir="$1"; shift
491   local dir="$1"; shift
492
493   local combo="$appsdir/$dir"
494
495   # go with the default user running the script.
496   user_name="$USER"
497   if [ ! -z "$user_name" -a "$user_name" != "root" ]; then
498     echo "$(date_stringer): Chowning the app folder to be owned by: $user_name" >> "$SSM_LOG_FILE"
499 #hmmm: have to hope for now for standard group named after user 
500     sudo chown -R "$user_name:$user_name" "$combo"
501     exit_on_error "Chowning $combo to be owned by $user_name"
502   else
503     echo "$(date_stringer): user name failed checks for chowning, was found as '$user_name'" >> "$SSM_LOG_FILE"
504   fi
505
506   # 
507 #probably not enough for path!
508   fix_site_perms "$combo"
509 }
510
511 # Jumps to an application directory given the app name.  If no app name is
512 # given, it will show a menu to pick the app.
513 function switch_to()
514 {
515   # check for parameters.
516   app_dirname="$1"; shift
517
518   check_apps_root "$app_dirname"
519
520   # find proper webroot where the site will be initialized.
521   if [ -z "$app_dirname" ]; then
522     # no dir was passed, so guess it.
523     export NO_AUTOMATIC_FOLDER_GUESS=true
524     find_app_folder "$BASE_APPLICATION_PATH"
525   else
526     test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
527   fi
528   if [ $? -ne 0 ]; then
529     if [ "$app_dirname" != "Quit" ]; then
530       echo "Could not locate the application directory: ${app_dirname}"
531     fi
532     return 1
533   fi
534
535   # where we expect to find our checkout folder underneath.
536   full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
537
538   pushd $full_app_dir/$CHECKOUT_DIR_NAME
539 #redundant if pushd  pwd
540 }
541