camput --init; First-time config helper.

This commit is contained in:
Brad Fitzpatrick 2011-01-17 18:28:38 -08:00
parent c761e1afc8
commit 3eaf825674
5 changed files with 167 additions and 22 deletions

View File

@ -2,6 +2,7 @@ include $(GOROOT)/src/Make.inc
PREREQ=$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/camli/schema.a \
$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/camli/client.a \
$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/camli/jsonsign.a \
$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/crypto/openpgp/packet.a \
$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/crypto/openpgp/error.a \
$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/crypto/openpgp/armor.a
@ -9,6 +10,7 @@ PREREQ=$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/camli/schema.a \
TARG=camput
GOFILES=\
camput.go\
init.go\
include $(GOROOT)/src/Make.cmd

View File

@ -20,11 +20,12 @@ import (
)
// Things that can be uploaded. (at most one of these)
var flagBlob *bool = flag.Bool("blob", false, "upload a file's bytes as a single blob")
var flagFile *bool = flag.Bool("file", false, "upload a file's bytes as a blob, as well as its JSON file record")
var flagPermanode *bool = flag.Bool("permanode", false, "create a new permanode")
var flagBlob = flag.Bool("blob", false, "upload a file's bytes as a single blob")
var flagFile = flag.Bool("file", false, "upload a file's bytes as a blob, as well as its JSON file record")
var flagPermanode = flag.Bool("permanode", false, "create a new permanode")
var flagInit = flag.Bool("init", false, "First-time configuration.")
var flagVerbose *bool = flag.Bool("verbose", false, "be verbose")
var flagVerbose = flag.Bool("verbose", false, "be verbose")
var wereErrors = false
@ -190,10 +191,11 @@ func usage(msg string) {
fmt.Println("Error:", msg)
}
fmt.Println(`
Usage: camliup
Usage: camput
camliup --blob <filename(s) to upload as blobs>
camliup --file <filename(s) to upload as blobs + JSON metadata>
camput --init # first time configuration
camput --blob <filename(s) to upload as blobs>
camput --file <filename(s) to upload as blobs + JSON metadata>
`)
flag.PrintDefaults()
os.Exit(1)
@ -215,13 +217,17 @@ func handleResult(what string, pr *client.PutResult, err os.Error) {
func main() {
flag.Parse()
if sumSet(flagFile, flagBlob, flagPermanode) != 1 {
usage("Exactly one of --blob and --file may be set")
if sumSet(flagFile, flagBlob, flagPermanode, flagInit) != 1 {
// TODO: say which ones are conflicting
usage("Conflicting mode options.")
}
uploader := &Uploader{client.NewOrFail()}
switch {
case *flagInit:
doInit()
return
case *flagPermanode:
if flag.NArg() > 0 {
log.Exitf("--permanode doesn't take any additional arguments")

95
clients/go/camput/init.go Normal file
View File

@ -0,0 +1,95 @@
package main
import (
"camli/blobref"
"camli/client"
"camli/jsonsign"
"crypto/sha1"
"exec"
"flag"
"os"
"io/ioutil"
"path"
"json"
"log"
)
var flagGpgKey = flag.String("gpgkey", "", "(init option only) GPG key to use for signing.")
func doInit() {
blobDir := path.Join(client.ConfigDir(), "blobs")
os.Mkdir(client.ConfigDir(), 0700)
os.Mkdir(blobDir, 0700)
keyId := *flagGpgKey
if keyId == "" {
keyId = os.Getenv("GPGKEY")
}
if keyId == "" {
// TODO: run and parse gpg --list-secret-keys and see if there's just one and suggest that? Or show
// a list of them?
log.Exitf("Initialization requires your public GPG key. Set --gpgkey=<pubid> or set $GPGKEY in your environment. Run gpg --list-secret-keys to find their key IDs.")
}
if os.Getenv("GPG_AGENT_INFO") == "" {
log.Printf("No GPG_AGENT_INFO found in environment; you should setup gnupg-agent. camput will be annoying otherwise.")
}
// TODO: use same command-line flag as the jsonsign package.
// unify them into a shared package just for gpg-related stuff?
gpgBinary, err := exec.LookPath("gpg")
if err != nil {
log.Exitf("Failed to find gpg binary in your path.")
}
cmd, err := exec.Run(gpgBinary,
[]string{"gpg", "--export", "--armor", keyId},
os.Environ(),
"/",
exec.DevNull,
exec.Pipe,
exec.DevNull)
if err != nil {
log.Exitf("Error running gpg to export public key: %v", err)
}
keyBytes, err := ioutil.ReadAll(cmd.Stdout)
if err != nil {
log.Exitf("Error read from gpg to export public key: %v", err)
}
hash := sha1.New()
hash.Write(keyBytes)
bref := blobref.FromHash("sha1", hash)
keyBlobPath := path.Join(blobDir, bref.String() + ".camli")
if err = ioutil.WriteFile(keyBlobPath, keyBytes, 0644); err != nil {
log.Exitf("Error writing public key blob to %q: %v", keyBlobPath, err)
}
if ok, err := jsonsign.VerifyPublicKeyFile(keyBlobPath, keyId); !ok {
log.Exitf("Error verifying public key at %q: %v", keyBlobPath, err)
}
log.Printf("Your Camlistore identity (your GPG public key's blobref) is: %s", bref.String())
_, err = os.Stat(client.ConfigFilePath())
if err == nil {
log.Exitf("Config file %q already exists; quitting without touching it.", client.ConfigFilePath())
}
if f, err := os.Open(client.ConfigFilePath(), os.O_CREAT|os.O_WRONLY, 0600); err == nil {
defer f.Close()
m := make(map[string]interface{})
m["blobServer"] = "http://localhost:3179/"
m["blobServerPassword"] = "test"
m["publicKeyBlobref"] = bref.String()
jsonBytes, err := json.MarshalIndent(m, "", " ")
if err != nil {
log.Exitf("JSON serialization error: %v", err)
}
_, err = f.Write(jsonBytes)
if err != nil {
log.Exitf("Error writing to %q: %v", client.ConfigFilePath(), err)
}
log.Printf("Wrote %q; modify as necessary.", client.ConfigFilePath())
}
}

View File

@ -17,26 +17,30 @@ import (
var flagServer *string = flag.String("blobserver", "", "camlistore blob server")
var flagPassword *string = flag.String("password", "", "password for blob server")
func configFilePath() string {
func ConfigDir() string {
return path.Join(os.Getenv("HOME"), ".camli")
}
func ConfigFilePath() string {
return path.Join(os.Getenv("HOME"), ".camli", "config")
}
var configOnce sync.Once
var config = make(map[string]interface{})
func parseConfig() {
f, err := os.Open(configFilePath(), os.O_RDONLY, 0)
f, err := os.Open(ConfigFilePath(), os.O_RDONLY, 0)
switch {
case err != nil && err.(*os.PathError).Error.(os.Errno) == syscall.ENOENT:
// TODO: write empty file?
return
case err != nil:
log.Printf("Error opening config file %q: %v", configFilePath(), err)
log.Printf("Error opening config file %q: %v", ConfigFilePath(), err)
return
default:
defer f.Close()
dj := json.NewDecoder(f)
if err := dj.Decode(&config); err != nil {
log.Printf("Error parsing JSON in config file %q: %v", configFilePath(), err)
log.Printf("Error parsing JSON in config file %q: %v", ConfigFilePath(), err)
}
}
}
@ -58,9 +62,16 @@ func blobServerOrDie() string {
return cleanServer(*flagServer)
}
configOnce.Do(parseConfig)
log.Exitf("No --blobserver parameter specified.")
return ""
value, ok := config["blobServer"]
var server string
if ok {
server = value.(string)
}
server = cleanServer(server)
if !ok || server == "" {
log.Exitf("Missing or invalid \"blobServer\" in %q", ConfigFilePath())
}
return server
}
func passwordOrDie() string {
@ -68,9 +79,22 @@ func passwordOrDie() string {
return *flagPassword
}
configOnce.Do(parseConfig)
log.Exitf("No --password parameter specified.")
return ""
value, ok := config["blobServerPassword"]
var password string
if ok {
password, ok = value.(string)
}
if !ok {
log.Exitf("No --password parameter specified, and no \"blobServerPassword\" defined in %q", ConfigFilePath())
}
if password == "" {
// TODO: provide way to override warning?
// Or make a way to do deferred errors? A blank password might
// be valid, but it might also signal the root cause of an error
// in the future.
log.Printf("Warning: blank \"blobServerPassword\" defined in %q", ConfigFilePath())
}
return password
}
// Returns blobref of signer's public key, or nil if unconfigured.
@ -79,18 +103,18 @@ func (c *Client) SignerPublicKeyBlobref() *blobref.BlobRef {
key := "publicKeyBlobref"
v, ok := config[key]
if !ok {
log.Printf("No key %q in JSON configuration file %q", key, configFilePath())
log.Printf("No key %q in JSON configuration file %q", key, ConfigFilePath())
return nil
}
s, ok := v.(string)
if !ok {
log.Printf("Expected a string value for key %q in JSON file %q",
key, configFilePath())
key, ConfigFilePath())
}
ref := blobref.Parse(s)
if ref == nil {
log.Printf("Bogus value %#v for key %q in file %q; not a valid blobref",
s, key, configFilePath())
s, key, ConfigFilePath())
}
return ref
}

View File

@ -8,10 +8,28 @@ import (
"io/ioutil"
"crypto/openpgp/armor"
"crypto/openpgp/packet"
"strings"
)
const publicKeyMaxSize = 256 * 1024
func VerifyPublicKeyFile(file, keyid string) (bool, os.Error) {
f, err := os.Open(file, os.O_RDONLY, 0)
if err != nil {
return false, err
}
key, err := openArmoredPublicKeyFile(f)
if err != nil {
return false, err
}
if key.KeyIdString() != strings.ToUpper(keyid) {
return false, os.NewError(fmt.Sprintf("Key in file %q has id %q; expected %q",
file, key.KeyIdString(), keyid))
}
return true, nil
}
func openArmoredPublicKeyFile(reader io.ReadCloser) (*packet.PublicKeyPacket, os.Error) {
defer reader.Close()