sensorlog_lite: Syncgroup create.

measured creates the syncgroup, transferring privileges on prefixes
to a specified admin blessing.
Updated runner scripts.

Change-Id: Id65d0d4cb1eaad88abfcc574cf79999b74b64f09
diff --git a/go/src/v.io/x/sensorlog/internal/config/defaults.go b/go/src/v.io/x/sensorlog/internal/config/defaults.go
index fdb3483..8e40688 100644
--- a/go/src/v.io/x/sensorlog/internal/config/defaults.go
+++ b/go/src/v.io/x/sensorlog/internal/config/defaults.go
@@ -5,9 +5,23 @@
 // Sensor Log configuration constants and default flag values.
 package config
 
+import (
+	"v.io/v23/naming"
+)
+
 const (
 	DefaultSbService = "syncbase"
 
 	AppName = "sensorlog_lite"
 	DBName  = "sldb"
+
+	SyncPriority = 42
+
+	StreamDefTable = "strdef"
 )
+
+var AllTables = []string{StreamDefTable}
+
+func SyncGroupName(publishService, devId string) string {
+	return naming.Join(publishService, "%%sync", "sllite", "dev", devId)
+}
diff --git a/go/src/v.io/x/sensorlog/internal/measure/doc.go b/go/src/v.io/x/sensorlog/internal/measure/doc.go
new file mode 100644
index 0000000..e753d44
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/internal/measure/doc.go
@@ -0,0 +1,8 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package measure implements Sensor Log measured methods, intended to run
+// against the measured device Syncbase. It supports configuring the device
+// syncgroup.
+package measure
diff --git a/go/src/v.io/x/sensorlog/internal/measure/syncgroup.go b/go/src/v.io/x/sensorlog/internal/measure/syncgroup.go
new file mode 100644
index 0000000..3b16a8b
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/internal/measure/syncgroup.go
@@ -0,0 +1,80 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Measured methods for syncgroup management.
+
+package measure
+
+import (
+	"fmt"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security/access"
+	nosql_wire "v.io/v23/services/syncbase/nosql"
+	"v.io/v23/syncbase/nosql"
+	"v.io/v23/verror"
+	"v.io/x/sensorlog_lite/internal/config"
+	"v.io/x/sensorlog_lite/internal/sbutil"
+)
+
+// InitSyncGroup creates the syncgroup for the measuring device devId, giving
+// full configuration access to admin. The syncgroup uses sgPublishSb and
+// sgMountTables for publishing (create/join) and syncing, respectively.
+// InitSyncGroup must not be called concurrently for the same devId, or
+// retried with different parameters for the same devId, otherwise behaviour
+// is unspecified.
+func InitSyncGroup(ctx *context.T, db nosql.Database, devId, admin, sgPublishSb string, sgMountTables []string) error {
+	sgName := config.SyncGroupName(sgPublishSb, devId)
+	// Check for syncgroup. If it already exists, we have nothing to do.
+	if sgs, err := db.GetSyncGroupNames(ctx); err != nil {
+		return err
+	} else {
+		for _, sg := range sgs {
+			if sg == sgName {
+				return nil
+			}
+		}
+	}
+
+	// Both measured and admin client have full permissions on the syncgroup.
+	sgAcl := access.Permissions{}
+	sbutil.AddPermsForPrincipal(&sgAcl, v23.GetPrincipal(ctx), access.AllTypicalTags()...)
+	sbutil.AddPermsForPattern(&sgAcl, admin, access.AllTypicalTags()...)
+
+	// Maps all syncgroup prefixes to ACLs.
+	prefixSpec := make(map[nosql_wire.SyncGroupPrefix]access.Permissions)
+
+	// StreamDef : <devId>
+	// Admin client has full permissions, measured drops to readonly.
+	prefixStreamDef := nosql_wire.SyncGroupPrefix{
+		TableName: config.StreamDefTable,
+		RowPrefix: devId,
+	}
+	aclStreamDef := access.Permissions{}
+	sbutil.AddPermsForPrincipal(&aclStreamDef, v23.GetPrincipal(ctx), access.Resolve, access.Read)
+	sbutil.AddPermsForPattern(&aclStreamDef, admin, access.AllTypicalTags()...)
+	prefixSpec[prefixStreamDef] = aclStreamDef
+
+	var prefixes []nosql_wire.SyncGroupPrefix
+	// Apply prefix ACLs to all syncgroup prefixes.
+	for prefix, prefixAcl := range prefixSpec {
+		// Ignore ErrNoAccess, assume we already dropped permissions.
+		err := db.Table(prefix.TableName).SetPrefixPermissions(ctx, nosql.Prefix(prefix.RowPrefix), prefixAcl)
+		if err != nil && verror.ErrorID(err) != verror.ErrNoAccess.ID {
+			return err
+		}
+		prefixes = append(prefixes, prefix)
+	}
+
+	sgSpec := nosql_wire.SyncGroupSpec{
+		Description: fmt.Sprintf("measured-main-%s", devId),
+		Perms:       sgAcl,
+		Prefixes:    prefixes,
+		MountTables: sgMountTables,
+	}
+	sgMemberInfo := nosql_wire.SyncGroupMemberInfo{SyncPriority: config.SyncPriority}
+
+	return db.SyncGroup(sgName).Create(ctx, sgSpec, sgMemberInfo)
+}
diff --git a/go/src/v.io/x/sensorlog/internal/measure/syncgroup_test.go b/go/src/v.io/x/sensorlog/internal/measure/syncgroup_test.go
new file mode 100644
index 0000000..5c2d473
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/internal/measure/syncgroup_test.go
@@ -0,0 +1,55 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package measure_test
+
+import (
+	"reflect"
+	"testing"
+
+	"v.io/v23/verror"
+	_ "v.io/x/ref/runtime/factories/generic"
+	sbtu "v.io/x/ref/services/syncbase/testutil"
+	"v.io/x/sensorlog_lite/internal/config"
+	"v.io/x/sensorlog_lite/internal/measure"
+	"v.io/x/sensorlog_lite/internal/sbutil"
+)
+
+func TestCreateSyncgroup(t *testing.T) {
+	_, ctxMeasured, sbName, _, cleanup := sbtu.SetupOrDieCustom("one", "one/sb", nil)
+	defer cleanup()
+
+	// Open app/db (create both) as measured.
+	db, err := sbutil.CreateOrOpenDB(ctxMeasured, sbName, config.AllTables)
+	if err != nil {
+		t.Fatalf("CreateOrOpenDB should have succeeded, got error: %v", err)
+	}
+
+	devId := "measured1"
+	admin := "root/two"
+	syncMts := []string{}
+
+	// Creating the syncgroup should succeed.
+	if err := measure.InitSyncGroup(ctxMeasured, db, devId, admin, sbName, syncMts); err != nil {
+		t.Fatalf("InitSyncGroup failed: %v", err)
+	}
+
+	sgName := config.SyncGroupName(sbName, devId)
+	if sgs, err := db.GetSyncGroupNames(ctxMeasured); err != nil {
+		t.Fatalf("GetSyncGroupNames failed: %v", err)
+	} else if got, want := sgs, []string{sgName}; !reflect.DeepEqual(got, want) {
+		t.Errorf("GetSyncGroupNames got: %v, want: %v", got, want)
+	}
+
+	// Creating the syncgroup should be idempotent.
+	if err := measure.InitSyncGroup(ctxMeasured, db, devId, admin, sbName, syncMts); err != nil {
+		t.Errorf("InitSyncGroup should be idempotent, retry failed: %v", err)
+	}
+
+	// measured should have dropped privileges on <StreamDefTable>/<devId>.
+	sgDataRow := db.Table(config.StreamDefTable).Row(devId + "$" + "foo")
+	if err := sgDataRow.Put(ctxMeasured, "bar"); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+		t.Errorf("Put by measured should have failed with ErrNoAccess, got: %v", err)
+	}
+}
diff --git a/go/src/v.io/x/sensorlog/internal/sbutil/syncbase.go b/go/src/v.io/x/sensorlog/internal/sbutil/syncbase.go
new file mode 100644
index 0000000..8c7c708
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/internal/sbutil/syncbase.go
@@ -0,0 +1,87 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Utilities for creating and opening the database in Syncbase.
+package sbutil
+
+import (
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/syncbase"
+	"v.io/v23/syncbase/nosql"
+	"v.io/v23/verror"
+	"v.io/x/sensorlog_lite/internal/config"
+)
+
+// CreateOrOpenDB opens the Sensor Log database hosted on specified Syncbase
+// instance, creating it if missing, initializing specified tables.
+func CreateOrOpenDB(ctx *context.T, sbService string, tables []string) (nosql.Database, error) {
+	aclFull := access.Permissions{}
+	// Allow everyone to resolve app/database to allow joining syncgroups.
+	AddPermsForPattern(&aclFull, string(security.AllPrincipals), access.Resolve)
+	// Restrict other permissions to self.
+	AddPermsForPrincipal(&aclFull, v23.GetPrincipal(ctx), access.Read, access.Write, access.Admin, access.Debug)
+
+	sbs := syncbase.NewService(sbService)
+	app := sbs.App(config.AppName)
+	if err := createIfMissing(ctx, app, aclFull); err != nil {
+		return nil, err
+	}
+
+	// TODO(ivanpi): Add schema version.
+	db := app.NoSQLDatabase(config.DBName, nil)
+	if err := createIfMissing(ctx, db, aclFull); err != nil {
+		return nil, err
+	}
+
+	// TODO(ivanpi): Add table schemas when available.
+	for _, tn := range tables {
+		tb := db.Table(tn)
+		if err := createIfMissing(ctx, tb, aclFull); err != nil {
+			return nil, err
+		}
+	}
+
+	return db, nil
+}
+
+// creatable is satisfied by Syncbase hierarchy layers (app, db, table) that
+// can be created and tested for existence.
+type creatable interface {
+	Create(ctx *context.T, acl access.Permissions) error
+	Exists(ctx *context.T) (bool, error)
+}
+
+// createIfMissing checks if the given creatable layer exists and, if not,
+// creates it.
+// TODO(ivanpi): Syncbase client helpers for MustExist / CreateIfMissing.
+func createIfMissing(ctx *context.T, target creatable, acl access.Permissions) error {
+	if exists, err := target.Exists(ctx); err != nil {
+		return err
+	} else if exists {
+		return nil
+	}
+	if err := target.Create(ctx, acl); err != nil && verror.ErrorID(err) != verror.ErrExist.ID {
+		return err
+	}
+	return nil
+}
+
+// AddPermsForPrincipal adds to the ACL all specified permissions for all
+// default blessings of the provided principal.
+func AddPermsForPrincipal(acl *access.Permissions, principal security.Principal, tags ...access.Tag) {
+	for _, pattern := range security.DefaultBlessingPatterns(principal) {
+		AddPermsForPattern(acl, string(pattern), tags...)
+	}
+}
+
+// AddPermsForPattern adds to the ACL all specified permissions for the
+// specified pattern.
+func AddPermsForPattern(acl *access.Permissions, pattern string, tags ...access.Tag) {
+	for _, tag := range tags {
+		acl.Add(security.BlessingPattern(pattern), string(tag))
+	}
+}
diff --git a/go/src/v.io/x/sensorlog/internal/util/syncbase_test.go b/go/src/v.io/x/sensorlog/internal/sbutil/syncbase_test.go
similarity index 70%
rename from go/src/v.io/x/sensorlog/internal/util/syncbase_test.go
rename to go/src/v.io/x/sensorlog/internal/sbutil/syncbase_test.go
index 22de38c..40c2016 100644
--- a/go/src/v.io/x/sensorlog/internal/util/syncbase_test.go
+++ b/go/src/v.io/x/sensorlog/internal/sbutil/syncbase_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package util_test
+package sbutil_test
 
 import (
 	"bytes"
@@ -13,25 +13,27 @@
 	"v.io/v23/verror"
 	_ "v.io/x/ref/runtime/factories/generic"
 	sbtu "v.io/x/ref/services/syncbase/testutil"
-	"v.io/x/sensorlog_lite/internal/util"
+	"v.io/x/sensorlog_lite/internal/sbutil"
 )
 
 func TestCreateOrOpenDB(t *testing.T) {
-	_, ctxOwner, sbName, rootPrincipal, cleanup := sbtu.SetupOrDieCustom("one", "syncbase/one", nil)
+	_, ctxOwner, sbName, rootPrincipal, cleanup := sbtu.SetupOrDieCustom("one", "one/sb", nil)
 	defer cleanup()
 	ctxGuest := sbtu.NewCtx(ctxOwner, rootPrincipal, "two")
 
+	mockTables := []string{"tfoo", "tbar"}
+
 	// Try to open app/db (create both) as guest, fail with ErrNoAccess.
-	if _, err := util.CreateOrOpenDB(ctxGuest, sbName); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+	if _, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, mockTables); verror.ErrorID(err) != verror.ErrNoAccess.ID {
 		t.Errorf("CreateOrOpenDB should have failed with ErrNoAccess, got error: %v", err)
 	}
 	// Open app/db (create both) as owner.
-	dbOwner, err := util.CreateOrOpenDB(ctxOwner, sbName)
+	dbOwner, err := sbutil.CreateOrOpenDB(ctxOwner, sbName, mockTables)
 	if err != nil {
 		t.Fatalf("CreateOrOpenDB should have succeeded, got error: %v", err)
 	}
 	// Open existing app/db as guest.
-	if _, err := util.CreateOrOpenDB(ctxGuest, sbName); err != nil {
+	if _, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, mockTables); err != nil {
 		t.Errorf("CreateOrOpenDB should have succeeded, got error: %v", err)
 	}
 	// Destroy db (but not app) to simulate interrupted creation.
@@ -39,16 +41,17 @@
 		t.Errorf("dbOwner.Destroy should have succeeded, got error: %v", err)
 	}
 	// Try to open app/db (create db) as guest, fail with ErrNoAccess.
-	if _, err := util.CreateOrOpenDB(ctxGuest, sbName); verror.ErrorID(err) != verror.ErrNoAccess.ID {
+	if _, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, mockTables); verror.ErrorID(err) != verror.ErrNoAccess.ID {
 		t.Errorf("CreateOrOpenDB should have failed with ErrNoAccess, got error: %v", err)
 	}
 	// Open app/db (recreate db) as owner.
-	dbOwner, err = util.CreateOrOpenDB(ctxOwner, sbName)
+	dbOwner, err = sbutil.CreateOrOpenDB(ctxOwner, sbName, mockTables)
 	if err != nil {
 		t.Fatalf("CreateOrOpenDB should have succeeded, got error: %v", err)
 	}
 	// Open recreated app/db as guest.
-	if _, err := util.CreateOrOpenDB(ctxGuest, sbName); err != nil {
+	dbGuest, err := sbutil.CreateOrOpenDB(ctxGuest, sbName, mockTables)
+	if err != nil {
 		t.Errorf("CreateOrOpenDB should have succeeded, got error: %v", err)
 	}
 	// Expect db permissions with full access for owner, resolve only for others.
@@ -67,4 +70,11 @@
 	} else if got, want := perms, expectPerms; !reflect.DeepEqual(got, want) {
 		t.Errorf("Unexpected database permissions: got %v, want %v", got, want)
 	}
+	// Check that all tables exist.
+	for _, tn := range mockTables {
+		tb := dbGuest.Table(tn)
+		if exists, err := tb.Exists(ctxGuest); err != nil || !exists {
+			t.Errorf("Expected table %s to exist, got: %v (error: %v)", tb.Name(), exists, err)
+		}
+	}
 }
diff --git a/go/src/v.io/x/sensorlog/internal/util/syncbase.go b/go/src/v.io/x/sensorlog/internal/util/syncbase.go
deleted file mode 100644
index 97dcb61..0000000
--- a/go/src/v.io/x/sensorlog/internal/util/syncbase.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Utilities for creating and opening the database in Syncbase.
-package util
-
-import (
-	"v.io/v23"
-	"v.io/v23/context"
-	"v.io/v23/security"
-	"v.io/v23/security/access"
-	"v.io/v23/syncbase"
-	"v.io/v23/syncbase/nosql"
-	"v.io/v23/verror"
-	"v.io/x/sensorlog_lite/internal/config"
-)
-
-// CreateOrOpenDB opens the Sensor Log database hosted on specified Syncbase
-// instance, creating it if missing.
-// TODO(ivanpi): Syncbase client helpers for MustExist / CreateIfMissing.
-func CreateOrOpenDB(ctx *context.T, sbService string) (nosql.Database, error) {
-	patterns := security.DefaultBlessingPatterns(v23.GetPrincipal(ctx))
-	acl := access.Permissions{}
-	// Allow everyone to resolve app/database to allow joining syncgroups.
-	acl.Add(security.AllPrincipals, string(access.Resolve))
-	// Restrict other permissions to self.
-	for _, tag := range access.AllTypicalTags() {
-		if tag != access.Resolve {
-			for _, pattern := range patterns {
-				acl.Add(pattern, string(tag))
-			}
-		}
-	}
-
-	sbs := syncbase.NewService(sbService)
-	app := sbs.App(config.AppName)
-	if err := app.Create(ctx, acl); err != nil && verror.ErrorID(err) != verror.ErrExist.ID {
-		return nil, err
-	}
-	// TODO(ivanpi): Add schema version.
-	db := app.NoSQLDatabase(config.DBName, nil)
-	if err := db.Create(ctx, acl); err != nil && verror.ErrorID(err) != verror.ErrExist.ID {
-		return nil, err
-	}
-	return db, nil
-}
diff --git a/go/src/v.io/x/sensorlog/measured/measured.go b/go/src/v.io/x/sensorlog/measured/measured.go
index f98fdf0..a353c51 100644
--- a/go/src/v.io/x/sensorlog/measured/measured.go
+++ b/go/src/v.io/x/sensorlog/measured/measured.go
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// measured is the Sensor Log measuring daemon. Runs on any device, sampling
-// data points and writing them to Syncbase. Sampling configuration is read
-// from Syncbase as written by the client.
+// measured is the Sensor Log Lite measuring daemon. Runs on any device,
+// sampling data points and writing them to Syncbase. Sampling configuration
+// is read from Syncbase as written by the client.
 package main
 
 import (
@@ -12,15 +12,21 @@
 	"os"
 
 	"v.io/v23"
+	"v.io/v23/naming"
 	"v.io/x/lib/vlog"
 	"v.io/x/ref/lib/signals"
 	_ "v.io/x/ref/runtime/factories/generic"
 	"v.io/x/sensorlog_lite/internal/config"
-	"v.io/x/sensorlog_lite/internal/util"
+	"v.io/x/sensorlog_lite/internal/measure"
+	"v.io/x/sensorlog_lite/internal/sbutil"
 )
 
 var (
 	flagSbService = flag.String("service", config.DefaultSbService, "Location of the Syncbase service to connect to. Can be absolute or relative to the namespace root.")
+	flagDevId     = flag.String("devid", "", "DevId to be claimed by this measured. Must be specified.")
+	// Flags below are only applied the first time measured is run against a Syncbase service with the same devid.
+	flagAdmin     = flag.String("admin", "", "Blessing of admin user allowed to join the syncgroup. Must be specified.")
+	flagPublishSb = flag.String("publish-sb", "", "Syncbase service to publish the syncgroup at. Must be absolute. Must be specified. The syncgroup is published as '"+config.SyncGroupName("<publish-sb>", "<devid>")+"'.")
 )
 
 func main() {
@@ -30,15 +36,34 @@
 func runMain() int {
 	ctx, shutdown := v23.Init()
 	defer shutdown()
-	vlog.VI(2).Infof("Default blessings: %v", v23.GetPrincipal(ctx).BlessingStore().Default())
 
-	db, err := util.CreateOrOpenDB(ctx, *flagSbService)
+	if *flagDevId == "" {
+		vlog.Errorf("-devid must be specified")
+		return 1
+	}
+	if *flagAdmin == "" {
+		vlog.Errorf("-admin must be specified")
+		return 1
+	}
+	if !naming.Rooted(*flagPublishSb) {
+		vlog.Errorf("-publish-sb must be rooted")
+		return 1
+	}
+	publishMts := v23.GetNamespace(ctx).Roots()
+
+	db, err := sbutil.CreateOrOpenDB(ctx, *flagSbService, config.AllTables)
 	if err != nil {
 		vlog.Errorf("Failed opening Syncbase db: %v", err)
 		return 1
 	}
 	vlog.VI(0).Infof("measured connected to %s", db.FullName())
 
+	if err := measure.InitSyncGroup(ctx, db, *flagDevId, *flagAdmin, *flagPublishSb, publishMts); err != nil {
+		vlog.Errorf("Failed initializing syncgroup: %v", err)
+		return 1
+	}
+
 	<-signals.ShutdownOnSignals(ctx)
+
 	return 0
 }
diff --git a/go/src/v.io/x/sensorlog/scripts/run_cli_syncbased.sh b/go/src/v.io/x/sensorlog/scripts/run_cli_syncbased.sh
index 3bff033..0e3d43a 100755
--- a/go/src/v.io/x/sensorlog/scripts/run_cli_syncbased.sh
+++ b/go/src/v.io/x/sensorlog/scripts/run_cli_syncbased.sh
@@ -10,16 +10,18 @@
 source "${JIRI_ROOT}/experimental/projects/sensorlog_lite/src/v.io/x/sensorlog_lite/scripts/runner_lib.sh"
 
 # Must be run with V23_CREDENTIALS set or through the agent.
-# Optional environment variables: SL_BLESSNAME, SL_NAME, SL_IPADDR_PORT, SL_TMPDIR
+# Optional environment variables: SL_PREFIX, SL_DEVID, SL_IPADDR_PORT, SL_TMPDIR
 function main() {
-  local -r BLESSNAME="${SL_BLESSNAME:-sluser}"
-  local -r NAME="${SL_NAME:-client1}"
-  local -r IPADDR_PORT="${SL_IPADDR_PORT:-127.0.0.1:8909}"
+  local -r PREFIX="${SL_PREFIX:-sl/client}"
+  local -r DEVID="${SL_DEVID:-$(gen_uuid)}"
+  local -r NAME="${PREFIX}/${DEVID}"
+  local -r IPADDR_PORT="${SL_IPADDR_PORT:-$(dig $(hostname) +short):8707}"
   local -r TMPDIR="${SL_TMPDIR:-sltmp}/${NAME}"
+
   mkdir -p "${TMPDIR}"
   trap "kill_child_processes; exit 1" ERR EXIT
-  run_mounttabled "${BLESSNAME}" "${NAME}" "${IPADDR_PORT}"
-  run_syncbased "${BLESSNAME}" "/${IPADDR_PORT}" "syncbased/slcli/${NAME}" "${TMPDIR}"
+  run_mounttabled "${NAME}" "${IPADDR_PORT}"
+  run_syncbased "/${IPADDR_PORT}" "${NAME}" "${TMPDIR}"
   # Wait for signal.
   while true; do
     sleep 10
diff --git a/go/src/v.io/x/sensorlog/scripts/run_measured.sh b/go/src/v.io/x/sensorlog/scripts/run_measured.sh
index 68e5fb0..5590274 100755
--- a/go/src/v.io/x/sensorlog/scripts/run_measured.sh
+++ b/go/src/v.io/x/sensorlog/scripts/run_measured.sh
@@ -4,22 +4,33 @@
 # license that can be found in the LICENSE file.
 
 # Starts an instance of measured and required services.
+#
+# mounttabled is started locally with syncbased. measured connects to it
+# and bootstraps the syncgroup joinable at:
+# /$IPADDR_PORT/$PREFIX/$DEVID/syncbased.
+# measured drops most permissions on prefixes in the syncgroup. Full admin
+# permissions are granted to $ADMIN (as a blessing extension of the same
+# default blessing as the one running this script).
 
 set -eu
 
 source "${JIRI_ROOT}/experimental/projects/sensorlog_lite/src/v.io/x/sensorlog_lite/scripts/runner_lib.sh"
 
 # Must be run with V23_CREDENTIALS set or through the agent.
-# Optional environment variables: SL_BLESSNAME, SL_NAME, SL_IPADDR_PORT, SL_TMPDIR
+# Optional environment variables: SL_PREFIX, SL_DEVID, SL_ADMIN, SL_IPADDR_PORT, SL_TMPDIR
 function main() {
-  local -r BLESSNAME="${SL_BLESSNAME:-sluser}"
-  local -r NAME="${SL_NAME:-measured1}"
-  local -r IPADDR_PORT="${SL_IPADDR_PORT:-127.0.0.1:8707}"
+  local -r PREFIX="${SL_PREFIX:-sl/measured}"
+  local -r DEVID="${SL_DEVID:-$(gen_uuid)}"
+  local -r ADMIN="${SL_ADMIN:-sl/client}"
+  local -r NAME="${PREFIX}/${DEVID}"
+  local -r IPADDR_PORT="${SL_IPADDR_PORT:-$(dig $(hostname) +short):8707}"
   local -r TMPDIR="${SL_TMPDIR:-sltmp}/${NAME}"
+
   mkdir -p "${TMPDIR}"
   trap "kill_child_processes; exit 1" ERR EXIT
-  run_mounttabled "${BLESSNAME}" "${NAME}" "${IPADDR_PORT}"
-  run_measured "${BLESSNAME}" "/${IPADDR_PORT}" "${NAME}" "${TMPDIR}"
+  run_mounttabled "${NAME}" "${IPADDR_PORT}"
+  run_syncbased "/${IPADDR_PORT}" "${NAME}" "${TMPDIR}"
+  run_measured "/${IPADDR_PORT}" "${NAME}" "${DEVID}" "${ADMIN}"
   # Wait for signal.
   while true; do
     sleep 10
diff --git a/go/src/v.io/x/sensorlog/scripts/runner_lib.sh b/go/src/v.io/x/sensorlog/scripts/runner_lib.sh
index c81b82b..16c76d7 100644
--- a/go/src/v.io/x/sensorlog/scripts/runner_lib.sh
+++ b/go/src/v.io/x/sensorlog/scripts/runner_lib.sh
@@ -5,10 +5,12 @@
 
 # Functions for starting the Sensor Log daemon and required services with
 # appropriate blessings. Expected to be run with V23_CREDENTIALS or through
-# an agent.
+# an agent. NAME parameters are used both in service names and in blessing
+# extensions.
 
 set -eu
 
+# Kills all child processes of the current process.
 function kill_child_processes() {
   kill -TERM -- -"${BASHPID}" || true
   sleep 1
@@ -16,32 +18,48 @@
 }
 export -f kill_child_processes
 
+# Generates a hex-encoded 16-byte random UUID.
+function gen_uuid() {
+  head -c 256 /dev/urandom | sha256sum | cut -c 1-32
+}
+export -f gen_uuid
+
+readonly BLESSING_CHAIN_SEPARATOR='/'
+
+# Converts name to blessing extension.
+# name_to_blessing NAME
 function name_to_blessing() {
-  local -r CHAIN_SEPARATOR='/'
-  sed -e "s,/,${CHAIN_SEPARATOR},g" <<< "$@"
+  sed -e "s,/,${BLESSING_CHAIN_SEPARATOR},g" <<< "$@"
 }
 export -f name_to_blessing
 
+# Gets first default blessing for the principal set in the environment.
+function get_blessing_root() {
+  "${JIRI_ROOT}"/release/go/bin/principal dump -s | cut -d ' ' -f 1 | cut -d ',' -f 1
+}
+export -f get_blessing_root
+
+# Starts mounttabled at IPADDR:PORT.
+# run_mounttabled NAME IPADDR:PORT
 function run_mounttabled() {
-  local -r BLESSNAME="$1"
-  local -r NAME="$2"
-  local -r IPADDR_PORT="$3"
-  "${JIRI_ROOT}"/release/go/bin/vbecome -name="$(name_to_blessing "${BLESSNAME}/sl/mounttabled/${NAME}")" \
+  local -r NAME="$1"
+  local -r IPADDR_PORT="$2"
+  # TODO(ivanpi): Lock down mounttable permissions.
+  "${JIRI_ROOT}"/release/go/bin/vbecome -name="$(name_to_blessing "${NAME}/mounttabled")" \
     "${JIRI_ROOT}"/release/go/bin/mounttabled -v23.tcp.address "${IPADDR_PORT}" \
     &
   sleep 1
 }
 export -f run_mounttabled
 
+# Starts syncbased with permissions other than resolve restricted to
+# <blessing_root>:$NAME.
+# run_syncbased MT NAME TMPDIR
 function run_syncbased() {
-  local -r BLESSNAME="$1"
-  local -r MT="$2"
-  local -r NAME="$3"
-  local -r TMPDIR="$4"
-  local -r DEF_BLESSING_ROOT="$("${JIRI_ROOT}"/release/go/bin/principal dump -s | cut -d ' ' -f 1 | cut -d ',' -f 1)"
-  local -r DEF_BLESSING_RUNNER="$(name_to_blessing "${DEF_BLESSING_ROOT}/${BLESSNAME}/sl/${NAME}")"
-  # Everyone can Resolve to be able to join the syncgroup.
-  echo "$DEF_BLESSING_RUNNER"
+  local -r MT="$1"
+  local -r NAME="$2"
+  local -r TMPDIR="$3"
+  local -r DEF_BLESSING_RUNNER="$(name_to_blessing "$(get_blessing_root)/${NAME}")"
   local -r PERMISSIONS_LITERAL="{\
 \"Admin\":{\"In\":[\"${DEF_BLESSING_RUNNER}\"]}, \
 \"Read\":{\"In\":[\"${DEF_BLESSING_RUNNER}\"]}, \
@@ -49,24 +67,28 @@
 \"Debug\":{\"In\":[\"${DEF_BLESSING_RUNNER}\"]}, \
 \"Resolve\":{\"In\":[\"...\"]} \
 }"
-  "${JIRI_ROOT}"/release/go/bin/vbecome -name="$(name_to_blessing "${BLESSNAME}/sl/syncbased/${NAME}")" \
-    "${JIRI_ROOT}"/release/go/bin/syncbased -v23.namespace.root "${MT}" \
-    -name "syncbased/${NAME}" -engine leveldb -root-dir "${TMPDIR}/syncbased/${NAME}" \
-    -v23.permissions.literal="${PERMISSIONS_LITERAL}" \
+  "${JIRI_ROOT}"/release/go/bin/vbecome -name "$(name_to_blessing "${NAME}/syncbased")" \
+    "${JIRI_ROOT}"/release/go/bin/syncbased -v23.namespace.root "${MT}" -name "${NAME}/syncbased" \
+    -engine leveldb -root-dir "${TMPDIR}/${NAME}/syncbased" \
+    -v23.permissions.literal "${PERMISSIONS_LITERAL}" \
     &
   sleep 1
 }
 export -f run_syncbased
 
+# Starts measured publishing the syncgroup at the local mounttable. Expects a
+# syncbase instance to have been started at $MT with the same $NAME.
+# run_measured MT NAME DEVID ADMIN
 function run_measured() {
-  local -r BLESSNAME="$1"
-  local -r MT="$2"
-  local -r NAME="$3"
-  local -r TMPDIR="$4"
-  run_syncbased "${BLESSNAME}" "${MT}" "measured/${NAME}" "${TMPDIR}"
-  "${JIRI_ROOT}"/release/go/bin/vbecome -name="$(name_to_blessing "${BLESSNAME}/sl/measured/${NAME}")" \
+  local -r MT="$1"
+  local -r NAME="$2"
+  local -r DEVID="$3"
+  local -r ADMIN="$4"
+  local -r DEF_BLESSING_ADMIN="$(name_to_blessing "$(get_blessing_root)/${ADMIN}")"
+  "${JIRI_ROOT}"/release/go/bin/vbecome -name="$(name_to_blessing "${NAME}")" \
     "${JIRI_ROOT}"/experimental/projects/sensorlog_lite/bin/measured -v23.namespace.root "${MT}" \
-    -service "syncbased/measured/${NAME}" -alsologtostderr \
+    -service "${NAME}/syncbased" -devid="${DEVID}" -admin="${DEF_BLESSING_ADMIN}" \
+    -publish-sb "${MT}/${NAME}/syncbased" -alsologtostderr \
     &
   sleep 1
 }