diff --git a/README.md b/README.md index cd9ba09..defcffe 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ACME Client Utilities [![Build Status](https://travis-ci.org/hlandau/acme.svg?branch=master)](https://travis-ci.org/hlandau/acme) +# ACME Client Utilities [![Build Status](https://travis-ci.org/hlandau/acme.svg?branch=master)](https://travis-ci.org/hlandau/acme) [![Issue Stats](http://issuestats.com/github/hlandau/acme/badge/issue)](http://issuestats.com/github/hlandau/acme) acmetool is an easy-to-use command line tool for automatically acquiring certificates from ACME servers (such as Let's Encrypt). Designed to flexibly @@ -65,10 +65,6 @@ $ sudo acmetool want example.com www.example.com $ ls -l /var/lib/acme/live/example.com/ ``` - - The `quickstart` subcommand is a recommended wizard which guides you through the setup of ACME on your system. @@ -83,43 +79,8 @@ hostnames are satisfied by valid certificates which aren't soon to expire. If you run `acmetool reconcile` on a cronjob to facilitate automatic renewal, pass `--batch` to ensure it doesn't attempt to interact with a terminal. - +You can increase logging severity for debugging purposes by passing +`--xlog.severity=debug`. ## Validation Options diff --git a/cmd/acmetool/main.go b/cmd/acmetool/main.go index 39831c6..eea1b20 100644 --- a/cmd/acmetool/main.go +++ b/cmd/acmetool/main.go @@ -34,6 +34,8 @@ var ( batchFlag = kingpin.Flag("batch", "Do not attempt interaction; useful for cron jobs"). Bool() + stdioFlag = kingpin.Flag("stdio", "Don't attempt to use console dialogs; fall back to stdio prompts").Bool() + reconcileCmd = kingpin.Command("reconcile", "Reconcile ACME state").Default() wantCmd = kingpin.Command("want", "Add a target with one or more hostnames") @@ -66,6 +68,10 @@ func main() { interaction.NonInteractive = true } + if *stdioFlag { + interaction.NoDialog = true + } + switch cmd { case "reconcile": cmdReconcile() diff --git a/cmd/acmetool/quickstart.go b/cmd/acmetool/quickstart.go index e63c520..491f572 100644 --- a/cmd/acmetool/quickstart.go +++ b/cmd/acmetool/quickstart.go @@ -211,7 +211,7 @@ func isCronjobInstalled() bool { return installed } -func formulateCron() string { +func formulateCron(root bool) string { // Randomise cron time to avoid hammering the ACME server. var b [2]byte _, err := rand.Read(b[:]) @@ -220,10 +220,14 @@ func formulateCron() string { m := b[0] % 60 h := b[1] % 24 s := "" - if runningAsRoot() { + if root { s = "SHELL=/bin/sh\nPATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin\nMAILTO=root\n" } - s += fmt.Sprintf("%d %d * * * %s --batch ", m, h, exepath.Abs) + s += fmt.Sprintf("%d %d * * * ", m, h) + if root { + s += "root " + } + s += fmt.Sprintf("%s --batch ", exepath.Abs) if *stateFlag != storage.RecommendedPath { s += fmt.Sprintf(`--state="%s" `, *stateFlag) } @@ -241,9 +245,8 @@ func promptCron() { return } - cronString := formulateCron() - var err error + cronString := formulateCron(runningAsRoot()) if runningAsRoot() { _, err = os.Stat("/etc/cron.d") } else { @@ -514,7 +517,7 @@ The Let's Encrypt Staging Server does not issue publically trusted certificates. ResponseType: interaction.RTSelect, Options: []interaction.Option{ { - Title: "Let's Encrypt Live Server - I have been invited and want live certificates", + Title: "Let's Encrypt Live Server - I want live certificates", Value: acmeapi.LELiveURL, }, { diff --git a/interaction/auto.go b/interaction/auto.go index 193adb8..3fdf875 100644 --- a/interaction/auto.go +++ b/interaction/auto.go @@ -10,6 +10,8 @@ var Auto Interactor = autoInteractor{} var Interceptor Interactor +var NoDialog = false + func (autoInteractor) Prompt(c *Challenge) (*Response, error) { if NonInteractive { return nil, fmt.Errorf("cannot prompt the user: currently non-interactive") @@ -19,9 +21,11 @@ func (autoInteractor) Prompt(c *Challenge) (*Response, error) { return Interceptor.Prompt(c) } - r, err := Dialog.Prompt(c) - if err == nil { - return r, nil + if !NoDialog { + r, err := Dialog.Prompt(c) + if err == nil { + return r, nil + } } return Stdio.Prompt(c) @@ -52,9 +56,11 @@ func (autoInteractor) Status(info *StatusInfo) (StatusSink, error) { return s, err } - r, err := Dialog.Status(info) - if err == nil { - return r, nil + if !NoDialog { + r, err := Dialog.Status(info) + if err == nil { + return r, nil + } } return Stdio.Status(info) diff --git a/interaction/dialog.go b/interaction/dialog.go index 657b3f0..aef3691 100644 --- a/interaction/dialog.go +++ b/interaction/dialog.go @@ -198,6 +198,13 @@ func (dialogInteractor) Prompt(c *Challenge) (*Response, error) { return nil, err } + // If we get error code >1 (particularly 255) the dialog command probably + // doesn't support some option we pass it. Return an error, which should make + // us fall back to stdio. + if rc > 1 { + return nil, xerr + } + res := &Response{} if pipeW != nil { pipeW.Close() @@ -230,7 +237,8 @@ func findDialogCommand() (string, string) { return dialogCommand, dialogCommandType } - for _, s := range []string{"whiptail", "dialog"} { + // not using whiptail for now, see #18 + for _, s := range []string{"dialog"} { p, err := exec.LookPath(s) if err == nil { dialogCommand = p