[ostree] admin: Rework /ostree/deploy to support multiple independent operating systems



commit 3832544ac40b5bbfcee3621cc5ab096ac64e578d
Author: Colin Walters <walters verbum org>
Date:   Wed Dec 19 17:52:46 2012 -0500

    admin: Rework /ostree/deploy to support multiple independent operating systems
    
    The real vision of OSTree is to "multiple versions of multiple
    operating systems".  Up until now, it's worked to install gnome-ostree
    inside a host distribution, but several things don't work quite right
    if you try to do completely different systems.
    
    In the new model, there's the concept of an "osname" which encompasses
    a few properties:
    
    1) Its own /var
    2) A set of trees deployed in /ostree/deploy/OSNAME/
    3) Its own "current" and "previous" links.
    
    Now it no longer really makes sense to boot with "ostree=current".
    Instead, you specify e.g. "ostree=gnome/current".
    
    This is an incompatible change to the deployment code - you will need
    to run init-os gnome and redeploy.
    
    All "ostree admin" subcommands now take an OSNAME argument.

 Makefile-ostree.am                                 |    8 +-
 src/ostree/ostree-curl-fetcher.c                   |  204 ++++++++++++++++++++
 src/ostree/ostree-curl-fetcher.h                   |   59 ++++++
 src/ostree/ot-admin-builtin-deploy.c               |   73 ++++----
 src/ostree/ot-admin-builtin-diff.c                 |   17 ++-
 src/ostree/ot-admin-builtin-install.c              |  200 +++++++++++++++++++
 src/ostree/ot-admin-builtin-os-init.c              |  105 ++++++++++
 src/ostree/ot-admin-builtin-prune.c                |   21 ++-
 src/ostree/ot-admin-builtin-pull-deploy.c          |   91 +++-------
 src/ostree/ot-admin-builtin-update-kernel.c        |   19 ++-
 ...n-builtin-init.c => ot-admin-builtin-upgrade.c} |   32 +++-
 src/ostree/ot-admin-builtins.h                     |    4 +-
 src/ostree/ot-admin-functions.c                    |   53 +-----
 src/ostree/ot-admin-functions.h                    |    2 +
 src/ostree/ot-builtin-admin.c                      |    4 +-
 src/switchroot/ostree-switch-root.c                |   78 ++++----
 16 files changed, 758 insertions(+), 212 deletions(-)
---
diff --git a/Makefile-ostree.am b/Makefile-ostree.am
index d4649c5..0066765 100644
--- a/Makefile-ostree.am
+++ b/Makefile-ostree.am
@@ -22,8 +22,10 @@ bin_PROGRAMS += ostree
 endif
 
 ostree_SOURCES = src/ostree/main.c \
-	src/ostree/ot-builtins.h \
+	src/ostree/ostree-curl-fetcher.h \
+	src/ostree/ostree-curl-fetcher.c \
 	src/ostree/ot-builtin-admin.c \
+	src/ostree/ot-builtins.h \
 	src/ostree/ot-builtin-cat.c \
 	src/ostree/ot-builtin-config.c \
 	src/ostree/ot-builtin-checkout.c \
@@ -46,12 +48,14 @@ ostree_SOURCES = src/ostree/main.c \
 
 # Admin subcommand
 ostree_SOURCES += \
-	src/ostree/ot-admin-builtin-init.c \
 	src/ostree/ot-admin-builtin-init-fs.c \
 	src/ostree/ot-admin-builtin-diff.c \
 	src/ostree/ot-admin-builtin-deploy.c \
 	src/ostree/ot-admin-builtin-prune.c \
 	src/ostree/ot-admin-builtin-pull-deploy.c \
+	src/ostree/ot-admin-builtin-os-init.c \
+	src/ostree/ot-admin-builtin-install.c \
+	src/ostree/ot-admin-builtin-upgrade.c \
 	src/ostree/ot-admin-builtin-update-kernel.c \
 	src/ostree/ot-admin-builtins.h \
 	src/ostree/ot-admin-functions.h \
diff --git a/src/ostree/ostree-curl-fetcher.c b/src/ostree/ostree-curl-fetcher.c
new file mode 100644
index 0000000..fbe96ab
--- /dev/null
+++ b/src/ostree/ostree-curl-fetcher.c
@@ -0,0 +1,204 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011,2012 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include "ostree-curl-fetcher.h"
+#include "ostree.h"
+
+struct OstreeCurlFetcher
+{
+  GObject parent_instance;
+
+  GFile *tmpdir;
+  
+  GSSubprocess *curl_proc;
+  GQueue *queue;
+};
+
+G_DEFINE_TYPE (OstreeCurlFetcher, ostree_curl_fetcher, G_TYPE_OBJECT)
+
+static void
+ostree_curl_fetcher_finalize (GObject *object)
+{
+  OstreeCurlFetcher *self;
+
+  self = OSTREE_CURL_FETCHER (object);
+
+  g_clear_object (&self->curl_proc);
+  g_queue_free (self->queue);
+
+  G_OBJECT_CLASS (ostree_curl_fetcher_parent_class)->finalize (object);
+}
+
+static void
+ostree_curl_fetcher_class_init (OstreeCurlFetcherClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize = ostree_curl_fetcher_finalize;
+}
+
+static void
+ostree_curl_fetcher_init (OstreeCurlFetcher *self)
+{
+  self->queue = g_queue_new ();
+}
+
+OstreeCurlFetcher *
+ostree_curl_fetcher_new (GFile *tmpdir)
+{
+  OstreeCurlFetcher *self = (OstreeCurlFetcher*)g_object_new (OSTREE_TYPE_CURL_FETCHER, NULL);
+
+  self->tmpdir = g_object_ref (tmpdir);
+ 
+  return self;
+}
+
+typedef struct {
+  OstreeCurlFetcher *self;
+  gchar *uri;
+  GFile *tmpfile;
+  GCancellable *cancellable;
+  GSimpleAsyncResult *result;
+} OstreeCurlFetcherOp;
+
+static void
+fetcher_op_free (OstreeCurlFetcherOp *op)
+{
+  g_clear_object (&op->self);
+  g_free (op->uri);
+  g_clear_object (&op->tmpfile);
+  g_clear_object (&op->cancellable);
+  g_free (op);
+}
+
+static void
+maybe_fetch (OstreeCurlFetcher     *self);
+
+static void
+on_curl_exited (GObject       *object,
+                GAsyncResult  *result,
+                gpointer       user_data)
+{
+  GSSubprocess *proc = GS_SUBPROCESS (object);
+  OstreeCurlFetcherOp *op = user_data;
+  GError *error = NULL;
+  int estatus;
+  
+  if (!gs_subprocess_wait_finish (proc, result, &estatus, &error))
+    goto out;
+
+  if (!g_spawn_check_exit_status (estatus, &error))
+    goto out;
+
+ out:
+  if (error)
+      g_simple_async_result_take_error (op->result, error);
+  
+  g_simple_async_result_complete (op->result);
+  
+  g_clear_object (&op->self->curl_proc);
+
+  maybe_fetch (op->self);
+
+  g_object_unref (op->result);
+}
+
+static void
+maybe_fetch (OstreeCurlFetcher     *self)
+{
+  OstreeCurlFetcherOp *op;
+  GError *error = NULL;
+  gs_unref_object GSSubprocessContext *context = NULL;
+
+  if (self->curl_proc != NULL
+      || g_queue_is_empty (self->queue))
+    return;
+
+  op = g_queue_pop_head (self->queue);
+
+  if (!ostree_create_temp_regular_file (self->tmpdir, NULL, NULL,
+                                        &op->tmpfile, NULL,
+                                        op->cancellable, &error))
+    goto out;
+
+  context = gs_subprocess_context_newv ("curl", op->uri, "-o",
+                                        gs_file_get_path_cached (op->tmpfile),
+                                        NULL);
+  g_assert (self->curl_proc == NULL);
+  self->curl_proc = gs_subprocess_new (context, op->cancellable, &error);
+  if (!self->curl_proc)
+    goto out;
+
+  gs_subprocess_wait (self->curl_proc, op->cancellable,
+                      on_curl_exited, op);
+
+ out:
+  if (error)
+    {
+      g_simple_async_result_take_error (op->result, error);
+      g_simple_async_result_complete (op->result);
+    }
+}
+
+void
+ostree_curl_fetcher_request_uri_async (OstreeCurlFetcher          *self,
+                                       const char                 *uri,
+                                       GCancellable               *cancellable,
+                                       GAsyncReadyCallback         callback,
+                                       gpointer                    user_data)
+{
+  OstreeCurlFetcherOp *op;
+
+  op = g_new0 (OstreeCurlFetcherOp, 1);
+  op->self = g_object_ref (self);
+  op->uri = g_strdup (uri);
+  op->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+  op->result = g_simple_async_result_new ((GObject*) self, callback, user_data,
+                                          ostree_curl_fetcher_request_uri_async);
+
+  g_queue_push_tail (self->queue, op);
+
+  g_simple_async_result_set_op_res_gpointer (op->result, op,
+                                             (GDestroyNotify) fetcher_op_free);
+  
+  maybe_fetch (self);
+}
+
+GFile *
+ostree_curl_fetcher_request_uri_finish (OstreeCurlFetcher     *self,
+                                        GAsyncResult          *result,
+                                        GError               **error)
+{
+  GSimpleAsyncResult *simple;
+  OstreeCurlFetcherOp *op;
+
+  g_return_val_if_fail (g_simple_async_result_is_valid (result, (GObject*)self, ostree_curl_fetcher_request_uri_async), FALSE);
+
+  simple = G_SIMPLE_ASYNC_RESULT (result);
+  if (g_simple_async_result_propagate_error (simple, error))
+    return NULL;
+  op = g_simple_async_result_get_op_res_gpointer (simple);
+
+  return g_object_ref (op->tmpfile);
+}
diff --git a/src/ostree/ostree-curl-fetcher.h b/src/ostree/ostree-curl-fetcher.h
new file mode 100644
index 0000000..d7276aa
--- /dev/null
+++ b/src/ostree/ostree-curl-fetcher.h
@@ -0,0 +1,59 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _OSTREE_CURL_FETCHER
+#define _OSTREE_CURL_FETCHER
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define OSTREE_TYPE_CURL_FETCHER         (ostree_curl_fetcher_get_type ())
+#define OSTREE_CURL_FETCHER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), OSTREE_TYPE_CURL_FETCHER, OstreeCurlFetcher))
+#define OSTREE_CURL_FETCHER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), OSTREE_TYPE_CURL_FETCHER, OstreeCurlFetcherClass))
+#define OSTREE_IS_CURL_FETCHER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSTREE_TYPE_CURL_FETCHER))
+#define OSTREE_IS_CURL_FETCHER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), OSTREE_TYPE_CURL_FETCHER))
+#define OSTREE_CURL_FETCHER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), OSTREE_TYPE_CURL_FETCHER, OstreeCurlFetcherClass))
+
+typedef struct OstreeCurlFetcherClass   OstreeCurlFetcherClass;
+typedef struct OstreeCurlFetcher        OstreeCurlFetcher;
+
+struct OstreeCurlFetcherClass
+{
+  GObjectClass parent_class;
+};
+
+GType   ostree_curl_fetcher_get_type (void) G_GNUC_CONST;
+
+OstreeCurlFetcher *ostree_curl_fetcher_new (GFile *tmpdir);
+
+void ostree_curl_fetcher_request_uri_async (OstreeCurlFetcher         *self,
+                                            const char                *uri,
+                                            GCancellable              *cancellable,
+                                            GAsyncReadyCallback        callback,
+                                            gpointer                   user_data);
+
+GFile *ostree_curl_fetcher_request_uri_finish (OstreeCurlFetcher         *self,
+                                               GAsyncResult          *result,
+                                               GError               **error);
+
+G_END_DECLS
+
+#endif
diff --git a/src/ostree/ot-admin-builtin-deploy.c b/src/ostree/ot-admin-builtin-deploy.c
index 7153d20..d870a99 100644
--- a/src/ostree/ot-admin-builtin-deploy.c
+++ b/src/ostree/ot-admin-builtin-deploy.c
@@ -31,6 +31,8 @@
 typedef struct {
   OstreeRepo  *repo;
   GFile *ostree_dir;
+  char  *osname;
+  GFile *osname_dir;
 } OtAdminDeploy;
 
 static gboolean opt_no_kernel;
@@ -63,21 +65,18 @@ update_current (OtAdminDeploy      *self,
 {
   gboolean ret = FALSE;
   ot_lobj GFile *current_path = NULL;
-  ot_lobj GFile *current_etc_path = NULL;
   ot_lobj GFile *previous_path = NULL;
   ot_lobj GFile *tmp_current_path = NULL;
-  ot_lobj GFile *tmp_current_etc_path = NULL;
   ot_lobj GFile *tmp_previous_path = NULL;
   ot_lobj GFileInfo *previous_info = NULL;
   ot_lfree char *relative_current = NULL;
   ot_lfree char *relative_current_etc = NULL;
   ot_lfree char *relative_previous = NULL;
 
-  current_path = g_file_get_child (self->ostree_dir, "current");
-  current_etc_path = g_file_get_child (self->ostree_dir, "current-etc");
-  previous_path = g_file_get_child (self->ostree_dir, "previous");
+  current_path = g_file_get_child (self->osname_dir, "current");
+  previous_path = g_file_get_child (self->osname_dir, "previous");
 
-  relative_current = g_file_get_relative_path (self->ostree_dir, deploy_target);
+  relative_current = g_file_get_relative_path (self->osname_dir, deploy_target);
   g_assert (relative_current);
   relative_current_etc = g_strconcat (relative_current, "-etc", NULL);
 
@@ -92,10 +91,10 @@ update_current (OtAdminDeploy      *self,
           return TRUE;
         }
 
-      tmp_previous_path = g_file_get_child (self->ostree_dir, "tmp-previous");
+      tmp_previous_path = g_file_get_child (self->osname_dir, "tmp-previous");
       (void) gs_file_unlink (tmp_previous_path, NULL, NULL);
 
-      relative_previous = g_file_get_relative_path (self->ostree_dir, current_deployment);
+      relative_previous = g_file_get_relative_path (self->osname_dir, current_deployment);
       g_assert (relative_previous);
       if (symlink (relative_previous, gs_file_get_path_cached (tmp_previous_path)) < 0)
         {
@@ -104,7 +103,7 @@ update_current (OtAdminDeploy      *self,
         }
     }
 
-  tmp_current_path = g_file_get_child (self->ostree_dir, "tmp-current");
+  tmp_current_path = g_file_get_child (self->osname_dir, "tmp-current");
   (void) gs_file_unlink (tmp_current_path, NULL, NULL);
 
   if (symlink (relative_current, gs_file_get_path_cached (tmp_current_path)) < 0)
@@ -113,20 +112,9 @@ update_current (OtAdminDeploy      *self,
       goto out;
     }
 
-  tmp_current_etc_path = g_file_get_child (self->ostree_dir, "tmp-current-etc");
-  (void) gs_file_unlink (tmp_current_etc_path, NULL, NULL);
-  if (symlink (relative_current_etc, gs_file_get_path_cached (tmp_current_etc_path)) < 0)
-    {
-      ot_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-
   if (!gs_file_rename (tmp_current_path, current_path,
                        cancellable, error))
     goto out;
-  if (!gs_file_rename (tmp_current_etc_path, current_etc_path,
-                       cancellable, error))
-    goto out;
 
   if (tmp_previous_path)
     {
@@ -367,7 +355,7 @@ merge_etc_changes (OtAdminDeploy  *self,
  * deploy_tree:
  *
  * Look up @revision in the repository, and check it out in
- * OSTREE_DIR/deploy/DEPLOY_TARGET.
+ * OSTREE_DIR/deploy/OS/DEPLOY_TARGET.
  *
  * Merge configuration changes from the old deployment, if any.
  *
@@ -383,9 +371,8 @@ deploy_tree (OtAdminDeploy     *self,
              GError           **error)
 {
   gboolean ret = FALSE;
-  const char *current_deployment_ref = "deployment/current";
-  const char *previous_deployment_ref = "deployment/previous";
-  ot_lobj GFile *deploy_dir = NULL;
+  gs_free char *current_deployment_ref = NULL;
+  gs_free char *previous_deployment_ref = NULL;
   ot_lfree char *deploy_target_fullname = NULL;
   ot_lfree char *deploy_target_fullname_tmp = NULL;
   ot_lobj GFile *deploy_target_path = NULL;
@@ -410,7 +397,16 @@ deploy_tree (OtAdminDeploy     *self,
   if (!revision)
     revision = deploy_target;
 
-  deploy_dir = g_file_get_child (self->ostree_dir, "deploy");
+  current_deployment_ref = g_strdup_printf ("deployment/%s/current", self->osname);
+  previous_deployment_ref = g_strdup_printf ("deployment/%s/previous", self->osname);
+
+  if (!g_file_query_exists (self->osname_dir, cancellable))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "No OS \"%s\" found in \"%s\"", self->osname,
+                   gs_file_get_path_cached (self->osname_dir));
+      goto out;
+    }
 
   if (!ostree_repo_resolve_rev (self->repo, revision, FALSE, &resolved_commit, error))
     goto out;
@@ -426,17 +422,17 @@ deploy_tree (OtAdminDeploy     *self,
     goto out;
 
   deploy_target_fullname = g_strconcat (deploy_target, "-", resolved_commit, NULL);
-  deploy_target_path = g_file_resolve_relative_path (deploy_dir, deploy_target_fullname);
+  deploy_target_path = g_file_resolve_relative_path (self->osname_dir, deploy_target_fullname);
 
   deploy_target_fullname_tmp = g_strconcat (deploy_target_fullname, ".tmp", NULL);
-  deploy_target_path_tmp = g_file_resolve_relative_path (deploy_dir, deploy_target_fullname_tmp);
+  deploy_target_path_tmp = g_file_resolve_relative_path (self->osname_dir, deploy_target_fullname_tmp);
 
   deploy_parent = g_file_get_parent (deploy_target_path);
   if (!gs_file_ensure_directory (deploy_parent, TRUE, cancellable, error))
     goto out;
 
   deploy_target_etc_name = g_strconcat (deploy_target, "-", resolved_commit, "-etc", NULL);
-  deploy_target_etc_path = g_file_resolve_relative_path (deploy_dir, deploy_target_etc_name);
+  deploy_target_etc_path = g_file_resolve_relative_path (self->osname_dir, deploy_target_etc_name);
 
   /* Delete any previous temporary data */
   if (!gs_shutil_rm_rf (deploy_target_path_tmp, cancellable, error))
@@ -470,7 +466,7 @@ deploy_tree (OtAdminDeploy     *self,
       goto out;
     }
 
-  if (!ot_admin_get_current_deployment (self->ostree_dir, &previous_deployment,
+  if (!ot_admin_get_current_deployment (self->ostree_dir, self->osname, &previous_deployment,
                                         cancellable, error))
     goto out;
   if (previous_deployment)
@@ -588,6 +584,7 @@ do_update_kernel (OtAdminDeploy     *self,
   ot_ptrarray_add_many (args, "ostree", "admin",
                         "--ostree-dir", gs_file_get_path_cached (self->ostree_dir),
                         "update-kernel",
+                        self->osname,
                         gs_file_get_path_cached (deploy_path), NULL);
   if (opt_no_kernel)
     g_ptr_array_add (args, "--modules-only");
@@ -618,22 +615,23 @@ ot_admin_builtin_deploy (int argc, char **argv, GFile *ostree_dir, GError **erro
   gboolean ret = FALSE;
   ot_lobj GFile *repo_path = NULL;
   ot_lobj GFile *deploy_path = NULL;
+  const char *osname = NULL;
   const char *deploy_target = NULL;
   const char *revision = NULL;
   __attribute__((unused)) GCancellable *cancellable = NULL;
 
   memset (self, 0, sizeof (*self));
 
-  context = g_option_context_new ("NAME [REVISION] - Check out revision NAME (or REVISION as NAME)");
+  context = g_option_context_new ("OSNAME TREENAME [REVISION] - In operating system OS, check out revision TREENAME (or REVISION as TREENAME)");
 
   g_option_context_add_main_entries (context, options, NULL);
 
   if (!g_option_context_parse (context, &argc, &argv, error))
     goto out;
 
-  if (argc < 2)
+  if (argc < 3)
     {
-      ot_util_usage_error (context, "NAME must be specified", error);
+      ot_util_usage_error (context, "OSNAME and TREENAME must be specified", error);
       goto out;
     }
 
@@ -647,10 +645,13 @@ ot_admin_builtin_deploy (int argc, char **argv, GFile *ostree_dir, GError **erro
   if (!ostree_repo_check (self->repo, error))
     goto out;
 
-  deploy_target = argv[1];
-  if (argc > 2)
-    revision = argv[2];
+  osname = argv[1];
+  deploy_target = argv[2];
+  if (argc > 3)
+    revision = argv[3];
 
+  self->osname = g_strdup (osname);
+  self->osname_dir = ot_gfile_get_child_build_path (self->ostree_dir, "deploy", osname, NULL);
   if (!deploy_tree (self, deploy_target, revision, &deploy_path,
                     cancellable, error))
     goto out;
@@ -661,7 +662,9 @@ ot_admin_builtin_deploy (int argc, char **argv, GFile *ostree_dir, GError **erro
   ret = TRUE;
  out:
   g_clear_object (&self->repo);
+  g_free (self->osname);
   g_clear_object (&self->ostree_dir);
+  g_clear_object (&self->osname_dir);
   if (context)
     g_option_context_free (context);
   return ret;
diff --git a/src/ostree/ot-admin-builtin-diff.c b/src/ostree/ot-admin-builtin-diff.c
index 7b9eb49..64c2bcc 100644
--- a/src/ostree/ot-admin-builtin-diff.c
+++ b/src/ostree/ot-admin-builtin-diff.c
@@ -37,6 +37,7 @@ ot_admin_builtin_diff (int argc, char **argv, GFile *ostree_dir, GError **error)
 {
   GOptionContext *context;
   gboolean ret = FALSE;
+  const char *osname;
   ot_lobj GFile *repo_path = NULL;
   ot_lobj GFile *deployment = NULL;
   ot_lobj GFile *deploy_parent = NULL;
@@ -47,7 +48,7 @@ ot_admin_builtin_diff (int argc, char **argv, GFile *ostree_dir, GError **error)
   ot_lobj GFile *new_etc_path = NULL;
   __attribute__((unused)) GCancellable *cancellable = NULL;
 
-  context = g_option_context_new ("[NAME] - Diff configuration for revision NAME");
+  context = g_option_context_new ("OSNAME [REVISION] - Diff configuration for OSNAME");
 
   g_option_context_add_main_entries (context, options, NULL);
 
@@ -56,9 +57,17 @@ ot_admin_builtin_diff (int argc, char **argv, GFile *ostree_dir, GError **error)
   
   repo_path = g_file_get_child (ostree_dir, "repo");
 
-  if (argc > 1)
+  if (argc < 2)
     {
-      deployment = ot_gfile_get_child_build_path (ostree_dir, "deploy", argv[1], NULL);
+      ot_util_usage_error (context, "OSNAME must be specified", error);
+      goto out;
+    }
+
+  osname = argv[1];
+
+  if (argc > 2)
+    {
+      deployment = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, argv[2], NULL);
       if (!g_file_query_exists (deployment, NULL))
         {
           g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
@@ -68,7 +77,7 @@ ot_admin_builtin_diff (int argc, char **argv, GFile *ostree_dir, GError **error)
     }
   else
     {
-      if (!ot_admin_get_current_deployment (ostree_dir, &deployment,
+      if (!ot_admin_get_current_deployment (ostree_dir, osname, &deployment,
                                             cancellable, error))
         goto out;
     }
diff --git a/src/ostree/ot-admin-builtin-install.c b/src/ostree/ot-admin-builtin-install.c
new file mode 100644
index 0000000..7e0cb45
--- /dev/null
+++ b/src/ostree/ot-admin-builtin-install.c
@@ -0,0 +1,200 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include "ot-admin-builtins.h"
+#include "ot-admin-functions.h"
+#include "ostree-curl-fetcher.h"
+#include "otutil.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <glib/gi18n.h>
+
+typedef struct {
+  GMainLoop *loop;
+  GFile *osconfig_path;
+} OtAdminBuiltinInstall;
+
+static GOptionEntry options[] = {
+  { NULL }
+};
+
+static void
+on_keyfile_retrieved (GObject       *obj,
+                      GAsyncResult  *result,
+                      gpointer       user_data)
+{
+  OtAdminBuiltinInstall *self = user_data;
+  GError *error = NULL;
+  
+  self->osconfig_path = ostree_curl_fetcher_request_uri_finish ((OstreeCurlFetcher*)obj, result, &error);
+  if (!self->osconfig_path)
+    goto out;
+  
+ out:
+  if (error)
+    {
+      g_printerr ("%s\n", error->message);
+      exit (1);
+    }
+  g_main_loop_quit (self->loop);
+}
+
+gboolean
+ot_admin_builtin_install (int argc, char **argv, GFile *ostree_dir, GError **error)
+{
+  OtAdminBuiltinInstall self_data;
+  OtAdminBuiltinInstall *self = &self_data;
+  GOptionContext *context;
+  gboolean ret = FALSE;
+  const char *keyfile_arg = NULL;
+  const char *treename_arg = NULL;
+  ot_lobj GFile *deploy_dir = NULL;
+  ot_lobj GFile *osdir = NULL;
+  ot_lobj GFile *dest_osconfig_path = NULL;
+  gs_unref_ptrarray GPtrArray *subproc_args = NULL;
+  ot_lfree char *osname = NULL;
+  ot_lfree char *repoarg = NULL;
+  ot_lfree char *ostree_dir_arg = NULL;
+  ot_lfree char *tree_to_deploy = NULL;
+  GKeyFile *keyfile = NULL;
+  __attribute__((unused)) GCancellable *cancellable = NULL;
+
+  memset (self, 0, sizeof (*self));
+
+  context = g_option_context_new ("KEYFILE [TREE] - Initialize, download, and deploy operating system");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (argc < 2)
+    {
+      ot_util_usage_error (context, "KEYFILE must be specified", error);
+      goto out;
+    }
+
+  self->loop = g_main_loop_new (NULL, TRUE);
+
+  keyfile_arg = argv[1];
+  if (argc > 2)
+    treename_arg = argv[2];
+
+  keyfile = g_key_file_new ();
+
+  if (g_str_has_prefix (keyfile_arg, "http://";) || g_str_has_prefix (keyfile_arg, "https://";))
+    {
+      ot_lobj GFile *tmp = g_file_new_for_path (g_get_tmp_dir ());
+      ot_lobj OstreeCurlFetcher *fetcher = ostree_curl_fetcher_new (tmp);
+
+      g_print ("Fetching %s...\n", keyfile_arg);
+      ostree_curl_fetcher_request_uri_async (fetcher, keyfile_arg, cancellable,
+                                             on_keyfile_retrieved, self);
+
+      g_main_loop_run (self->loop);
+    }
+  else
+    {
+      self->osconfig_path = g_file_new_for_path (keyfile_arg);
+    }
+
+  if (!g_key_file_load_from_file (keyfile, gs_file_get_path_cached (self->osconfig_path), 0,
+                                  error))
+    goto out;
+
+  osname = g_key_file_get_string (keyfile, "os", "Name", error);
+
+  ostree_dir_arg = g_strconcat ("--ostree-dir=",
+                                gs_file_get_path_cached (ostree_dir),
+                                NULL);
+
+  if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+                                      GS_SUBPROCESS_STREAM_DISPOSITION_NULL,
+                                      cancellable, error,
+                                      "ostree", "admin", ostree_dir_arg, "os-init", osname, NULL))
+    goto out;
+
+  if (treename_arg)
+    {
+      tree_to_deploy = g_strdup (treename_arg);
+    }
+  else
+    {
+      tree_to_deploy = g_key_file_get_string (keyfile, "os", "TreeDefault", error);
+      if (!tree_to_deploy)
+        goto out;
+    }
+
+  osdir = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, NULL);
+  dest_osconfig_path = ot_gfile_get_child_strconcat (osdir, osname, ".cfg", NULL);
+
+  if (!g_file_copy (self->osconfig_path, dest_osconfig_path, G_FILE_COPY_OVERWRITE | G_FILE_COPY_TARGET_DEFAULT_PERMS,
+                    cancellable, NULL, NULL, error))
+    goto out;
+
+  if (!gs_file_unlink (self->osconfig_path, cancellable, error))
+    goto out;
+
+  repoarg = g_strconcat ("--repo=",
+                         gs_file_get_path_cached (ostree_dir), "/repo",
+                         NULL);
+
+  {
+    ot_lfree char *repourl = NULL;
+      
+    repourl = g_key_file_get_string (keyfile, "os", "Repo", error);
+    if (!repourl)
+      goto out;
+    
+    if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+                                        GS_SUBPROCESS_STREAM_DISPOSITION_NULL,
+                                        cancellable, error,
+                                        "ostree", repoarg, "remote", "add",
+                                        osname, repourl, tree_to_deploy, NULL))
+      goto out;
+  }
+
+  if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+                                      GS_SUBPROCESS_STREAM_DISPOSITION_NULL,
+                                      cancellable, error,
+                                        "ostree", "pull", repoarg, osname, NULL))
+    goto out;
+
+  if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+                                      GS_SUBPROCESS_STREAM_DISPOSITION_NULL,
+                                      cancellable, error,
+                                      "ostree", "admin", ostree_dir_arg, "deploy", osname,
+                                      tree_to_deploy, NULL))
+    goto out;
+
+  ret = TRUE;
+ out:
+  if (self->loop)
+    g_main_loop_unref (self->loop);
+  g_clear_object (&self->osconfig_path);
+  g_clear_pointer (&keyfile, (GDestroyNotify) g_key_file_unref);
+  if (context)
+    g_option_context_free (context);
+  return ret;
+}
diff --git a/src/ostree/ot-admin-builtin-os-init.c b/src/ostree/ot-admin-builtin-os-init.c
new file mode 100644
index 0000000..3cd38aa
--- /dev/null
+++ b/src/ostree/ot-admin-builtin-os-init.c
@@ -0,0 +1,105 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters verbum org>
+ */
+
+#include "config.h"
+
+#include "ot-admin-builtins.h"
+#include "ot-admin-functions.h"
+#include "otutil.h"
+
+#include <glib/gi18n.h>
+
+static GOptionEntry options[] = {
+  { NULL }
+};
+
+gboolean
+ot_admin_builtin_os_init (int argc, char **argv, GFile *ostree_dir, GError **error)
+{
+  GOptionContext *context;
+  gboolean ret = FALSE;
+  const char *osname = NULL;
+  ot_lobj GFile *deploy_dir = NULL;
+  ot_lobj GFile *dir = NULL;
+  __attribute__((unused)) GCancellable *cancellable = NULL;
+
+  context = g_option_context_new ("OSNAME - Initialize empty state for given operating system");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (!ot_admin_ensure_initialized (ostree_dir, cancellable, error))
+    goto out;
+
+  if (argc < 2)
+    {
+      ot_util_usage_error (context, "OSNAME must be specified", error);
+      goto out;
+    }
+
+  osname = argv[1];
+
+  deploy_dir = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, NULL);
+
+  /* Ensure core subdirectories of /var exist, since we need them for
+   * dracut generation, and the host will want them too.
+   */
+  g_clear_object (&dir);
+  dir = ot_gfile_get_child_build_path (deploy_dir, "var", "log", NULL);
+  if (!gs_file_ensure_directory (dir, TRUE, cancellable, error))
+    goto out;
+
+  g_clear_object (&dir);
+  dir = ot_gfile_get_child_build_path (deploy_dir, "var", "tmp", NULL);
+  if (!gs_file_ensure_directory (dir, TRUE, cancellable, error))
+    goto out;
+  if (chmod (gs_file_get_path_cached (dir), 01777) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  g_clear_object (&dir);
+  dir = ot_gfile_get_child_build_path (deploy_dir, "var", "lib", NULL);
+  if (!gs_file_ensure_directory (dir, TRUE, cancellable, error))
+    goto out;
+
+  g_clear_object (&dir);
+  dir = ot_gfile_get_child_build_path (deploy_dir, "var", "run", NULL);
+  if (!g_file_test (gs_file_get_path_cached (dir), G_FILE_TEST_IS_SYMLINK))
+    {
+      if (symlink ("../run", gs_file_get_path_cached (dir)) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+
+  g_print ("%s initialized as OSTree root\n", gs_file_get_path_cached (deploy_dir));
+
+  ret = TRUE;
+ out:
+  if (context)
+    g_option_context_free (context);
+  return ret;
+}
diff --git a/src/ostree/ot-admin-builtin-prune.c b/src/ostree/ot-admin-builtin-prune.c
index 14ebee6..d2b2cc5 100644
--- a/src/ostree/ot-admin-builtin-prune.c
+++ b/src/ostree/ot-admin-builtin-prune.c
@@ -103,29 +103,38 @@ ot_admin_builtin_prune (int argc, char **argv, GFile *ostree_dir, GError **error
   GOptionContext *context;
   gboolean ret = FALSE;
   guint i;
+  const char *osname;
   ot_lobj GFile *repo_path = NULL;
+  ot_lobj GFile *deploy_dir = NULL;
   ot_lobj GFile *current_deployment = NULL;
   ot_lobj GFile *previous_deployment = NULL;
   ot_lptrarray GPtrArray *deployments = NULL;
   __attribute__((unused)) GCancellable *cancellable = NULL;
 
-  context = g_option_context_new ("- Delete untagged deployments and repository objects");
+  context = g_option_context_new ("OSNAME - Delete untagged deployments and repository objects");
 
   g_option_context_add_main_entries (context, options, NULL);
 
   if (!g_option_context_parse (context, &argc, &argv, error))
     goto out;
 
-  if (!ot_admin_ensure_initialized (ostree_dir, cancellable, error))
-    goto out;
+  if (argc < 2)
+    {
+      ot_util_usage_error (context, "OSNAME must be specified", error);
+      goto out;
+    }
+
+  osname = argv[1];
+
+  deploy_dir = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, NULL);
 
   deployments = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
-  if (!list_deployments (ostree_dir, deployments, cancellable, error))
+  if (!list_deployments (deploy_dir, deployments, cancellable, error))
     goto out;
 
-  if (!ot_admin_get_current_deployment (ostree_dir, &current_deployment,
+  if (!ot_admin_get_current_deployment (ostree_dir, osname, &current_deployment,
                                         cancellable, error));
-  if (!ot_admin_get_previous_deployment (ostree_dir, &previous_deployment,
+  if (!ot_admin_get_previous_deployment (ostree_dir, osname, &previous_deployment,
                                          cancellable, error));
 
   for (i = 0; i < deployments->len; i++)
diff --git a/src/ostree/ot-admin-builtin-pull-deploy.c b/src/ostree/ot-admin-builtin-pull-deploy.c
index 4c67f6b..47c675d 100644
--- a/src/ostree/ot-admin-builtin-pull-deploy.c
+++ b/src/ostree/ot-admin-builtin-pull-deploy.c
@@ -36,13 +36,13 @@ static GOptionEntry options[] = {
 };
 
 static char *
-parse_deploy_name_from_path (GFile   *ostree_dir,
+parse_deploy_name_from_path (GFile   *osdir,
                              GFile   *path)
 {
-  ot_lobj GFile *deploy_dir = g_file_get_child (ostree_dir, "deploy");
-  ot_lfree char *relpath = g_file_get_relative_path (deploy_dir, path);
+  ot_lfree char *relpath = g_file_get_relative_path (osdir, path);
   const char *last_dash;
 
+  g_assert (relpath);
   last_dash = strrchr (relpath, '-');
   if (!last_dash)
     g_error ("Failed to parse deployment name %s", relpath);
@@ -50,80 +50,37 @@ parse_deploy_name_from_path (GFile   *ostree_dir,
   return g_strndup (relpath, last_dash - relpath);
 }
 
-static char *
-remote_name_from_path (GKeyFile    *repo_config,
-                       const char  *deploy_path)
-{
-  const char *group_prefix = "remote \"";
-  char **groups = NULL;
-  char **group_iter = NULL;
-  char *ret = NULL;
-
-  groups = g_key_file_get_groups (repo_config, NULL);
-  for (group_iter = groups; *group_iter; group_iter++)
-    {
-      const char *group = *group_iter;
-      char **configured_branches = NULL;
-      char **branch_iter = NULL;
-      gboolean found = FALSE;
-
-      if (!(g_str_has_prefix (group, group_prefix)
-            && g_str_has_suffix (group, "\"")))
-        continue;
-
-      configured_branches = g_key_file_get_string_list (repo_config, group, "branches", NULL, NULL);
-      if (!configured_branches)
-        continue;
-
-      for (branch_iter = configured_branches; *branch_iter; branch_iter++)
-        {
-          const char *branch = *branch_iter;
-
-          if (!strcmp (branch, deploy_path))
-            {
-              found = TRUE;
-              break;
-            }
-        }
-      
-      if (found)
-        break;
-    }
-
-  if (*group_iter)
-    {
-      const char *group = *group_iter;
-      size_t len;
-      ret = g_strdup (group + strlen (group_prefix));
-      len = strlen (ret);
-      g_assert (len > 0 && ret[len-1] == '\"');
-      ret[len-1] = '\0';
-    }
-  g_strfreev (groups);
-  return ret;
-}
-
 gboolean
 ot_admin_builtin_pull_deploy (int argc, char **argv, GFile *ostree_dir, GError **error)
 {
   GOptionContext *context;
   gboolean ret = FALSE;
+  const char *osname;
   ot_lobj GFile *repo_path = NULL;
-  ot_lobj OstreeRepo *repo = NULL;
   ot_lobj GFile *current_deployment = NULL;
   ot_lfree char *deploy_name = NULL;
+  ot_lobj GFile *deploy_dir = NULL;
+  ot_lobj GFile *os_dir = NULL;
   ot_lfree char *remote_name = NULL;
   ot_lptrarray GPtrArray *subproc_args = NULL;
   __attribute__((unused)) GCancellable *cancellable = NULL;
 
-  context = g_option_context_new (" - Upgrade and redeploy current tree");
+  context = g_option_context_new ("OSNAME - Upgrade and redeploy current tree");
 
   g_option_context_add_main_entries (context, options, NULL);
 
   if (!g_option_context_parse (context, &argc, &argv, error))
     goto out;
 
-  if (!ot_admin_get_current_deployment (ostree_dir, &current_deployment,
+  if (argc < 2)
+    {
+      ot_util_usage_error (context, "OSNAME must be specified", error);
+      goto out;
+    }
+
+  osname = argv[1];
+
+  if (!ot_admin_get_current_deployment (ostree_dir, osname, &current_deployment,
                                         cancellable, error))
     goto out;
 
@@ -134,15 +91,13 @@ ot_admin_builtin_pull_deploy (int argc, char **argv, GFile *ostree_dir, GError *
       goto out;
     }
 
-  deploy_name = parse_deploy_name_from_path (ostree_dir, current_deployment);
+  deploy_dir = g_file_get_child (ostree_dir, "deploy");
+  os_dir = g_file_get_child (deploy_dir, osname);
+  g_print ("%s\n%s\n", gs_file_get_path_cached (os_dir),
+           gs_file_get_path_cached (current_deployment));
+  deploy_name = parse_deploy_name_from_path (os_dir, current_deployment);
 
   repo_path = g_file_get_child (ostree_dir, "repo");
-  repo = ostree_repo_new (repo_path);
-  if (!ostree_repo_check (repo, error))
-    goto out;
-
-  remote_name = remote_name_from_path (ostree_repo_get_config (repo),
-                                       deploy_name);
 
   {
     ot_lfree char *repo_arg = g_strconcat ("--repo=",
@@ -152,7 +107,7 @@ ot_admin_builtin_pull_deploy (int argc, char **argv, GFile *ostree_dir, GError *
     if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
                                         GS_SUBPROCESS_STREAM_DISPOSITION_NULL,
                                         cancellable, error,
-                                        "ostree", "pull", repo_arg, remote_name, NULL))
+                                        "ostree", "pull", repo_arg, osname, NULL))
       goto out;
   }
 
@@ -163,7 +118,7 @@ ot_admin_builtin_pull_deploy (int argc, char **argv, GFile *ostree_dir, GError *
     if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
                                         GS_SUBPROCESS_STREAM_DISPOSITION_NULL,
                                         cancellable, error,
-                                        "ostree", "admin", opt_ostree_dir_arg, "deploy",
+                                        "ostree", "admin", opt_ostree_dir_arg, "deploy", osname,
                                         deploy_name, NULL))
       goto out;
   }
diff --git a/src/ostree/ot-admin-builtin-update-kernel.c b/src/ostree/ot-admin-builtin-update-kernel.c
index e0fc074..1fd9ac7 100644
--- a/src/ostree/ot-admin-builtin-update-kernel.c
+++ b/src/ostree/ot-admin-builtin-update-kernel.c
@@ -33,6 +33,7 @@ typedef struct {
   const char  *deploy_path;
   GFile       *kernel_path;
   char        *release;
+  char        *osname;
 } OtAdminUpdateKernel;
 
 static gboolean opt_modules_only;
@@ -200,7 +201,8 @@ update_initramfs (OtAdminUpdateKernel  *self,
                                    cancellable, error))
         goto out;
 
-      ostree_vardir = g_file_get_child (self->ostree_dir, "var");
+      ostree_vardir = ot_gfile_get_child_build_path (self->ostree_dir, "deploy",
+                                                     self->osname, "var", NULL);
       if (opt_host_kernel)
         ostree_moduledir = g_file_get_child (self->ostree_dir, "modules");
 
@@ -213,6 +215,7 @@ update_initramfs (OtAdminUpdateKernel  *self,
       if (!g_output_stream_close (tmp_log_out, cancellable, error))
         goto out;
 
+      mkinitramfs_args = g_ptr_array_new ();
       /* Note: the hardcoded /tmp path below is not actually a
        * security flaw, because we've bind-mounted dracut's view
        * of /tmp to the securely-created tmpdir above.
@@ -403,14 +406,22 @@ ot_admin_builtin_update_kernel (int argc, char **argv, GFile *ostree_dir, GError
 
   memset (self, 0, sizeof (*self));
 
-  context = g_option_context_new ("[OSTREE_REVISION - Update kernel and regenerate initial ramfs");
+  context = g_option_context_new ("OSNAME DEPLOY_PATH - Update kernel and regenerate initial ramfs");
   g_option_context_add_main_entries (context, options, NULL);
 
   if (!g_option_context_parse (context, &argc, &argv, error))
     goto out;
 
-  if (argc > 1)
-    self->deploy_path = argv[1];
+  if (argc < 2)
+    {
+      ot_util_usage_error (context, "OSNAME must be specified", error);
+      goto out;
+    }
+
+  self->osname = g_strdup (argv[1]);
+
+  if (argc > 2)
+    self->deploy_path = argv[2];
   else
     self->deploy_path = "current";
 
diff --git a/src/ostree/ot-admin-builtin-init.c b/src/ostree/ot-admin-builtin-upgrade.c
similarity index 55%
rename from src/ostree/ot-admin-builtin-init.c
rename to src/ostree/ot-admin-builtin-upgrade.c
index 8774fd2..635ae1f 100644
--- a/src/ostree/ot-admin-builtin-init.c
+++ b/src/ostree/ot-admin-builtin-upgrade.c
@@ -26,6 +26,8 @@
 #include "ot-admin-functions.h"
 #include "otutil.h"
 
+#include <unistd.h>
+#include <stdlib.h>
 #include <glib/gi18n.h>
 
 static GOptionEntry options[] = {
@@ -33,23 +35,43 @@ static GOptionEntry options[] = {
 };
 
 gboolean
-ot_admin_builtin_init (int argc, char **argv, GFile *ostree_dir, GError **error)
+ot_admin_builtin_upgrade (int argc, char **argv, GFile *ostree_dir, GError **error)
 {
   GOptionContext *context;
   gboolean ret = FALSE;
-  ot_lobj GFile *dir = NULL;
+  const char *osname = NULL;
+  gs_free char *ostree_dir_arg = NULL;
   __attribute__((unused)) GCancellable *cancellable = NULL;
 
-  context = g_option_context_new ("- Initialize /ostree directory");
+  context = g_option_context_new ("OSNAME - pull, deploy, and prune");
   g_option_context_add_main_entries (context, options, NULL);
 
   if (!g_option_context_parse (context, &argc, &argv, error))
     goto out;
 
-  if (!ot_admin_ensure_initialized (ostree_dir, cancellable, error))
+  if (argc < 2)
+    {
+      ot_util_usage_error (context, "OSNAME must be specified", error);
+      goto out;
+    }
+
+  osname = argv[1];
+
+  ostree_dir_arg = g_strconcat ("--ostree-dir=",
+                                gs_file_get_path_cached (ostree_dir),
+                                NULL);
+
+  if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+                                      GS_SUBPROCESS_STREAM_DISPOSITION_NULL,
+                                      cancellable, error,
+                                      "ostree", "admin", ostree_dir_arg, "pull-deploy", osname, NULL))
     goto out;
 
-  g_print ("%s initialized as OSTree root\n", gs_file_get_path_cached (ostree_dir));
+  if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+                                      GS_SUBPROCESS_STREAM_DISPOSITION_NULL,
+                                      cancellable, error,
+                                      "ostree", "admin", ostree_dir_arg, "prune", osname, NULL))
+    goto out;
 
   ret = TRUE;
  out:
diff --git a/src/ostree/ot-admin-builtins.h b/src/ostree/ot-admin-builtins.h
index 6994c96..2d535f0 100644
--- a/src/ostree/ot-admin-builtins.h
+++ b/src/ostree/ot-admin-builtins.h
@@ -27,13 +27,15 @@
 
 G_BEGIN_DECLS
 
-gboolean ot_admin_builtin_init (int argc, char **argv, GFile *ostree_dir, GError **error);
+gboolean ot_admin_builtin_os_init (int argc, char **argv, GFile *ostree_dir, GError **error);
+gboolean ot_admin_builtin_install (int argc, char **argv, GFile *ostree_dir, GError **error);
 gboolean ot_admin_builtin_init_fs (int argc, char **argv, GFile *ostree_dir, GError **error);
 gboolean ot_admin_builtin_deploy (int argc, char **argv, GFile *ostree_dir, GError **error);
 gboolean ot_admin_builtin_prune (int argc, char **argv, GFile *ostree_dir, GError **error);
 gboolean ot_admin_builtin_pull_deploy (int argc, char **argv, GFile *ostree_dir, GError **error);
 gboolean ot_admin_builtin_diff (int argc, char **argv, GFile *ostree_dir, GError **error);
 gboolean ot_admin_builtin_update_kernel (int argc, char **argv, GFile *ostree_dir, GError **error);
+gboolean ot_admin_builtin_upgrade (int argc, char **argv, GFile *ostree_dir, GError **error);
 
 G_END_DECLS
 
diff --git a/src/ostree/ot-admin-functions.c b/src/ostree/ot-admin-functions.c
index 0336e80..7623062 100644
--- a/src/ostree/ot-admin-functions.c
+++ b/src/ostree/ot-admin-functions.c
@@ -45,11 +45,6 @@ ot_admin_ensure_initialized (GFile         *ostree_dir,
     goto out;
 
   g_clear_object (&dir);
-  dir = g_file_get_child (ostree_dir, "modules");
-  if (!gs_file_ensure_directory (dir, TRUE, cancellable, error))
-    goto out;
-
-  g_clear_object (&dir);
   dir = ot_gfile_get_child_build_path (ostree_dir, "repo", "objects", NULL);
   if (!g_file_query_exists (dir, NULL))
     {
@@ -65,40 +60,6 @@ ot_admin_ensure_initialized (GFile         *ostree_dir,
         }
     }
 
-  /* Ensure core subdirectories of /var exist, since we need them for
-   * dracut generation, and the host will want them too.
-   */
-  g_clear_object (&dir);
-  dir = ot_gfile_get_child_build_path (ostree_dir, "var", "log", NULL);
-  if (!gs_file_ensure_directory (dir, TRUE, cancellable, error))
-    goto out;
-
-  g_clear_object (&dir);
-  dir = ot_gfile_get_child_build_path (ostree_dir, "var", "tmp", NULL);
-  if (!gs_file_ensure_directory (dir, TRUE, cancellable, error))
-    goto out;
-  if (chmod (gs_file_get_path_cached (dir), 01777) < 0)
-    {
-      ot_util_set_error_from_errno (error, errno);
-      goto out;
-    }
-
-  g_clear_object (&dir);
-  dir = ot_gfile_get_child_build_path (ostree_dir, "var", "lib", NULL);
-  if (!gs_file_ensure_directory (dir, TRUE, cancellable, error))
-    goto out;
-
-  g_clear_object (&dir);
-  dir = ot_gfile_get_child_build_path (ostree_dir, "var", "run", NULL);
-  if (!g_file_test (gs_file_get_path_cached (dir), G_FILE_TEST_IS_SYMLINK))
-    {
-      if (symlink ("../run", gs_file_get_path_cached (dir)) < 0)
-        {
-          ot_util_set_error_from_errno (error, errno);
-          goto out;
-        }
-    }
-
   ret = TRUE;
  out:
   return ret;
@@ -183,13 +144,15 @@ query_symlink_target_allow_noent (GFile         *path,
  */
 gboolean
 ot_admin_get_current_deployment (GFile           *ostree_dir,
+                                 const char      *osname,
                                  GFile          **out_deployment,
                                  GCancellable    *cancellable,
                                  GError         **error)
 {
   ot_lobj GFile *current_path = NULL;
 
-  current_path = g_file_get_child (ostree_dir, "current");
+  current_path = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname,
+                                                "current", NULL);
 
   return query_symlink_target_allow_noent (current_path, out_deployment,
                                            cancellable, error);
@@ -204,13 +167,15 @@ ot_admin_get_current_deployment (GFile           *ostree_dir,
  */
 gboolean
 ot_admin_get_previous_deployment (GFile           *ostree_dir,
-                                 GFile          **out_deployment,
-                                 GCancellable    *cancellable,
-                                 GError         **error)
+                                  const char      *osname,
+                                  GFile          **out_deployment,
+                                  GCancellable    *cancellable,
+                                  GError         **error)
 {
   ot_lobj GFile *previous_path = NULL;
 
-  previous_path = g_file_get_child (ostree_dir, "previous");
+  previous_path = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname,
+                                                 "previous", NULL);
 
   return query_symlink_target_allow_noent (previous_path, out_deployment,
                                            cancellable, error);
diff --git a/src/ostree/ot-admin-functions.h b/src/ostree/ot-admin-functions.h
index af114aa..3c21ad1 100644
--- a/src/ostree/ot-admin-functions.h
+++ b/src/ostree/ot-admin-functions.h
@@ -32,10 +32,12 @@ gboolean ot_admin_ensure_initialized (GFile         *ostree_dir,
 				      GError       **error);
 
 gboolean ot_admin_get_current_deployment (GFile           *ostree_dir,
+                                          const char      *osname,
                                           GFile          **out_deployment,
                                           GCancellable    *cancellable,
                                           GError         **error);
 gboolean ot_admin_get_previous_deployment (GFile           *ostree_dir,
+                                           const char      *osname,
                                            GFile          **out_deployment,
                                            GCancellable    *cancellable,
                                            GError         **error);
diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c
index 2f2340e..e80ac03 100644
--- a/src/ostree/ot-builtin-admin.c
+++ b/src/ostree/ot-builtin-admin.c
@@ -43,9 +43,11 @@ typedef struct {
 } OstreeAdminCommand;
 
 static OstreeAdminCommand admin_subcommands[] = {
-  { "init", ot_admin_builtin_init },
+  { "os-init", ot_admin_builtin_os_init },
   { "init-fs", ot_admin_builtin_init_fs },
   { "deploy", ot_admin_builtin_deploy },
+  { "install", ot_admin_builtin_install },
+  { "upgrade", ot_admin_builtin_upgrade },
   { "pull-deploy", ot_admin_builtin_pull_deploy },
   { "prune", ot_admin_builtin_prune },
   { "update-kernel", ot_admin_builtin_update_kernel },
diff --git a/src/switchroot/ostree-switch-root.c b/src/switchroot/ostree-switch-root.c
index 6330223..86c6855 100644
--- a/src/switchroot/ostree-switch-root.c
+++ b/src/switchroot/ostree-switch-root.c
@@ -153,17 +153,20 @@ main(int argc, char *argv[])
   const char *initramfs_move_mounts[] = { "/dev", "/proc", "/sys", "/run", NULL };
   const char *toproot_bind_mounts[] = { "/home", "/root", "/tmp", NULL };
   const char *ostree_bind_mounts[] = { "/var", NULL };
-  /* ostree_readonly_bind_mounts /lib/modules -> modules */
   const char *readonly_bind_mounts[] = { "/bin", "/lib", "/sbin", "/usr",
 					 NULL };
   const char *root_mountpoint = NULL;
   const char *ostree_target = NULL;
   const char *ostree_subinit = NULL;
+  const char *p = NULL;
+  char *ostree_osname = NULL;
   char ostree_target_path[PATH_MAX];
+  char *deploy_path = NULL;
   char srcpath[PATH_MAX];
   char destpath[PATH_MAX];
   struct stat stbuf;
   char **init_argv = NULL;
+  size_t len;
   int initramfs_fd;
   int i;
   int before_init_argc = 0;
@@ -183,6 +186,14 @@ main(int argc, char *argv[])
   ostree_subinit = argv[3];
   before_init_argc++;
 
+  p = strchr (ostree_target, '/');
+  if (p == NULL)
+    {
+      fprintf (stderr, "Malformed OSTree target %s; expected OSNAME/TREENAME\n", ostree_target);
+      exit (1);
+    }
+  ostree_osname = strndup (ostree_target, p - ostree_target);
+
   /* For now, we just remount the root filesystem read/write.  This is
    * kind of ugly, but to do this properly we'd basically have to have
    * to be fully integrated into the init process.
@@ -193,7 +204,7 @@ main(int argc, char *argv[])
       exit (1);
     }
 
-  snprintf (destpath, sizeof(destpath), "%s/ostree/%s",
+  snprintf (destpath, sizeof(destpath), "%s/ostree/deploy/%s",
 	    root_mountpoint, ostree_target);
   if (stat (destpath, &stbuf) < 0)
     {
@@ -204,7 +215,7 @@ main(int argc, char *argv[])
   for (i = 0; initramfs_move_mounts[i] != NULL; i++)
     {
       const char *path = initramfs_move_mounts[i];
-      snprintf (destpath, sizeof(destpath), "%s/ostree/%s%s", root_mountpoint, ostree_target, path);
+      snprintf (destpath, sizeof(destpath), "%s/ostree/deploy/%s%s", root_mountpoint, ostree_target, path);
       if (mount (path, destpath, NULL, MS_MOVE, NULL) < 0)
 	{
 	  perrorv ("failed to move mount of %s to %s", path, destpath);
@@ -247,45 +258,38 @@ main(int argc, char *argv[])
    * so we no longer refer to root_mountpoint.
    */
 
-  snprintf (destpath, sizeof(destpath), "/ostree/%s", ostree_target);
+  snprintf (destpath, sizeof(destpath), "/ostree/deploy/%s", ostree_target);
   fprintf (stderr, "Examining %s\n", destpath);
   if (lstat (destpath, &stbuf) < 0)
     {
       perrorv ("Second stat of ostree root '%s' failed: ", destpath);
       exit (1);
     }
-  if (S_ISLNK (stbuf.st_mode))
+  if (!S_ISLNK (stbuf.st_mode))
     {
-      if (readlink (destpath, ostree_target_path, PATH_MAX) < 0)
-	{
-	  perrorv ("readlink(%s) failed: ", destpath);
-	  exit (1);
-	}
-      fprintf (stderr, "Resolved OSTree target to: %s\n", ostree_target_path);
+      fprintf (stderr, "OSTree target is not a symbolic link: %s\n", destpath);
+      exit (1);
     }
-  else
+  if (readlink (destpath, ostree_target_path, PATH_MAX) < 0)
     {
-      strncpy (ostree_target_path, ostree_target, PATH_MAX);
-      fprintf (stderr, "OSTree target is: %s\n", ostree_target_path);
-    }
-  
-  snprintf (destpath, sizeof(destpath), "/ostree/%s/sysroot", ostree_target_path);
-  if (mount ("/", destpath, NULL, MS_BIND, NULL) < 0)
-    {
-      perrorv ("Failed to bind mount / to '%s'", destpath);
+      perrorv ("readlink(%s) failed: ", destpath);
       exit (1);
     }
+  len = strlen (ostree_target_path);
+  if (ostree_target_path[len-1] == '/')
+    ostree_target_path[len-1] = '\0';
+  fprintf (stderr, "Resolved OSTree target to: %s\n", ostree_target_path);
+  asprintf (&deploy_path, "/ostree/deploy/%s/%s", ostree_osname, ostree_target_path);
 
-  snprintf (srcpath, sizeof(srcpath), "/ostree/%s-etc", ostree_target_path);
-  snprintf (destpath, sizeof(destpath), "/ostree/%s/etc", ostree_target_path);
-  if (mount (srcpath, destpath, NULL, MS_BIND, NULL) < 0)
+  snprintf (destpath, sizeof(destpath), "%s/sysroot", deploy_path);
+  if (mount ("/", destpath, NULL, MS_BIND, NULL) < 0)
     {
-      perrorv ("Failed to bind mount '%s' to '%s'", srcpath, destpath);
+      perrorv ("Failed to bind mount / to '%s'", destpath);
       exit (1);
     }
 
-  snprintf (srcpath, sizeof(srcpath), "%s", "/ostree/var");
-  snprintf (destpath, sizeof(destpath), "/ostree/%s/var", ostree_target_path);
+  snprintf (srcpath, sizeof(srcpath), "%s-etc", deploy_path);
+  snprintf (destpath, sizeof(destpath), "%s/etc", deploy_path);
   if (mount (srcpath, destpath, NULL, MS_BIND, NULL) < 0)
     {
       perrorv ("Failed to bind mount '%s' to '%s'", srcpath, destpath);
@@ -294,7 +298,7 @@ main(int argc, char *argv[])
 
   for (i = 0; toproot_bind_mounts[i] != NULL; i++)
     {
-      snprintf (destpath, sizeof(destpath), "/ostree/%s%s", ostree_target, toproot_bind_mounts[i]);
+      snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, toproot_bind_mounts[i]);
       if (mount (toproot_bind_mounts[i], destpath, NULL, MS_BIND & ~MS_RDONLY, NULL) < 0)
 	{
 	  perrorv ("failed to bind mount (class:toproot) %s to %s", toproot_bind_mounts[i], destpath);
@@ -304,8 +308,8 @@ main(int argc, char *argv[])
 
   for (i = 0; ostree_bind_mounts[i] != NULL; i++)
     {
-      snprintf (srcpath, sizeof(srcpath), "/ostree/%s", ostree_bind_mounts[i]);
-      snprintf (destpath, sizeof(destpath), "/ostree/%s%s", ostree_target_path, ostree_bind_mounts[i]);
+      snprintf (srcpath, sizeof(srcpath), "/ostree/deploy/%s%s", ostree_osname, ostree_bind_mounts[i]);
+      snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, ostree_bind_mounts[i]);
       if (mount (srcpath, destpath, NULL, MS_MGC_VAL|MS_BIND, NULL) < 0)
 	{
 	  perrorv ("failed to bind mount (class:bind) %s to %s", srcpath, destpath);
@@ -315,7 +319,7 @@ main(int argc, char *argv[])
 
   for (i = 0; readonly_bind_mounts[i] != NULL; i++)
     {
-      snprintf (destpath, sizeof(destpath), "/ostree/%s%s", ostree_target_path, readonly_bind_mounts[i]);
+      snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, readonly_bind_mounts[i]);
       if (mount (destpath, destpath, NULL, MS_BIND, NULL) < 0)
 	{
 	  perrorv ("failed to bind mount (class:readonly) %s", destpath);
@@ -328,19 +332,9 @@ main(int argc, char *argv[])
 	}
     }
 
-  /* This should come after we've bind mounted /lib */
-  snprintf (srcpath, sizeof(srcpath), "/ostree/modules");
-  snprintf (destpath, sizeof(destpath), "/ostree/%s/lib/modules", ostree_target_path);
-  if (mount (srcpath, destpath, NULL, MS_MGC_VAL|MS_BIND, NULL) < 0)
-    {
-      perrorv ("failed to bind mount %s to %s", srcpath, destpath);
-      exit (1);
-    }
-
-  snprintf (destpath, sizeof(destpath), "/ostree/%s", ostree_target_path);
-  if (chroot (destpath) < 0)
+  if (chroot (deploy_path) < 0)
     {
-      perrorv ("failed to change root to '%s'", destpath);
+      perrorv ("failed to change root to '%s'", deploy_path);
       exit (1);
     }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]