summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md5
-rw-r--r--README.md70
-rw-r--r--bin/user/S3upload.py177
-rw-r--r--install.py25
-rw-r--r--skins/S3upload/skin.conf14
5 files changed, 290 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..874a5e5
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Change Log
+
+## v0.1 - 29 April 2015
+
+- Initial upload. This one is not ready for prime time.
diff --git a/README.md b/README.md
index f897897..3a9a96d 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,70 @@
# weewx-S3upload
-Extension to upload web files to an AWS S3 bucket from where they are publicly served
+Extension to upload web files to an AWS S3 bucket from where they are publicly served.
+
+Rather than serving the web pages generated by weewx directly from a
+webserver running on the same system as weewx, I upload those pages
+to an AWS S3 bucket. The bucket is configured to be a static website
+and is costing me around USD0.50/month. This is cheaper than usual
+website hosting and saves me the worry of someone hacking into the
+system running weewx.
+
+## Setup
+
+Before installing this extensions, get an S3 bucket at Amazon
+https://aws.amazon.com/. Sign into the management consule, create an
+account if necessary, then create an S3 bucket.
+
+Search their help pages for how to set up the bucket as a static
+website.
+
+Clone this repo to your weewx extensions directory; for example
+
+```
+git clone git@github.com:wmadill/weewx-S3upload /home/weewx/extensions/weewx-S3upload
+```
+
+## Installation instructions:
+
+1. run the installer
+
+```
+cd /home/weewx
+setup.py install --extension extensions/weewx-S3upload
+```
+
+2. modify the S3upload stanza in weewx.conf and set your
+S3 access code, secret token, and bucket name.
+
+3. restart weewx:
+
+```
+sudo /etc/init.d/weewx stop
+sudo /etc/init.d/weewx start
+```
+
+## Manual installation instructions:
+
+1. copy files to the weewx user directory:
+
+```
+cp -rp skins/S3upload /home/weewx/skins
+cp -rp bin/user/S3upload /home/weewx/bin/user
+```
+
+2. add the following to weewx.con
+
+```
+[StdReport]
+ ...
+ [[S3upload]]
+ skin = S3upload
+ acces_key = 'REPLACE_WITH_YOUR_S3_ACCESS_KEY'
+ secret_token ='REPLACE_WITH_YOUR_SECRET_TOKEN'
+ bucket_name = 'REPLACE_WITH_YOUR_S3_BUCKET_NAME'
+```
+
+3. start weewx
+
+```
+sudo /etc/init.d/weewx start
+```
diff --git a/bin/user/S3upload.py b/bin/user/S3upload.py
new file mode 100644
index 0000000..7751e5d
--- /dev/null
+++ b/bin/user/S3upload.py
@@ -0,0 +1,177 @@
+#
+# Copyright (c) 2015 Bill Madill <bill@jamimi.com>
+# Derivative of extensions/alarm.py, credit to Tom Keffer <tkeffer@gmail.com>
+#
+# See the file LICENSE.txt for your full rights.
+#
+"""Upload the generated HTML files to an S3 bucket
+
+********************************************************************************
+
+To use this uploader, add the following to your configuration file in the
+[StdReport] section:
+
+ [[S3upload]]
+ skin = S3upload
+ access_key = "PARM1"
+ secret_token = "PARM2"
+ bucket_name = "BUCKETNAME"
+
+********************************************************************************
+"""
+
+import errno
+import glob
+import os.path
+import re
+import subprocess
+import sys
+import syslog
+import threading
+import time
+import traceback
+
+import configobj
+
+from weeutil.weeutil import timestamp_to_string, option_as_list
+# from weewx.reportengine import ReportGenerator
+import weewx.manager
+
+# Inherit from the base class ReportGenerator
+class S3uploadGenerator(weewx.reportengine.ReportGenerator):
+ """Custom service to upload files to an S3 bucket"""
+
+ def run(self):
+ syslog.syslog(syslog.LOG_INFO, """reportengine: S3generator""")
+
+ try:
+ # Get the options from the configuration dictionary.
+ # Raise an exception if a required option is missing.
+ html_root = self.config_dict['StdReport']['HTML_ROOT']
+ self.local_root = os.path.join(self.config_dict['WEEWX_ROOT'], html_root) + "/"
+ self.access_key = self.skin_dict['access_key']
+ self.secret_token = self.skin_dict['secret_token']
+ self.bucket_name = self.skin_dict['bucket_name']
+
+ syslog.syslog(syslog.LOG_INFO, "S3upload: upload configured from '%s' to '%s'" % (self.local_root, self.bucket_name))
+
+ except KeyError, e:
+ syslog.syslog(syslog.LOG_INFO, "S3upload: no upload configured. %s" % e)
+
+ syslog.syslog(syslog.LOG_DEBUG, "S3upload: uploading")
+
+ # Launch in a separate thread so it doesn't block the main LOOP thread:
+ t = threading.Thread(target=S3uploadGenerator.uploadFiles, args=(self, ))
+ t.start()
+ syslog.syslog(syslog.LOG_DEBUG, "S3upload: reeturn from upload thread")
+
+ def uploadFiles(self):
+ start_ts = time.time()
+ t_str = timestamp_to_string(start_ts)
+ syslog.syslog(syslog.LOG_INFO, "S3upload: start upload at %s" % t_str)
+
+ # Build command
+ cmd = ["/usr/local/bin/s3cmd"]
+ cmd.extend(["sync"])
+ cmd.extend(["--access_key=%s" % self.access_key])
+ cmd.extend(["--secret_key=%s" % self.secret_token])
+ cmd.extend([self.local_root])
+ cmd.extend(["s3://%s" % self.bucket_name])
+
+ syslog.syslog(syslog.LOG_DEBUG, "S3upload command: %s" % cmd)
+ try:
+ S3upload_cmd = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+
+ stdout = s3upload_cmd.communicate()[0]
+ stroutput = stdout.strip()
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ syslog.syslog(syslog.LOG_ERR, "S3upload: s3cmd does not appear to be installed on this system. (errno %d, \"%s\")" % (e.errno, e.strerror))
+ raise
+
+ if weewx.debug == 1:
+ syslog.syslog(syslog.LOG_DEBUG, "S3upload: s3cmd output: %s" % stroutput)
+ for line in iter(stroutput.splitlines()):
+ syslog.syslog(syslog.LOG_DEBUG, "S3upload: s3cmd output: %s" % line)
+
+ # S3upload output. generate an appropriate message
+ if stroutput.find('Done. Uploaded ') >= 0:
+ file_cnt = 0
+ for line in iter(stroutput.splitlines()):
+ if line.find('File ') >= 0:
+ file_cnt += 1
+ if line.find('Done. Uploaded ') >= 0:
+ # get number of bytes uploaded
+ m = re.search(r"Uploaded (\d*) bytes", line)
+ if m:
+ byte_cnt = int(m.group(1))
+ else:
+ byte_cnt = "Unknown"
+
+ # format message
+ try:
+ if file_cnt is not None and byte_cnt is not None:
+ S3upload_message = "uploaded %d files (%s bytes) in %%0.2f seconds" % (int(file_cnt), byte_cnt)
+ else:
+ S3upload_message = "executed in %0.2f seconds"
+ except:
+ S3upload_message = "executed in %0.2f seconds"
+ else:
+ # suspect we have an s3cmd error so display a message
+ syslog.syslog(syslog.LOG_INFO, "S3upload: s3cmd reported errors: %s" % stroutput)
+ S3upload_message = "executed in %0.2f seconds"
+
+ stop_ts = time.time()
+ syslog.syslog(syslog.LOG_INFO, "S3upload: " + S3upload_message % (stop_ts - start_ts))
+
+ t_str = timestamp_to_string(stop_ts)
+ syslog.syslog(syslog.LOG_INFO, "S3upload: end upload at %s" % t_str)
+
+if __name__ == '__main__':
+ """This section is used for testing the code. """
+ # Note that this fails!
+ import sys
+ import configobj
+ from optparse import OptionParser
+
+
+ usage_string ="""Usage:
+
+ S3upload.py config_path
+
+ Arguments:
+
+ config_path: Path to weewx.conf"""
+
+ parser = OptionParser(usage=usage_string)
+ (options, args) = parser.parse_args()
+
+ if len(args) < 1:
+ sys.stderr.write("Missing argument(s).\n")
+ sys.stderr.write(parser.parse_args(["--help"]))
+ exit()
+
+ config_path = args[0]
+
+ weewx.debug = 1
+
+ try :
+ config_dict = configobj.ConfigObj(config_path, file_error=True)
+ except IOError:
+ print "Unable to open configuration file ", config_path
+ exit()
+
+ if 'S3upload' not in config_dict:
+ print >>sys.stderr, "No [S3upload] section in the configuration file %s" % config_path
+ exit(1)
+
+ engine = None
+ S3upload = uploadFiles(engine, config_dict)
+
+ rec = {'extraTemp1': 1.0,
+ 'outTemp' : 38.2,
+ 'dateTime' : int(time.time())}
+
+ event = weewx.Event(weewx.NEW_ARCHIVE_RECORD, record=rec)
+ S3upload.newArchiveRecord(event)
+
diff --git a/install.py b/install.py
new file mode 100644
index 0000000..2e3a759
--- /dev/null
+++ b/install.py
@@ -0,0 +1,25 @@
+# installer for S3 file upload extension
+
+from setup import ExtensionInstaller
+
+def loader():
+ return S3uploadInstaller()
+
+class S3uploadInstaller(ExtensionInstaller):
+ def __init__(self):
+ super(S3uploadInstaller, self).__init__(
+ version="0.1",
+ name='S3upload',
+ description='Upload files to an S3 bucket',
+ author='Bill Madill',
+ author_email='bill@jamimi.com',
+ config={
+ 'StdReport': {
+ 'S3upload': {
+ 'skin': 'S3upload',
+ 'access_key': 'REPLACE_WITH_YOUR_S3_ACCESS_KEY',
+ 'secrect_token': 'REPLACE_WITH_YOUR_SECRET_TOKEN',
+ 'bucket_name': 'REPLACE_WITH_YOUR_S3_BUCKET_NAME',}}},
+ files=[('bin/user', ['bin/user/S3upload.py']),
+ ('skins/S3upload', ['skins/S3upload/skin.conf'])],
+ )
diff --git a/skins/S3upload/skin.conf b/skins/S3upload/skin.conf
new file mode 100644
index 0000000..ebcd513
--- /dev/null
+++ b/skins/S3upload/skin.conf
@@ -0,0 +1,14 @@
+###############################################################################
+# Copyright (c) 2015 Bill Madill <bill@jamimi.com> #
+# with credit to Will Page <compenguy@gmail.com> for the Rsync code and #
+# With credit to Tom Keffer <tkeffer@gmail.com> #
+# #
+# S3upload CONFIGURATION FILE #
+# This 'report' does not generate any files. Instead, the report engine #
+# invokes s3cmd to upload the generated HTML files to an S3 bucket from #
+# where the HTML files are served publicly. #
+###############################################################################
+
+[Generators]
+ generator_list = user.S3upload.S3syncGenerator
+