-- @@SHEBANG -- -*- Lua -*- -- gitano-setup -- -- Git (with) Augmented network operations -- Instance setup tool -- -- Copyright 2012 Daniel Silverstone -- -- -- @@GITANO_LUA_PATH local gitano = require "gitano" local gall = require "gall" local luxio = require "luxio" local sio = require "luxio.simple" local clod = require "clod" -- @@GITANO_BIN_PATH -- @@GITANO_SHARE_PATH local possible_answers = {...} if possible_answers[1] == "--help" or possible_answers[1] == "-h" or possible_answers[1] == "--usage" then sio.stderr:write([[ usage: gitano-setup [...] This tool creates the basic repository setup for a Gitano instance. This is an interactive tool where if it has any questions for you, it will visit each answers file in turn until it finds the answer. This means that for automation purposes you can specify multiple answers files with the earlier ones overriding the later ones. In summary, the behaviour is as follows: The repository root is created if it does not exist, and a gitano-admin.git repository is created within it. Said repository is populated with the example administration repository rules and an admin user and group. ]]) return 1 end local conf = clod.parse("") gitano.log.set_prefix("gitano-setup") gitano.log.bump_level(gitano.log.level.CHAT) for i = #possible_answers, 1, -1 do gitano.log.debug("Parsing answers file:", possible_answers[1]) local one_conf = assert(clod.parse(assert(io.open(possible_answers[1], "r")):read "*a", "@" .. possible_answers[1])) gitano.log.debug("Combining answers into conf...") for k,v in one_conf:each() do gitano.log.ddebug(tostring(k) .. " = " .. tostring(v)) conf.settings[k] = v end end gitano.log.chat("Welcome to the Gitano setup process") function get(key) return conf.settings[key] end function ask_for(key, prompt, default) local cur_value = conf.settings[key] or default gitano.log.ddebug("ask_for(", tostring(key), ", ", tostring(prompt), ", ", tostring(default), ") [", tostring(cur_value), " ]") if not conf.settings["setup.batch"] then local default_str = (cur_value == nil) and "" or " [" .. tostring(cur_value) .. "]" sio.stdout:write((prompt or key) .. default_str .. ": ") local new_value = sio.stdin:read("*l") if new_value and new_value ~= "" then cur_value = new_value end end gitano.log.info("Setting:", key, "is", tostring(cur_value)) conf.settings[key] = cur_value return cur_value end function look_for_path(path) local ret, stat = luxio.stat(path) if ret ~= 0 then return false, path .. ": " .. luxio.strerror(stat) end if luxio.S_ISDIR(stat.mode) == 0 then return false, path .. ": not a directory" end return true end function validate_path(path) local ok, msg = look_for_path(path) if not ok then error(msg, 2) end end function file_exists(path) local fh = io.open(tostring(path), "r") if not fh then return false end fh:close() return true end function validate_name(n) if not n:match("^[a-z_][a-z0-9_%-]*$") then error("Invalid name: " .. n, 2) end end if conf.settings["setup.batch"] then gitano.log.info("Batch mode engaged") else gitano.log.info("Interactive mode engaged") end gitano.log.chat("Step 1: Determine everything") validate_path(ask_for("paths.home", "Home directory for new Gitano user", os.getenv "HOME")) ask_for("paths.ssh", "SSH directory for new Gitano user", get("paths.home") .. "/.ssh") local pubkey_path if look_for_path(get("paths.ssh")) then -- Try and find a pubkey to use for _, ktype in ipairs { "rsa", "ecdsa" } do local pk = get("paths.ssh") .. "/id_" .. ktype .. ".pub" if file_exists(pk) then pubkey_path = pk break end end end assert(file_exists(ask_for("paths.pubkey", "Public key file for admin user", pubkey_path)), "Cannot find public key") ask_for("paths.repos", "Repository path for new Gitano instance", get("paths.home") .. "/repos") validate_name(ask_for("admin.username", "User name for admin user", "admin")) ask_for("admin.realname", "Real name for admin user", "Administrator") ask_for("admin.email", "Email address for admin user", "admin@administrator.local") validate_name(ask_for("admin.keyname", "Key name for administrator", "default")) ask_for("site.name", "Site name", "a random Gitano instance") ask_for("log.prefix", "Site log prefix", "gitano") gitano.log.chat("Step 2: Gather required content") gitano.log.info("=> Prepare site config") local completely_flat = {} local site_conf = clod.parse("") site_conf.settings["site_name"] = get "site.name" site_conf.settings["log.prefix"] = get "log.prefix" completely_flat["site.conf"] = site_conf:serialise() -- Acquire the contents of the skeleton gitano-admin repository gitano.log.info("=> Acquire skeleton gitano-admin") local skel_path = gitano.config.share_path() .. "/skel/gitano-admin" local skel = assert(sio.opendir(skel_path)) local function acquire(dir, base, path) gitano.log.ddebug("Acquire skeleton in:", path) for ent in dir:iterate() do if not (ent == "." or ent == "..") then local entpath = path .. "/" .. ent local treeent = base .. ent if look_for_path(entpath) then local subdir = assert(sio.opendir(entpath)) acquire(subdir, treeent .. "/", entpath) subdir:close() else local fh = io.open(entpath, "r") completely_flat[treeent] = fh:read "*a" fh:close() end end end end acquire(skel, "", skel_path) skel:close() -- Now build the user files gitano.log.info("=> Preparing administration user (" .. get("admin.username") .. ")") local userpath = "users/" .. get("admin.username") .. "/user.conf" local keypath = "users/" .. get("admin.username") .. "/" .. get("admin.keyname") .. ".key" local userconf = clod.parse("") userconf.settings.real_name = get("admin.realname") userconf.settings.email_address = get("admin.email") completely_flat[userpath] = userconf:serialise() completely_flat[keypath] = assert(sio.open(get("paths.pubkey"), "r")):read "*a" -- And now the gitano-admin group gitano.log.info("=> Preparing gitano-admin group") local groupconf = clod.parse("") groupconf.settings.description = "Gitano Instance Administrators" groupconf.settings["members.*"] = get("admin.username") completely_flat["groups/gitano-admin.conf"] = groupconf:serialise() gitano.log.chat("Step 3: Write out paths and gitano-admin.git") gitano.log.info("=> Make paths") gitano.util.mkdir_p(get("paths.repos") .. "/.graveyard") gitano.util.mkdir_p(get "paths.ssh") assert(sio.chmod(get "paths.ssh", "0700")) gitano.config.repo_path(get "paths.repos") gitano.log.info("=> Prepare repository") local raw_repo = assert(gall.repository.create(get("paths.repos") .. "/gitano-admin.git")) gitano.log.info("=> Create a flattened tree") for k, v in pairs(completely_flat) do gitano.log.debug(" => Make object", k) completely_flat[k] = gall.object.create(raw_repo, "blob", v) end gitano.log.info("=> Commit that tree") local real_tree = assert(gall.tree.create(raw_repo, completely_flat)) local person = { realname = get "admin.realname", email = get "admin.email", } local commit_data = { author = person, committer = person, tree = real_tree, message = "Initial setup", } local commit_obj = assert(gall.commit.create(raw_repo, commit_data)) gitano.log.info("=> Attach that commit to master") assert(raw_repo:update_ref("refs/heads/master", commit_obj.sha, "Create initial master ref")) gitano.log.info("=> Ensure we can parse our resultant admin repository") local admin_head = raw_repo:get(raw_repo.HEAD) if not admin_head then gitano.log.fatal("Unable to find the HEAD of the administration repository. Cannot continue"); end local config = assert(gitano.config.parse(admin_head)) -- Restore the prefix for our logging gitano.log.set_prefix("gitano-setup") -- Verify that our user exists assert(config.users[get "admin.username"], "Could not find user") assert(config.groups["gitano-admin"].filtered_members[get "admin.username"], "User was not a gitano-admin") gitano.log.info("=> Change the admin ref for gitano-admin.git") config.repo:set_description("Instance administration repository") config.repo:set_owner(get "admin.username") gitano.log.info("=> Write the SSH authorized_keys file out") gitano.config.writessh(config, get("paths.ssh") .. "/authorized_keys") assert(sio.chmod(get("paths.ssh") .. "/authorized_keys", "0600"))