diff --git a/.hgtags b/.hgtags
index 85c17be67df8ae3200760ea90689a76afc72ad7f..07ddf93c11858f1c5952b42e0e18501b5d6de1f0 100755
--- a/.hgtags
+++ b/.hgtags
@@ -470,3 +470,4 @@ d40c66e410741de7e90b1ed6dac28dd8a2d7e1f6 3.6.8-release
 70eda3721d36df3e00730629c42a1304e5bc65b8 3.6.9-release
 5b54b36862ff8bc3b6935673c9d1c1f22ee8d521 3.6.10-release
 2feb70a4cfde43f2898d95ff8fcae3e67805c7c2 3.6.11-release
+88bbfd7a6971033f3aa103f3a3500ceb4c73521b 3.6.12-release
diff --git a/BuildParams b/BuildParams
index 31e7e841ad0e39329cfd7fc3e29558a9dc49058f..6b63448c52743cc83987552425d1addb5d8981f5 100755
--- a/BuildParams
+++ b/BuildParams
@@ -51,13 +51,13 @@ viewer_channel = "Second Life Test"
 sourceid = ""
 additional_packages = "Amazon Desura B C"
 Amazon_sourceid = "1207v_Amazon"
-Amazon_viewer_channel_suffix = " Amazon"
+Amazon_viewer_channel_suffix = "Amazon"
 Desura_sourceid = "1208_desura"
-Desura_viewer_channel_suffix = " Desura"
+Desura_viewer_channel_suffix = "Desura"
 B_sourceid = "1301_B"
-B_viewer_channel_suffix = " B"
+B_viewer_channel_suffix = "B"
 C_sourceid = "1302_C"
-C_viewer_channel_suffix = " C"
+C_viewer_channel_suffix = "C"
 
 # Report changes since...
 viewer-development.show_changes_since = last_sprint
diff --git a/build.sh b/build.sh
index 4875ef39f770d5259cc25f998ea5d3c424769963..ccc8476ceafb0e4c3edc5ea12692a1712211b647 100755
--- a/build.sh
+++ b/build.sh
@@ -347,8 +347,7 @@ then
   if $build_viewer
   then
     begin_section Upload Installer
-    # Upload installer - note that ONLY THE FIRST ITEM uploaded as "installer"
-    # will appear in the version manager.
+    # Upload installer
     package=$(installer_$arch)
     if [ x"$package" = x ] || test -d "$package"
     then
@@ -372,7 +371,7 @@ then
         then
           upload_item installer "$package" binary/octet-stream
         else
-          record_failure "Failed to upload $package_id package."
+          record_failure "Failed to upload $package_id package ($package::$additional_package_name)."
         fi
       done
       export additional_package_name=""
diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py
index 52b4acbc94dd8974e3bf2c721e1e2e60f13c33cc..1d85aa297802ea473d49bca683dd805b5e6552ca 100755
--- a/indra/lib/python/indra/util/llmanifest.py
+++ b/indra/lib/python/indra/util/llmanifest.py
@@ -85,7 +85,8 @@ def get_default_platform(dummy):
             }[sys.platform]
 
 DEFAULT_SRCTREE = os.path.dirname(sys.argv[0])
-RELEASE_CHANNEL = 'Second Life Release'
+CHANNEL_VENDOR_BASE = 'Second Life'
+RELEASE_CHANNEL = CHANNEL_VENDOR_BASE + ' Release'
 
 ARGUMENTS=[
     dict(name='actions',
@@ -112,13 +113,14 @@ def get_default_platform(dummy):
          default="Release"),
     dict(name='dest', description='Destination directory.', default=DEFAULT_SRCTREE),
     dict(name='grid',
-         description="""Which grid the client will try to connect to. Even
-        though it's not strictly a grid, 'firstlook' is also an acceptable
-        value for this parameter.""",
-         default=""),
+         description="""Which grid the client will try to connect to.""",
+         default=None),
     dict(name='channel',
          description="""The channel to use for updates, packaging, settings name, etc.""",
          default='CHANNEL UNSET'),
+    dict(name='channel_suffix',
+         description="""Addition to the channel for packaging and channel value, but not application name (used internally)""",
+         default=None),
     dict(name='installer_name',
          description=""" The name of the file that the installer should be
         packaged up into. Only used on Linux at the moment.""",
@@ -213,9 +215,9 @@ def main():
             print "Unable to read versionfile '%s'" % args['versionfile']
             raise
 
-    # default and agni are default
-    if args['grid'] in ['default', 'agni']:
-        args['grid'] = ''
+    # unspecified, default, and agni are default
+    if args['grid'] in ['', 'default', 'agni']:
+        args['grid'] = None
 
     if 'actions' in args:
         args['actions'] = args['actions'].split()
@@ -286,21 +288,24 @@ def main():
         base_channel_name = args['channel']
         # Build each additional package.
         package_id_list = additional_packages.split(" ")
+        args['channel'] = base_channel_name
         for package_id in package_id_list:
             try:
-                args['package_id'] = package_id
-                args['channel'] = base_channel_name + os.environ[package_id + "_viewer_channel_suffix"]
+                if package_id + "_viewer_channel_suffix" in os.environ:
+                    args['channel_suffix'] = os.environ[package_id + "_viewer_channel_suffix"]
+                else:
+                    args['channel_suffix'] = None
                 if package_id + "_sourceid" in os.environ:
                     args['sourceid'] = os.environ[package_id + "_sourceid"]
                 else:
-                    args['sourceid'] = ""
+                    args['sourceid'] = None
                 args['dest'] = base_dest_prefix + os.sep + package_id + os.sep + base_dest_postfix
             except KeyError:
                 sys.stderr.write("Failed to create package for package_id: %s" % package_id)
                 sys.stderr.flush()
                 continue
             if touch:
-                print 'Creating additional package for ', package_id, ' in ', args['dest']
+                print 'Creating additional package for "', package_id, '" in ', args['dest']
             wm = LLManifest.for_platform(args['platform'], args.get('arch'))(args)
             wm.do(*args['actions'])
             if touch:
@@ -332,7 +337,7 @@ class LLManifest(object):
     manifests = {}
     def for_platform(self, platform, arch = None):
         if arch:
-            platform = platform + '_' + arch
+            platform = platform + '_' + arch + '_'
         return self.manifests[platform.lower()]
     for_platform = classmethod(for_platform)
 
@@ -349,8 +354,6 @@ def __init__(self, args):
         self.created_paths = []
         self.package_name = "Unknown"
         
-    def default_grid(self):
-        return self.args.get('grid', None) == ''
     def default_channel(self):
         return self.args.get('channel', None) == RELEASE_CHANNEL
 
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 1fea6dea9f3b493cd4c827486f1fd41d22b77b67..c5e1cde4e6c765b2c17a8c804110961276063d26 100755
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -1748,6 +1748,7 @@ if (WINDOWS)
       ARGS
         ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py
         --actions=copy
+        --arch=${ARCH}
         --artwork=${ARTWORK_DIR}
         --build=${CMAKE_CURRENT_BINARY_DIR}
         --buildtype=${CMAKE_BUILD_TYPE}
@@ -1815,6 +1816,7 @@ if (WINDOWS)
         COMMAND ${PYTHON_EXECUTABLE}
         ARGS
           ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py
+          --arch=${ARCH}
           --artwork=${ARTWORK_DIR}
           --build=${CMAKE_CURRENT_BINARY_DIR}
           --buildtype=${CMAKE_BUILD_TYPE}
@@ -1939,7 +1941,6 @@ if (LINUX)
         --configuration=${CMAKE_CFG_INTDIR}
         --dest=${CMAKE_CURRENT_BINARY_DIR}/packaged
         --grid=${GRID}
-        --installer_name=${product}
         --source=${CMAKE_CURRENT_SOURCE_DIR}
         --touch=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/.${product}.touched
       DEPENDS
@@ -2017,6 +2018,7 @@ if (DARWIN)
     ARGS
       ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py
       --actions=copy
+      --arch=${ARCH}
       --artwork=${ARTWORK_DIR}
       --build=${CMAKE_CURRENT_BINARY_DIR}
       --buildtype=${CMAKE_BUILD_TYPE}
@@ -2049,6 +2051,7 @@ if (DARWIN)
         COMMAND ${PYTHON_EXECUTABLE}
         ARGS
           ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py
+          --arch=${ARCH}
           --artwork=${ARTWORK_DIR}
           --build=${CMAKE_CURRENT_BINARY_DIR}
           --buildtype=${CMAKE_BUILD_TYPE}
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index 8b7b0b52e55c33b0447d3fd1c564c8b7b4a42ed5..3609cf77078c22803a5732ba2871c3414b95d826 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-3.6.12
+3.6.13
diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi
index 8a6114f0d5736c8c0bcf3e0eac16246d00cb0145..dd316cdbdf420ca77d9947d5baa8fd9d10a7c8c0 100755
--- a/indra/newview/installers/windows/installer_template.nsi
+++ b/indra/newview/installers/windows/installer_template.nsi
@@ -72,16 +72,8 @@ LangString LanguageCode ${LANG_RUSSIAN}  "ru"
 LangString LanguageCode ${LANG_TURKISH}  "tr"
 LangString LanguageCode ${LANG_TRADCHINESE}  "zh"
 
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Tweak for different servers/builds (this placeholder is replaced by viewer_manifest.py)
-;; For example:
-;; !define INSTFLAGS "%(flags)s"
-;; !define INSTNAME   "SecondLife%(grid_caps)s"
-;; !define SHORTCUT   "Second Life (%(grid_caps)s)"
-;; !define URLNAME   "secondlife%(grid)s"
-;; !define UNINSTALL_SETTINGS 1
-
-%%GRID_VARS%%
+;; this placeholder is replaced by viewer_manifest.py
+%%INST_VARS%%
 
 Name ${INSTNAME}
 
@@ -109,7 +101,6 @@ Page instfiles
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 Var INSTPROG
 Var INSTEXE
-Var INSTFLAGS
 Var INSTSHORTCUT
 Var COMMANDLINE         ; command line passed to this installer, set in .onInit
 Var SHORTCUT_LANG_PARAM ; "--set InstallLanguage de", passes language to viewer
@@ -147,7 +138,7 @@ label_ask_launch:
         
 label_launch:
 	# Assumes SetOutPath $INSTDIR
-	Exec '"$INSTDIR\$INSTEXE" $INSTFLAGS $SHORTCUT_LANG_PARAM'
+	Exec '"$INSTDIR\$INSTEXE" $SHORTCUT_LANG_PARAM'
 label_no_launch:
 	Pop $R0
 FunctionEnd
@@ -720,7 +711,6 @@ ShowUninstDetails show
 Section Uninstall
 
 ; Start with some default values.
-StrCpy $INSTFLAGS ""
 StrCpy $INSTPROG "${INSTNAME}"
 StrCpy $INSTEXE "${INSTEXE}"
 StrCpy $INSTSHORTCUT "${SHORTCUT}"
@@ -919,7 +909,6 @@ Section ""						; (default section)
 SetShellVarContext all			; install for all users (if you change this, change it in the uninstall as well)
 
 ; Start with some default values.
-StrCpy $INSTFLAGS "${INSTFLAGS}"
 StrCpy $INSTPROG "${INSTNAME}"
 StrCpy $INSTEXE "${INSTEXE}"
 StrCpy $INSTSHORTCUT "${SHORTCUT}"
@@ -966,7 +955,7 @@ StrCpy $SHORTCUT_LANG_PARAM "--set InstallLanguage $(LanguageCode)"
 CreateDirectory	"$SMPROGRAMS\$INSTSHORTCUT"
 SetOutPath "$INSTDIR"
 CreateShortCut	"$SMPROGRAMS\$INSTSHORTCUT\$INSTSHORTCUT.lnk" \
-				"$INSTDIR\$INSTEXE" "$INSTFLAGS $SHORTCUT_LANG_PARAM"
+				"$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM"
 
 
 WriteINIStr		"$SMPROGRAMS\$INSTSHORTCUT\SL Create Account.url" \
@@ -985,9 +974,9 @@ CreateShortCut	"$SMPROGRAMS\$INSTSHORTCUT\Uninstall $INSTSHORTCUT.lnk" \
 ; Other shortcuts
 SetOutPath "$INSTDIR"
 CreateShortCut "$DESKTOP\$INSTSHORTCUT.lnk" \
-        "$INSTDIR\$INSTEXE" "$INSTFLAGS $SHORTCUT_LANG_PARAM"
+        "$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM"
 CreateShortCut "$INSTDIR\$INSTSHORTCUT.lnk" \
-        "$INSTDIR\$INSTEXE" "$INSTFLAGS $SHORTCUT_LANG_PARAM"
+        "$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM"
 CreateShortCut "$INSTDIR\Uninstall $INSTSHORTCUT.lnk" \
 				'"$INSTDIR\uninst.exe"' ''
 
@@ -996,7 +985,6 @@ CreateShortCut "$INSTDIR\Uninstall $INSTSHORTCUT.lnk" \
 ; Write registry
 WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "" "$INSTDIR"
 WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Version" "${VERSION_LONG}"
-WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Flags" "$INSTFLAGS"
 WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Shortcut" "$INSTSHORTCUT"
 WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Exe" "$INSTEXE"
 WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "DisplayName" "$INSTPROG (remove only)"
@@ -1009,13 +997,13 @@ WriteRegStr HKEY_CLASSES_ROOT "${URLNAME}" "URL Protocol" ""
 WriteRegStr HKEY_CLASSES_ROOT "${URLNAME}\DefaultIcon" "" '"$INSTDIR\$INSTEXE"'
 ;; URL param must be last item passed to viewer, it ignores subsequent params
 ;; to avoid parameter injection attacks.
-WriteRegExpandStr HKEY_CLASSES_ROOT "${URLNAME}\shell\open\command" "" '"$INSTDIR\$INSTEXE" $INSTFLAGS -url "%1"'
+WriteRegExpandStr HKEY_CLASSES_ROOT "${URLNAME}\shell\open\command" "" '"$INSTDIR\$INSTEXE" -url "%1"'
 WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info"(default)" "URL:Second Life"
 WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info" "URL Protocol" ""
 WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info\DefaultIcon" "" '"$INSTDIR\$INSTEXE"'
 ;; URL param must be last item passed to viewer, it ignores subsequent params
 ;; to avoid parameter injection attacks.
-WriteRegExpandStr HKEY_CLASSES_ROOT "x-grid-location-info\shell\open\command" "" '"$INSTDIR\$INSTEXE" $INSTFLAGS -url "%1"'
+WriteRegExpandStr HKEY_CLASSES_ROOT "x-grid-location-info\shell\open\command" "" '"$INSTDIR\$INSTEXE" -url "%1"'
 
 ; write out uninstaller
 WriteUninstaller "$INSTDIR\uninst.exe"
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 894e3684276f008247dea56cf708d68b0bb657f8..9e8623c1f9c12910a6eff8c81ea8bdde5bb8c09f 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -38,7 +38,7 @@
 # Put it FIRST because some of our build hosts have an ancient install of
 # indra.util.llmanifest under their system Python!
 sys.path.insert(0, os.path.join(viewer_dir, os.pardir, "lib", "python"))
-from indra.util.llmanifest import LLManifest, main, proper_windows_path, path_ancestors
+from indra.util.llmanifest import LLManifest, main, proper_windows_path, path_ancestors, CHANNEL_VENDOR_BASE, RELEASE_CHANNEL
 try:
     from llbase import llsd
 except ImportError:
@@ -112,21 +112,29 @@ def construct(self):
                                   Persist=1,
                                   Type='String',
                                   Value=''),
+                    CmdLineGridChoice=dict(Comment='Default grid',
+                                  Persist=0,
+                                  Type='String',
+                                  Value=''),
                     CmdLineChannel=dict(Comment='Command line specified channel name',
                                         Persist=0,
                                         Type='String',
                                         Value=''))
                 settings_install = {}
-                for key, setting in (("sourceid", "sourceid"),
-                                     ("channel", "CmdLineChannel")):
-                    if key in self.args:
-                        # only set if value is non-empty
-                        if self.args[key]:
-                            # copy corresponding setting from settings_template
-                            settings_install[setting] = settings_template[setting].copy()
-                            # then fill in Value
-                            settings_install[setting]["Value"] = self.args[key]
-                            print "Put %s '%s' in settings_install.xml" % (setting, self.args[key])
+                if 'sourceid' in self.args and self.args['sourceid']:
+                    settings_install['sourceid'] = settings_template['sourceid'].copy()
+                    settings_install['sourceid']['Value'] = self.args['sourceid']
+                    print "Set sourceid in settings_install.xml to '%s'" % self.args['sourceid']
+
+                if 'channel_suffix' in self.args and self.args['channel_suffix']:
+                    settings_install['CmdLineChannel'] = settings_template['CmdLineChannel'].copy()
+                    settings_install['CmdLineChannel']['Value'] = self.channel_with_pkg_suffix()
+                    print "Set CmdLineChannel in settings_install.xml to '%s'" % self.channel_with_pkg_suffix()
+
+                if 'grid' in self.args and self.args['grid']:
+                    settings_install['CmdLineGridChoice'] = settings_template['CmdLineGridChoice'].copy()
+                    settings_install['CmdLineGridChoice']['Value'] = self.grid()
+                    print "Set CmdLineGridChoice in settings_install.xml to '%s'" % self.grid()
 
                 # did we actually copy anything into settings_install dict?
                 if settings_install:
@@ -197,62 +205,72 @@ def construct(self):
 
     def grid(self):
         return self.args['grid']
+
     def channel(self):
         return self.args['channel']
-    def channel_unique(self):
-        return self.channel().replace("Second Life", "").strip()
-    def channel_oneword(self):
-        return "".join(self.channel_unique().split())
-    def channel_lowerword(self):
-        return self.channel_oneword().lower()
+
+    def channel_with_pkg_suffix(self):
+        fullchannel=self.channel()
+        if 'channel_suffix' in self.args and self.args['channel_suffix']:
+            fullchannel+=' '+self.args['channel_suffix']
+        return fullchannel
+
+    def channel_variant(self):
+        global CHANNEL_VENDOR_BASE
+        return self.channel().replace(CHANNEL_VENDOR_BASE, "").strip()
+
+    def channel_type(self): # returns 'release', 'beta', 'project', or 'test'
+        global CHANNEL_VENDOR_BASE
+        channel_qualifier=self.channel().replace(CHANNEL_VENDOR_BASE, "").lower().strip()
+        if channel_qualifier.startswith('release'):
+            channel_type='release'
+        elif channel_qualifier.startswith('beta'):
+            channel_type='beta'
+        elif channel_qualifier.startswith('project'):
+            channel_type='project'
+        else:
+            channel_type='test'
+        return channel_type
+
+    def channel_variant_app_suffix(self):
+        # get any part of the compiled channel name after the CHANNEL_VENDOR_BASE
+        suffix=self.channel_variant()
+        # by ancient convention, we don't use Release in the app name
+        if self.channel_type() == 'release':
+            suffix=suffix.replace('Release', '').strip()
+        # for the base release viewer, suffix will now be null - for any other, append what remains
+        if len(suffix) > 0:
+            suffix = "_"+ ("_".join(suffix.split()))
+        # the additional_packages mechanism adds more to the installer name (but not to the app name itself)
+        if 'channel_suffix' in self.args and self.args['channel_suffix']:
+            suffix+='_'+("_".join(self.args['channel_suffix'].split()))
+        return suffix
+
+    def installer_base_name(self):
+        global CHANNEL_VENDOR_BASE
+        # a standard map of strings for replacing in the templates
+        substitution_strings = {
+            'channel_vendor_base' : '_'.join(CHANNEL_VENDOR_BASE.split()),
+            'channel_variant_underscores':self.channel_variant_app_suffix(),
+            'version_underscores' : '_'.join(self.args['version']),
+            'arch':self.args['arch']
+            }
+        return "%(channel_vendor_base)s%(channel_variant_underscores)s_%(version_underscores)s_%(arch)s" % substitution_strings
 
     def app_name(self):
-        app_suffix='Test'
-        channel_type=self.channel_lowerword()
-        if channel_type.startswith('release') :
+        global CHANNEL_VENDOR_BASE
+        channel_type=self.channel_type()
+        if channel_type == 'release':
             app_suffix='Viewer'
-        elif re.match('^(beta|project).*',channel_type) :
-            app_suffix=self.channel_unique()
-        return "Second Life "+app_suffix
-        
+        else:
+            app_suffix=self.channel_variant()
+        return CHANNEL_VENDOR_BASE + ' ' + app_suffix
+
+    def app_name_oneword(self):
+        return ''.join(self.app_name().split())
+    
     def icon_path(self):
-        icon_path="icons/"
-        channel_type=self.channel_lowerword()
-        print "Icon channel type '%s'" % channel_type
-        if channel_type.startswith('release') :
-            icon_path += 'release'
-        elif re.match('^beta.*',channel_type) :
-            icon_path += 'beta'
-        elif re.match('^project.*',channel_type) :
-            icon_path += 'project'
-        else :
-            icon_path += 'test'
-        return icon_path
-
-    def flags_list(self):
-        """ Convenience function that returns the command-line flags
-        for the grid"""
-
-        # The original role of this method seems to have been to build a
-        # grid-specific viewer: one that would, on launch, preselect a
-        # particular grid. (Apparently that dates back to when the protocol
-        # between viewer and simulator required them to be updated in
-        # lockstep, so that "the beta grid" required "a beta viewer.") But
-        # those viewer command-line switches no longer work without tweaking
-        # user_settings/grids.xml. In fact, going forward, it's unclear what
-        # use case that would address.
-
-        # This method also set a channel-specific (or grid-and-channel-
-        # specific) user_settings/settings_something.xml file. It has become
-        # clear that saving user settings in a channel-specific file causes
-        # more problems (confusion) than it solves, so we've discontinued that.
-
-        # In fact we now avoid forcing viewer command-line switches at all,
-        # instead introducing a settings_install.xml file. Command-line
-        # switches don't aggregate well; for instance the generated --channel
-        # switch actually prevented the user specifying --channel on the
-        # command line. Settings files have well-defined override semantics.
-        return None
+        return "icons/" + self.channel_type()
 
     def extract_names(self,src):
         try:
@@ -277,15 +295,9 @@ def extract_names(self,src):
         random.shuffle(names)
         return ', '.join(names)
 
-class WindowsManifest(ViewerManifest):
+class Windows_i686_Manifest(ViewerManifest):
     def final_exe(self):
-        app_suffix="Test"
-        channel_type=self.channel_lowerword()
-        if channel_type.startswith('release') :
-            app_suffix=''
-        elif re.match('^(beta|project).*',channel_type) :
-            app_suffix=''.join(self.channel_unique().split())
-        return "SecondLife"+app_suffix+".exe"
+        return self.app_name_oneword()+".exe"
 
     def test_msvcrt_and_copy_action(self, src, dst):
         # This is used to test a dll manifest.
@@ -334,7 +346,7 @@ def test_for_no_msvcrt_manifest_and_copy_action(self, src, dst):
             print "Doesn't exist:", src
         
     def construct(self):
-        super(WindowsManifest, self).construct()
+        super(Windows_i686_Manifest, self).construct()
 
         if self.is_packaging_viewer():
             # Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe.
@@ -567,65 +579,33 @@ def package_finish(self):
             'version_short' : '.'.join(self.args['version'][:-1]),
             'version_dashes' : '-'.join(self.args['version']),
             'final_exe' : self.final_exe(),
-            'grid':self.args['grid'],
-            'grid_caps':self.args['grid'].upper(),
             'flags':'',
-            'channel':self.channel(),
-            'channel_oneword':self.channel_oneword(),
-            'channel_unique':self.channel_unique(),
-            'subchannel_underscores':'_'.join(self.channel_unique().split())
+            'app_name':self.app_name(),
+            'app_name_oneword':self.app_name_oneword()
             }
 
+        installer_file = self.installer_base_name() + '_Setup.exe'
+        substitution_strings['installer_file'] = installer_file
+        
         version_vars = """
         !define INSTEXE  "%(final_exe)s"
         !define VERSION "%(version_short)s"
         !define VERSION_LONG "%(version)s"
         !define VERSION_DASHES "%(version_dashes)s"
         """ % substitution_strings
-        if self.default_channel():
-            if self.default_grid():
-                # release viewer
-                installer_file = "Second_Life_%(version_dashes)s_Setup.exe"
-                grid_vars_template = """
-                OutFile "%(installer_file)s"
-                !define INSTFLAGS "%(flags)s"
-                !define INSTNAME   "SecondLifeViewer"
-                !define SHORTCUT   "Second Life Viewer"
-                !define URLNAME   "secondlife"
-                Caption "Second Life"
-                """
-            else:
-                # alternate grid viewer
-                installer_file = "Second_Life_%(version_dashes)s_(%(grid_caps)s)_Setup.exe"
-                grid_vars_template = """
-                OutFile "%(installer_file)s"
-                !define INSTFLAGS "%(flags)s"
-                !define INSTNAME   "SecondLife%(grid_caps)s"
-                !define SHORTCUT   "Second Life (%(grid_caps)s)"
-                !define URLNAME   "secondlife%(grid)s"
-                !define UNINSTALL_SETTINGS 1
-                Caption "Second Life %(grid)s ${VERSION}"
-                """
+        
+        if self.channel_type() == 'release':
+            substitution_strings['caption'] = CHANNEL_VENDOR_BASE
         else:
-            # some other channel (grid name not used)
-            installer_file = "Second_Life_%(version_dashes)s_%(subchannel_underscores)s_Setup.exe"
-            grid_vars_template = """
+            substitution_strings['caption'] = self.app_name() + ' ${VERSION}'
+
+        inst_vars_template = """
             OutFile "%(installer_file)s"
-            !define INSTFLAGS "%(flags)s"
-            !define INSTNAME   "SecondLife%(channel_oneword)s"
-            !define SHORTCUT   "%(channel)s"
+            !define INSTNAME   "%(app_name_oneword)s"
+            !define SHORTCUT   "%(app_name)s"
             !define URLNAME   "secondlife"
-            !define UNINSTALL_SETTINGS 1
-            Caption "%(channel)s ${VERSION}"
+            Caption "%(caption)s"
             """
-        if 'installer_name' in self.args:
-            installer_file = self.args['installer_name']
-        else:
-            installer_file = installer_file % substitution_strings
-        if len(self.args['package_id']) > 0:
-            installer_file = installer_file.replace(self.args['package_id'], "")
-            installer_file = installer_file.replace(".exe", self.args['package_id'] + ".exe")
-        substitution_strings['installer_file'] = installer_file
 
         tempfile = "secondlife_setup_tmp.nsi"
         # the following replaces strings in the nsi template
@@ -633,7 +613,7 @@ def package_finish(self):
         self.replace_in("installers/windows/installer_template.nsi", tempfile, {
                 "%%VERSION%%":version_vars,
                 "%%SOURCE%%":self.get_src_prefix(),
-                "%%GRID_VARS%%":grid_vars_template % substitution_strings,
+                "%%INST_VARS%%":inst_vars_template % substitution_strings,
                 "%%INSTALL_FILES%%":self.nsi_file_commands(True),
                 "%%DELETE_FILES%%":self.nsi_file_commands(False)})
 
@@ -663,7 +643,7 @@ def package_finish(self):
         self.package_file = installer_file
 
 
-class DarwinManifest(ViewerManifest):
+class Darwin_i386_Manifest(ViewerManifest):
     def is_packaging_viewer(self):
         # darwin requires full app bundle packaging even for debugging.
         return True
@@ -685,7 +665,7 @@ def construct(self):
 
             # most everything goes in the Resources directory
             if self.prefix(src="", dst="Resources"):
-                super(DarwinManifest, self).construct()
+                super(Darwin_i386_Manifest, self).construct()
 
                 if self.prefix("cursors_mac"):
                     self.path("*.tif")
@@ -821,6 +801,7 @@ def copy_finish(self):
             self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script))
 
     def package_finish(self):
+        global CHANNEL_VENDOR_BASE
         # Sign the app if requested.
         if 'signature' in self.args:
             identity = self.args['signature']
@@ -850,17 +831,9 @@ def package_finish(self):
         # MBW -- If the mounted volume name changes, it breaks the .DS_Store's background image and icon positioning.
         #  If we really need differently named volumes, we'll need to create multiple DS_Store file images, or use some other trick.
 
-        volname="Second Life Installer"  # DO NOT CHANGE without understanding comment above
+        volname=CHANNEL_VENDOR_BASE+" Installer"  # DO NOT CHANGE without understanding comment above
 
-        if len(self.args['package_id']) > 0:
-            imagename = imagename + self.args['package_id']
-        elif self.default_channel():
-            if not self.default_grid():
-                # beta case
-                imagename = imagename + '_' + self.args['grid'].upper()
-        else:
-            # first look, etc
-            imagename = imagename + '_' + self.channel_oneword().upper()
+        imagename = self.installer_base_name()
 
         sparsename = imagename + ".sparseimage"
         finalname = imagename + ".dmg"
@@ -894,7 +867,7 @@ def package_finish(self):
             # will use the release .DS_Store, and will look broken.
             # - Ambroff 2008-08-20
             dmg_template = os.path.join(
-                'installers', 'darwin', '%s-dmg' % self.channel_lowerword())
+                'installers', 'darwin', '%s-dmg' % self.channel_type())
 
             if not os.path.exists (self.src_path_of(dmg_template)):
                 dmg_template = os.path.join ('installers', 'darwin', 'release-dmg')
@@ -977,8 +950,9 @@ def construct(self):
             # recurse
             self.end_prefix("res-sdl")
 
-        # Get the icons based on the channel
+        # Get the icons based on the channel type
         icon_path = self.icon_path()
+        print "DEBUG: icon_path '%s'" % icon_path
         if self.prefix(src=icon_path, dst="") :
             self.path("secondlife_256.png","secondlife_icon.png")
             if self.prefix(src="",dst="res-sdl") :
@@ -1004,18 +978,7 @@ def copy_finish(self):
             self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script))
 
     def package_finish(self):
-        if 'installer_name' in self.args:
-            installer_name = self.args['installer_name']
-        else:
-            installer_name_components = ['SecondLife_', self.args.get('arch')]
-            installer_name_components.extend(self.args['version'])
-            installer_name = "_".join(installer_name_components)
-            if self.default_channel():
-                if not self.default_grid():
-                    installer_name += '_' + self.args['grid'].upper()
-            else:
-                installer_name += '_' + self.channel_oneword().upper()
-        installer_name = installer_name + self.args['package_id']
+        installer_name = self.installer_base_name()
 
         self.strip_binaries()
 
@@ -1057,9 +1020,9 @@ def strip_binaries(self):
             print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build"
             self.run_command(r"find %(d)r/bin %(d)r/lib -type f \! -name update_install | xargs --no-run-if-empty strip -S" % {'d': self.get_dst_prefix()} ) # makes some small assumptions about our packaged dir structure
 
-class Linux_i686Manifest(LinuxManifest):
+class Linux_i686_Manifest(LinuxManifest):
     def construct(self):
-        super(Linux_i686Manifest, self).construct()
+        super(Linux_i686_Manifest, self).construct()
 
         if self.prefix("../packages/lib/release", dst="lib"):
             self.path("libapr-1.so")
@@ -1145,9 +1108,9 @@ def construct(self):
             self.strip_binaries()
 
 
-class Linux_x86_64Manifest(LinuxManifest):
+class Linux_x86_64_Manifest(LinuxManifest):
     def construct(self):
-        super(Linux_x86_64Manifest, self).construct()
+        super(Linux_x86_64_Manifest, self).construct()
 
         # support file for valgrind debug tool
         self.path("secondlife-i686.supp")
diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp
index c28ad76c77f81d18fed2c26ef18d0822e0bbf9b2..c42112af807d24e9e94a9e51342a3f2706c2b305 100755
--- a/indra/viewer_components/updater/llupdatedownloader.cpp
+++ b/indra/viewer_components/updater/llupdatedownloader.cpp
@@ -77,7 +77,8 @@ class LLUpdateDownloader::Implementation:
 	void run(void);
 	void startDownloading(LLURI const & uri, std::string const & hash);
 	void throwOnCurlError(CURLcode code);
-	bool validateDownload(void);
+	bool validateDownload(const std::string& filePath);
+	bool validateOrRemove(const std::string& filePath);
 
 	LOG_CLASS(LLUpdateDownloader::Implementation);
 };
@@ -295,9 +296,8 @@ void LLUpdateDownloader::Implementation::resume(void)
 			{
 				resumeDownloading(fileStatus.st_size);
 			}
-			else if(!validateDownload())
+			else if(!validateOrRemove(filePath))
 			{
-				LLFile::remove(filePath);
 				download(LLURI(mDownloadData["url"].asString()),
 						 mDownloadData["hash"].asString(),
 						 mDownloadData["update_channel"].asString(),
@@ -421,19 +421,13 @@ void LLUpdateDownloader::Implementation::run(void)
 	if(code == CURLE_OK)
 	{
 		LLFile::remove(mDownloadRecordPath);
-		if(validateDownload())
+		if(validateOrRemove(mDownloadData["path"]))
 		{
 			LL_INFOS("UpdaterService") << "download successful" << LL_ENDL;
 			mClient.downloadComplete(mDownloadData);
 		}
 		else
 		{
-			LL_INFOS("UpdaterService") << "download failed hash check" << LL_ENDL;
-			std::string filePath = mDownloadData["path"].asString();
-			if(filePath.size() != 0)
-			{
-				LLFile::remove(filePath);
-			}
 			mClient.downloadError("failed hash check");
 		}
 	}
@@ -449,7 +443,9 @@ void LLUpdateDownloader::Implementation::run(void)
 		LLFile::remove(mDownloadRecordPath);
 		if(mDownloadData.has("path"))
 		{
-			LLFile::remove(mDownloadData["path"].asString());
+			std::string filePath = mDownloadData["path"].asString();
+			LL_INFOS("UpdaterService") << "removing " << filePath << LL_ENDL;
+			LLFile::remove(filePath);
 		}
 		mClient.downloadError("curl error");
 	}
@@ -561,31 +557,49 @@ void LLUpdateDownloader::Implementation::throwOnCurlError(CURLcode code)
 	}
 }
 
+bool LLUpdateDownloader::Implementation::validateOrRemove(const std::string& filePath)
+{
+	bool valid = validateDownload(filePath);
+	if (! valid)
+	{
+		LL_INFOS("UpdaterService") << "removing " << filePath << LL_ENDL;
+		LLFile::remove(filePath);
+	}
+	return valid;
+}
 
-bool LLUpdateDownloader::Implementation::validateDownload(void)
+bool LLUpdateDownloader::Implementation::validateDownload(const std::string& filePath)
 {
-	std::string filePath = mDownloadData["path"].asString();
 	llifstream fileStream(filePath, std::ios_base::in | std::ios_base::binary);
 	if(!fileStream)
 	{
+		LL_INFOS("UpdaterService") << "can't open " << filePath << ", invalid" << LL_ENDL;
 		return false;
 	}
 
 	std::string hash = mDownloadData["hash"].asString();
-	if(hash.size() != 0)
+	if (! hash.empty())
 	{
-		LL_INFOS("UpdaterService") << "checking hash..." << LL_ENDL;
 		char digest[33];
 		LLMD5(fileStream).hex_digest(digest);
-		if(hash != digest)
+		if (hash == digest)
+		{
+			LL_INFOS("UpdaterService") << "verified hash " << hash
+									   << " for downloaded " << filePath << LL_ENDL;
+			return true;
+		}
+		else
 		{
-			LL_WARNS("UpdaterService") << "download hash mismatch; expected " << hash <<
-				" but download is " << digest << LL_ENDL;
+			LL_WARNS("UpdaterService") << "download hash mismatch for "
+									   << filePath << ": expected " << hash
+									   << " but computed " << digest << LL_ENDL;
+			return false;
 		}
-		return hash == digest;
 	}
 	else
 	{
+		LL_INFOS("UpdaterService") << "no hash specified for " << filePath
+								   << ", unverified" << LL_ENDL;
 		return true; // No hash check provided.
 	}
 }
diff --git a/indra/viewer_components/updater/llupdaterservice.cpp b/indra/viewer_components/updater/llupdaterservice.cpp
index 16950e1d62ea821010119022e227c2b7ecba3654..cb3be5bbdcd5a7879a601ee57c72243a3e612684 100755
--- a/indra/viewer_components/updater/llupdaterservice.cpp
+++ b/indra/viewer_components/updater/llupdaterservice.cpp
@@ -296,37 +296,49 @@ bool LLUpdaterServiceImpl::checkForInstall(bool launchInstaller)
 		update_marker.close();
 
 		// Get the path to the installer file.
-		LLSD path = update_info.get("path");
-		if(update_info["current_version"].asString() != ll_get_version())
+		std::string path(update_info.get("path"));
+		std::string downloader_version(update_info["current_version"]);
+		if (downloader_version != ll_get_version())
 		{
 			// This viewer is not the same version as the one that downloaded
-			// the update.  Do not install this update.
-			if(!path.asString().empty())
+			// the update. Do not install this update.
+			LL_INFOS("UpdaterService") << "ignoring update downloaded by "
+									   << "different viewer version "
+									   << downloader_version << LL_ENDL;
+			if (! path.empty())
 			{
-				LL_INFOS("UpdaterService") << "ignoring update dowloaded by different client version" << LL_ENDL;;
-				LLFile::remove(path.asString());
+				LL_INFOS("UpdaterService") << "removing " << path << LL_ENDL;
+				LLFile::remove(path);
 				LLFile::remove(update_marker_path());
 			}
-			else
-			{
-				; // Nothing to clean up.
-			}
-			
+
 			foundInstall = false;
 		} 
-		else if(path.isDefined() && !path.asString().empty())
+		else if (path.empty())
+		{
+			LL_WARNS("UpdaterService") << "Marker file " << update_marker_path()
+									   << " 'path' entry empty, ignoring" << LL_ENDL;
+			foundInstall = false;
+		}
+		else if (! LLFile::isfile(path))
+		{
+			LL_WARNS("UpdaterService") << "Nonexistent installer " << path
+									   << ", ignoring" << LL_ENDL;
+			foundInstall = false;
+		}
+		else
 		{
 			if(launchInstaller)
 			{
 				setState(LLUpdaterService::INSTALLING);
-				
+
 				LLFile::remove(update_marker_path());
 
 				int result = ll_install_update(install_script_path(),
-											   update_info["path"].asString(),
+											   path,
 											   update_info["required"].asBoolean(),
 											   install_script_mode());	
-				
+
 				if((result == 0) && mAppExitCallback)
 				{
 					mAppExitCallback();
@@ -360,7 +372,8 @@ bool LLUpdaterServiceImpl::checkForResume()
 			LLSD download_info;
 			LLSDSerialize::fromXMLDocument(download_info, download_marker_stream);
 			download_marker_stream.close();
-			if(download_info["current_version"].asString() == ll_get_version())
+			std::string downloader_version(download_info["current_version"]);
+			if (downloader_version == ll_get_version())
 			{
 				mIsDownloading = true;
 				mNewVersion = download_info["update_version"].asString();
@@ -371,10 +384,13 @@ bool LLUpdaterServiceImpl::checkForResume()
 			else 
 			{
 				// The viewer that started this download is not the same as this viewer; ignore.
-				LL_INFOS("UpdaterService") << "ignoring partial download from different viewer version" << LL_ENDL;;
+				LL_INFOS("UpdaterService") << "ignoring partial download "
+										   << "from different viewer version "
+										   << downloader_version << LL_ENDL;
 				std::string path = download_info["path"].asString();
 				if(!path.empty())
 				{
+					LL_INFOS("UpdaterService") << "removing " << path << LL_ENDL;
 					LLFile::remove(path);
 				}
 				LLFile::remove(download_marker_path);
@@ -539,7 +555,7 @@ bool LLUpdaterServiceImpl::onMainLoop(LLSD const & event)
 		// Check for failed install.
 		if(LLFile::isfile(ll_install_failed_marker_path()))
 		{
-			LL_DEBUGS("UpdaterService") << "found marker " << ll_install_failed_marker_path() << LL_ENDL;;
+			LL_DEBUGS("UpdaterService") << "found marker " << ll_install_failed_marker_path() << LL_ENDL;
 			int requiredValue = 0; 
 			{
 				llifstream stream(ll_install_failed_marker_path());
@@ -552,12 +568,12 @@ bool LLUpdaterServiceImpl::onMainLoop(LLSD const & event)
 			// TODO: notify the user.
 			LL_WARNS("UpdaterService") << "last install attempt failed" << LL_ENDL;;
 			LLFile::remove(ll_install_failed_marker_path());
-			
+
 			LLSD event;
 			event["type"] = LLSD(LLUpdaterService::INSTALL_ERROR);
 			event["required"] = LLSD(requiredValue);
 			LLEventPumps::instance().obtain(LLUpdaterService::pumpName()).post(event);
-			
+
 			setState(LLUpdaterService::TERMINAL);
 		}
 		else
diff --git a/indra/viewer_components/updater/scripts/darwin/update_install.py b/indra/viewer_components/updater/scripts/darwin/update_install.py
index 10d507c9eff035b4a5b16fd49f0be46267f0efac..08f4f0ebb9649bd284d301090a690658107b1436 100755
--- a/indra/viewer_components/updater/scripts/darwin/update_install.py
+++ b/indra/viewer_components/updater/scripts/darwin/update_install.py
@@ -199,6 +199,11 @@ def fail(message):
         # prepare for other cleanup
         with Janitor(LOGF) as janitor:
 
+            # Under some circumstances, this script seems to be invoked with a
+            # nonexistent pathname. Check for that.
+            if not os.path.isfile(dmgfile):
+                fail(dmgfile + " has been deleted")
+
             # Try to derive the name of the running viewer app bundle from our
             # own pathname. (Hopefully the old viewer won't copy this script
             # to a temp dir before running!)
@@ -376,6 +381,13 @@ def fail(message):
             log(' '.join(command))
             subprocess.check_call(command, stdout=LOGF, stderr=subprocess.STDOUT)
 
+        # If all the above succeeded, delete the .dmg file. We don't do this
+        # as a janitor.later() operation because we only want to do it if we
+        # get this far successfully. Note that this is out of the scope of the
+        # Janitor: we must detach the .dmg before removing it!
+        log("rm " + dmgfile)
+        os.remove(dmgfile)
+
     except Exception, err:
         # Because we carefully set sys.excepthook -- and even modify it to log
         # the problem once we have our log file open -- you might think we