aboutsummaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2013-01-26 23:58:22 +0100
committerTrygve Laugstøl <trygvis@inamo.no>2013-01-26 23:58:22 +0100
commit110ffae47db27a49bbc43f86ba3737bccc1b3085 (patch)
treed74934b12e2904b8aff5fe87421a6501b8ea5c8e /bin
parentca27d6f6d9ccc35bf55db3d360d1d464d5e206e7 (diff)
downloadapp.sh-110ffae47db27a49bbc43f86ba3737bccc1b3085.tar.gz
app.sh-110ffae47db27a49bbc43f86ba3737bccc1b3085.tar.bz2
app.sh-110ffae47db27a49bbc43f86ba3737bccc1b3085.tar.xz
app.sh-110ffae47db27a49bbc43f86ba3737bccc1b3085.zip
o Rewriting most of this stuff to make it feel more like git.
Diffstat (limited to 'bin')
-rwxr-xr-xbin/app-conf135
-rwxr-xr-xbin/app-init79
-rwxr-xr-xbin/app-instance502
-rwxr-xr-xbin/app-operate73
-rwxr-xr-xbin/pid-method113
5 files changed, 902 insertions, 0 deletions
diff --git a/bin/app-conf b/bin/app-conf
new file mode 100755
index 0000000..7193c20
--- /dev/null
+++ b/bin/app-conf
@@ -0,0 +1,135 @@
+#!/bin/bash
+
+# TODO: Add a 'get' command that returns a single value
+# Exit with 0 if found, 1 otherwise.
+
+if [[ $APPSH_HOME == "" ]]
+then
+ APPSH_HOME=`dirname "$0"`
+ APPSH_HOME=`cd "$APPSH_HOME/.." && pwd`
+fi
+
+. $APPSH_HOME/lib/common
+
+PATH=$APPSH_HOME/libexec:$PATH
+
+key_expr="[a-zA-Z][_a-zA-Z0-9]*"
+
+format_conf() {
+ local IFS==
+ while read key value
+ do
+ printf "%-20s %-20s" "$key" "$value"
+ echo
+ done
+}
+
+assert_valid_config_name() {
+ local name=$1
+
+ local x=`echo $name | sed -n "/^$key_expr\\.$key_expr$/p"`
+ if [ -z "$x" ]
+ then
+ echo "Invalid name: $name" >&2
+ exit 1
+ fi
+}
+
+conf_set() {
+ local name=$1; shift
+ local value=$1; shift
+
+ assert_valid_config_name "$name"
+
+ if [ -r $file ]
+ then
+ sed "/^$name[ ]*=.*/d" $file > $file.tmp
+ fi
+
+ echo "$name=$value" >> $file.tmp
+ mv $file.tmp $file
+}
+
+conf_delete() {
+ local name=$1; shift
+
+ assert_valid_config_name "$name"
+
+ sed "/^$name[ ]*=.*/d" $file > $file.tmp
+ mv $file.tmp $file
+}
+
+usage() {
+ if [ -n "$1" ]
+ then
+ echo "Error: $@" >&2
+ fi
+
+ echo "usage: $0 conf <command>" >&2
+ echo ""
+ echo "Available commands:" >&2
+ echo " list - list all config values" >&2
+ echo " set [name] [value] - set a config parameter" >&2
+ echo " delete [name] - deletes a config parameter" >&2
+ exit 1
+}
+
+if [ $# -gt 0 ]
+then
+ command=$1
+ shift
+else
+ command=list
+fi
+
+file=".app/config"
+
+assert_is_app -C
+
+case "$command" in
+ get)
+ if [ $# != 1 ]
+ then
+ usage
+ exit 1
+ fi
+
+ app-cat-conf -f "$file" -n "$1" | cut -f 2 -d = | format_conf | sed "s, *$,,"
+ ;;
+ list)
+ if [ $# -gt 0 ]
+ then
+ usage "Extra options."
+ exit 1
+ fi
+
+ app-cat-conf -f "$file" | format_conf
+ ;;
+ set)
+ if [ $# -ne 2 ]
+ then
+ usage
+ exit 1
+ fi
+
+ conf_set "$1" "$2"
+ ;;
+ delete)
+ if [ $# -ne 1 ]
+ then
+ usage "Missing [name] argument."
+ exit 1
+ fi
+
+ conf_delete "$1" "$2"
+ ;;
+ *)
+ if [ -z "$command" ]
+ then
+ usage
+ else
+ usage "Unknown command: $command"
+ fi
+ exit 1
+ ;;
+esac
diff --git a/bin/app-init b/bin/app-init
new file mode 100755
index 0000000..e758916
--- /dev/null
+++ b/bin/app-init
@@ -0,0 +1,79 @@
+#!/bin/bash -e
+
+set -u
+
+if [[ $APPSH_HOME == "" ]]
+then
+ APPSH_HOME=`dirname "$0"`
+ APPSH_HOME=`cd "$APPSH_HOME/.." && pwd`
+fi
+
+. $APPSH_HOME/lib/common
+
+usage() {
+ echo "usage: $0 -d dir <resolver> <resolver args>"
+ exit 1
+}
+
+fatal() {
+ echo "$0: $@"
+ exit 1
+}
+
+while getopts "d:" opt
+do
+ case $opt in
+ d)
+ dir=$OPTARG
+ shift 2
+ OPTIND=1
+ ;;
+ esac
+done
+
+if [ $# -lt 1 ]
+then
+ usage
+fi
+
+resolver_name="$1"; shift
+
+if [ -z "$dir" ]
+then
+ usage
+fi
+
+if [ -e "$dir" ]
+then
+ fatal "Already initialized: $dir" 2>&1
+fi
+
+# TODO: install a trap handler and rm -rf "$dir"
+
+resolver=`grep_path "/app-resolver-$resolver_name$" "$PATH:$APPSH_HOME/libexec" | head -n 1`
+
+if [ -z "$resolver" ]
+then
+ echo "No such resolver: $resolver_name" 2>&1
+ exit 1
+fi
+
+mkdir -- "$dir" "$dir/.app"
+cd "$dir"
+
+app-conf set app.resolver "$resolver_name"
+
+"$resolver" init "$@"
+"$resolver" resolve-version
+
+version=`app-conf get app.version`
+
+if [[ $version == "" ]]
+then
+ echo "Unable to resolve version" 2>&1
+ exit
+fi
+
+echo "Resolved version to $version"
+
+"$resolver" download-version -v "$version" -f .app/latest.zip
diff --git a/bin/app-instance b/bin/app-instance
new file mode 100755
index 0000000..02e3c0f
--- /dev/null
+++ b/bin/app-instance
@@ -0,0 +1,502 @@
+#!/bin/bash
+
+if [[ $APPSH_HOME == "" ]]
+then
+ APPSH_HOME=`dirname "$0"`
+ APPSH_HOME=`cd "$APPSH_HOME/.." && pwd`
+fi
+
+if [ -n "$APPSH_REPO" ]
+then
+ repo="$APPSH_REPO"
+else
+ repo="http://repo1.maven.org"
+fi
+
+calculate_md5() {
+ local file="$1"; shift
+
+ md5sum "$file" | cut -c 1-32
+}
+
+# TODO: support file:// repositories
+# TODO: look in the local repository first
+get() {
+ local url=$1
+ local file=$2
+ local exit
+
+ curl -o $file $url -D curl.tmp
+
+ exit=`grep "^HTTP/[0-9]\.[0-9] 200 .*" curl.tmp >/dev/null; echo $?`
+ head=`head -n 1 curl.tmp`
+ rm -f curl.tmp
+ if [ "$exit" != 0 ]
+ then
+ echo "Unable to download $url: $head" >&2
+ exit 1
+ fi
+}
+
+resolve_snapshot() {
+ local groupId=$1; shift
+ local groupIdSlash=$1; shift
+ local artifactId=$1; shift
+ local version=$1; shift
+
+ local metadata=$apps/.app/var/download/$groupId-$artifactId-$version-metadata.xml
+ local base_url=$repo/$groupIdSlash/$artifactId/$version
+ get $base_url/maven-metadata.xml $metadata
+ local resolved_version=`xmlstarlet sel -t -m '//snapshotVersion[extension[text()="zip"]]' -v value $metadata`
+ echo $resolved_version
+}
+
+download_artifact() {
+ local file="$1"; shift
+ local url="$1"; shift
+
+ echo "Downloading $url.md5"
+ get $url.md5 $file.md5
+ local expected_md5="`cat $file.md5`"
+
+ if [ -r $file ]
+ then
+ if [ "$expected_md5" == "`calculate_md5 $file`" ]
+ then
+ echo "Artifact already downloaded."
+ else
+ rm -f "$file"
+ fi
+ return 0
+ fi
+ echo "Downloading artifact: $url"
+ get $url $file
+
+ local actual_md5="`calculate_md5 $file`"
+ if [ "$expected_md5" == "$actual_md5" ]
+ then
+ echo "Artifact downloaded."
+ else
+ echo "Invalid checksum. Expected $expected_md5, got $actual_md5" >&2
+ exit 1
+ fi
+}
+
+method_install_usage() {
+ if [ -n "$1" ]
+ then
+ echo "Error:" "$@" >&2
+ fi
+
+ echo "usage: install <-r resolver> -u <url>" >&2
+ echo "" >&2
+ echo "Install package from a Maven repository:" >&2
+ echo " $0 [-n name] [-i instance] instance install -r maven -u groupId:artifactId:version" >&2
+ echo "" >&2
+ echo "Install package from a file:" >&2
+ echo " $0 [-n name] [-i instance] instance install -r file -u file [-v version]" >&2
+ echo "The version defaults to the current timestamp" >&2
+ exit 1
+}
+
+method_install() {
+ local name="$1"; shift
+ local instance="$1"; shift
+ local version
+ local resolver
+ local url
+ local groupId
+ local artifactId
+ local zip_file
+
+
+ if [ $# -eq 0 ]
+ then
+ method_install_usage
+ fi
+
+ while getopts "n:i:v:r:u:" opt
+ do
+ case $opt in
+ n)
+ name=$OPTARG
+ ;;
+ i)
+ instance=$OPTARG
+ ;;
+ v)
+ version=$OPTARG
+ ;;
+ r)
+ resolver=$OPTARG
+ ;;
+ u)
+ url=$OPTARG
+ ;;
+ \?)
+ method_install_usage "Invalid option: -$OPTARG"
+ ;;
+ esac
+ done
+
+ if [ -z "$name" ]
+ then
+ method_install_usage "Missing required argument: -i name."
+ fi
+
+ if [ -z "$instance" ]
+ then
+ method_install_usage "Missing required argument: -i instance."
+ fi
+
+ if [ -z "$resolver" ]
+ then
+ method_install_usage "Missing required option: -r resolver"
+ fi
+
+ if [ -z "$url" ]
+ then
+ method_install_usage "Missing required option: -u url"
+ fi
+
+ case "$resolver" in
+ maven)
+ url=`echo $url | tr ":" " "`; set -- $url
+ groupId=$1
+ artifactId=$2
+ version=$3
+
+ if [ -z "$groupId" -o -z "$artifactId" -o -z "$version" ]
+ then
+ method_install_usage "Invalid Maven url."
+ fi
+
+ local groupIdSlash=$(echo $groupId | sed "s,\.,/,g")
+ if [ "`echo $version | sed -n s,.*-SNAPSHOT$,SNAPSHOT,p`" == "SNAPSHOT" ]
+ then
+ echo "Resolving version $version..."
+ local resolved_version=`resolve_snapshot $groupId $groupIdSlash $artifactId $version`
+ if [ -z "$resolved_version" ]
+ then
+ echo "Unable to resolve version."
+ exit 1
+ fi
+ echo "Resolved version $version to $resolved_version"
+ else
+ resolved_version=$version
+ fi
+
+ zip_file=$apps/.app/var/download/$groupId-$artifactId-$resolved_version.zip
+ artifact_url=$repo/$groupIdSlash/$artifactId/$version/$artifactId-$resolved_version.zip
+
+ download_artifact "$zip_file" "$artifact_url"
+ ;;
+ file)
+ if [ ! -r "$url" ]
+ then
+ echo "Could not read file: $url" >&2
+ exit 1
+ fi
+
+ # TODO: should the zip file be copied into download/ so that
+ # there's always a local copy?
+ zip_file=$url
+
+ if [ -z "$version" ]
+ then
+ version=`TZ=UTC date +"%Y%m%d-%H%M%S"`
+ fi
+
+ resolved_version=$version
+ ;;
+ *)
+ method_install_usage "Invalid resolver type: $resolver"
+ ;;
+ esac
+
+ if [ -d $name/$instance/versions/$resolved_version ]
+ then
+ echo "Version $resolved_version is already installed"
+ exit 1
+ fi
+
+ if [ ! -d $name/$instance ]
+ then
+ echo "Creating instance '$instance' for '$name'"
+ mkdir -p $name/$instance
+ fi
+
+ mkdir -p $name/$instance/versions/$resolved_version
+
+ echo "Unpacking..."
+ unzip -q -d $name/$instance/versions/$resolved_version $zip_file
+
+ if [ ! -d $name/$instance/versions/$resolved_version/root ]
+ then
+ echo "Invalid zip file, did not contain a ./root directory." >&2
+ exit 1
+ fi
+
+ echo "Changing current symlink"
+ rm -f $apps/$name/$instance/current
+ ln -s versions/$resolved_version/root $apps/$name/$instance/current
+
+ if [ -d $name/$instance/current/bin ]
+ then
+ (
+ cd $name/$instance/current
+ find bin -type f | xargs chmod +x
+ )
+ fi
+
+ (
+ cd $name/$instance/versions/$resolved_version
+ if [ -d scripts ]
+ then
+ find scripts | xargs chmod +x
+ fi
+
+ if [ -x scripts/postinstall ]
+ then
+ echo "Running postinstall..."
+ cd root
+ set +e
+ env -i \
+ PATH=/bin:/usr/bin \
+ APPSH_APPS=$apps \
+ APPSH_HOME=$APPSH_HOME \
+ APPSH_NAME=$name \
+ APPSH_INSTANCE=$instance \
+ APPSH_VERSION=$resolved_version \
+ ../scripts/postinstall
+ set -e
+ ret=`echo $?`
+ if [ "$ret" != 0 ]
+ then
+ echo "Postinstall failed!"
+ exit 1
+ fi
+ echo "Postinstall completed successfully"
+ fi
+ )
+
+ if [ -r $apps/.app/var/list ]
+ then
+ sed "/^$name:$instance/d" $apps/.app/var/list > $apps/.app/var/list.new
+ fi
+ echo "$name:$instance:$version:$url" >> $apps/.app/var/list.new
+ mv $apps/.app/var/list.new $apps/.app/var/list
+}
+
+method_set_current_usage() {
+ if [ -n "$1" ]
+ then
+ echo "Error:" "$@" >&2
+ fi
+
+ echo "usage: set-current -v version" >&2
+ exit 1
+}
+
+method_set_current() {
+ local name="$1"; shift
+ local instance="$1"; shift
+ local version
+
+ if [ $# -eq 0 ]
+ then
+ method_set_current_usage
+ fi
+
+ while getopts "n:i:v:" opt
+ do
+ case $opt in
+ n)
+ name=$OPTARG
+ ;;
+ i)
+ instance=$OPTARG
+ ;;
+ v)
+ version=$OPTARG
+ ;;
+ \?)
+ method_set_current_usage "Invalid option: -$OPTARG"
+ ;;
+ esac
+ done
+
+ if [ -z "$version" ]
+ then
+ echo "Missing required option -v version." >&2
+ exit 1
+ fi
+
+ assert_is_instance method_set_current_usage "$name" "$instance" "no"
+
+ if [ ! -d $apps/$name/$instance/versions/$version ]
+ then
+ echo "Invalid version: $version."
+ exit 1
+ fi
+
+ rm -f $apps/$name/$instance/current
+ ln -s versions/$version/root $apps/$name/$instance/current
+
+ return 0
+}
+
+method_list_usage() {
+ if [ -n "$1" ]
+ then
+ echo "Error:" "$@" >&2
+ fi
+
+ echo "usage: list [-n name] [-P field]" >&2
+ echo ""
+ echo "List all installed applications:" >&2
+ echo " list" >&2
+ echo ""
+ echo "List all applications with the selected fields with parseable output:" >&2
+ echo " list -P instance -P version -n foo" >&2
+ exit 1
+}
+
+method_list() {
+ local filter_name="$1"; shift
+ local filter_instance="$1"; shift
+ local mode="pretty"
+ local vars
+ local filter_name
+
+ while getopts "P:n:i:" opt
+ do
+ case $opt in
+ P)
+ mode="parseable"
+ vars="$vars $OPTARG"
+ ;;
+ n)
+ filter_name=$OPTARG
+ ;;
+ i)
+ filter_instance=$OPTARG
+ ;;
+ \?)
+ method_list_usage "Invalid option: -$OPTARG"
+ ;;
+ esac
+ done
+
+ if [ ! -r $apps/.app/var/list ]
+ then
+ return
+ fi
+
+ if [ $mode = "pretty" ]
+ then
+ printf "%-20s %-20s %-20s\n" "Name" "Instance" "Version"
+ list_apps "$filter_name" "$filter_instance" name instance version | (IFS=:; while read name instance version
+ do
+ printf "%-20s %-20s %-20s\n" "$name" "$instance" "$version"
+ done)
+ else
+ list_apps "$filter_name" "$filter_instance" $vars
+ fi
+}
+
+method_list_versions_usage() {
+ if [ -n "$1" ]
+ then
+ echo "Error:" "$@" >&2
+ fi
+
+ echo "usage: list-versions -n name -i instance [-P]" >&2
+ exit 1
+}
+
+method_list_versions() {
+ local filter_name="$1"; shift
+ local instance="$1"; shift
+ local version
+ local mode="pretty"
+
+ if [ $# -eq 0 ]
+ then
+ method_list_versions_usage
+ fi
+
+ while getopts "n:i:P" opt
+ do
+ case $opt in
+ n)
+ name=$OPTARG
+ ;;
+ i)
+ instance=$OPTARG
+ ;;
+ v)
+ version=$OPTARG
+ ;;
+ P)
+ mode="parseable"
+ ;;
+ \?)
+ method_list_versions_usage "Invalid option: -$OPTARG"
+ ;;
+ esac
+ done
+
+ assert_is_instance method_list_versions_usage "$name" "$instance" "no"
+
+ if [ $mode = "pretty" ]
+ then
+ echo "Available versions for $name/$instance:"
+ fi
+
+ find_versions $name $instance
+
+ return 0
+}
+
+method_instance_usage() {
+ if [ -n "$1" ]
+ then
+ echo "Error:" $@ >&2
+ fi
+
+ echo "usage: $0 instance <method>" >&2
+ echo "" >&2
+ echo "Available methods:" >&2
+ echo " install - Installs an application" >&2
+ echo " list - List all installed applications" >&2
+ echo " list-versions - List all available versions for a single application" >&2
+ echo " set-current - Set the current version" >&2
+}
+
+method_instance() {
+ local name="$1"; shift
+ local instance="$1"; shift
+ local method="$1"
+
+ if [ $# -gt 0 ]
+ then
+ shift
+ fi
+
+ case "$method" in
+ install) method_install "$name" "$instance" "$@" ;;
+ list) method_list "$name" "$instance" "$@" ;;
+ list-versions) method_list_versions "$name" "$instance" "$@" ;;
+ set-current) method_set_current "$name" "$instance" "$@" ;;
+ *)
+ if [ -z "$method" ]
+ then
+ method_instance_usage
+ else
+ method_instance_usage "Unknown method $method"
+ fi
+ ;;
+ esac
+ exit $?
+}
diff --git a/bin/app-operate b/bin/app-operate
new file mode 100755
index 0000000..dc16780
--- /dev/null
+++ b/bin/app-operate
@@ -0,0 +1,73 @@
+#!/bin/bash
+
+if [[ $APPSH_HOME == "" ]]
+then
+ APPSH_HOME=`dirname "$0"`
+ APPSH_HOME=`cd "$APPSH_HOME/.." && pwd`
+fi
+
+operate_usage() {
+ if [ -n "$1" ]
+ then
+ echo "Error:" "$@" >&2
+ fi
+
+ echo "usage: $0 [operate method] -n name -i instance" >&2
+ exit 1
+}
+
+method_operate_usage() {
+ if [ -n "$1" ]
+ then
+ echo "Error:" $@ >&2
+ fi
+
+ echo "usage: $0 operate <operate method>" >&2
+ echo "" >&2
+ echo "Available operate methods:" >&2
+ echo " start" >&2
+ echo " stop" >&2
+ echo " restart" >&2
+ echo " status" >&2
+}
+
+method_operate() {
+ local name="$1"; shift
+ local instance="$1"; shift
+ local method="$1"
+
+ if [ $# -gt 0 ]
+ then
+ shift
+ fi
+
+ bin=`$APPSH_HOME/bin/app-cat-conf -f $apps/$name/$instance/current/etc/app.conf -g app -k method | cut -f 2 -d =`
+
+ if [ -z "$bin" ]
+ then
+ bin=$APPSH_HOME/.app/lib/pid-method
+ fi
+
+ if [ ! -x "$name/$instance/current/$bin" ]
+ then
+ echo "Invalid executable: $bin" >&2
+ exit 1
+ fi
+
+ case "$method" in
+ start) run_app "$name" "$instance" "$bin" "start" "$@" ;;
+ stop) run_app "$name" "$instance" "$bin" "stop" "$@" ;;
+ status) run_app "$name" "$instance" "$bin" "status" "$@" ;;
+ restart) run_app "$name" "$instance" "$bin" "restart" "$@" ;;
+ run) run_app "$name" "$instance" "$bin" "run" "$@" ;;
+ *)
+ if [ -z "$method" ]
+ then
+ method_operate_usage
+ else
+ method_operate_usage "Unknown method $method"
+ fi
+ ;;
+ esac
+ exit $?
+}
diff --git a/bin/pid-method b/bin/pid-method
new file mode 100755
index 0000000..29f6b4f
--- /dev/null
+++ b/bin/pid-method
@@ -0,0 +1,113 @@
+#!/bin/bash -e
+
+set -u
+
+. $APPSH_HOME/.app/lib/app-conf
+
+pid_file=$APPSH_APPS/.app/var/pid/$APPSH_NAME-$APPSH_INSTANCE.pid
+bin=`get_conf $APPSH_APPS $APPSH_NAME $APPSH_INSTANCE app.bin`
+
+cd $APPSH_APPS/$APPSH_NAME/$APPSH_INSTANCE/current
+
+if [ -z "$bin" ]
+then
+ echo "Missing required configuration: app.bin." >&2
+ exit 1
+fi
+
+if [ ! -r "$bin" ]
+then
+ echo "No such file: $bin" >&2
+ exit 1
+fi
+
+chmod +x "$bin"
+
+PID=
+if [ -r $pid_file ]
+then
+ PID="`cat $pid_file`"
+fi
+
+do_status() {
+ if [ -z "$PID" ]
+ then
+ echo stopped
+ else
+ if [ `ps -p "$PID" 2>/dev/null | wc -l` -gt 1 ]
+ then
+ echo running
+ else
+ echo crashed
+ fi
+ fi
+}
+
+method_start() {
+ case `do_status` in
+ running)
+ echo "The application is already running as $PID."
+ exit 1
+ ;;
+ esac
+
+ $bin <&- 1<&- 2<&- &
+
+ PID=$!
+ echo "Application launched as $PID"
+ echo $PID > $pid_file
+
+ return 0
+}
+
+method_stop() {
+ case `do_status` in
+ stopped)
+ echo "The application not running."
+ exit 1
+ ;;
+ crashed)
+ echo "The application crashed. Was running as $PID"
+ # TODO: should this remove the PID file?
+ # That makes it possible to run "stop" to stop "status" from showing "crashed"
+ exit 1
+ ;;
+ esac
+
+ signal="-9"
+ echo -n "Sending kill $signal to $PID, waiting for shutdown"
+ kill $signal $PID
+
+ while [ "`do_status`" == "running" ]
+ do
+ sleep 1
+ echo -n "."
+ done
+
+ echo " OK"
+ rm -f $pid_file
+ return 0
+}
+
+method_status() {
+ case `do_status` in
+ running)
+ echo "$APPSH_NAME/$APPSH_INSTANCE is running as $PID"
+ ;;
+ stopped)
+ echo "$APPSH_NAME/$APPSH_INSTANCE is not running"
+ ;;
+ crashed)
+ echo "$APPSH_NAME/$APPSH_INSTANCE crashed. Was running as $PID"
+ ;;
+ esac
+}
+
+case "$APPSH_METHOD" in
+ start) method_start ;;
+ stop) method_stop ;;
+ status) method_status ;;
+ *) exit 1 ;;
+esac
+
+exit $?