closer to working for both cases
[feisty_meow.git] / scripts / site_avenger / shared_site_mgr.sh
1 #!/bin/bash
2
3 # Author: Chris Koeritz
4 # Author: Kevin Wentworth
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   if [ "$dir" == " " ]; then
233     # trickery here means we don't expect an intermediate directory component.
234     combo="$appsdir"
235   fi
236
237   if [ ! -d "$combo" ]; then
238     # the directory wasn't there yet, so we will auto-create it.  this should
239     # hopefully be the right decision usually.
240     echo "$(date_stringer): Creating app directory: $combo" >> "$SSM_LOG_FILE"
241     mkdir "$combo"
242     exit_on_error "Making application directory when not already present"
243   else
244     # the directory does exist.  let's test out a theory that it might not be
245     # an official site avenger style folder, in which case we need to patch a
246     # variable to set expectations.
247     if [ ! -d "$combo/$CHECKOUT_DIR_NAME" ]; then
248       echo "Dropping expectation for intermediate checkout directory name."
249       CHECKOUT_DIR_NAME=" "
250     fi
251   fi
252
253 echo yo modulopius on the variables:
254 var combo CHECKOUT_DIR_NAME
255
256   locate_config_file "$dir"
257 }
258
259 # eases some permissions to enable apache to write log files and do other shopkeeping.
260 function fix_site_perms()
261 {
262   local site_dir="$1"; shift
263
264   if [ -f "$site_dir/bin/cake" ]; then
265     sudo chmod -R a+rx "$site_dir/bin/cake"
266     exit_on_error "Enabling execute bit on cake binary"
267   fi
268
269   if [ -d "$site_dir/logs" ]; then
270     sudo chmod -R g+w "$site_dir/logs"
271     exit_on_error "Enabling group write on site's Logs directory"
272   fi
273
274   if [ -d "$site_dir/tmp" ]; then
275     sudo chmod -R g+w "$site_dir/tmp"
276     exit_on_error "Enabling group write on site's tmp directory"
277   fi
278 }
279
280 # tosses out any cached object data that originated from the database.
281 function clear_orm_cache()
282 {
283   local site_dir="$1"; shift
284
285   if [ -f "$site_dir/bin/cake" ]; then
286     # flush any cached objects from db.
287     "$site_dir/bin/cake" orm_cache clear
288     exit_on_error "Clearing ORM cache"
289   fi
290 }
291
292 # checks that the directory provided is a valid git repository.
293 function is_valid_git_repo()
294 {
295   local complete_path="$1"; shift
296   
297   # see if the directory even exists.
298   if [ ! -d "$complete_path" ]; then
299     # nope, that's not a git repo since it's not even there.
300     false
301     return
302   fi
303
304   # directory exists, so let's test it out.
305   pushd "$complete_path" &>/dev/null
306   exit_on_error "Switching to directory for check out: $complete_path"
307
308   # ask for repository name (without .git).
309   if git rev-parse --git-dir > /dev/null 2>&1; then
310     # this is a valid git repo.
311     true
312     return
313   fi
314  
315   # no, this is not a valid git repository.
316   popd &>/dev/null
317   false
318 }
319
320 # updates the revision control repository passed in.  this expects that the
321 # repo will live in a folder called "checkout_dirname" under the app path,
322 # which is the standard for deployed site avenger sites.  if that directory is
323 # missing, then we assume a checkout of the top-level repository instead.
324 # important: this also sets a global variable called site_store_path to the full
325 # path of the application.
326 function update_repo()
327 {
328   local full_app_dir="$1"; shift
329   local checkout_dirname="$1"; shift
330   local repo_root="$1"; shift
331   local repo_name="$1"; shift
332
333 echo "$(date_stringer): here are parms in update repo:" >> "$SSM_LOG_FILE"
334 echo "$(date_stringer): $(var full_app_dir checkout_dirname repo_root repo_name)" >> "$SSM_LOG_FILE"
335
336   # forget any prior value, since we are going to validate the path.
337   unset site_store_path
338
339 #  pushd "$full_app_dir" &>/dev/null
340 #  exit_on_error "Switching to our app dir '$full_app_dir'"
341
342   local complete_path="$full_app_dir"
343   if [ ! "$checkout_dirname" == " " ]; then
344     # make the full path using the non-empty checkout dir name.
345     complete_path+="/$checkout_dirname"
346   fi
347
348 echo set complete_path: $complete_path
349   # store the local version into our special global.
350   site_store_path="$complete_path"
351
352   # check out the directory to see if it's a git repository.
353   if ! is_valid_git_repo "$complete_path"; then
354     if [ -d "$complete_path" ]; then
355       # we don't consider the state of having the dir exist but the repo be wrong as good.
356       echo "There is a problem; this folder is not a valid repository:"
357       echo "  $complete_path"
358       echo "This script cannot continue unless the git repository is valid."
359       exit 1
360     fi
361     # okay, so the directory doesn't even exist.  that means we will try to
362     # clone the project anew.
363     mkdir "$complete_path"
364     exit_on_error "Making project directory prior to new clone: $complete_path"
365     pushd "$complete_path/.." &>/dev/null
366     exit_on_error "Switching to parent directory prior to new clone: $complete_path/.."
367     echo "Cloning repository $repo_name now."
368     git clone "$repo_root/$repo_name.git" $checkout_dirname
369     exit_on_error "Git clone of repository: $repo_name"
370     popd &>/dev/null
371   fi
372
373   # a repository was found, so update the version here and leave.
374   pushd "$complete_path" &>/dev/null
375   exit_on_error "Switching to directory for repo update: $complete_path"
376   echo "Repository $repo_name exists.  Updating it."
377   git pull --tags --all
378   exit_on_error "Recursive checkout on: $complete_path"
379   popd &>/dev/null
380 }
381
382 # this function goes to the directory specified and makes it right with
383 # composer install.  this is as opposed to composer update, which could
384 # change the state. 
385 function composer_repuff()
386 {
387   local site_store_path="$1"; shift
388
389   pushd "$site_store_path" &>/dev/null
390   exit_on_error "Switching to our app dir '$site_store_path'"
391
392   echo "Updating site with composer..."
393
394   composer -n install
395   exit_on_error "Composer installation step on '$site_store_path'."
396   echo "Site updated."
397
398 #hmmm: untested, had wrong path here and was never being run.
399   dir="vendor/siteavenger/avcore"
400   if [ -d "$dir" ]; then
401     echo "Running avcore database migrations..."
402     logfile="$TMP/problem-avcore_db_migration-$(date_stringer).log"
403     ./bin/cake migrations migrate -p Avcore &>"$logfile"
404     if [ $? -ne 0 ]; then
405       echo "** FAILED: Database migrations for avcore.  Check log file in: $logfile"
406       # we keep going, because some sites aren't ready for this yet.
407     else
408       \rm "$logfile"
409       echo "Database for avcore migrated."
410     fi
411   fi
412
413   clear_orm_cache
414
415   popd &>/dev/null
416 }
417
418 # this function creates the links needed to make the site function properly given our
419 # various dependencies and infrastructure.
420 function create_site_links()
421 {
422   local site_store_path="$1"; shift
423   local theme_name="$1"; shift
424
425   echo "Creating symbolic links for site assets..."
426
427   # jump into the site path so we can start making relative links.
428   pushd "$site_store_path" &>/dev/null
429   exit_on_error "Switching to our app dir '$site_store_path'"
430
431   pushd webroot &>/dev/null
432   exit_on_error "Switching to our webroot dir"
433
434   # remove all symlinks that might plague us.
435   find . -maxdepth 1 -type l -exec rm -f {} ';'
436   exit_on_error "Cleaning out links in webroot"
437
438   # link in the avcore plugin.
439   make_safe_link "../vendor/siteavenger/avcore/webroot" avcore
440
441   # make the link for our theme as a lower-case version of the theme.
442   themelower=${theme_name,,}
443   make_safe_link "../plugins/$theme_name/webroot" "$themelower"
444
445   # link in any favicon files.
446   if [ -d "../plugins/$theme_name/favicon" ]; then
447     local fave
448     for fave in "../plugins/$theme_name/favicon"/*; do
449       make_safe_link "$fave" .
450     done
451   fi
452
453   # get back out of webroot.
454   popd &>/dev/null
455
456   # hop up a level above where we had been.  this is a level above the
457   # site store path, which will not be appropriate for all projects, so
458   # we must tread carefully.
459   pushd .. &>/dev/null
460
461   # we only do the following linking exercises when we are sure this is a
462   # site avenger style application.  otherwise we would be creating links
463   # above our own heads, sort of.
464   if [ -d "$CHECKOUT_DIR_NAME" ]; then
465     # link 'public' to webroot.
466     if [ -L public ]; then
467       # public is a symlink.
468       \rm public
469       exit_on_error "Removing public directory symlink"
470     elif [ -d public ]; then
471       # public is a folder with default files.
472 #hmmm: is that safe?
473       \rm -rf public
474       exit_on_error "Removing public directory and contents"
475     fi
476
477     # create the main 'public' symlink
478 #hmmm: argh global
479     make_safe_link $CHECKOUT_DIR_NAME/webroot public
480     exit_on_error "Creating link to webroot called 'public'"
481 #hmmm: public/$themelower/im will be created automatically by system user with appropriate permissions
482
483   else
484     echo "Skipping 'public' link for project without '$CHECKOUT_DIR_NAME' folder."
485   fi
486
487   popd &>/dev/null
488   popd &>/dev/null
489
490   echo Created symbolic links.
491 }
492
493 # fetches composer to make sure it's up to date.
494 # (if powerup runs, composer install doesn't update git origin.)
495 function update_composer_repository()
496 {
497   local site_store_path="$1"; shift
498
499   pushd "$site_store_path" &>/dev/null
500
501   if git config remote.composer.url &>/dev/null; then
502     git pull composer
503     echo "Updated the composer repository."
504   else
505     echo "No composer repository was found for updating."
506   fi
507 }
508
509 # fixes the ownership for a site avenger or php application.
510 # this almost certainly will require sudo capability, if there are any ownership problems
511 # that need to be resolved.
512 function fix_appdir_ownership()
513 {
514   local appsdir="$1"; shift
515   local dir="$1"; shift
516
517   local combo="$appsdir/$dir"
518
519   # go with the default user running the script.
520   user_name="$USER"
521   if [ ! -z "$user_name" -a "$user_name" != "root" ]; then
522     echo "$(date_stringer): Chowning the app folder to be owned by: $user_name" >> "$SSM_LOG_FILE"
523 #hmmm: have to hope for now for standard group named after user 
524     sudo chown -R "$user_name:$user_name" "$combo"
525     exit_on_error "Chowning $combo to be owned by $user_name"
526   else
527     echo "$(date_stringer): user name failed checks for chowning, was found as '$user_name'" >> "$SSM_LOG_FILE"
528   fi
529
530 #hmmm: is this variable set by this point?  it's the right thing to pass down there anyway.
531   fix_site_perms "$site_store_path"
532 }
533
534 # Jumps to an application directory given the app name.  If no app name is
535 # given, it will show a menu to pick the app.
536 function switch_to()
537 {
538   # check for parameters.
539   app_dirname="$1"; shift
540
541   check_apps_root "$app_dirname"
542
543   # find proper webroot where the site will be initialized.
544   if [ -z "$app_dirname" ]; then
545     # no dir was passed, so guess it.
546     export NO_AUTOMATIC_FOLDER_GUESS=true
547     find_app_folder "$BASE_APPLICATION_PATH"
548   else
549     test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
550   fi
551   if [ $? -ne 0 ]; then
552     if [ "$app_dirname" != "Quit" ]; then
553       echo "Could not locate the application directory: ${app_dirname}"
554     fi
555     return 1
556   fi
557
558   # where we expect to find our checkout folder underneath.
559   full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
560
561   pushd $full_app_dir/$CHECKOUT_DIR_NAME
562 #redundant if pushd  pwd
563 }
564