Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
hlandau committed Dec 7, 2015
2 parents 12e0b54 + cadc305 commit 405a2b9
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 55 deletions.
45 changes: 3 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -65,10 +65,6 @@ $ sudo acmetool want example.com www.example.com
$ ls -l /var/lib/acme/live/example.com/
```

<!-- # Renew certificates automatically:
# Change '42' to a random integer in [0,59] to distribute the load on the server.
$ sudo /bin/sh -c "echo '42 0 * * * root /usr/local/bin/acmetool -batch' > /etc/cron.d/acmetool" -->

The `quickstart` subcommand is a recommended wizard which guides you through the
setup of ACME on your system.

Expand All @@ -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.

<!--
## Introduction
- A command line tool for acquiring certificates using a certificate storage
repository to automatically determine what certificates need to be requested.
- Acquiring a certificate is as simple as this:
`# acmetool want example.com`
If successfully acquired, the certificate will be placed in
`/var/lib/acme/live/example.com/{cert,chain,fullchain,privkey}`.
Running `acmetool -``-batch` as root on a cronjob will allow it to
automatically reacquire certificates before they expire. The certificate data
in `/var/lib/acme/live/example.com` will be updated automatically with the
new certificate. acmetool can optionally invoke a shell script after having
changed certificates if you need to reload a webserver.
- Works with Let's Encrypt.
- acmetool is designed to work like `make`. A filesystem-based certificate
repository expresses target domain names, and whenever acmetool is invoked,
it ensures that valid certificates are available to meet those names.
Certificates which will expire soon are renewed. The certificate matching
each target is symlinked into `/var/lib/acme/live/DOMAIN`, so the right
certificate for a given domain is always at `/var/lib/acme/live/DOMAIN`.
acmetool is thus idempotent and it minimises the use of state. All state is
explicitly kept in the certificate repository. There are essentially no
proprietary file formats or configuration or state files; only a repository
of certificates, a repository of ACME account keys and a set of targets. On
each invocation, ACME figures out which certificates satisfy which targets
and obtains certificates as necessary.
[Details on the state directory format.](https://github.com/hlandau/acme/blob/master/_doc/SCHEMA.md)
-->
You can increase logging severity for debugging purposes by passing
`--xlog.severity=debug`.

## Validation Options

Expand Down
6 changes: 6 additions & 0 deletions cmd/acmetool/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -66,6 +68,10 @@ func main() {
interaction.NonInteractive = true
}

if *stdioFlag {
interaction.NoDialog = true
}

switch cmd {
case "reconcile":
cmdReconcile()
Expand Down
15 changes: 9 additions & 6 deletions cmd/acmetool/quickstart.go
Original file line number Diff line number Diff line change
Expand Up @@ -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[:])
Expand All @@ -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)
}
Expand All @@ -241,9 +245,8 @@ func promptCron() {
return
}

cronString := formulateCron()

var err error
cronString := formulateCron(runningAsRoot())
if runningAsRoot() {
_, err = os.Stat("/etc/cron.d")
} else {
Expand Down Expand Up @@ -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,
},
{
Expand Down
18 changes: 12 additions & 6 deletions interaction/auto.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 9 additions & 1 deletion interaction/dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 405a2b9

Please sign in to comment.