aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Ryder <tom@sanctum.geek.nz>2017-10-05 19:35:54 +1300
committerTom Ryder <tom@sanctum.geek.nz>2017-10-05 19:35:54 +1300
commit2489ac37036bb580a8011d9cb1ab81baea72b456 (patch)
tree6b0564dda9aecf08e3d465e4c52efb13984af419
parentRemove croncryptsh (diff)
parentRename/separate out into app and module (diff)
downloadMail-Run-Crypt-2489ac37036bb580a8011d9cb1ab81baea72b456.tar.gz
Mail-Run-Crypt-2489ac37036bb580a8011d9cb1ab81baea72b456.zip
Merge branch 'perlmod'
-rw-r--r--.gitignore18
-rw-r--r--Changes8
-rw-r--r--LICENSE157
-rw-r--r--MANIFEST9
-rw-r--r--MANIFEST.SKIP9
-rw-r--r--Makefile.PL27
-rw-r--r--README63
-rw-r--r--README.markdown45
-rwxr-xr-xbin/croncrypt112
-rwxr-xr-xbin/runcrypt254
-rw-r--r--lib/Mail/Run/Crypt.pm287
-rw-r--r--share/man/man1/croncrypt.127
-rw-r--r--xt/manifest.t15
-rw-r--r--xt/pod-coverage.t24
-rw-r--r--xt/pod.t16
15 files changed, 730 insertions, 341 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1236413
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+Makefile
+Makefile.old
+Build
+Build.bat
+META.*
+MYMETA.*
+.build/
+_build/
+cover_db/
+blib/
+inc/
+.lwpcookies
+.last_cover_stats
+nytprof.out
+pod2htm*.tmp
+pm_to_blib
+Mail-Cron-Crypt-*
+Mail-Cron-Crypt-*.tar.gz
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..29a6124
--- /dev/null
+++ b/Changes
@@ -0,0 +1,8 @@
+Revision history for Perl module Mail::Run::Crypt
+
+This project began life as a monolithic Perl script. It does much the same
+thing now but it's refactored out into an application and a module.
+
+0.01 2017-10-05
+
+ - First perlmodded release, refactoring and rewriting much older code.
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index f23c229..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,157 +0,0 @@
-Artistic License 2.0
-
-Copyright (c) 2000-2006, The Perl Foundation.
-
-Everyone is permitted to copy and distribute verbatim copies of this license
-document, but changing it is not allowed. Preamble
-
-This license establishes the terms under which a given free software Package may
-be copied, modified, distributed, and/or redistributed. The intent is that the
-Copyright Holder maintains some artistic control over the development of that
-Package while still keeping the Package available as open source and free
-software.
-
-You are always permitted to make arrangements wholly outside of this license
-directly with the Copyright Holder of a given Package. If the terms of this
-license do not permit the full use that you propose to make of the Package, you
-should contact the Copyright Holder and seek a different licensing arrangement.
-Definitions
-
-"Copyright Holder" means the individual(s) or organization(s) named in the
-copyright notice for the entire Package.
-
-"Contributor" means any party that has contributed code or other material to the
-Package, in accordance with the Copyright Holder's procedures.
-
-"You" and "your" means any person who would like to copy, distribute, or modify
-the Package.
-
-"Package" means the collection of files distributed by the Copyright Holder, and
-derivatives of that collection and/or of those files. A given Package may
-consist of either the Standard Version, or a Modified Version.
-
-"Distribute" means providing a copy of the Package or making it accessible to
-anyone else, or in the case of a company or organization, to others outside of
-your company or organization.
-
-"Distributor Fee" means any fee that you charge for Distributing this Package or
-providing support for this Package to another party. It does not mean licensing
-fees.
-
-"Standard Version" refers to the Package if it has not been modified, or has
-been modified only in ways explicitly requested by the Copyright Holder.
-
-"Modified Version" means the Package, if it has been changed, and such changes
-were not explicitly requested by the Copyright Holder.
-
-"Original License" means this Artistic License as Distributed with the Standard
-Version of the Package, in its current version or as it may be modified by The
-Perl Foundation in the future.
-
-"Source" form means the source code, documentation source, and configuration
-files for the Package.
-
-"Compiled" form means the compiled bytecode, object code, binary, or any other
-form resulting from mechanical transformation or translation of the Source form.
-Permission for Use and Modification Without Distribution
-
-(1) You are permitted to use the Standard Version and create and use Modified
-Versions for any purpose without restriction, provided that you do not
-Distribute the Modified Version. Permissions for Redistribution of the Standard
-Version
-
-(2) You may Distribute verbatim copies of the Source form of the Standard
-Version of this Package in any medium without restriction, either gratis or for
-a Distributor Fee, provided that you duplicate all of the original copyright
-notices and associated disclaimers. At your discretion, such verbatim copies may
-or may not include a Compiled form of the Package.
-
-(3) You may apply any bug fixes, portability changes, and other modifications
-made available from the Copyright Holder. The resulting Package will still be
-considered the Standard Version, and as such will be subject to the Original
-License. Distribution of Modified Versions of the Package as Source
-
-(4) You may Distribute your Modified Version as Source (either gratis or for a
-Distributor Fee, and with or without a Compiled form of the Modified Version)
-provided that you clearly document how it differs from the Standard Version,
-including, but not limited to, documenting any non-standard features,
-executables, or modules, and provided that you do at least ONE of the following:
-
-(a) make the Modified Version available to the Copyright Holder of the Standard
-Version, under the Original License, so that the Copyright Holder may include
-your modifications in the Standard Version. (b) ensure that installation of your
-Modified Version does not prevent the user installing or running the Standard
-Version. In addition, the Modified Version must bear a name that is different
-from the name of the Standard Version. (c) allow anyone who receives a copy of
-the Modified Version to make the Source form of the Modified Version available
-to others under (i) the Original License or (ii) a license that permits the
-licensee to freely copy, modify and redistribute the Modified Version using the
-same licensing terms that apply to the copy that the licensee received, and
-requires that the Source form of the Modified Version, and of any works derived
-from it, be made freely available in that license fees are prohibited but
-Distributor Fees are allowed. Distribution of Compiled Forms of the Standard
-Version or Modified Versions without the Source
-
-(5) You may Distribute Compiled forms of the Standard Version without the
-Source, provided that you include complete instructions on how to get the Source
-of the Standard Version. Such instructions must be valid at the time of your
-distribution. If these instructions, at any time while you are carrying out such
-distribution, become invalid, you must provide new instructions on demand or
-cease further distribution. If you provide valid instructions or cease
-distribution within thirty days after you become aware that the instructions are
-invalid, then you do not forfeit any of your rights under this license.
-
-(6) You may Distribute a Modified Version in Compiled form without the Source,
-provided that you comply with Section 4 with respect to the Source of the
-Modified Version. Aggregating or Linking the Package
-
-(7) You may aggregate the Package (either the Standard Version or Modified
-Version) with other packages and Distribute the resulting aggregation provided
-that you do not charge a licensing fee for the Package. Distributor Fees are
-permitted, and licensing fees for other components in the aggregation are
-permitted. The terms of this license apply to the use and Distribution of the
-Standard or Modified Versions as included in the aggregation.
-
-(8) You are permitted to link Modified and Standard Versions with other works,
-to embed the Package in a larger work of your own, or to build stand-alone
-binary or bytecode versions of applications that include the Package, and
-Distribute the result without restriction, provided the result does not expose a
-direct interface to the Package. Items That are Not Considered Part of a
-Modified Version
-
-(9) Works (including, but not limited to, modules and scripts) that merely
-extend or make use of the Package, do not, by themselves, cause the Package to
-be a Modified Version. In addition, such works are not considered parts of the
-Package itself, and are not subject to the terms of this license. General
-Provisions
-
-(10) Any use, modification, and distribution of the Standard or Modified
-Versions is governed by this Artistic License. By using, modifying or
-distributing the Package, you accept this license. Do not use, modify, or
-distribute the Package, if you do not accept this license.
-
-(11) If your Modified Version has been derived from a Modified Version made by
-someone other than you, you are nevertheless required to ensure that your
-Modified Version complies with the requirements of this license.
-
-(12) This license does not grant you the right to use any trademark, service
-mark, tradename, or logo of the Copyright Holder.
-
-(13) This license includes the non-exclusive, worldwide, free-of-charge patent
-license to make, have made, use, offer to sell, sell, import and otherwise
-transfer the Package with respect to any patent claims licensable by the
-Copyright Holder that are necessarily infringed by the Package. If you institute
-patent litigation (including a cross-claim or counterclaim) against any party
-alleging that the Package constitutes direct or contributory patent
-infringement, then this Artistic License to you shall terminate on the date that
-such litigation is filed.
-
-(14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND
-CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
-WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
-NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW.
-UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY
-OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGE.
-
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..f04da1b
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,9 @@
+Changes
+bin/runcrypt
+lib/Mail/Run/Crypt.pm
+Makefile.PL
+MANIFEST
+README
+xt/manifest.t
+xt/pod-coverage.t
+xt/pod.t
diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP
new file mode 100644
index 0000000..40c4b8d
--- /dev/null
+++ b/MANIFEST.SKIP
@@ -0,0 +1,9 @@
+^\.git
+^Mail-Run-Crypt-.+\.tar\.gz$
+^MANIFEST\.SKIP$
+^MYMETA\.
+^Makefile$
+^Makefile\.old$
+^README\.markdown
+^blib
+^pm_to_blib$
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..299a4b6
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,27 @@
+use 5.006;
+use strict;
+use warnings;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+ NAME => 'Mail::Run::Crypt',
+ AUTHOR => q{Tom Ryder <tom@sanctum.geek.nz>},
+ VERSION_FROM => 'lib/Mail/Run/Crypt.pm',
+ ABSTRACT_FROM => 'lib/Mail/Run/Crypt.pm',
+ LICENSE => 'artistic_2',
+ PL_FILES => {},
+ MIN_PERL_VERSION => '5.006',
+ CONFIGURE_REQUIRES => {
+ 'ExtUtils::MakeMaker' => '0',
+ },
+ BUILD_REQUIRES => {
+ 'Test::More' => '0',
+ },
+ PREREQ_PM => {
+ #'ABC' => '1.6',
+ #'Foo::Bar::Module' => '5.0401',
+ },
+ EXE_FILES => ['bin/runcrypt'],
+ dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
+ clean => { FILES => 'Mail-Run-Crypt-*' },
+);
diff --git a/README b/README
new file mode 100644
index 0000000..436e7d9
--- /dev/null
+++ b/README
@@ -0,0 +1,63 @@
+Mail-Run-Crypt
+
+Wrapper to sign and encrypt cron(8) output and errors with PGP/MIME
+before sending them to the MAILTO destination.
+
+
+INSTALLATION
+
+To install this module, run the following commands:
+
+ perl Makefile.PL
+ make
+ make test
+ make install
+
+SUPPORT AND DOCUMENTATION
+
+After installing, you can find documentation for this module with the
+perldoc command.
+
+ perldoc Mail::Run::Crypt
+
+
+LICENSE AND COPYRIGHT
+
+Copyright (C) 2017 Tom Ryder
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the the Artistic License (2.0). You may obtain a
+copy of the full license at:
+
+L<http://www.perlfoundation.org/artistic_license_2_0>
+
+Any use, modification, and distribution of the Standard or Modified
+Versions is governed by this Artistic License. By using, modifying or
+distributing the Package, you accept this license. Do not use, modify,
+or distribute the Package, if you do not accept this license.
+
+If your Modified Version has been derived from a Modified Version made
+by someone other than you, you are nevertheless required to ensure that
+your Modified Version complies with the requirements of this license.
+
+This license does not grant you the right to use any trademark, service
+mark, tradename, or logo of the Copyright Holder.
+
+This license includes the non-exclusive, worldwide, free-of-charge
+patent license to make, have made, use, offer to sell, sell, import and
+otherwise transfer the Package with respect to any patent claims
+licensable by the Copyright Holder that are necessarily infringed by the
+Package. If you institute patent litigation (including a cross-claim or
+counterclaim) against any party alleging that the Package constitutes
+direct or contributory patent infringement, then this Artistic License
+to you shall terminate on the date that such litigation is filed.
+
+Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
+AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
+THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
+YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
+CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
+CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/README.markdown b/README.markdown
deleted file mode 100644
index be1457e..0000000
--- a/README.markdown
+++ /dev/null
@@ -1,45 +0,0 @@
-Croncrypt
-=========
-
-Wrapper to sign and encrypt `cron(8)` output and errors with PGP/MIME before
-sending them to the `MAILTO` destination.
-
- $ crontab -l
- CRONCRYPT_KEYID=0x0A1B2C3D4E5F6G7H
- CRONCRYPT_PASSPHRASE=hibbityboo
- MAILTO=me@mynet
-
- 0 1 * * * croncrypt rsync /home/tom/important-file /home/backups
-
-The main design goal is simplicity; just whack `croncrypt` in front of all your
-`crontab(5)` entries, provided they don’t use pipes or `stderr`/`stdout`
-redirects, in which case you should consider putting it all into a script file
-anyway.
-
-Don’t use your own GPG key for signing! I recommend you create a dedicated key
-just for Croncrypt, and sign it locally with `gpg --lsign` so that your
-software trusts it locally.
-
-Installation
-------------
-
-Put the `croncrypt` binary somewhere in your `crontab`’s `PATH`, and install
-the following Perl modules:
-
-* `Carp`
-* `IPC::Run3`
-* `Mail::GnuPG`
-* `MIME::Entity`
-
-On Debian-derived systems, this should do the trick:
-
- # aptitude install perl-base perl-modules libmail-gnupg-perl \
- libmime-tools-perl libipc-run3-perl
-
-License
--------
-
-Copyright (c) [Tom Ryder][1]. Distributed under [Artistic License 2.0][2].
-
-[1]: https://sanctum.geek.nz/
-[2]: http://opensource.org/licenses/artistic-license-2.0
diff --git a/bin/croncrypt b/bin/croncrypt
deleted file mode 100755
index 002dd56..0000000
--- a/bin/croncrypt
+++ /dev/null
@@ -1,112 +0,0 @@
-#!/usr/bin/env perl
-
-#
-# croncrypt: Wrapper to sign and encrypt cron output and errors with PGP/MIME
-# before sending them to the default MAILTO destination.
-#
-# CRONCRYPT_KEYID=0x0A1B2C3D4E5F6G7H
-# CRONCRYPT_PASSPHRASE=hibbityboo
-# MAILTO=tom@sanctum.geek.nz
-# 0 1 * * * tom croncrypt rsync /home/tom/important-file /home/backups
-#
-# The main design goal is simplicity; just whack a «croncrypt» in front of all
-# your cron tasks, provided they don't use pipes or stderr/stdout redirects,
-# in which case you should consider putting it all into a script file anyway.
-#
-# Don't use your own GPG key for signing! Create a dedicated key just for
-# croncrypt, and sign it locally with «gpg --lsign» maybe.
-#
-# Author: Tom Ryder <tom@sanctum.geek.nz>
-# Copyright: 2014 Sanctum
-# License: Artistic 2.0 <http://opensource.org/licenses/artistic-license-2.0>
-#
-# $Id$
-#
-package Sanctum::Croncrypt;
-
-# Force me to write this properly
-use strict;
-use warnings;
-use utf8;
-use autodie;
-
-# Decree minimum Perl version required (v5.8).
-use 5.008;
-
-# Decree package version to pacify Perl::Critic
-our $VERSION = 0.1;
-
-# Pull in some required modules
-use Carp;
-use IPC::Run3;
-use Mail::GnuPG;
-use MIME::Entity;
-
-# Bail if run without arguments
-if ( !@ARGV ) {
- printf "%s\n", 'USAGE: croncrypt <command>';
- exit 1;
-}
-
-# Bail if we don't have the environment variables we need
-my @fails;
-if ( !exists $ENV{'CRONCRYPT_KEYID'} ) {
- push @fails, 'CRONCRYPT_KEYID is not set; set it to your key ID.';
-}
-if ( !exists $ENV{'CRONCRYPT_PASSPHRASE'} ) {
- push @fails,
- 'CRONCRYPT_PASSPHRASE is not set; set it to your key\'s passphrase.';
-}
-if ( !exists $ENV{'MAILTO'} ) {
- push @fails, 'MAILTO is not set; set it to the message\'s destination.';
-}
-if (@fails) {
- foreach my $fail (@fails) {
- printf {*STDERR} "croncrypt: FAIL: %s\n", $fail;
- }
- exit 1;
-}
-
-# Read details from environment
-my $recipient = $ENV{MAILTO};
-my $key = $ENV{CRONCRYPT_KEYID};
-my $passphrase = $ENV{CRONCRYPT_PASSPHRASE};
-
-# Run the command in the arguments and wait for it to finish
-my ( @output, @errors );
-run3( \@ARGV, undef, \@output, \@errors );
-
-# If there was output, mail it
-if (@output) {
- my $subject = sprintf 'croncrypt output: %s', join q{ }, @ARGV;
- mail( $subject, \@output );
-}
-
-# If there were errors, mail them
-if (@errors) {
- my $subject = sprintf 'croncrypt errors: %s', join q{ }, @ARGV;
- mail( $subject, \@errors );
-}
-
-# Send the message to the address in $ENV{MAILTO}
-sub mail {
- my ( $subject, $content ) = @_;
-
- # Build MIME object with plaintext message
- my $mime = MIME::Entity->build(
- To => $recipient,
- Subject => $subject,
- Data => $content,
- );
-
- # Encrypt the MIME object
- my $mgpg = Mail::GnuPG->new(
- key => $key,
- passphrase => $passphrase,
- );
- $mgpg->mime_signencrypt( $mime, $recipient );
-
- # Send it
- return $mime->send();
-}
-
diff --git a/bin/runcrypt b/bin/runcrypt
new file mode 100755
index 0000000..d9a24fc
--- /dev/null
+++ b/bin/runcrypt
@@ -0,0 +1,254 @@
+#!/usr/bin/env perl
+package main;
+
+# Force me to write this properly
+use strict;
+use warnings;
+use utf8;
+
+# Target Debian-Squeeze-era Perl
+# We may be able to go older; not sure yet
+use 5.010;
+
+# Import required modules
+use Carp;
+use Getopt::Long::Descriptive;
+use Mail::Run::Crypt;
+
+# Specify package version
+our $VERSION = '0.01';
+
+# Name ourselves
+our $SELF = 'runcrypt';
+
+# Read command-line options
+my ( $opt, $usage ) = describe_options(
+ "$SELF %o COMMAND [ARG1...]",
+
+ # Key ID defaults to environment RUNCRYPT_KEYID if set
+ [
+ 'keyid|k=s',
+ 'OpenPGP key ID',
+ { default => $ENV{RUNCRYPT_KEYID} // undef },
+ ],
+
+ # Key passphrase defaults to environment RUNCRYPT_PASSPHRASE if set
+ [
+ 'passphrase|p=s',
+ 'OpenPGP passphrase',
+ { default => $ENV{RUNCRYPT_PASSPHRASE} // undef },
+ ],
+
+ # MAILTO address defaults to environment MAILTO if set
+ [
+ 'mailto|m=s',
+ 'Mail destination address (MAILTO)',
+ { default => $ENV{RUNCRYPT_MAILTO} // $ENV{MAILTO} // undef },
+ ],
+
+ # Instance name (for email subjects) defaults to $SELF
+ [
+ 'name|n=s',
+ 'Instance name (included in subject lines)',
+ { default => $SELF },
+ ],
+
+ # Newline
+ [],
+
+ # Help option
+ [ 'help', 'print usage message and exit', { shortcircuit => 1 } ],
+);
+
+# Print help if requested
+if ( $opt->help ) {
+ print $usage->text
+ or carp 'Failed stdout write';
+ exit 0;
+}
+
+# Bail if run without arguments
+if ( !@ARGV ) {
+ printf {*STDERR} $usage->text
+ or carp 'Failed stderr write';
+ exit 2;
+}
+
+# Create an MCC object
+my $mrc = Mail::Run::Crypt->new(
+ keyid => $opt->keyid,
+ passphrase => $opt->passphrase,
+ mailto => $opt->mailto,
+ name => $opt->name,
+);
+
+# Run the command given in the arguments, exiting appropriately
+$mrc->run(@ARGV);
+exit $mrc->bail;
+
+__END__
+
+=pod
+
+=encoding utf8
+
+=for stopwords
+runcrypt decrypt stdout stderr GPG OpenPGP tradename licensable MERCHANTABILITY
+
+=head1 NAME
+
+runcrypt - Encrypt and mail output from command in arguments
+
+=head1 USAGE
+
+ runcrypt
+ [--keyid KEYID]
+ [--passphrase PASSPHRASE]
+ [--mailto MAILTO]
+ [--name NAME]
+ COMMAND [ARG1 ...]
+
+=head1 DESCRIPTION
+
+This program applies C<Mail::Run::Crypt> to run a command and send any output
+or error content to the specified address. More information is available in the
+documentation for that module.
+
+=head1 REQUIRED ARGUMENTS
+
+The arguments beyond the options are used as the command name to run:
+
+ runcrypt rsync -a /mnt/a remote:mnt/b
+
+=head1 OPTIONS
+
+=over 4
+
+=item C<--keyid>
+
+The GnuPG key ID that should be used to sign and encrypt the messages. This
+defaults to the value of the environment variable C<RUNCRYPT_KEYID>.
+
+=item C<--passphrase>
+
+The passphrase use to decrypt the key. This defaults to the value of the
+environment variable C<RUNCRYPT_PASSPHRASE>.
+
+=item C<--mailto>
+
+The recipient address for the encryption portion of the email. This defaults to
+the value of the environment variable C<RUNCRYPT_MAILTO> if that is set, or
+C<MAILTO> failing that, to make it suitable for use in a C<crontab(5)> file.
+
+=item C<--name>
+
+The name for this instance of the module, which will be used as the first word
+of the subject line of any email messages it sends. This defaults to
+C<runcrypt>, which is probably good enough in most cases.
+
+=head1 DIAGNOSTICS
+
+=over 4
+
+=item Failed stdout write
+
+Usage information could not be written to the standard output stream.
+
+=item Failed stderr write
+
+Usage information could not be written to the standard error stream.
+
+=head1 EXIT STATUS
+
+The program exits with the same exit value of the command that it ran, or 127
+if the command could not be run at all. See the C<bail()> method in
+C<Mail::Run::Crypt>.
+
+=head1 CONFIGURATION
+
+You will need to have a functioning GnuPG public key setup for this to work,
+including the secret key. You should definitely not use your personal key;
+generate one specifically for mail signing and encryption instead.
+
+=head1 DEPENDENCIES
+
+=over 4
+
+=item *
+
+Perl 5.10 or newer
+
+=item *
+
+C<Carp>
+
+=item *
+
+C<Getopt::Long::Descriptive>
+
+=item *
+
+C<Mail::Run::Crypt>
+
+=back
+
+=head1 INCOMPATIBILITIES
+
+This module uses C<Mail::GnuPG> and other GPG-specific code, so it won't work
+with any other OpenPGP implementations.
+
+=head1 BUGS AND LIMITATIONS
+
+Providing the C<passphrase> directly from an environment variable is not great.
+The documentation needs to make that clear and offer a less bad alternative,
+probably specifying the path to a secure file that it refuses to use unless it
+has sufficiently restrictive permissions.
+
+This is very hard to test and the author has not yet figured out how to do
+that.
+
+=head1 AUTHOR
+
+Tom Ryder C<< <tom@sanctum.geek.nz> >>
+
+=head1 LICENSE AND COPYRIGHT
+
+Copyright (C) 2017 Tom Ryder
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the Artistic License (2.0). You may obtain a copy of the full
+license at:
+
+L<http://www.perlfoundation.org/artistic_license_2_0>
+
+Any use, modification, and distribution of the Standard or Modified Versions is
+governed by this Artistic License. By using, modifying or distributing the
+Package, you accept this license. Do not use, modify, or distribute the
+Package, if you do not accept this license.
+
+If your Modified Version has been derived from a Modified Version made by
+someone other than you, you are nevertheless required to ensure that your
+Modified Version complies with the requirements of this license.
+
+This license does not grant you the right to use any trademark, service mark,
+tradename, or logo of the Copyright Holder.
+
+This license includes the non-exclusive, worldwide, free-of-charge patent
+license to make, have made, use, offer to sell, sell, import and otherwise
+transfer the Package with respect to any patent claims licensable by the
+Copyright Holder that are necessarily infringed by the Package. If you
+institute patent litigation (including a cross-claim or counterclaim) against
+any party alleging that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the date
+that such litigation is filed.
+
+Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND
+CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW.
+UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY
+OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+=cut
diff --git a/lib/Mail/Run/Crypt.pm b/lib/Mail/Run/Crypt.pm
new file mode 100644
index 0000000..c37a622
--- /dev/null
+++ b/lib/Mail/Run/Crypt.pm
@@ -0,0 +1,287 @@
+package Mail::Run::Crypt;
+
+# Force me to write this properly
+use strict;
+use warnings;
+use utf8;
+
+# Target Debian-Squeeze-era Perl
+# We may be able to go older; not sure yet
+use 5.010;
+
+# Import required modules
+use Carp;
+use Const::Fast;
+use English '-no_match_vars';
+use IPC::Run3;
+use Mail::GnuPG;
+use MIME::Entity;
+
+# Specify package verson
+our $VERSION = '0.01';
+
+# Default exit value
+const our $DEFAULT_EXIT => 127;
+
+# Oldschool constructor
+sub new {
+ my ( $class, %opts ) = @_;
+
+ # Blindly slurp in all the options given
+ my $self = {%opts};
+
+ # We must have a key ID and a recipient, but not necessarily a passphrase
+ for my $req (qw(keyid mailto)) {
+ $self->{$req} // croak "$req required";
+ }
+
+ # Default the instance name to the package name if it wasn't given;
+ # croncrypt(1p) will pass it in
+ $self->{name} //= $class;
+
+ # Return objectified self
+ return bless $self, $class;
+}
+
+# Run a given command
+sub run {
+ my ( $self, @command ) = @_;
+
+ # Run the command and wait for it to finish; keep its exit value for later
+ my ( @out, @err );
+ eval { run3 \@command, undef, \@out, \@err }
+ or carp "Command failed: $EVAL_ERROR";
+ $self->{exit} = $CHILD_ERROR >> 8;
+
+ # If there was output, mail it
+ if (@out) {
+ my $command = join q{ }, @command;
+ my $subject = "$self->{name} output: $command";
+ $self->_mail( $subject, \@out );
+ }
+
+ # If there were errors, mail them
+ if (@err) {
+ my $command = join q{ }, @command;
+ my $subject = "$self->{name} errors: $command";
+ $self->_mail( $subject, \@err );
+ }
+
+ # Return status reflecting the command exit value
+ return $self->{exit} == 0;
+}
+
+# Return the value of the most recently run command, or 1 otherwise
+sub bail { return shift->{exit} // $DEFAULT_EXIT }
+
+# Send the message to the address in $ENV{MAILTO}
+sub _mail {
+ my ( $self, $subject, $content ) = @_;
+
+ # Build MIME object with plaintext message
+ my $mime = MIME::Entity->build(
+ To => $self->{mailto},
+ Subject => $subject,
+ Data => $content,
+ );
+
+ # Encrypt the MIME object
+ my $mgpg = Mail::GnuPG->new(
+ key => $self->{keyid},
+ passphrase => $self->{passphrase},
+ );
+ $mgpg->mime_signencrypt( $mime, $self->{mailto} );
+
+ # Send it
+ return $mime->send();
+}
+
+1;
+
+__END__
+
+=pod
+
+=for stopwords
+mailserver decrypt croncrypt GPG OpenPGP tradename licensable MERCHANTABILITY
+
+=encoding utf8
+
+=head1 NAME
+
+Mail::Run::Crypt - Encrypt and mail output from command runs
+
+=head1 VERSION
+
+Version 0.01
+
+=head1 DESCRIPTION
+
+This module runs commands with C<IPC::Run3::run3()>, and collects any standard
+output and standard error it emits. If there is any standard output or standard
+error content, it is mailed (each stream separately) to a specified recipient
+address.
+
+The idea is to allow you to view the output of automated commands while having
+the content encrypted as it passes through to your mailserver, and have some
+assurance that the content was actually generated by the server concerned.
+C<cron(8)> scripts are the ideal use case, but this would also with C<at(1)> or
+anything else that might non-interactively run jobs for which output is
+significant.
+
+You probably want to call this with the C<croncrypt(1)> program installed by
+this distribution, which provides a means to set the properties for the module
+via environment variables or command-line options.
+
+=head1 SYNOPSIS
+
+ my $mrc = Mail::Run::Crypt->new(
+ keyid => 0x1234DEAD,
+ passphrase => 'extremely_insecure',
+ mailto => 'you@example.net',
+ );
+ $mrc->run(qw(rsync -a /mnt/a/ remote:mnt/b));
+
+=head1 SUBROUTINES/METHODS
+
+=head2 B<new(%opts)>
+
+Constructor accepts the following named parameters:
+
+=over 4
+
+=item C<keyid>
+
+The GnuPG key ID that should be used to encrypt the messages.
+
+=item C<passphrase>
+
+The passphrase used to decrypt the key.
+
+=item C<mailto>
+
+The recipient email address for the content.
+
+=item C<name>
+
+(Optional) The name of the object. When called from the C<croncrypt(1)>
+program, this will be the string "croncrypt".
+
+=head2 B<run(@command)>
+
+Run the specified arguments as a command with C<IPC::Run3::run3()>, and email
+any output or error content to the email recipient.
+
+=head2 B<bail()>
+
+Return the exit status of the most recently run command, or 127 if no command
+has been successfully run.
+
+=head1 DIAGNOSTICS
+
+=over 4
+
+=item %s required
+
+One of the required properties was not passed to the constructor.
+
+=head1 CONFIGURATION AND ENVIRONMENT
+
+You will need to have a functioning GnuPG public key setup for this to work,
+including the secret key. You should definitely not use your personal key;
+generate one specifically for mail signing and encryption instead.
+
+=head1 DEPENDENCIES
+
+=over 4
+
+=item *
+
+Perl v5.10.0 or newer
+
+=item *
+
+C<Carp>
+
+=item *
+
+C<Const::Fast>
+
+=item *
+
+C<English>
+
+=item *
+
+C<IPC::Run3>
+
+=item *
+
+C<Mail::GnuPG>
+
+=item *
+
+C<MIME::Entity>
+
+=back
+
+=head1 INCOMPATIBILITIES
+
+This module uses C<Mail::GnuPG> and other GPG-specific code, so it won't work
+with any other OpenPGP implementations.
+
+=head1 BUGS AND LIMITATIONS
+
+Providing the C<passphrase> directly from an environment variable is not great.
+The documentation needs to make that clear and offer a less bad alternative,
+probably specifying the path to a secure file that it refuses to use unless it
+has sufficiently restrictive permissions.
+
+This is very hard to test and the author has not yet figured out how to do
+that.
+
+=head1 AUTHOR
+
+Tom Ryder C<< <tom@sanctum.geek.nz> >>
+
+=head1 LICENSE AND COPYRIGHT
+
+Copyright (C) 2017 Tom Ryder
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the Artistic License (2.0). You may obtain a copy of the full
+license at:
+
+L<http://www.perlfoundation.org/artistic_license_2_0>
+
+Any use, modification, and distribution of the Standard or Modified Versions is
+governed by this Artistic License. By using, modifying or distributing the
+Package, you accept this license. Do not use, modify, or distribute the
+Package, if you do not accept this license.
+
+If your Modified Version has been derived from a Modified Version made by
+someone other than you, you are nevertheless required to ensure that your
+Modified Version complies with the requirements of this license.
+
+This license does not grant you the right to use any trademark, service mark,
+tradename, or logo of the Copyright Holder.
+
+This license includes the non-exclusive, worldwide, free-of-charge patent
+license to make, have made, use, offer to sell, sell, import and otherwise
+transfer the Package with respect to any patent claims licensable by the
+Copyright Holder that are necessarily infringed by the Package. If you
+institute patent litigation (including a cross-claim or counterclaim) against
+any party alleging that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the date
+that such litigation is filed.
+
+Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND
+CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW.
+UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY
+OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+=cut
diff --git a/share/man/man1/croncrypt.1 b/share/man/man1/croncrypt.1
deleted file mode 100644
index dd5b508..0000000
--- a/share/man/man1/croncrypt.1
+++ /dev/null
@@ -1,27 +0,0 @@
-.TH CRONCRYPT 1 "May 2014" "Manual page for croncrypt"
-.SH NAME
-.B croncrypt
-\- sign and encrypt cron(8) output with PGP/MIME
-.SH SYNOPSIS
- $ crontab -l
- CRONCRYPT_KEYID=0x0A1B2C3D4E5F6G7H
- CRONCRYPT_PASSPHRASE=hibbityboo
- MAILTO=me@mynet
- 0 1 * * * croncrypt rsync /home/tom/important-file /home/backups
-.SH DESCRIPTION
-.B croncrypt
-signs and encrypts cron(8) output and errors with PGP/MIME before emailing it
-to the MAILTO address.
-.PP
-The main design goal is simplicity; just whack it in front of all your
-crontab(5) entries, provided they don't use pipes or stderr/stdout redirects,
-in which case you should consider putting it all into a script file anyway.
-.PP
-Don't use your own GPG key for signing! I recommend you create a dedicated key
-just for Croncrypt, and sign it locally with gpg --lsign so that your software
-trusts it locally.
-.SH SEE ALSO
-gpg(1), gpg-agent(1), IPC::Run3(3pm), Mail::GnuPG(3pm), MIME::Entity(3pm)
-.SH AUTHOR
-Tom Ryder <tom@sanctum.geek.nz>
-
diff --git a/xt/manifest.t b/xt/manifest.t
new file mode 100644
index 0000000..e0b558e
--- /dev/null
+++ b/xt/manifest.t
@@ -0,0 +1,15 @@
+#!perl -T
+use 5.006;
+use strict;
+use warnings;
+use Test::More;
+
+unless ( $ENV{RELEASE_TESTING} ) {
+ plan( skip_all => "Author tests not required for installation" );
+}
+
+my $min_tcm = 0.9;
+eval "use Test::CheckManifest $min_tcm";
+plan skip_all => "Test::CheckManifest $min_tcm required" if $@;
+
+ok_manifest();
diff --git a/xt/pod-coverage.t b/xt/pod-coverage.t
new file mode 100644
index 0000000..f5728a5
--- /dev/null
+++ b/xt/pod-coverage.t
@@ -0,0 +1,24 @@
+#!perl -T
+use 5.006;
+use strict;
+use warnings;
+use Test::More;
+
+unless ( $ENV{RELEASE_TESTING} ) {
+ plan( skip_all => "Author tests not required for installation" );
+}
+
+# Ensure a recent version of Test::Pod::Coverage
+my $min_tpc = 1.08;
+eval "use Test::Pod::Coverage $min_tpc";
+plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
+ if $@;
+
+# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
+# but older versions don't recognize some common documentation styles
+my $min_pc = 0.18;
+eval "use Pod::Coverage $min_pc";
+plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
+ if $@;
+
+all_pod_coverage_ok();
diff --git a/xt/pod.t b/xt/pod.t
new file mode 100644
index 0000000..4d3a0ce
--- /dev/null
+++ b/xt/pod.t
@@ -0,0 +1,16 @@
+#!perl -T
+use 5.006;
+use strict;
+use warnings;
+use Test::More;
+
+unless ( $ENV{RELEASE_TESTING} ) {
+ plan( skip_all => "Author tests not required for installation" );
+}
+
+# Ensure a recent version of Test::Pod
+my $min_tp = 1.22;
+eval "use Test::Pod $min_tp";
+plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
+
+all_pod_files_ok();