diff --git a/garnet/go/src/pm/cmd/pm/serve/serve.go b/garnet/go/src/pm/cmd/pm/serve/serve.go
index c7706f2240dae1e42637474beac93669b481ad9e..de4143b96bb2e591f1345c2a3e17abac34b15b0a 100644
--- a/garnet/go/src/pm/cmd/pm/serve/serve.go
+++ b/garnet/go/src/pm/cmd/pm/serve/serve.go
@@ -5,9 +5,12 @@
 package serve
 
 import (
+	"bufio"
 	"compress/gzip"
 	"flag"
 	"fmt"
+	"io/ioutil"
+	"log"
 	"net/http"
 	"os"
 	"path/filepath"
@@ -20,6 +23,9 @@ import (
 	"fuchsia.googlesource.com/pm/repo"
 )
 
+// server is a default http server only parameterized for tests.
+var server http.Server
+
 func Run(cfg *build.Config, args []string) error {
 	fs := flag.NewFlagSet("serve", flag.ExitOnError)
 	repoDir := fs.String("d", "", "(deprecated, use -repo) path to the repository")
@@ -31,6 +37,7 @@ func Run(cfg *build.Config, args []string) error {
 	auto := fs.Bool("a", true, "Host auto endpoint for realtime client updates")
 	quiet := fs.Bool("q", false, "Don't print out information about requests")
 	encryptionKey := fs.String("e", "", "Path to a symmetric blob encryption key *UNSAFE*")
+	publishList := fs.String("p", "", "path to a package list file to be auto-published")
 
 	fs.Usage = func() {
 		fmt.Fprintf(os.Stderr, "usage: %s serve", filepath.Base(os.Args[0]))
@@ -44,16 +51,20 @@ func Run(cfg *build.Config, args []string) error {
 	config.ApplyDefaults()
 
 	if *repoDir == "" {
-		*repoDir = filepath.Join(config.RepoDir, "repository")
+		*repoDir = config.RepoDir
 	}
+	repoPubDir := filepath.Join(*repoDir, "repository")
 
-	fi, err := os.Stat(*repoDir)
+	repo, err := repo.New(*repoDir)
 	if err != nil {
-		return fmt.Errorf("repository path %q is not valid: %s", *repoDir, err)
+		return err
+	}
+	if *encryptionKey != "" {
+		repo.EncryptWith(*encryptionKey)
 	}
 
-	if !fi.IsDir() {
-		return fmt.Errorf("repository path %q is not a directory", *repoDir)
+	if err := repo.Init(); err != nil && err != os.ErrExist {
+		return fmt.Errorf("repository at %q is not valid or could not be initialized: %s", *repoDir, err)
 	}
 
 	if *auto {
@@ -63,25 +74,71 @@ func Run(cfg *build.Config, args []string) error {
 		if err != nil {
 			return fmt.Errorf("failed to initialize fsnotify: %s", err)
 		}
-		timestampPath := filepath.Join(*repoDir, "timestamp.json")
-		err = w.Add(timestampPath)
-		if err != nil {
+
+		publishAll := func() {
+			if *publishList != "" {
+				f, err := os.Open(*publishList)
+				if err != nil {
+					log.Printf("reading package list %q: %s", *publishList, err)
+					return
+				}
+				defer f.Close()
+
+				s := bufio.NewScanner(f)
+				for s.Scan() {
+					m := s.Text()
+					if _, err := os.Stat(m); err == nil {
+						repo.PublishManifest(m)
+						if err := w.Add(m); err != nil {
+							log.Printf("unable to watch %q", m)
+						}
+					} else {
+						log.Printf("unable to publish %q", m)
+					}
+				}
+				if err := repo.CommitUpdates(config.TimeVersioned); err != nil {
+					log.Printf("committing repo: %s", err)
+				}
+			}
+		}
+
+		timestampPath := filepath.Join(repoPubDir, "timestamp.json")
+		if err = w.Add(timestampPath); err != nil {
 			return fmt.Errorf("failed to watch %s: %s", timestampPath, err)
 		}
+		if *publishList != "" {
+			if err := w.Add(*publishList); err != nil {
+				return fmt.Errorf("failed to watch %s: %s", *publishList, err)
+			}
+		}
 		go func() {
-			for range w.Events {
-				fi, err := os.Stat(timestampPath)
-				if err != nil {
-					continue
+			for event := range w.Events {
+				switch event.Name {
+				case timestampPath:
+					fi, err := os.Stat(timestampPath)
+					if err != nil {
+						continue
+					}
+					as.Broadcast("timestamp.json", fi.ModTime().Format(http.TimeFormat))
+				case *publishList:
+					publishAll()
+				default:
+					if err := repo.PublishManifest(event.Name); err != nil {
+						log.Printf("publishing %q: %s", event.Name, err)
+						continue
+					}
+					if err := repo.CommitUpdates(config.TimeVersioned); err != nil {
+						log.Printf("committing repo update %q: %s", event.Name, err)
+					}
 				}
-				as.Broadcast("timestamp.json", fi.ModTime().Format(http.TimeFormat))
 			}
 		}()
 
 		http.Handle("/auto", as)
+		publishAll()
 	}
 
-	dirServer := http.FileServer(http.Dir(*repoDir))
+	dirServer := http.FileServer(http.Dir(repoPubDir))
 	http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		switch r.URL.Path {
 		case "/":
@@ -93,7 +150,13 @@ func Run(cfg *build.Config, args []string) error {
 		}
 	}))
 
-	cs := pmhttp.NewConfigServer(*repoDir, *encryptionKey)
+	cs := pmhttp.NewConfigServer(func() []byte {
+		b, err := ioutil.ReadFile(filepath.Join(repoPubDir, "root.json"))
+		if err != nil {
+			log.Printf("%s", err)
+		}
+		return b
+	}, *encryptionKey)
 	http.Handle("/config.json", cs)
 
 	if !*quiet {
@@ -101,7 +164,8 @@ func Run(cfg *build.Config, args []string) error {
 			time.Now().Format("2006-01-02 15:04:05"), *repoDir, *listen)
 	}
 
-	return http.ListenAndServe(*listen, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+	server.Addr = *listen
+	server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		if !strings.HasPrefix(r.RequestURI, "/blobs") && strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
 			gw := &pmhttp.GZIPWriter{
 				w,
@@ -117,5 +181,7 @@ func Run(cfg *build.Config, args []string) error {
 			fmt.Printf("%s [pm serve] %d %s\n",
 				time.Now().Format("2006-01-02 15:04:05"), lw.Status, r.RequestURI)
 		}
-	}))
+	})
+
+	return server.ListenAndServe()
 }
diff --git a/garnet/go/src/pm/fswatch/fswatch.go b/garnet/go/src/pm/fswatch/fswatch.go
index f8e4dc2a534f20d0e27ba0aca444f163e0f54077..ae2234700a26c6805a724bc5dfa0951b55ba8a51 100644
--- a/garnet/go/src/pm/fswatch/fswatch.go
+++ b/garnet/go/src/pm/fswatch/fswatch.go
@@ -10,6 +10,6 @@ import (
 	"github.com/fsnotify/fsnotify"
 )
 
-type Watcher fsnotify.Watcher
+type Watcher = fsnotify.Watcher
 
 var NewWatcher = fsnotify.NewWatcher
diff --git a/garnet/go/src/pm/pmhttp/config.go b/garnet/go/src/pm/pmhttp/config.go
index 43511e64d54c721b949b4278c2433b1955d15ef5..6b1f6914c841308d70a6eb5c206ca336defe2703 100644
--- a/garnet/go/src/pm/pmhttp/config.go
+++ b/garnet/go/src/pm/pmhttp/config.go
@@ -10,17 +10,15 @@ import (
 	"io/ioutil"
 	"log"
 	"net/http"
-	"os"
-	"path/filepath"
 )
 
 type ConfigServer struct {
-	repoDir       string
-	encryptionKey string
+	rootKeyFetcher func() []byte
+	encryptionKey  string
 }
 
-func NewConfigServer(repoDir, encryptionKey string) *ConfigServer {
-	return &ConfigServer{repoDir: repoDir, encryptionKey: encryptionKey}
+func NewConfigServer(rootKeyFetcher func() []byte, encryptionKey string) *ConfigServer {
+	return &ConfigServer{rootKeyFetcher: rootKeyFetcher, encryptionKey: encryptionKey}
 }
 
 func (c *ConfigServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -31,35 +29,6 @@ func (c *ConfigServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	repoUrl := fmt.Sprintf("%s%s", scheme, r.Host)
 
-	var signedKeys struct {
-		Signed struct {
-			Keys map[string]struct {
-				Keytype string
-				Keyval  struct {
-					Public string
-				}
-			}
-			Roles struct {
-				Root struct {
-					Keyids []string
-				}
-				Threshold int
-			}
-		}
-	}
-	f, err := os.Open(filepath.Join(c.repoDir, "root.json"))
-	if err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		log.Printf("root.json missing or unreadable: %s", err)
-		return
-	}
-	defer f.Close()
-	if err := json.NewDecoder(f).Decode(&signedKeys); err != nil {
-		w.WriteHeader(http.StatusInternalServerError)
-		log.Printf("root.json parsing error: %s", err)
-		return
-	}
-
 	cfg := struct {
 		ID          string
 		RepoURL     string
@@ -101,8 +70,15 @@ func (c *ConfigServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		copy(cfg.BlobKey.Data[:], keyBytes)
 	}
 
-	for _, id := range signedKeys.Signed.Roles.Root.Keyids {
-		k := signedKeys.Signed.Keys[id]
+	var keys signedKeys
+	if err := json.Unmarshal(c.rootKeyFetcher(), &keys); err != nil {
+		log.Printf("root.json parsing error: %s", err)
+		w.WriteHeader(http.StatusInternalServerError)
+		return
+	}
+
+	for _, id := range keys.Signed.Roles.Root.Keyids {
+		k := keys.Signed.Keys[id]
 		cfg.RootKeys = append(cfg.RootKeys, struct{ Type, Value string }{
 			Type:  k.Keytype,
 			Value: k.Keyval.Public,
@@ -110,3 +86,20 @@ func (c *ConfigServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	}
 	json.NewEncoder(w).Encode(cfg)
 }
+
+type signedKeys struct {
+	Signed struct {
+		Keys map[string]struct {
+			Keytype string
+			Keyval  struct {
+				Public string
+			}
+		}
+		Roles struct {
+			Root struct {
+				Keyids []string
+			}
+			Threshold int
+		}
+	}
+}