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