Git Product home page Git Product logo

acmetool's Introduction

acmetool

[webchat: freenode #acmetool]
[download count] [version]
[ppa
debian/ubuntu] Build Status

acmetool is an easy-to-use command line tool for automatically acquiring certificates from ACME servers (such as Let's Encrypt). Designed to flexibly integrate into your webserver setup to enable automatic verification. Unlike the official Let's Encrypt client, this doesn't modify your web server configuration.

✅ Zero-downtime autorenewal
✅ Supports any webserver
✅ Fully automatable
✅ Single-file dependency-free binary
✅ Idempotent
✅ Fast setup

You can perform verifications using port 80 or 443 (if you don't yet have a server running on one of them); via webroot; by configuring your webserver to proxy requests for /.well-known/acme-challenge/ to a special port (402) which acmetool can listen on; or by configuring your webserver not to listen on port 80, and instead running acmetool's built in HTTPS redirector (and challenge responder) on port 80. This is useful if all you want to do with port 80 is redirect people to port 443.

You can run acmetool on a cron job to renew certificates automatically (acmetool --batch). The preferred certificate for a given hostname is always at /var/lib/acme/live/HOSTNAME/{cert,chain,fullchain,privkey}. You can configure acmetool to reload your webserver automatically when it renews a certificate.

acmetool is intended to be "magic-free". All of acmetool's state is stored in a simple, comprehensible directory of flat files. The schema for this directory is documented.

acmetool is intended to work like "make". The state directory 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. acmetool is thus idempotent and minimises the use of state.

acmetool can optionally be used without running it as root. If you have existing certificates issued using the official client, acmetool can import those certificates, keys and account keys (acmetool import-le).

acmetool supports both RSA and ECDSA keys and certificates. acmetool's notification hooks system allows you to write arbitrary shell scripts to be executed when new certificates are obtained. By default, this is used to reload webservers automatically, but it can also be used to distribute certificates to other servers or for other purposes.

Getting Started

Binary releases: Binary releases are available.

Download the release appropriate for your platform and simply copy the acmetool binary to /usr/bin.

_cgo releases are preferred over non-_cgo releases where available, but non-_cgo releases may be more compatible with older OSes.

Ubuntu users: A binary release PPA, ppa:hlandau/rhea (package acmetool) is available.

$ sudo add-apt-repository ppa:hlandau/rhea
$ sudo apt-get update
$ sudo apt-get install acmetool

You can also download .deb files manually.

(Note: There is no difference between the .deb files for different Ubuntu release codenames; they are interchangeable and completely equivalent.)

Debian users: The Ubuntu binary release PPA also works with Debian:

# echo 'deb http://ppa.launchpad.net/hlandau/rhea/ubuntu xenial main' > /etc/apt/sources.list.d/rhea.list
# apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9862409EF124EC763B84972FF5AC9651EDB58DFA
# apt-get update
# apt-get install acmetool

You can also download .deb files manually.

(Note: There is no difference between the .deb files for different Ubuntu release codenames; they are interchangeable and completely equivalent.)

RPM-based distros: A copr RPM repository is available.

If you have dnf installed:

$ sudo dnf copr enable hlandau/acmetool
$ sudo dnf install acmetool

Otherwise use the .repo files on the repository page and use yum, or download RPMs and use rpm directly.

Void Linux users: acmetool is in the repositories:

$ sudo xbps-install acmetool

Arch Linux users: An AUR PKGBUILD for building from source is available.

$ wget https://aur.archlinux.org/cgit/aur.git/snapshot/acmetool-git.tar.gz
$ tar xvf acmetool-git.tar.gz
$ cd acmetool-git
$ makepkg -s
$ sudo pacman -U ./acmetool*.pkg.tar.xz

Alpine Linux users: An APKBUILD for building from source is available.

FreeBSD users: FreeBSD port is available.

Building from source: You will need Go installed to build from source.

If you are on Linux, you will need to make sure the development files for libcap are installed. This is probably a package for your distro called libcap-dev or libcap-devel or similar.

# This is necessary to work around a change in Git's default configuration
# which hasn't yet been accounted for in some places.
$ git config --global http.followRedirects true

$ git clone https://github.com/hlandau/acme
$ cd acme
$ make && sudo make install

  # (People familiar with Go with a GOPATH setup can alternatively use go get/go install:)
  $ git config --global http.followRedirects true
  $ go get github.com/hlandau/acmetool

After installation

# Run the quickstart wizard. Sets up account, cronjob, etc.
# (If you want to use ECDSA keys or set RSA key size, pass "--expert".)
$ sudo acmetool quickstart

# Configure your webserver to serve challenges if necessary.
# See https://hlandau.github.io/acmetool/userguide#web-server-configuration
$ ...

# Request the hostnames you want:
$ sudo acmetool want example.com www.example.com

# Now you have certificates:
$ 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.

The want subcommand states that you want a certificate for the given hostnames. (If you want separate certificates for each of the hostnames, run the want subcommand separately for each hostname.)

The default subcommand, reconcile, is like "make" and makes sure all desired hostnames are satisfied by valid certificates which aren't soon to expire. want calls reconcile automatically.

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

[screenshot]

Webroot: acmetool can place challenge files in a given directory, allowing your normal web server to serve them. The files must be served from the path you specify at /.well-known/acme-challenge/.

Information on configuring your web server.

Proxy: acmetool can respond to validation challenges by serving them on port 402. In order for this to be useful, you must configure your webserver to proxy requests under /.well-known/acme-challenge/ to http://127.0.0.1:402/.well-known/acme-challenge.

Information on configuring your web server.

Stateless: You configure your webserver to respond statelessly to challenges for a given account key without consulting acmetool. This requires nothing more than a one-time web server configuration change and no "moving parts". Information on configuring stateless challenges.

Redirector: acmetool redirector starts an HTTP server on port 80 which redirects all requests to HTTPS, as well as serving any necessary validation responses. The acmetool quickstart wizard can set it up for you if you use systemd. Otherwise, you'll need to configure your system to run acmetool redirector --service.uid=USERNAME --service.daemon=1 as a service, where USERNAME is the username you want the daemon to drop to.

Make sure your web server is not listening on port 80.

Listen: If you are for some reason not running anything on port 80 or 443, acmetool will use those ports. Either port being available is sufficient. This is only really useful for development purposes.

Hook: You can write custom shell scripts (or binary executables) which acmetool invokes to provision challenge files at the desired location. For example, you could rsync challenge files to a directory on a remote server. More information.

Renewal

acmetool will try to renew certificates automatically once they are 30 days from expiry, or 66% through their validity period, whichever is lower. Note that Let's Encrypt currently issues 90 day certificates.

acmetool will exit with an error message with nonzero exit status if it cannot renew a certificate, so it is suitable for use in a cronjob. Ensure your system is configured so that you get notifications of failing cronjobs.

If a cronjob fails, you should intervene manually to see what went wrong by running acmetool (possibly with --xlog.severity=debug for verbose logging).

Library

The client library which these utilities use can be used independently by any Go code. README and source code. Godoc.

Comparison with...

certbot: A heavyweight Python implementation which is a bit too “magic” for my tastes. Tries to mutate your webserver configuration automatically.

acmetool is a single-file binary which only depends on basic system libraries (on Linux, these are libc, libpthread, libcap, libattr). It doesn't do anything to your webserver; it just places certificates at a standard location and can also reload your webserver (whichever webserver it is) by executing hook shell scripts.

acmetool isn't based around individual transactions for obtaining certificates; it's about satisfying expressed requirements by any means necessary. Its comprehensible, magic-free state directory makes it as stateless and idempotent as possible.

lego: Like acmetool, xenolf/lego provides a library and client utility. The utility provides commands for creating certificates, but doesn't provide a compelling system for managing the lifetime of the short-lived certificates offered by Let's Encrypt. The user is expected to generate and install all certificates manually.

gethttpsforfree: diafygi/gethttpsforfree provides an HTML file which uses JavaScript to make requests to an ACME server and obtain certificates. It's a functional user interface, but like lego it provides no answer for the automation issue, and is thus impractical given the short lifetime of certificates issued by Let's Encrypt.

Comparison, list of client implementations

acmetoolcertbotlegogethttpsforfree
Automatic renewalYesNot yetNoNo
SAN supportYesYesYesYes
ECC supportYesNoNoNo
OCSP Must Staple supportYesNoNoNo
Revocation supportYesYesYesNo
State managementYes†Yes
Single-file binaryYesNoYesYes
Quickstart wizardYesYesNoNo
Modifies webserver configNoBy defaultNoNo
Non-root supportOptionalOptionalOptional
Supports ApacheYesYes
Supports nginxYesExperimental
Supports HAProxyYesNo
Supports HitchYesNo
Supports any web serverYesWebroot‡
Authorization via webrootYesYesManual
Authorization via port 80 redirectorYesNoNoNo
Authorization via proxyYesNoNoNo
Authorization via listener§YesYesYesNo
Authorization via DNSHook onlyNoYesNo
Authorization via custom hookYesNoNoNo
Import state from official clientYes
Windows (basic) supportNoNoYes
Windows integration supportNoNoNo

† acmetool has a different philosophy to state management and configuration to the Let's Encrypt client; see the beginning of this README.

‡ The webroot method does not appear to provide any means of reloading the webserver once the certificate has been changed, which means auto-renewal requires manual intervention.

§ Requires downtime.

This table is maintained in good faith; I believe the above comparison to be accurate. If notified of any inaccuracies, I will rectify the table and publish a notice of correction here:

  • This table previously stated that the official Let's Encrypt client doesn't support non-root operation. This was incorrect; it can be installed at user level and be configured to use user-writable directories.

Documentation & Support

For more documentation see:

If your question or issue isn't resolved by any of the above, file an issue.

IRC: #acmetool on Freenode (webchat).

Licence

© 2015—2019 Hugo Landau <[email protected]>    MIT License

Licenced under the licence with SHA256 hash fd80a26fbb3f644af1fa994134446702932968519797227e07a1368dea80f0bc, a copy of which can be found here.

acmetool's People

Contributors

akissa avatar cpu avatar csstaub avatar denisbr avatar dvrkps avatar glasser avatar hlandau avatar joneskoo avatar josephlhall avatar joto avatar k3a avatar krono avatar l-p avatar mhutter avatar petercolberg avatar pricechild avatar samm-git avatar towo avatar vaelatern avatar wayned avatar willnorris avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

acmetool's Issues

hooks execution duplicated, run-parts not portable

i've remapped run-parts and i'm seeing it runs several times:

<0>root@tako(6)~# /usr/local/bin/acmetool want www.psu.je
/usr/local/sbin/run-parts -a live-updated -a psu.je /usr/lib/acme/hooks
/usr/local/sbin/run-parts -a live-updated -a www.psu.je /usr/lib/acme/hooks
/usr/local/sbin/run-parts -a live-updated -a psu.je /usr/lib/acme/hooks
/usr/local/sbin/run-parts -a live-updated -a www.psu.je /usr/lib/acme/hooks
/usr/local/sbin/run-parts -a live-updated -a psu.je /usr/lib/acme/hooks
/usr/local/sbin/run-parts -a live-updated -a www.psu.je /usr/lib/acme/hooks
<0>root@tako(8)~# cat /usr/local/sbin/run-parts 
#!/bin/bash
echo "$0 $*"

also, run-parts on RH/CentOS doesn't accept any args:

/usr/bin/run-parts -a live-updated -a www.psu.je /usr/lib/acme/hooks
Not a directory: -a

i'd suggest re-implementing run-parts internally and feed hooks with list of modified domains on STDIN.

ECC support

  • for account keys
  • for certificate requests

In reality this is blocked on Let's Encrypt support for either.

Is using /var/run to store challenges a security issue?

Since /var/run is a tmpfs on modern systems, this could be an issue, especially if an attacker could use a symlink to cause something else to be overwritten as root. Or if someone symlinked to it from their web directory, it could be used to serve data under .well-known/acme-challenge.

Currently the redirector GID option and the webroot depositor in the HTTP responder try to be a bit paranoid about this by refusing to follow symlinks, but the semantics of O_NOFOLLOW mean that isn't 100% effective, because it only restricts the last path component.

Maybe /var/run/acme/acme-challenge should be moved to /var/cache/acme/acme-challenge or /var/spool/acme/acme-challenge to reduce the attack surface.

Add --no-reconcile option to want subcommand

I've been attempting to build a server image, but with missing certs which will be filled in by acmetool reconcile at boot time.

The ideal situation would be to call acmetool want <url> during server build, and then at server boot reconcile will do the rest. The key issue is that when want is called, the server is unable to respond to the challenge/response - it can only do that later when it's up and running.

Thoughts? I don't think this will be difficult to achieve looking at the codebase, but I thought I'd open a ticket :)

Problems with webroot

Hi,

with your help and the mention of --xlog.severity=debug in Issue #27 I managed to dig a little deeper into my problems of requesting certificates. I have configured acmetool to use the webroot-method during quickstart setup, and created a /var/run/acme/acme-challenge/ directory for which the acme user has full access rights. My nginx is configured to serve this directory on challenge requests.
However, it just isn't working. Is acmetool requested to delete the challenges after presenting them to the servers? Because the directory is empty...

And the debug output is strange as well. Here it is:

20151207135239 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/directory
20151207135239 [DEBUG] acme.api: response: &{200 OK 200 HTTP/1.1 1 1 map[Expires:[Mon, 07 Dec 2015 12:52:39 GMT] Cache-Control:[max-age=0, no-cache, no-store] Pragma:[no-cache] Date:[Mon, 07 Dec 2015 12:52:39 GMT] Server:[nginx] Content-Type:[application/json] X-Frame-Options:[DENY] Connection:[keep-alive] Content-Length:[263] Replay-Nonce:[UMkeqsvY1jKa3g_jozGKhoFXbUndeTLkwh-kv_itvxo] Strict-Transport-Security:[max-age=604800]] 0xc82035c140 263 [] false map[] 0xc82030e8c0 0xc8202173f0} <nil>
20151207135239 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/acme/new-reg
20151207135239 [DEBUG] acme.api: response: &{409 Conflict 409 HTTP/1.1 1 1 map[Cache-Control:[max-age=0, no-cache, no-store] Server:[nginx] Content-Length:[94] Location:[https://acme-v01.api.letsencrypt.org/acme/reg/54081] Replay-Nonce:[reOzzmScj6BGqJeXl_eRFy3lmr4Quady0XLVoBgKYHE] Content-Type:[application/problem+json] Expires:[Mon, 07 Dec 2015 12:52:39 GMT] Pragma:[no-cache] Date:[Mon, 07 Dec 2015 12:52:39 GMT]] 0xc820406180 94 [] true map[] 0xc8203307e0 0xc8202173f0} <nil>
20151207135239 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/acme/reg/54081
20151207135240 [DEBUG] acme.api: response: &{202 Accepted 202 HTTP/1.1 1 1 map[Connection:[keep-alive] Server:[nginx] Content-Type:[application/json] Content-Length:[583] Link:[<https://acme-v01.api.letsencrypt.org/acme/new-authz>;rel="next" <https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf>;rel="terms-of-service"] Replay-Nonce:[R3l9epz_1EnPYwvRTzYbpYaVrDrHU7ufEcHt7ZvnARE] Date:[Mon, 07 Dec 2015 12:52:40 GMT] Expires:[Mon, 07 Dec 2015 12:52:40 GMT] Cache-Control:[max-age=0, no-cache, no-store] Pragma:[no-cache]] 0xc8201e0bc0 583 [] false map[] 0xc820106460 0xc8201c8580} <nil>
20151207135240 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/directory
20151207135240 [DEBUG] acme.api: response: &{200 OK 200 HTTP/1.1 1 1 map[Server:[nginx] Content-Type:[application/json] X-Frame-Options:[DENY] Expires:[Mon, 07 Dec 2015 12:52:40 GMT] Connection:[keep-alive] Content-Length:[263] Replay-Nonce:[7P_CbwJZELC9mywMqnf1L0QKOQanxP71sR4yeNRxrRI] Strict-Transport-Security:[max-age=604800] Cache-Control:[max-age=0, no-cache, no-store] Pragma:[no-cache] Date:[Mon, 07 Dec 2015 12:52:40 GMT]] 0xc8204e0080 263 [] false map[] 0xc8203308c0 0xc8201c8580} <nil>
20151207135240 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/acme/new-authz
20151207135240 [DEBUG] acme.api: response: &{201 Created 201 HTTP/1.1 1 1 map[Content-Length:[568] Location:[https://acme-v01.api.letsencrypt.org/acme/authz/fHJ0-wMCapI1bWOjMfbhPQu4rrQpRO5IHxTim_i5q0w] X-Frame-Options:[DENY] Strict-Transport-Security:[max-age=604800] Expires:[Mon, 07 Dec 2015 12:52:40 GMT] Pragma:[no-cache] Connection:[keep-alive] Server:[nginx] Content-Type:[application/json] Link:[<https://acme-v01.api.letsencrypt.org/acme/new-cert>;rel="next"] Replay-Nonce:[59Mt6eafpL-Us05QW17rjwImUcHwQd8rSjdcuiDnSBY] Cache-Control:[max-age=0, no-cache, no-store] Date:[Mon, 07 Dec 2015 12:52:40 GMT]] 0xc8203800c0 568 [] false map[] 0xc82044c000 0xc8201c8580} <nil>
20151207135240 [DEBUG] acme.solver: attempting challenge type tls-sni-01
20151207135240 [DEBUG] acme.solver: challenge start failed: listen tcp :443: bind: permission denied
20151207135240 [DEBUG] acme.solver: attempting challenge type http-01
20151207135240 [DEBUG] acme.responder: failed to listen on :80: listen tcp :80: bind: permission denied
20151207135240 [DEBUG] acme.responder: failed to listen on 127.0.0.1:402: listen tcp 127.0.0.1:402: bind: permission denied
20151207135240 [DEBUG] acme.responder: failed to listen on [::1]:402: listen tcp [::1]:402: bind: permission denied
20151207135240 [DEBUG] acme.responder: http-01 self test
20151207135240 [INFO] acme.responder: http-01 self test failed: non-200 status code when doing self-test
20151207135240 [DEBUG] acme.solver: challenge start failed: non-200 status code when doing self-test
20151207135240 [CRITICAL] acmetool: fatal: reconcile: failed all combinations

I am a little unsure why acmetool tries to bind on :80 or :402. This should be the behaviour when running acmetool as a redirector or with nginx proxying the requests to acmetool. So why does acmetool try to bind here? Is it some kind of fallback method if webroot method fails? But i have honestly no idea why webroot method fails. The nginx logfile states that there was a request by "Let's Encrypt validation server" on a certain challenge which was answered by my nginx with an HTTP 200 answer.

I have double-checked the procedure with the proxy method, i got a proper handshake and a response from the LE server, so maybe the webroot is some kind of buggy?

document error behavior

Reading the documentation it's not clear what happens if a requested certificate cannot be obtained, or if a certificate renewal cannot be prerformed in time. I'd hope for reasonable error messages and nonzero exit status of the command. Documenting this is important to allow for planning for these exceptional circumstances that can't easily be tested.

HAProxy support

haproxy requires pem file composed of key/cert/intermediates/dhparams which is not covered by acme-sss.
the hook below assembles this pem and instructs haproxy to reload.
i'm not sending PR as your code has 'reload' hook baked in, please decide whether any additional hooks should be placed somewhere in distribution. feel free to use it:
https://gist.github.com/asquelt/77435c9061c84d7aebff

Add challenge test mode.

It would be nice if there was a command to test that challenge response was working properly.

Reconcile fails in Ubuntu 14.04

I tried both the binary and compiling the source code (make && make install) with Go 1.5.2 installed.

If I run acmetool quickstart it guides me correctly and everything goes great.

But when I try to acmetool want myserver.com , it always fails with a message that goes like:

YYYYMMDDHHmm [CRITICAL] acmetool: fatal: reconcile: failed all combinations

I've tried googling for that error, but I have found nothing.

Any help?

Relocate logs errors of unexpected operator

Running make:

-e  [RELOCATE]
/bin/sh: 8: [: ./Makefile: unexpected operator
/bin/sh: 8: [: ./README.md: unexpected operator
/bin/sh: 8: [: ./_doc: unexpected operator
/bin/sh: 8: [: ./acmeapi: unexpected operator
/bin/sh: 8: [: ./acmeutils: unexpected operator
/bin/sh: 8: [: ./cmd: unexpected operator
/bin/sh: 8: [: ./fdb: unexpected operator
/bin/sh: 8: [: ./interaction: unexpected operator
/bin/sh: 8: [: ./notify: unexpected operator
/bin/sh: 8: [: ./redirector: unexpected operator
/bin/sh: 8: [: ./responder: unexpected operator
/bin/sh: 8: [: ./solver: unexpected operator
/bin/sh: 8: [: ./storage: unexpected operator
/bin/sh: 8: [: ./.: unexpected operator
/bin/sh: 8: [: ./..: unexpected operator
/bin/sh: 8: [: ./.git: unexpected operator
/bin/sh: 8: [: ./.travis: unexpected operator
/bin/sh: 8: [: ./.travis.yml: unexpected operator
-e  [GO-GET]      github.com/hlandau/acme
-e  [DIRS]
-e  [DIRS]
-e  [GO-INSTALL]      github.com/hlandau/acme/cmd/acmetool

Allow multiple domains in one cert via SAN

Using Subject Alternative Name (SAN) fields in the cert you are able to specify multiple domains, eg

example.com www.example.com web.example.com

can all be valid under the same certificate. Useful in the situation were ELBs can only have 1 cert.

Something like ~~ ./acmetool want example.com --sni www.example.com,web.example.com

Unable to get webroot working

A response file:

acmetool-quickstart-choose-server: https://acme-v01.api.letsencrypt.org/directory
acmetool-quickstart-choose-method: webroot
acme-enter-email: [email protected]
acmetool-quickstart-complete: true
acmetool-quickstart-webroot-path: /srv/app/dirname/.well-known/acme-challenge
acmetool-quickstart-install-cronjob: true
acmetool-quickstart-install-haproxy-script: false
acmetool-quickstart-install-redirector-systemd: false
acmetool-quickstart-rsa-key-size: 4096
acme-agreement:https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf: true

Running quickstart gives me this:

> sudo acmetool quickstart --response-file=/tmp/responses.yaml
[-------------------------------------------------------------------] 0.00 % 12s

Running want gives me this - which says it fails to bind 80 & 443, which it shouldn't do under webroot mode. Am I missing something here?

> sudo acmetool --xlog.severity=debug want subdomain.domain.com
20151208164026 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/directory
20151208164027 [DEBUG] acme.api: response: &{200 OK 200 HTTP/1.1 1 1 map[Cache-Control:[max-age=0, no-cache, no-store] Pragma:[no-cache] Date:[Tue, 08 Dec 2015 16:40:27 GMT] Connection:[keep-alive] Content-Type:[application/json] Replay-Nonce:[P9QJjb7bz0bWGZzKL2Q3W3voCwjRsWrjKiq7GdIUPwE] X-Frame-Options:[DENY] Strict-Transport-Security:[max-age=604800] Expires:[Tue, 08 Dec 2015 16:40:27 GMT] Server:[nginx] Content-Length:[263]] 0xc82017ab80 263 [] false map[] 0xc820692000 0xc820096dc0} <nil>
20151208164027 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/acme/new-reg
20151208164027 [DEBUG] acme.api: response: &{409 Conflict 409 HTTP/1.1 1 1 map[Replay-Nonce:[mApHfKp8tas_xOT-pwKUcrY22HXRdWioDZ-Z58WSFj8] Cache-Control:[max-age=0, no-cache, no-store] Pragma:[no-cache] Date:[Tue, 08 Dec 2015 16:40:27 GMT] Location:[https://acme-v01.api.letsencrypt.org/acme/reg/61271] Content-Type:[application/problem+json] Content-Length:[94] Expires:[Tue, 08 Dec 2015 16:40:27 GMT] Server:[nginx]] 0xc8206a7f80 94 [] true map[] 0xc8204d3a40 0xc820096dc0} <nil>
20151208164027 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/acme/reg/61271
20151208164027 [DEBUG] acme.api: response: &{202 Accepted 202 HTTP/1.1 1 1 map[Server:[nginx] Content-Type:[application/json] Replay-Nonce:[1j74g_vMHXem5uj9e8ozawAIUtgtQBcFgRFIsfh1K3U] Expires:[Tue, 08 Dec 2015 16:40:27 GMT] Date:[Tue, 08 Dec 2015 16:40:27 GMT] Connection:[keep-alive] Content-Length:[908] Link:[<https://acme-v01.api.letsencrypt.org/acme/new-authz>;rel="next" <https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf>;rel="terms-of-service"] Cache-Control:[max-age=0, no-cache, no-store] Pragma:[no-cache]] 0xc8206ced80 908 [] false map[] 0xc8204cf500 0xc8204dbad0} <nil>
20151208164027 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/directory
20151208164027 [DEBUG] acme.api: response: &{200 OK 200 HTTP/1.1 1 1 map[Connection:[keep-alive] Strict-Transport-Security:[max-age=604800] Expires:[Tue, 08 Dec 2015 16:40:27 GMT] Date:[Tue, 08 Dec 2015 16:40:27 GMT] Replay-Nonce:[UX3ttCjAIgiv-w9vjSbAyyGsJhLncIdsTHGMpVpV-ZE] X-Frame-Options:[DENY] Cache-Control:[max-age=0, no-cache, no-store] Pragma:[no-cache] Server:[nginx] Content-Type:[application/json] Content-Length:[263]] 0xc8206cf000 263 [] false map[] 0xc8204781c0 0xc8204dbad0} <nil>
20151208164027 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/acme/new-authz
20151208164027 [DEBUG] acme.api: response: &{201 Created 201 HTTP/1.1 1 1 map[Link:[<https://acme-v01.api.letsencrypt.org/acme/new-cert>;rel="next"] Location:[https://acme-v01.api.letsencrypt.org/acme/authz/FVad8k7QoPaFXm3UUE0QEeNvLMuMribzyG8O4H30SjE] Replay-Nonce:[bmXXS6sVvGF7Z4lKYCO931qzKumkueJ6msnfUJITsxk] X-Frame-Options:[DENY] Strict-Transport-Security:[max-age=604800] Server:[nginx] Content-Type:[application/json] Content-Length:[566] Date:[Tue, 08 Dec 2015 16:40:27 GMT] Connection:[keep-alive] Expires:[Tue, 08 Dec 2015 16:40:27 GMT] Cache-Control:[max-age=0, no-cache, no-store] Pragma:[no-cache]] 0xc820668c40 566 [] false map[] 0xc820390d20 0xc8204dbad0} <nil>
20151208164027 [DEBUG] acme.solver: attempting challenge type tls-sni-01
20151208164027 [DEBUG] acme.solver: challenge start failed: listen tcp :443: bind: address already in use
20151208164027 [DEBUG] acme.solver: attempting challenge type http-01
20151208164027 [DEBUG] acme.responder: failed to listen on :80: listen tcp :80: bind: address already in use
20151208164027 [DEBUG] acme.responder: writing webroot challenge files
20151208164027 [DEBUG] acme.responder: http-01 self test
20151208164027 [INFO] acme.responder: http-01 self test failed: got 200 response when doing self-test, but with the wrong data
20151208164027 [DEBUG] acme.responder: removing webroot file /srv/app/dirname/.well-known/acme-challenge/oEu4ySycFSNtBtY4XEi3ppRN8rQiyBtH53pLCBiZCKU
20151208164027 [DEBUG] acme.responder: removing webroot file /var/run/acme/acme-challenge/oEu4ySycFSNtBtY4XEi3ppRN8rQiyBtH53pLCBiZCKU
20151208164027 [DEBUG] acme.solver: challenge start failed: got 200 response when doing self-test, but with the wrong data
20151208164027 [CRITICAL] acmetool: fatal: reconcile: failed all combinations

Needs a little more verbose error handling

I am currently testing acmetool with a domain i am supposing is rate limited, since i fired too many requests with it, because i couldn't figure how to properly use the "official" client.
However, i am unsure if the domain is still in rate limiting, or will be accepted by the letsencrypt-servers again.
However, testing it with acmetool results in 20151207111617 [CRITICAL] acmetool: fatal: reconcile: failed all combinations which seems to be a general exception in solver/respond.go. So, for now, i am unsure if my combination of acmetool-nosudo-webroot is faulty or if my domain is still not accepted by LE's servers. A little more verbosity in error-handling would be great.

Add hook/notify test mode

I was creating a hook script yesterday to copy and change owner of cert/privkey, so murmur (mumble) could use it.
But every time I needed test it, it required me to delete the acme/live/, so the hook script got called. I don't think that is optimal.

Regards Kristian

acmetool doesn't work at OpenBSD 5.7 i386

acmetool v0.0.12

$ sudo acmetool quickstart
Illegal instruction (core dumped)
$ sudo gdb -c acmetool.core bin/acmetool
GNU gdb 6.3
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-unknown-openbsd5.7"...
Core was generated by `acmetool'.
Program terminated with signal 4, Illegal instruction.
Die: DW_TAG_unspecified_type (abbrev = 10, offset = 939302)
has children: FALSE
attributes:
DW_AT_name (DW_FORM_string) string: ""
Dwarf Error: Cannot find type of die in module /home/estet/acmetool-v0.0.12-openbsd_386/bin/acmetool $

RFE: Link libcap statically into the binaries if possible

I am using acmetool on CentOS 5 since it seems very hard to get the official client to run on such an old operating system. Unfortunately, libcap.so.2 is not available there. The binary runs, apparently without issues, if I sed -i -e s/libcap\.so\.2/libcap.so.1/g acmetool (and also workaround the whiptail issue I'm filing next), but this is a horrible hack that I do not want to have to do.

Is it possible to link libcap statically? (That said, you need to be careful to not introduce a requirement for a too new glibc that way, which would only make things worse. At this time, acmetool works fine with the glibc 2.5 in CentOS 5. The highest requested symbol version I found was actually GLIBC_2.3.4.)

Hard coded user os /var/run seems to make this unworkable without root/sudo

Hi,

For fun of it tried to get this running on webfaction which is a great shared service that gives you a ton of control. You don't have a root account or sudo access though.

I have it setup using WEBROOT and all seems OK - i manually verified making a fake challenge file and loading it.

I got this working by manually setting --state and --hooks (as well as webroot) to be a path under my home dir.

But when I run want I get:

$ acmetool --state $HOME/acme --hooks $HOME/acme/hooks want <mydomain>
20151211122610 [ERROR] acme.responder: failed to open webroot file /var/run/acme/acme-challenge/LcjeWQJ2rMsQk0L0S1y4h0gCSguDAsESluhypLT9lOI: open /var/run/acme/acme-challenge/LcjeWQJ2rMsQk0L0S1y4h0gCSguDAsESluhypLT9lOI: no such file or directory
20151211122610 [ERROR] acme.storage: could not obtain authorization for <mydomain>: failed all combinations
20151211122610 [ERROR] acme.storage: failed to request certificate for target Target(<mydomain>;;0): failed all combinations
20151211122610 [CRITICAL] acmetool: fatal: reconcile: the following errors occurred:
error satisfying target Target(<mydomain>;;0): failed all combinations

Is it possible to have this temp dir configurable too such that this can work without access to /var/run or is the problem something else entirely?

fatal error: sys/capability.h: No such file or directory

Compilation may fail with the following error unless the package libcap-dev (Debian/Ubuntu) / libcap-devel (RedHat) is installed.

# gopkg.in/hlandau/service.v2/daemon/caps
GOPATH/src/gopkg.in/hlandau/service.v2/daemon/caps/caps-linuxc.go:9:28: fatal error: sys/capability.h: No such file or directory
 #include <sys/capability.h>

Webroot path should be per domain

If one wants to use multiple acmetool want … commands for several VHOSTs on the same server, the webroot path will be different for each of them. But at the moment, acmetool only asks for one webroot path for everything. It is probably (not tested yet) possible to work around that by manual symlink trickery, e.g.:

mkdir /var/lib/acme/.well-known
ln -s /var/lib/acme/.well-known /vhost/foo.example.com/.well-known
ln -s /var/lib/acme/.well-known /vhost/bar.example.com/.well-known

(and then setting /var/lib/acme/.well-known/acme-challenges as the path to store challenges in), but it would be much nicer if there were a way to set the path per domain. (The symlink trickery also only works if the HTTP server is configured to follow symlinks.)

Running make messes up the repository

Running the make command basically rewrites the whole directory, even removing the .git folder. So I clone the repo, run make & make install and then if some update is released, I need to trash that folder and re-clone the repo instead of pulling the new code.

webroot request hanging

Hi, I am trying to request a test certificate and while I see letsencrypt accessing my well-known URL, I get no answer:

20151206105359 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/directory
20151206105400 [DEBUG] acme.api: response: &{200 OK 200 HTTP/1.1 1 1 map[Connection:[keep-alive] Server:[nginx] Content-Length:[263] Strict-Transport-Security:[max-age=604800] Expires:[Sun, 06 Dec 2015 09:54:00 GMT] Cache-Control:[max-age=0, no-cache, no-store] Content-Type:[application/json] Replay-Nonce:[2YAuMdK60eFTRP3gAD4Ypaui2MD-M2R0Ff_bcbHpRew] X-Frame-Options:[DENY] Pragma:[no-cache] Date:[Sun, 06 Dec 2015 09:54:00 GMT]] 0xc820340d80 263 [] false map[] 0xc820309420 0xc82021a9a0} <nil>
20151206105400 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/acme/new-reg
20151206105400 [DEBUG] acme.api: response: &{409 Conflict 409 HTTP/1.1 1 1 map[Content-Length:[94] Cache-Control:[max-age=0, no-cache, no-store] Pragma:[no-cache] Date:[Sun, 06 Dec 2015 09:54:00 GMT] Expires:[Sun, 06 Dec 2015 09:54:00 GMT] Server:[nginx] Content-Type:[application/problem+json] Location:[https://acme-v01.api.letsencrypt.org/acme/reg/48696] Replay-Nonce:[Gor7ZLBKqlFHmyT7wBP0a4Qav_ZeZKAKdFp6Zj2PyA8]] 0xc82024e100 94 [] true map[] 0xc82024d880 0xc82021a9a0} <nil>
20151206105400 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/acme/reg/48696
20151206105400 [DEBUG] acme.api: response: &{202 Accepted 202 HTTP/1.1 1 1 map[Pragma:[no-cache] Server:[nginx] Content-Length:[576] Cache-Control:[max-age=0, no-cache, no-store] Expires:[Sun, 06 Dec 2015 09:54:00 GMT] Date:[Sun, 06 Dec 2015 09:54:00 GMT] Connection:[keep-alive] Content-Type:[application/json] Link:[<https://acme-v01.api.letsencrypt.org/acme/new-authz>;rel="next" <https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf>;rel="terms-of-service"] Replay-Nonce:[vtsqaFSHLAaa1wAk66yRfJacCRBHZ084B6SI41IX5Fc]] 0xc8202e0100 576 [] false map[] 0xc8200d7ea0 0xc82016d290} <nil>
20151206105400 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/directory
20151206105400 [DEBUG] acme.api: response: &{200 OK 200 HTTP/1.1 1 1 map[Server:[nginx] X-Frame-Options:[DENY] Pragma:[no-cache] Date:[Sun, 06 Dec 2015 09:54:00 GMT] Content-Type:[application/json] Content-Length:[263] Replay-Nonce:[GO4clmMvNX5hGr12udwIL4JZawg97_yk3QIfcLBGkIA] Strict-Transport-Security:[max-age=604800] Expires:[Sun, 06 Dec 2015 09:54:00 GMT] Cache-Control:[max-age=0, no-cache, no-store] Connection:[keep-alive]] 0xc8202e04c0 263 [] false map[] 0xc8202bc1c0 0xc82016d290} <nil>
20151206105400 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/acme/new-authz
20151206105400 [DEBUG] acme.api: response: &{201 Created 201 HTTP/1.1 1 1 map[Content-Length:[569] Link:[<https://acme-v01.api.letsencrypt.org/acme/new-cert>;rel="next"] Location:[https://acme-v01.api.letsencrypt.org/acme/authz/qXCrJhNlZupNvU29A-JewLDN7zUVCr8NfiLKBjyJzIM] Replay-Nonce:[QQTKkLXXIPVOAmiKUwxCFrbrWNHpGPNKGdWBKqzNwRI] X-Frame-Options:[DENY] Strict-Transport-Security:[max-age=604800] Expires:[Sun, 06 Dec 2015 09:54:00 GMT] Date:[Sun, 06 Dec 2015 09:54:00 GMT] Connection:[keep-alive] Server:[nginx] Content-Type:[application/json] Cache-Control:[max-age=0, no-cache, no-store] Pragma:[no-cache]] 0xc8204e8280 569 [] false map[] 0xc82055a0e0 0xc82016d290} <nil>
20151206105400 [DEBUG] acme.solver: attempting challenge type tls-sni-01
20151206105400 [DEBUG] acme.solver: challenge start failed: listen tcp :443: bind: address already in use
20151206105400 [DEBUG] acme.solver: attempting challenge type http-01
20151206105400 [DEBUG] acme.responder: http-01 self test
20151206105401 [DEBUG] acme.responder: http-01 started
20151206105401 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/acme/challenge/qXCrJhNlZupNvU29A-JewLDN7zUVCr8NfiLKBjyJzIM/1031567
20151206105401 [DEBUG] acme.api: response: &{202 Accepted 202 HTTP/1.1 1 1 map[Content-Length:[312] Location:[https://acme-v01.api.letsencrypt.org/acme/challenge/qXCrJhNlZupNvU29A-JewLDN7zUVCr8NfiLKBjyJzIM/1031567] Expires:[Sun, 06 Dec 2015 09:54:01 GMT] Pragma:[no-cache] Content-Type:[application/json] Link:[<https://acme-v01.api.letsencrypt.org/acme/authz/qXCrJhNlZupNvU29A-JewLDN7zUVCr8NfiLKBjyJzIM>;rel="up"] Replay-Nonce:[Ku79VytL_dm71iwa8q5uaX8hW1Ygi7TQqn37ejpABCM] Cache-Control:[max-age=0, no-cache, no-store] Date:[Sun, 06 Dec 2015 09:54:01 GMT] Connection:[keep-alive] Server:[nginx]] 0xc82024e0c0 312 [] false map[] 0xc820523420 0xc82016d290} <nil>
20151206105406 [DEBUG] acme.api: request: https://acme-v01.api.letsencrypt.org/acme/challenge/qXCrJhNlZupNvU29A-JewLDN7zUVCr8NfiLKBjyJzIM/1031567
20151206105406 [DEBUG] acme.api: response: &{202 Accepted 202 HTTP/1.1 1 1 map[Connection:[keep-alive] Link:[<https://acme-v01.api.letsencrypt.org/acme/authz/qXCrJhNlZupNvU29A-JewLDN7zUVCr8NfiLKBjyJzIM>;rel="up"] Replay-Nonce:[YY0xW99e6Lp2Kf9aGW87p1SgIuALmI5flC0kat0wvEE] Cache-Control:[max-age=0, no-cache, no-store] Date:[Sun, 06 Dec 2015 09:54:06 GMT] Expires:[Sun, 06 Dec 2015 09:54:06 GMT] Pragma:[no-cache] Server:[nginx] Content-Type:[application/json] Content-Length:[548] Location:[https://acme-v01.api.letsencrypt.org/acme/challenge/qXCrJhNlZupNvU29A-JewLDN7zUVCr8NfiLKBjyJzIM/1031567]] 0xc820340bc0 548 [] false map[] 0xc8202c8000 0xc82016d290} <nil>

at this point nothing happens anymore…

the access log shows:

185.11.136.221 - - [06/Dec/2015:10:54:01 +0100] "GET /.well-known/acme-challenge/SlmxjpPu7MxyVRQvvoY-id-iQ0UksEfbBot02rYoR7k HTTP/1.1" 200 275 "-" "Go-http-client/1.1"
66.133.109.36 - - [06/Dec/2015:10:54:01 +0100] "GET /.well-known/acme-challenge/SlmxjpPu7MxyVRQvvoY-id-iQ0UksEfbBot02rYoR7k HTTP/1.1" 200 294 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"

Any hints?

Rootless mode doesn’t seem to work correctly

I’m trying to use a dedicated user for acmetool. I’m specifying a custom state path, a custom hooks path and a response file but I have some problems:

  • the cron task is not added to the user’s crontab
  • I specified the proxy method in the response file but acmetool tries to open a webroot file somewhere it has no permissions (in /var/run/acme/acme-challenge). It shouldn’t try that.

Ubuntu PPA for more versions

Hello,

Currently you publish for Xenial only. Could you add other supported versions of Ubuntu? I believe most people run Trusty in a production environment.

Regards,
Francois

interaction/dialog.go crashes if whiptail does not understand --yes-button

CentOS 5 ships an old whiptail that does not understand --yes-button. This makes interaction/dialog.go crash when it prompts for accepting the license. As a result, I cannot go beyond the registration step. Workaround: sed -i -e s/whiptail/whipt4il/g acmetool, which makes it fall back to plain stdio (because there is no whipt4il binary).

I tried to install dialog, but that is also either too old or otherwise not compatible, because there, I get a complaint about --no-tags not being supported (but in that case, it at least seems to fall back to stdio just fine; I didn't try going through the process that way though, but simply uninstalled dialog which I had installed just for acmetool).

After working around both issue #17 and this issue, acmetool 0.12.0 runs on CentOS 5.

The `want` command won’t work if LE limit is reached for *any* domains

I’m trying Let’s Encrypt via your tool since two days now and it works great. But yesterday I hit the beta limit after generating 5 certificates for subdomains of belfalas.eu (my domain).
The problem is that now when I want to generate certificates for other unrelated domains, I got this error:

20151209140116 [CRITICAL] acmetool: fatal: reconcile: HTTP error: 429 Unknown
map[Server:[nginx] Replay-Nonce:[kfgpHwkR70jXVF8yD9gLEdvA86ieMJUP1Vxv3-leBo8] Expires:[Wed, 09 Dec 2015 13:01:11 GMT] Date:[Wed, 09 Dec 2015 13:01:11 GMT] Content-Type:[application/problem+json] Content-Length:[142] Cache-Control:[max-age=0, no-cache, no-store] Pragma:[no-cache]]
{"type":"urn:acme:error:rateLimited","detail":"Error creating new cert :: Too many certificates already issued for: belfalas.eu","status":429}

I tried with the official client, I could generate the wanted certificate without any problem, so there’s a bug in your client I think.
In the error it seems it tries to do a reconcile and I think this is the problem. The cron job this night didn’t work either and output the same error.

Version argument does not output acmetool version

When running acme tool --version all I get is version information about Go.

francois@kessel /var/www $ acmetool --version
go version go1.5.1 linux/amd64 gc cgo=true
build unknown

Then reason I want to see the version is that I had acmetool installed from source before and just started using the Ubuntu PPA release and would like to make sure apt-get correctly overwrote my previous installation.

Provide aarch64 (ARM64) binary

Hello

I have a ARM server at Runabove, where I host a Mumble server. I want to "install" a validated SSL certificate. But it seems like you don't provide a aarch64 binary. I could compile it myself, but it easier just to grab a tar and extract it.

Maybe?

Regards Kristian

acmetool not responding in proxy mode

I have acmetool configured via quickstart to run in proxy mode. When I start acmetool, my nginx server access logs show a request from Let's Encrypt for the challenge and the error log is not showing an error, but acmetool hangs and provides no output. How can I find out what's going wrong here?

Can't build on Debian 7

I am not a go programmer so it's probably a super n00b thing...

I installed the golang package via apt on Debian 7 and ran make. It ran for a while but stopped with the following error:

~/sources/acme$ make && sudo make install
    [RELOCATE]    
    [GO-GET]      github.com/hlandau/acme

src/gopkg.in/yaml.v2/decode.go:4:2: no Go source files in /usr/lib/go/src/pkg/encoding
make: *** [.gotten] Error 1

Any idea?

Build fails on Debian Jessie stable

Hi,
i used the compiled builds recently, but i tried to compile from source now, to maybe help development and try the "nightlies". But the build fails on Debian Jessie. I have installed go from the Debian repos, it is version 1.3.3.
But i get this error:

$ make
        [RELOCATE]
        [GO-GET]          github.com/hlandau/acme
# golang.org/x/crypto/ocsp
src/golang.org/x/crypto/ocsp/ocsp.go:510: undefined: crypto.Signer
# github.com/hlandau/acme/acmeapi
src/github.com/hlandau/acme/acmeapi/api.go:232: v.PublicKey.Curve.Params().Name undefined (type *elliptic.CurveParams has no field or method Name)
# github.com/hlandau/degoutils/os
src/github.com/hlandau/degoutils/os/appdata.go:7: undefined: os.LookupEnv
src/github.com/hlandau/degoutils/os/appdata.go:12: undefined: os.LookupEnv
src/github.com/hlandau/degoutils/os/appdata.go:17: undefined: os.LookupEnv
Makefile:76: recipe for target '.gotten' failed
make: *** [.gotten] Error 2

Is it maybe because my golang version is too old?

Allow for responses to be automatically provided to prompts

acmetool want foo popped up a dialog with 2 lines asking if I accepted the letsencrypt service agreement, and 39 lines of empty space. The remainer of the dialog was too large to display in my 80x40 terminal window, which made it unusable.

The quickstart has the same problem with assuming large screen size.

Personally, I don't want these dialogs at all; I want to be able to completely script use of it from the command line.

Docs for actual changes made by quickstart?

A really great little tool! Thanks.

I ran quickstart and everything is working wonderfully. I now need to know where acmetool has stored all it's config, so I can automate the install process Did I miss something in the docs?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.