Tuesday, March 27, 2012

[Service上線前你該知道的事]How to Debian-ize Your Web Application.

在布建一個Web Application時,用.war來佈屬服務,算是最沒有效率的一種方式,因為,這樣無法把web container所需要的設定檔一起佈建出去,同時,也無法板本控製你的應用程式;本文接下來會敘述,如何用maven & jetty & debian來佈屬你的Web Application。

首先,先講講我的需求,我的需求如下
  • Web Application的版本必需能夠簡單被管理,要能簡單的就知道目前安裝的版本,並且輕鬆的昇級到新版
  • Web Application被佈建時,要連Web Container及設定擋包在一起
  • Debian Box開關機時,要能自動開關Web Container

這些需求,我是靠 unix-maven-plugin ,幫我把我的 web application 包成一個 .deb 來做到的,包成 .deb 的好處是,在 debian 上可以簡單的安裝,若是公司有架 debian package repository, 那麼,打個 apt-get install 就可以安裝或昇級到最新版,同時間,可以透過 debian 安裝的機制,來把些 script 掛入 rc.*

由於 unix-maven-plugin 已被原作者遺棄,所以需要從 github下載並安裝後人修改過的版本。

然後依附錄的方式,增修你的設定檔,需要增修的檔案有
  • pom.xml: 加入 unix-maven-plugin 及服務的名稱及PORT
  • src/main/unix/resources/default/${service.user} : 給 jetty 用的 default 設定值,在這邊你可以修改 JAVA_OPTIONS 及 NO_START
  • src/main/unix/resources/etc/jetty.conf
  • src/main/unix/resources/etc/start.conf
  • src/main/unix/resources/scripts/${service.user} : 給 debian 用的 init script
  • src/main/unix/resources/scripts/post-install post-remove pre-install pre-remove : debian 在安裝及昇級 .deb 時會跑的 script

將這些檔案設定好後,在 debian 的機器上打 mvn package deploy ,就可以自動生成 .deb 的檔案,並且上傳到你的 maven repository 上去,如果貴公司有架 debian package repository 的話,還可以加一段程式碼把 .deb 傳到 debian package repository 去。

接著,打 dpkg -i xxxx.deb 就可以把你的 web application 當成一個 debian service 安裝到你的 debain machine 上去。

查詢目前安裝的板本則是打 dpkg -l xxxx 即可

||/ Name           Version        Description
+++-==============-==============-============================================
ii  backend       1.2.0-20120327.143115          backend


附錄



把底下的這段,加入你的 pom.xml


  4.0.0
  com.locadz
  backend
  war
  backend
  1.2.0-SNAPSHOT
  backend
  
    1.0-alpha-6.1
    7.4.5.v20110725

    Locadz Backend
    /var/lib/bluetang/locadz-backend
    locadz-backend
    9080
  
  
    
      debian-package
      
        
          unix
          linux
        
      
      
        
          
            maven-resources-plugin
            2.5
            
              
                copy-resources
                
                process-resources
                
                  copy-resources
                
                
                  ${basedir}/target
                  
                    
                      src/main/unix
                      unix
                      
                        **/*
                      
                      true
                    
                  
                
              
            
          

          
          
            org.mortbay.jetty.toolchain
            unix-maven-plugin
            ${version.maven.unix.plugin}
            true
            
              
                package
                
                  package-deb-attached
                
              
            
            
              
              true
              yho@bluetangstudio.com
              yho@bluetangstudio.com
              
                
                  ${service.user}
                  adm
                
                
                  ${service.user}
                  adm
                
              
              
                
bluetang
extra
org.eclipse.jetty:jetty-distribution:zip ${service.home} /jetty-distribution-${version.jetty}(.*) $1 */contexts/** */contexts-available/** */webapps/** */javadoc/** */LICENSES/** */logs/** **/win32/** ${project.build.directory}/${project.build.finalName}.${project.packaging} ${service.home}/webapps/ROOT.war ${basedir}/target/unix/resources/default/${service.user} /etc/default ${basedir}/target/unix/resources/etc /etc/bluetang/${service.user} ${basedir}/target/unix/scripts/${service.user} /etc/init.d 0754 ${service.user} admin
org.eclipse.jetty jetty-distribution ${version.jetty} zip * *

然後把下面這塊存成 src/main/unix/resources/default/${service.user}

# Defaults for jetty see /etc/${service.user}/jetty for more

# change to 0 to allow Jetty to start
NO_START=0

# change to 'no' or uncomment to use the default setting in /etc/default/rcS
VERBOSE=yes

# Run Jetty as this user ID (default: jetty)
# Set this to an empty string to prevent Jetty from starting automatically
JETTY_USER=${service.user}

# Listen to connections from this network host
# Use 0.0.0.0 as host to accept all connections.
# Uncomment to restrict access to localhost
#JETTY_HOST=$(uname -n)
JETTY_HOST=0.0.0.0

# The network port used by Jetty
JETTY_PORT=${service.port}

# Timeout in seconds for the shutdown of all webapps
#JETTY_SHUTDOWN=30

# Additional arguments to pass to Jetty
#JETTY_ARGS=

# Extra options to pass to the JVM
JAVA_OPTIONS="-Xmx256m -Djava.awt.headless=true -Duser.timezone=UTC"

# Home of Java installation.
#JAVA_HOME=

# The first existing directory is used for JAVA_HOME (if JAVA_HOME is not
# defined in /etc/default/jetty). Should contain a list of space separated directories.
#JDK_DIRS="/usr/lib/jvm/default-java /usr/lib/jvm/java-6-sun"

# Java compiler to use for translating JavaServer Pages (JSPs). You can use all
# compilers that are accepted by Ant's build.compiler property.
#JSP_COMPILER=jikes

# Jetty uses a directory to store temporary files like unpacked webapps
JETTY_TMP=/var/cache/bluetang/jetty

# Jetty uses a config file to setup its boot classpath
JETTY_START_CONFIG=/etc/bluetang/${service.user}/start.config

# Default for number of days to keep old log files in /var/log/jetty/
#LOGFILE_DAYS=14


然後把下面這塊存成 src/main/unix/resources/ect/jetty.conf

# list of jetty configuration and property files
/var/lib/bluetang/${service.user}/etc/jetty-logging.xml


然後把下面這塊存成 src/main/unix/resources/etc/start.conf

# This file controls what file are to be put on classpath or command line.
#
# Format is as follows:
# Each line contains entry for one JAR file.
# Format of line:
#
#  SUBJECT [ [!] CONDITION [AND|OR] ]*
#
# where SUBJECT:
#   ends with ".class" is the Main class to run.
#   ends with ".xml" is a configuration file for the command line
#   ends with "/" is a directory from which to add all jar and zip files.
#   ends with "/*" is a directory from which to add all unconsidered jar and zip files.
#   ends with "/**" is a directory from which to recursively add all unconsidered jar and zip files.
#   Containing = are used to assign system properties.
#   all other subjects are treated as files to be added to the classpath.
#
# Subjects may include system properties with $(propertyname) syntax.
#
# Files starting with "/" are considered absolute, all others are relative to
# the home directory.
#
# CONDITION is one of:
#   always
#   never
#   available classname        # true if class on classpath
#   property name              # true of set
#   java OPERATOR version      # java version compared to literal
#   nargs OPERATOR number      # number of command line args compared to literal
#   OPERATOR := one of "<",">","<=",">=","==","!="
#
# CONDITIONS can be combined with AND OR or !, with AND being the assume
# operator for a list of CONDITIONS.
# Classpath operations are evaluated on the fly, so once a class or jar is
# added to the classpath, subsequent available conditions will see that class.
#

$(jetty.class.path)                              always
$(jetty.lib)/**                                  exists $(jetty.lib)

jetty.home=/var/lib/bluetang/${service.user}                      always

# The main class to run
org.mortbay.xml.XmlConfiguration.class

# The default configuration files
$(jetty.home)/etc/jetty.xml                      nargs == 0

/usr/share/java/servlet-api-2.5.jar
/usr/share/java/slf4j-api.jar

# Optional stuff for libjetty-extra-java
/usr/share/java/gnumail.jar
/usr/share/java/activation.jar
/usr/share/java/ant.jar

# Set the jetty classpath
/usr/share/jetty/lib/**

# Add a resources directory if it is there
$(jetty.home)/resources/




然後把下面這塊存成 src/main/unix/scripts/${service.user}

#!/bin/bash  
### BEGIN INIT INFO
# Provides:          ${service.user}
# Required-Start:
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start daemon at boot time
# Description:       Enable service provided by daemon.
### END INIT INFO
#
# Startup script for jetty under *nix systems (it works under NT/cygwin too).
#
# Configuration files
#
# /etc/default/${service.user}
#   If it exists, this is read at the start of script. It may perform any 
#   sequence of shell commands, like setting relevant environment variables.
#
# /etc/bluetang/${service.user}/jetty.conf
#   If found, and no configurations were given on the command line,
#   the file will be used as this script's configuration. 
#   Each line in the file may contain:
#     - A comment denoted by the pound (#) sign as first non-blank character.
#     - The path to a regular file, which will be passed to jetty as a 
#       config.xml file.
#     - The path to a directory. Each *.xml file in the directory will be
#       passed to jetty as a config.xml file.
#
#   The files will be checked for existence before being passed to jetty.
#
# $JETTY_HOME/etc/jetty.xml
#   If found, used as this script's configuration file, but only if
#   /etc/bluetang/${service.user}/jetty.conf was not present. See above.
#   
# Configuration variables
#
# JAVA
#   Command to invoke Java. If not set, java (from the PATH) will be used.
#
# JAVA_OPTIONS
#   Extra options to pass to the JVM
#
# JETTY_HOME
#   Where Jetty is installed. If not set, the script will try go
#   guess it by first looking at the invocation path for the script,
#   and then by looking in standard locations as $HOME/opt/jetty
#   and /opt/jetty. The java system property "jetty.home" will be
#   set to this value for use by configure.xml files, f.e.:
#
#    /webapps/jetty.war
#
# JETTY_PORT
#   Override the default port for Jetty servers. If not set then the
#   default value in the xml configuration file will be used. The java
#   system property "jetty.port" will be set to this value for use in
#   configure.xml files. For example, the following idiom is widely
#   used in the demo config files to respect this property in Listener
#   configuration elements:
#
#    
#
#   Note: that the config file could ignore this property simply by saying:
#
#    8080
#
# JETTY_RUN
#   Where the jetty.pid file should be stored. It defaults to the
#   first available of /var/run, /usr/var/run, and /tmp if not set.
#  
# JETTY_PID
#   The Jetty PID file, defaults to $JETTY_RUN/jetty.pid
#   
# JETTY_ARGS
#   The default arguments to pass to jetty.
#
# JETTY_USER
#   if set, then used as a username to run the server as
#
JETTY_HOME=${service.home}
JETTY_USER=${service.user}
JETTY_PID=$JETTY_RUN/${service.user}.pid
usage()
{
    echo "Usage: ${0##*/} [-d] {start|stop|run|restart|check|supervise} [ CONFIGS ... ] "
    exit 1
}

[ $# -gt 0 ] || usage


##################################################
# Some utility functions
##################################################
findDirectory()
{
  local L OP=$1
  shift
  for L in "$@"; do
    [ "$OP" "$L" ] || continue
    printf %s "$L"
    break
  done
}

running()
{
  local PID=$(cat "$1" 2>/dev/null) || return 1
  kill -0 "$PID" 2>/dev/null
}

readConfig()
{
  (( DEBUG )) && echo "Reading $1.."
  source "$1"
}



##################################################
# Get the action & configs
##################################################
CONFIGS=()
NO_START=0
DEBUG=0

while [[ $1 = -* ]]; do
  case $1 in
    -d) DEBUG=1 ;;
  esac
  shift
done
ACTION=$1
shift

##################################################
# See if there's a default configuration file
##################################################
if [ -f /etc/default/${service.user} ] ; then
  . /etc/default/${service.user}
fi


##################################################
# Set tmp if not already set.
##################################################
TMPDIR=${TMPDIR:-/tmp}

##################################################
# Jetty's hallmark
##################################################
JETTY_INSTALL_TRACE_FILE="etc/jetty.xml"


##################################################
# Try to determine JETTY_HOME if not set
##################################################
if [ -z "$JETTY_HOME" ] 
then
  JETTY_SH=$0
  case "$JETTY_SH" in
    /*)   ;;
    ./*)  ;;
    *)    JETTY_SH=./$JETTY_SH ;;
  esac
  JETTY_HOME=${JETTY_SH%/*/*}

  if [ ! -f "${JETTY_SH%/*/*}/$JETTY_INSTALL_TRACE_FILE" ]
  then
    JETTY_HOME=
  fi
fi



##################################################
# if no JETTY_HOME, search likely locations.
##################################################
if [ -z "$JETTY_HOME" ] ; then
  STANDARD_LOCATIONS=(
        "/usr/share"
        "/usr/share/java"
        "${HOME}"
        "${HOME}/src"
        "${HOME}/opt"
        "/opt"
        "/java"
        "/usr/local"
        "/usr/local/share"
        "/usr/local/share/java"
        "/home"
        )
  JETTY_DIR_NAMES=(
        "jetty-7"
        "jetty7"
        "jetty-7.*"
        "jetty"
        "Jetty-7"
        "Jetty7"
        "Jetty-7.*"
        "Jetty"
        )

  for L in "${STANDARD_LOCATIONS[@]}"
  do
    for N in "${JETTY_DIR_NAMES[@]}"
    do
      POSSIBLE_JETTY_HOME=("$L/"$N)
      if [ ! -d "$POSSIBLE_JETTY_HOME" ]
      then
        # Not a directory. skip.
        unset POSSIBLE_JETTY_HOME
      elif [ ! -f "$POSSIBLE_JETTY_HOME/$JETTY_INSTALL_TRACE_FILE" ]
      then
        # Trace file not found. skip.
        unset POSSIBLE_JETTY_HOME
      else
        # Good hit, Use it
        JETTY_HOME=$POSSIBLE_JETTY_HOME
        # Break out of JETTY_DIR_NAMES loop
        break
      fi
    done
    if [ -n "$POSSIBLE_JETTY_HOME" ]
    then
      # We have found our JETTY_HOME
      # Break out of STANDARD_LOCATIONS loop
      break
    fi
  done
fi


##################################################
# No JETTY_HOME yet? We're out of luck!
##################################################
if [ -z "$JETTY_HOME" ]; then
  echo "** ERROR: JETTY_HOME not set, you need to set it or install in a standard location"
  exit 1
fi

cd "$JETTY_HOME"
JETTY_HOME=$PWD


#####################################################
# Check that jetty is where we think it is
#####################################################
if [ ! -r "$JETTY_HOME/$JETTY_INSTALL_TRACE_FILE" ]
then
  echo "** ERROR: Oops! Jetty doesn't appear to be installed in $JETTY_HOME"
  echo "** ERROR:  $JETTY_HOME/$JETTY_INSTALL_TRACE_FILE is not readable!"
  exit 1
fi

##################################################
# Try to find this script's configuration file,
# but only if no configurations were given on the
# command line.
##################################################
if [ -z "$JETTY_CONF" ] 
then
  if [ -f /etc/bluetang/${service.user}/jetty.conf ]
  then
     JETTY_CONF=/etc/bluetang/${service.user}/jetty.conf
  elif [ -f "${JETTY_HOME}/etc/jetty.conf" ]
  then
     JETTY_CONF="${JETTY_HOME}/etc/jetty.conf"
  fi
fi

##################################################
# Get the list of config.xml files from jetty.conf
##################################################
if [ -z "$CONFIGS" ] && [ -f "$JETTY_CONF" ] && [ -r "$JETTY_CONF" ]
then
  while read -r CONF
  do
    if expr "$CONF" : '#' >/dev/null ; then
      continue
    fi

    if [ -d "$CONF" ]
    then
      # assume it's a directory with configure.xml files
      # for example: /etc/jetty.d/
      # sort the files before adding them to the list of CONFIGS
      for file in "$CONF/"*.xml
      do
        if [ -r "$FILE" ] && [ -f "$FILE" ]
        then
          CONFIGS+=("$FILE")
        else
          echo "** WARNING: Cannot read '$FILE' specified in '$JETTY_CONF'"
        fi
      done
    else
      # assume it's a command line parameter (let start.jar deal with its validity)
      CONFIGS+=("$CONF")
    fi
  done < "$JETTY_CONF"
fi

#####################################################
# Find a location for the pid file
#####################################################
if [ -z "$JETTY_RUN" ]
then
  JETTY_RUN=$(findDirectory -w /var/run /usr/var/run /tmp)
fi

#####################################################
# Find a PID for the pid file
#####################################################
if [ -z "$JETTY_PID" ]
then
  JETTY_PID="$JETTY_RUN/jetty.pid"
fi

##################################################
# Setup JAVA if unset
##################################################
if [ -z "$JAVA" ]
then
  JAVA=$(which java)
fi

if [ -z "$JAVA" ]
then
  echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2
  exit 1
fi

#####################################################
# See if JETTY_PORT is defined
#####################################################
if [ "$JETTY_PORT" ]
then
  JAVA_OPTIONS+=("-Djetty.port=$JETTY_PORT")
fi

#####################################################
# See if JETTY_LOGS is defined
#####################################################
if [ "$JETTY_LOGS" ]
then
  JAVA_OPTIONS+=("-Djetty.logs=$JETTY_LOGS")
fi

#####################################################
# Are we running on Windows? Could be, with Cygwin/NT.
#####################################################
case "`uname`" in
CYGWIN*) PATH_SEPARATOR=";";;
*) PATH_SEPARATOR=":";;
esac


#####################################################
# Add jetty properties to Java VM options.
#####################################################
JAVA_OPTIONS+=("-Djetty.home=$JETTY_HOME" "-Djava.io.tmpdir=$TMPDIR")

[ -f "$JETTY_HOME/etc/start.config" ] && JAVA_OPTIONS=("-DSTART=$JETTY_HOME/etc/start.config" "${JAVA_OPTIONS[@]}")

#####################################################
# This is how the Jetty server will be started
#####################################################

JETTY_START=$JETTY_HOME/start.jar
[ ! -f "$JETTY_START" ] && JETTY_START=$JETTY_HOME/lib/start.jar

START_INI=$(dirname $JETTY_START)/start.ini
[ -r "$START_INI" ] || START_INI=""

RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$JETTY_START" $JETTY_ARGS "${CONFIGS[@]}")
RUN_CMD=("$JAVA" ${RUN_ARGS[@]})

#####################################################
# Comment these out after you're happy with what 
# the script is doing.
#####################################################
if (( DEBUG ))
then
  echo "JETTY_HOME     =  $JETTY_HOME"
  echo "JETTY_CONF     =  $JETTY_CONF"
  echo "JETTY_RUN      =  $JETTY_RUN"
  echo "JETTY_PID      =  $JETTY_PID"
  echo "JETTY_ARGS     =  $JETTY_ARGS"
  echo "CONFIGS        =  ${CONFIGS[*]}"
  echo "JAVA_OPTIONS   =  ${JAVA_OPTIONS[*]}"
  echo "JAVA           =  $JAVA"
  echo "RUN_CMD        =  ${RUN_CMD}"
fi

##################################################
# Do the action
##################################################
case "$ACTION" in
  start)
    echo -n "Starting ${service.name}: "

    if (( NO_START )); then
      echo "Not starting ${service.user} - NO_START=1";
      exit
    fi


    if type start-stop-daemon > /dev/null 2>&1
    then
      unset CH_USER
      if [ -n "$JETTY_USER" ]
      then
        CH_USER="-c$JETTY_USER"
      fi
      if start-stop-daemon -S -p"$JETTY_PID" $CH_USER -d"$JETTY_HOME" -b -m -a "$JAVA" -- "${RUN_ARGS[@]}" --daemon
      then
        sleep 1
        if running "$JETTY_PID"
        then
          echo "OK"
        else
          echo "FAILED"
        fi
      fi

    else

      if [ -f "$JETTY_PID" ]
      then
        if running $JETTY_PID
        then
          echo "Already Running!"
          exit 1
        else
          # dead pid file - remove
          rm -f "$JETTY_PID"
        fi
      fi

      if [ "$JETTY_USER" ]
      then
        touch "$JETTY_PID"
        chown "$JETTY_USER" "$JETTY_PID"
        # FIXME: Broken solution: wordsplitting, pathname expansion, arbitrary command execution, etc.
        su - "$JETTY_USER" -c "
          ${RUN_CMD[*]} --daemon &
          disown \$!
          echo \$! > '$JETTY_PID'"
      else
        "${RUN_CMD[@]}" &
        disown $!
        echo $! > "$JETTY_PID"
      fi

      echo "STARTED ${service.name} `date`"
    fi

    ;;

  stop)
    echo -n "Stopping ${service.name}: "
    if type start-stop-daemon > /dev/null 2>&1; then
      start-stop-daemon -K -p"$JETTY_PID" -d"$JETTY_HOME" -a "$JAVA" -s HUP

      TIMEOUT=30
      while running "$JETTY_PID"; do
        if (( TIMEOUT-- == 0 )); then
          start-stop-daemon -K -p"$JETTY_PID" -d"$JETTY_HOME" -a "$JAVA" -s KILL
        fi

        sleep 1
      done

      rm -f "$JETTY_PID"
      echo OK
    else
      PID=$(cat "$JETTY_PID" 2>/dev/null)
      kill "$PID" 2>/dev/null

      TIMEOUT=30
      while running $JETTY_PID; do
        if (( TIMEOUT-- == 0 )); then
          kill -KILL "$PID" 2>/dev/null
        fi

        sleep 1
      done

      rm -f "$JETTY_PID"
      echo OK
    fi

    ;;

  restart)
    JETTY_SH=$0
    if [ ! -f $JETTY_SH ]; then
      if [ ! -f $JETTY_HOME/bin/jetty.sh ]; then
        echo "$JETTY_HOME/bin/jetty.sh does not exist."
        exit 1
      fi
      JETTY_SH=$JETTY_HOME/bin/jetty.sh
    fi

    "$JETTY_SH" stop "$@"
    "$JETTY_SH" start "$@"

    ;;

  supervise)
    #
    # Under control of daemontools supervise monitor which
    # handles restarts and shutdowns via the svc program.
    #
    exec "${RUN_CMD[@]}"

    ;;

  run|demo)
    echo "Running Jetty: "

    if [ -f "$JETTY_PID" ]
    then
      if running "$JETTY_PID"
      then
        echo "Already Running!"
        exit 1
      else
        # dead pid file - remove
        rm -f "$JETTY_PID"
      fi
    fi

    exec "${RUN_CMD[@]}"

    ;;

  check)
    echo "Checking arguments to Jetty: "
    echo "JETTY_HOME     =  $JETTY_HOME"
    echo "JETTY_CONF     =  $JETTY_CONF"
    echo "JETTY_RUN      =  $JETTY_RUN"
    echo "JETTY_PID      =  $JETTY_PID"
    echo "JETTY_PORT     =  $JETTY_PORT"
    echo "JETTY_LOGS     =  $JETTY_LOGS"
    echo "START_INI      =  $START_INI"
    echo "CONFIGS        =  ${CONFIGS[*]}"
    echo "JAVA_OPTIONS   =  ${JAVA_OPTIONS[*]}"
    echo "JAVA           =  $JAVA"
    echo "CLASSPATH      =  $CLASSPATH"
    echo "RUN_CMD        =  ${RUN_CMD[*]}"
    echo

    if [ -f "$JETTY_RUN/jetty.pid" ]
    then
      echo "Jetty running pid=$(< "$JETTY_RUN/jetty.pid")"
      exit 0
    fi
    exit 1

    ;;

  *)
    usage

    ;;
esac

exit 0

然後把下面這塊存成 src/main/unix/scripts/post-install,並修改 USER=locadz-backend 這一行成跟你 ${service.user} 一樣的值
#!/bin/sh
set -e

USER=locadz-backend

case "$1" in
    configure)
        if ! id $USER > /dev/null 2>&1 ; then
            adduser --system --home /var/lib/bluetang/$USER --no-create-home \
                --group --disabled-password --shell /bin/false \
                $USER
        fi

        if [ ! -e /var/log/bluetang ]
        then
            mkdir -p /var/log/bluetang
        fi
        if [ ! -e /var/log/bluetang/$USER/ ]
        then
            ln -s /var/lib/bluetang/$USER/logs /var/log/bluetang/$USER
        fi
        if [ ! -e /var/cache/bluetang/$USER ]
        then
            mkdir -p /var/cache/bluetang/$USER
        fi


        chown -R $USER:adm /var/cache/bluetang/$USER /var/log/bluetang/$USER /var/lib/bluetang/$USER
        chmod 0744 /etc/init.d/$USER
        update-rc.d $USER defaults

    ;;

    abort-upgrade|abort-remove|abort-deconfigure)
    ;;

    *)
        echo "$0 called with unknown argument \`$1'" >&2
        exit 1
    ;;
esac

然後把下面這塊存成 src/main/unix/scripts/post-remove,並修改 USER=locadz-backend 這一行成跟你 ${service.user} 一樣的值
#!/bin/sh
set -e

USER=locadz-backend

#DEBHELPER#

# Remove cached files
rm -rf /var/cache/blutang/$USER/*

case "$1" in
    remove)
        # Remove ROOT webapp if not modified
        RWLOC="/var/lib/bluetang/$USER/webapps/root"
        RWFILES="$RWLOC/index.html $RWLOC/jetty_banner.gif"
        if [ "`(cat $RWFILES | md5sum -) 2>/dev/null | cut -d ' ' -f 1`" \
                            = "12471c4b3020defb7ebd30ef84c0f9dd" ] ; then
            rm $RWFILES
            rmdir --ignore-fail-on-non-empty \
                /var/lib/bluetang/$USER/webapps/root \
                /var/lib/bluetang/$USER/webapps \
                /var/lib/bluetang/$USER || true
        fi
        if [ -d "/var/cache/bluetang/$USER" ] ; then
            rm -rf /var/cache/bluetang/$USER
        fi
    ;;

    purge)
        # Remove user/group and log files (don't remove everything under
        # /var/lib/jetty because there might be user-installed webapps)
        deluser jetty || true
        rm -rf /var/log/bluetang/$USER
        if [ -d "/var/lib/bluetang/$USER" ] ; then
            rmdir --ignore-fail-on-non-empty /var/lib/bluetang/$USER || true
        fi
        rmdir --ignore-fail-on-non-empty /etc/bluetang/$USER/contexts /etc/bluetang/$USER || true
    ;;

    remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
        # Nothing to do here
    ;;

    *)
        echo "$0 called with unknown argument \`$1'" >&2
        exit 1
    ;;
esac
然後把下面這塊存成 src/main/unix/scripts/pre-install,並修改 USER=locadz-backend 這一行成跟你 ${service.user} 一樣的值
#!/bin/sh
#
# This is the preinst script for the Blue Tang Servers
#
# Written by Yung-Lin Ho yho@bluetangstudio.com

set -e
USER=locadz-backend
SCRIPT=/etc/init.d/$USER


case "$1" in
    upgrade|remove|purge)
        $SCRIPT stop
        ;;
    install|failed-upgrade|abort-install|abort-upgrade|disappear)
        ;;
    *)
        echo "preinst called with unknown argument \`$1'" >&2
        ;;
esac

exit 0
下面這塊存成 src/main/unix/scripts/pre-remove,並修改 USER=locadz-backend 這一行成跟你 ${service.user} 一樣的值
#!/bin/sh
#
# This is the prerm script for the Blue Tang Servers
#
# Written by Yung-Lin Ho yho@bluetangstudio.com

set -e
USER=locadz-backend
SCRIPT=/etc/init.d/$USER

case "$1" in
    upgrade)
        ;;
    remove|purge)
        $SCRIPT stop
        ;;
    failed-upgrade|abort-install|abort-upgrade|disappear)
        ;;
    *)
        echo "prerm called with unknown argument \`$1'" >&2
        ;;
esac

exit 0

Bravo to Spotify Apps.

spotify's app is pretty good business model. 之前我抱怨 #spotify 的 editorial material 比不上 #rhapsody. 用 Rhapsody 時,因為 Rhapsody 內有十多位人員負責編排本週推薦的音樂,還有客製化的播放清單及電台,所以訂閱 rhapsody 就像有 DJ 幫你找歌來聽。

但是用 Spotify ,就只能夠聽,你已知的歌手, spotify 沒有一個好用的推薦功能可用。

最近 spotify 的 gui client 開始可以放上第三方App的功能,馬上就把缺的地方補上,而且還更好。

像是串入 last.fm 就有推薦清單可用,soundrop可以當線上DJ,TuneWiki可以看歌詞,歌詞還會照時間來跳到現在唱的句子;這證明了 Open API的威力,你有缺的地方,用 3rd party app 來補上,而且可以做的更好。

其實 Rhapsody 內部早在 2005 已有這種 API ,我在 real.com 時就是做這一塊的,許多第三方的程式及硬體就是呼叫我們的API,如 sonos, sandisk sansa, 我自己也寫了隻程式給我的Nokia N82用,只是可惜不能放出來。

Rhapsody 再不把 API 放出來,恐怕就連美國這塊市場都要開始敗退了...

Wednesday, March 7, 2012

[Job]Job Market in US

這篇的草稿躺在我的部絡格兩個月了,看來完成遙遙無期,所以先把屍體放出來好了

要在美國求職,第一要務,就是先了解到,美國的求職市場是怎樣的環境。

大多人在台灣的求職的方式,是上 104 找適合的工作,然後再寄信過去應徵工作;然而,很不幸的是,你把求職市場放大十倍,這種方式就變得不是很有效率了。

首先,你要知道的是,在矽谷,一個工作被登上美國的求職網站,兩週內,可能吸引到2000個應徵者投遞履例表,這數字是什麼意思呢,這是說,當 HR 過濾履例表時,既使只花30秒在一份履例表上,也要花上整整16小時兩個工作天,才能篩檢完畢。我曾經看過同事在過濾履例時,就像在拿電風扇在吹一樣,看到過去工作過的公司有趣留下來,看到經驗有趣的留下來,看到名字念起來有趣的留下來;有時後,會不會被找來面識,真的是要靠一點運氣的。

所以說,要寫出一份好的履例表,讓 HR 一目了然,抓到重點,把你的履例表留下來細看,就是一件很重要的事了。


好的履例表,第一要鍵就是,格式要清楚,格式不清楚,當下就是被放一邊,不會有 HR 去細看的;第二則是,不管你經歷有多少,履例表的內容,一定要是一業滿版,比一頁多,你要刪成只有一頁,比一頁少,你不管怎樣就是要加到一頁滿版。

底下是我2005時,剛畢業時用的履例表;內容的格式有點小問題,不過這個板型也是我後來一直延用下去的格式。


這份履例表的排列是社會新鮮人用的,(未完待續)