7921143e351f7c4201e9595e8a3b74df05a054e0
[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 an avenger5 directory above our current PWD, then that
188       # might tell us our name.
189       if  find_named_parent_dir "avenger5"; then
190         # we can grab a name above the avenger5 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   locate_config_file "$dir"
240 }
241
242 # eases some permissions to enable apache to write log files and do other shopkeeping.
243 function fix_site_perms()
244 {
245   local app_dir="$1"; shift
246
247   local site_dir="$app_dir/$CHECKOUT_DIR_NAME"
248
249   if [ -f "$site_dir/bin/cake" ]; then
250     sudo chmod -R a+rx "$site_dir/bin/cake"
251     exit_on_error "Enabling execute bit on cake binary"
252   fi
253
254   if [ -d "$site_dir/logs" ]; then
255     sudo chmod -R g+w "$site_dir/logs"
256     exit_on_error "Enabling group write on site's Logs directory"
257   fi
258
259   if [ -d "$site_dir/tmp" ]; then
260     sudo chmod -R g+w "$site_dir/tmp"
261     exit_on_error "Enabling group write on site's tmp directory"
262   fi
263 }
264
265 # tosses out any cached object data that originated from the database.
266 function clear_orm_cache()
267 {
268   local site_dir="$1"; shift
269
270   if [ -f "$site_dir/bin/cake" ]; then
271     # flush any cached objects from db.
272     "$site_dir/bin/cake" orm_cache clear
273     exit_on_error "Clearing ORM cache"
274   fi
275 }
276
277 # updates the revision control repository passed in.  this expects that the
278 # repo will live in a folder called "checkout_dirname" under the app path,
279 # which is the standard for our deployed sites.
280 # important: this also sets a global variable called site_store_path to the full
281 # path of the application.
282 function update_repo()
283 {
284   local full_app_dir="$1"; shift
285   local checkout_dirname="$1"; shift
286   local repo_root="$1"; shift
287   local repo_name="$1"; shift
288
289 echo "$(date_stringer): here are parms in update repo:" >> "$SSM_LOG_FILE"
290 echo "$(date_stringer): $(var full_app_dir checkout_dirname repo_root repo_name)" >> "$SSM_LOG_FILE"
291
292   # forget any prior value, since we are going to validate the path.
293   unset site_store_path
294
295   pushd "$full_app_dir" &>/dev/null
296   exit_on_error "Switching to our app dir '$full_app_dir'"
297
298   local complete_path="$full_app_dir/$checkout_dirname"
299
300   # see if the checkout directory exits.  the repo_found variable is set to
301   # non-empty if we find it and it's a valid git repo.
302   repo_found=
303   if [ -d "$checkout_dirname" ]; then
304     # checkout directory exists, so let's check it.
305     pushd "$checkout_dirname" &>/dev/null
306     exit_on_error "Switching to our checkout directory: $checkout_dirname"
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       repo_found=yes
312     fi
313  
314     # we don't consider the state of having the dir exist but the repo be wrong as good.
315     if [ -z "$repo_found" ]; then
316       echo "There is a problem; this folder is not a valid repository:"
317       echo "  $full_app_dir"
318       echo "This script cannot continue unless the git repository is valid."
319       exit 1
320     fi
321     popd &>/dev/null
322   fi
323
324   if [ ! -z "$repo_found" ]; then
325     # a repository was found, so update the version here and leave.
326     echo "Repository $repo_name exists.  Updating it."
327     rgetem
328     exit_on_error "Recursive checkout on: $complete_path"
329   else
330     # clone the repo since it wasn't found.
331     echo "Cloning repository $repo_name now."
332     git clone "$repo_root/$repo_name.git" $checkout_dirname
333     exit_on_error "Git clone of repository: $repo_name"
334   fi
335
336 #not doing this here since powerup uses this and has no sudo.
337   #fix_site_perms "$complete_path"
338
339 #unused?
340   # construct the full path to where the app will actually live.
341   site_store_path="$complete_path"
342
343   popd &>/dev/null
344 }
345
346 # this function goes to the directory specified and makes it right with
347 # composer install.  this is as opposed to composer update, which could
348 # change the state. 
349 function composer_repuff()
350 {
351   local site_store_path="$1"; shift
352
353   pushd "$site_store_path" &>/dev/null
354   exit_on_error "Switching to our app dir '$site_store_path'"
355
356   echo "Updating site with composer..."
357
358   composer -n install
359   exit_on_error "Composer installation step on '$site_store_path'."
360   echo "Site updated."
361
362 #hmmm: argh global
363   dir="$site_store_path/$CHECKOUT_DIR_NAME/vendor/siteavenger/avcore"
364   if [ -d "$dir" ]; then
365     echo "Running avcore database migrations..."
366     logfile="$TMP/problem-avcore_db_migration-$(date_stringer).log"
367     ./bin/cake migrations migrate -p Avcore &>"$logfile"
368     if [ $? -ne 0 ]; then
369       echo "** FAILED: Database migrations for avcore.  Check log file in: $logfile"
370       # we keep going, because some sites aren't ready for this yet.
371     else
372       \rm "$logfile"
373       echo "Database for avcore migrated."
374     fi
375   fi
376
377   clear_orm_cache
378
379   popd &>/dev/null
380 }
381
382 # this function creates the links needed to make the site function properly given our
383 # various dependencies and infrastructure.
384 function create_site_links()
385 {
386   local site_store_path="$1"; shift
387   local theme_name="$1"; shift
388
389   echo "Creating symbolic links for site assets..."
390
391   # jump into the site path so we can start making relative links.
392   pushd "$site_store_path" &>/dev/null
393   exit_on_error "Switching to our app dir '$site_store_path'"
394
395   pushd webroot &>/dev/null
396
397   # remove all symlinks that might plague us.
398   find . -maxdepth 1 -type l -exec rm -f {} ';'
399   exit_on_error "Cleaning out links in webroot"
400
401   # link in the avcore plugin.
402   make_safe_link "../vendor/siteavenger/avcore/webroot" avcore
403
404   # make the link for our theme as a lower-case version of the theme.
405   themelower=${theme_name,,}
406   make_safe_link "../plugins/$theme_name/webroot" "$themelower"
407
408   # link in any favicon files.
409   if [ -d "../plugins/$theme_name/favicon" ]; then
410     local fave
411     for fave in "../plugins/$theme_name/favicon"/*; do
412       make_safe_link "$fave" .
413     done
414   fi
415
416   # get back out of webroot.
417   popd &>/dev/null
418
419   # hop up a level above where we had been.
420   pushd .. &>/dev/null
421
422   # link 'public' to webroot.
423   if [ -L public ]; then
424     # public is a symlink.
425     \rm public
426     exit_on_error "Removing public directory symlink"
427   elif [ -d public ]; then
428     # public is a folder with default files.
429 #hmmm: is that safe?
430     \rm -rf public
431     exit_on_error "Removing public directory and contents"
432   fi
433
434   # create the main 'public' symlink
435 #hmmm: argh global
436   make_safe_link $CHECKOUT_DIR_NAME/webroot public
437   exit_on_error "Creating link to webroot called 'public'"
438
439 #hmmm: public/$themelower/im will be created automatically by system user with appropriate permissions
440
441   echo Created symbolic links.
442
443   popd &>/dev/null
444   popd &>/dev/null
445 }
446
447 # fetches composer to make sure it's up to date.
448 # (if powerup runs, composer install doesn't update git origin.)
449 function update_composer_repository()
450 {
451   local site_store_path="$1"; shift
452
453   pushd "$site_store_path" &>/dev/null
454
455   if git config remote.composer.url &>/dev/null; then
456     git pull composer
457     echo "Updated the composer repository."
458   else
459     echo "No composer repository was found for updating."
460   fi
461 }
462
463 # fixes the ownership for a site avenger or php application.
464 # this almost certainly will require sudo capability, if there are any ownership problems
465 # that need to be resolved.
466 function fix_appdir_ownership()
467 {
468   local appsdir="$1"; shift
469   local dir="$1"; shift
470
471   local combo="$appsdir/$dir"
472
473   # go with the default user running the script.
474   user_name="$USER"
475   if [ ! -z "$user_name" -a "$user_name" != "root" ]; then
476     echo "$(date_stringer): Chowning the app folder to be owned by: $user_name" >> "$SSM_LOG_FILE"
477 #hmmm: have to hope for now for standard group named after user 
478     sudo chown -R "$user_name:$user_name" "$combo"
479     exit_on_error "Chowning $combo to be owned by $user_name"
480   else
481     echo "$(date_stringer): user name failed checks for chowning, was found as '$user_name'" >> "$SSM_LOG_FILE"
482   fi
483
484   # 
485 #probably not enough for path!
486   fix_site_perms "$combo"
487 }
488
489 # Jumps to an application directory given the app name.  If no app name is
490 # given, it will show a menu to pick the app.
491 function switch_to()
492 {
493   # check for parameters.
494   app_dirname="$1"; shift
495
496   check_apps_root "$app_dirname"
497
498   # find proper webroot where the site will be initialized.
499   if [ -z "$app_dirname" ]; then
500     # no dir was passed, so guess it.
501     export NO_AUTOMATIC_FOLDER_GUESS=true
502     find_app_folder "$BASE_APPLICATION_PATH"
503   else
504     test_app_folder "$BASE_APPLICATION_PATH" "$app_dirname"
505   fi
506   if [ $? -ne 0 ]; then
507     if [ "$app_dirname" != "Quit" ]; then
508       echo "Could not locate the application directory: ${app_dirname}"
509     fi
510     return 1
511   fi
512
513   # where we expect to find our checkout folder underneath.
514   full_app_dir="$BASE_APPLICATION_PATH/$app_dirname"
515
516   pushd $full_app_dir/$CHECKOUT_DIR_NAME
517 #redundant if pushd  pwd
518 }
519