├── .gitignore ├── LICENCE ├── README.md ├── Vagrantfile ├── app ├── __init__.py ├── assets.py ├── config │ ├── default.py │ ├── dev.py │ └── production.py ├── db.py ├── jinja.py ├── logger.py ├── static │ ├── css │ │ └── normalize.css │ └── js │ │ └── vendor │ │ └── rye-0.1.0.js ├── templates │ ├── base.html │ ├── errors │ │ ├── 403.html │ │ ├── 404.html │ │ └── 500.html │ └── main │ │ └── index.html └── views.py ├── db ├── alembic.ini ├── env.py └── script.py.mako ├── fabfile ├── __init__.py ├── app.py ├── bootstrap.py ├── config.py ├── db.py ├── deployment.py ├── deployment │ ├── hooks │ │ └── post-receive.tpl │ └── ssh ├── git.py ├── puppet.py ├── servers.json ├── servers.py ├── utils.py └── virtualenv.py ├── manage.py ├── puppet ├── manifests │ ├── bootstrap │ │ └── apt-update.pp │ ├── config.pp │ ├── default.pp │ ├── environments │ │ ├── production.pp │ │ └── vagrant.pp │ └── lib │ │ └── line.pp └── modules │ ├── apt │ ├── manifests │ │ ├── init.pp │ │ └── sources.pp │ └── templates │ │ ├── precise.list.tpl │ │ └── trusty.list.tpl │ ├── cssmin │ └── manifests │ │ └── init.pp │ ├── fabric │ └── manifests │ │ └── init.pp │ ├── ferm │ ├── LICENSE │ ├── README │ ├── files │ │ ├── ferm.conf │ │ └── ferm.default │ ├── manifests │ │ ├── hook.pp │ │ ├── init.pp │ │ ├── rule.pp │ │ ├── rule │ │ │ └── custom.pp │ │ └── rules │ │ │ ├── bittorrent.pp │ │ │ ├── dns.pp │ │ │ ├── ftp.pp │ │ │ ├── git.pp │ │ │ ├── imaps.pp │ │ │ ├── jabber.pp │ │ │ ├── rsync.pp │ │ │ ├── smtp.pp │ │ │ └── www.pp │ └── templates │ │ ├── defs.conf.erb │ │ ├── ferm-rule.erb │ │ └── hook.erb │ ├── git │ └── manifests │ │ └── init.pp │ ├── newrelic │ ├── files │ │ ├── newrelic.gpg │ │ └── newrelic.list │ ├── manifests │ │ └── servermon.pp │ └── templates │ │ └── nrsysmond.cfg.tpl │ ├── nginx │ ├── manifests │ │ ├── init.pp │ │ ├── site.pp │ │ └── ssl.pp │ └── templates │ │ ├── app.erb │ │ ├── redirects.erb │ │ └── uwsgi.erb │ ├── openssl │ └── manifests │ │ └── init.pp │ ├── postfix │ └── manifests │ │ └── init.pp │ ├── postgresql │ └── manifests │ │ └── init.pp │ ├── puppet │ └── manifests │ │ └── sudoers.pp │ ├── python │ └── manifests │ │ └── packages.pp │ ├── rsyslog │ └── manifests │ │ └── init.pp │ ├── sysctl │ └── manifests │ │ └── init.pp │ ├── uglifyjs │ └── manifests │ │ └── init.pp │ ├── users │ └── manifests │ │ └── deploy.pp │ ├── uwsgi │ ├── files │ │ └── etc │ │ │ ├── init │ │ │ └── uwsgi.conf │ │ │ ├── logrotate.d │ │ │ └── uwsgi │ │ │ └── uwsgi │ │ │ └── uwsgi.ini │ ├── manifests │ │ ├── init.pp │ │ └── tuning.pp │ └── templates │ │ └── app.ini.erb │ └── vagrant │ ├── files │ └── pip │ │ └── pip.conf │ └── manifests │ ├── init.pp │ ├── pip.pp │ └── postgresql.pp └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.cache 3 | *.pyc 4 | .codeintel/ 5 | .vagrant* 6 | .venv 7 | .DS_Store 8 | npm-debug.log 9 | app/static/__bundles 10 | build/ 11 | node_modules/ 12 | components/ 13 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Daniel Simmons 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-Bootstrap 2 | 3 | Welcome to my opinionated, lightweight Flask project template. 4 | 5 | ## Quick start 6 | 7 | 1. After installing [Vagrant](http://vagrantup.com/), create and boot the VM inside this directory: 8 | 9 | vagrant up 10 | 11 | 2. SSH to the VM: 12 | 13 | vagrant ssh 14 | 15 | 3. Run your app: 16 | 17 | fab run 18 | 19 | You will have a running app listening on http://localhost:5000/ at this point. 20 | 21 | ## Notes 22 | 23 | After initial boot, you should: 24 | 25 | * Freeze the newly-installed pip packages at their versions: 26 | 27 | pip freeze > requirements.txt 28 | 29 | * Set the `SECRET_KEY` for each environment in `app/config/`. 30 | 31 | ## Requirements 32 | 33 | *nix-flavoured OS. 34 | 35 | ## What's included 36 | 37 | The following software will be installed and configured automatically: 38 | 39 | * [Alembic](http://alembic.readthedocs.org/en/latest/) 40 | * [cssmin](https://pypi.python.org/pypi/cssmin) 41 | * [Fabric](http://www.fabfile.org/) (deployment scripts included) 42 | * [Flask](http://flask.pocoo.org/) 43 | * [Flask-AssetsLite](https://github.com/tobiasandtobias/flask-assetslite) 44 | * [New Relic](http://newrelic.com/) 45 | * [nginx](http://nginx.org/) 46 | * [PostgreSQL](http://www.postgresql.org/) 47 | * [Puppet](http://puppetlabs.com/) (manifests included) 48 | * [uWSGI](https://uwsgi-docs.readthedocs.org/en/latest/) 49 | * [Vagrant](http://www.vagrantup.com/) VM running Ubuntu 14.04 LTS 50 | 51 | ## Licence 52 | 53 | Licenced under the MIT licence (see LICENCE), so go ahead and fork this! 54 | 55 | ## Credits 56 | 57 | Originally based on https://gist.github.com/urschrei/2666927. 58 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = "2" 6 | 7 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 8 | # All Vagrant configuration is done here. The most common configuration 9 | # options are documented and commented below. For a complete reference, 10 | # please see the online documentation at vagrantup.com. 11 | 12 | # Every Vagrant virtual environment requires a box to build off of. 13 | config.vm.box = "ubuntu/trusty64" 14 | 15 | # Disable automatic box update checking. If you disable this, then 16 | # boxes will only be checked for updates when the user runs 17 | # `vagrant box outdated`. This is not recommended. 18 | # config.vm.box_check_update = false 19 | 20 | # Create a forwarded port mapping which allows access to a specific port 21 | # within the machine from a port on the host machine. In the example below, 22 | # accessing "localhost:8080" will access port 80 on the guest machine. 23 | config.vm.network "forwarded_port", guest: 5000, host: 5000 24 | 25 | # Create a private network, which allows host-only access to the machine 26 | # using a specific IP. 27 | # config.vm.network "private_network", ip: "192.168.33.10" 28 | config.vm.network "private_network", ip: "192.168.33.20" 29 | 30 | # Create a public network, which generally matched to bridged network. 31 | # Bridged networks make the machine appear as another physical device on 32 | # your network. 33 | # config.vm.network "public_network" 34 | 35 | # If true, then any SSH connections made will enable agent forwarding. 36 | # Default value: false 37 | config.ssh.forward_agent = true 38 | 39 | # Share an additional folder to the guest VM. The first argument is 40 | # the path on the host to the actual folder. The second argument is 41 | # the path on the guest to mount the folder. And the optional third 42 | # argument is a set of non-required options. 43 | # config.vm.synced_folder "../data", "/vagrant_data" 44 | 45 | # use NFS with 1 second attribute cache to prevent issues with editing files on the host 46 | config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ["acregmin=1", "acregmax=1"] 47 | 48 | # Provider-specific configuration so you can fine-tune various 49 | # backing providers for Vagrant. These expose provider-specific options. 50 | # Example for VirtualBox: 51 | # 52 | # config.vm.provider "virtualbox" do |vb| 53 | # # Don't boot with headless mode 54 | # vb.gui = true 55 | # 56 | # # Use VBoxManage to customize the VM. For example to change memory: 57 | # vb.customize ["modifyvm", :id, "--memory", "1024"] 58 | # end 59 | # 60 | # View the documentation for the provider you're using for more 61 | # information on available options. 62 | 63 | # Enable provisioning with CFEngine. CFEngine Community packages are 64 | # automatically installed. For example, configure the host as a 65 | # policy server and optionally a policy file to run: 66 | # 67 | # config.vm.provision "cfengine" do |cf| 68 | # cf.am_policy_hub = true 69 | # # cf.run_file = "motd.cf" 70 | # end 71 | # 72 | # You can also configure and bootstrap a client to an existing 73 | # policy server: 74 | # 75 | # config.vm.provision "cfengine" do |cf| 76 | # cf.policy_server_address = "10.0.2.15" 77 | # end 78 | 79 | # Enable provisioning with Puppet stand alone. Puppet manifests 80 | # are contained in a directory path relative to this Vagrantfile. 81 | # You will need to create the manifests directory and a manifest in 82 | # the file default.pp in the manifests_path directory. 83 | # 84 | # config.vm.provision "puppet" do |puppet| 85 | # puppet.manifests_path = "manifests" 86 | # puppet.manifest_file = "default.pp" 87 | # end 88 | 89 | # Bootstrap, for vagrant 90 | config.vm.provision "puppet" do |puppet| 91 | puppet.manifests_path = "puppet/manifests" 92 | puppet.module_path = "puppet/modules" 93 | puppet.manifest_file = "bootstrap/apt-update.pp" 94 | end 95 | 96 | # Default site run 97 | config.vm.provision "puppet" do |puppet| 98 | puppet.manifests_path = "puppet/manifests" 99 | puppet.module_path = "puppet/modules" 100 | puppet.manifest_file = "default.pp" 101 | end 102 | 103 | # Set up the app 104 | config.vm.provision :shell, :inline => "cd /vagrant && sudo -H -u vagrant stdbuf -o0 fab build; exit 0" 105 | 106 | # Enable provisioning with chef solo, specifying a cookbooks path, roles 107 | # path, and data_bags path (all relative to this Vagrantfile), and adding 108 | # some recipes and/or roles. 109 | # 110 | # config.vm.provision "chef_solo" do |chef| 111 | # chef.cookbooks_path = "../my-recipes/cookbooks" 112 | # chef.roles_path = "../my-recipes/roles" 113 | # chef.data_bags_path = "../my-recipes/data_bags" 114 | # chef.add_recipe "mysql" 115 | # chef.add_role "web" 116 | # 117 | # # You may also specify custom JSON attributes: 118 | # chef.json = { mysql_password: "foo" } 119 | # end 120 | 121 | # Enable provisioning with chef server, specifying the chef server URL, 122 | # and the path to the validation key (relative to this Vagrantfile). 123 | # 124 | # The Opscode Platform uses HTTPS. Substitute your organization for 125 | # ORGNAME in the URL and validation key. 126 | # 127 | # If you have your own Chef Server, use the appropriate URL, which may be 128 | # HTTP instead of HTTPS depending on your configuration. Also change the 129 | # validation key to validation.pem. 130 | # 131 | # config.vm.provision "chef_client" do |chef| 132 | # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME" 133 | # chef.validation_key_path = "ORGNAME-validator.pem" 134 | # end 135 | # 136 | # If you're using the Opscode platform, your validator client is 137 | # ORGNAME-validator, replacing ORGNAME with your organization name. 138 | # 139 | # If you have your own Chef Server, the default validation client name is 140 | # chef-validator, unless you changed the configuration. 141 | # 142 | # chef.validation_client_name = "ORGNAME-validator" 143 | end 144 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os.path import abspath, relpath 3 | from flask import Flask, request_finished, render_template 4 | 5 | import assets 6 | import jinja 7 | import logger 8 | import views 9 | 10 | def create_app(config=None): 11 | """ 12 | Create and initialise the application. 13 | """ 14 | app = Flask(__name__) 15 | app.config.from_pyfile('%s/config/default.py' % app.root_path) 16 | 17 | if config: 18 | app.config.from_pyfile(config) 19 | elif os.getenv('FLASK_CONFIG'): 20 | app.config.from_envvar('FLASK_CONFIG') 21 | 22 | logger.init(app) 23 | jinja.init(app) 24 | assets.init(app) 25 | 26 | app.register_blueprint(views.blueprint) 27 | 28 | @app.errorhandler(404) 29 | def not_found(error): 30 | return render_template('errors/404.html'), 404 31 | 32 | @app.errorhandler(403) 33 | def forbidden(error): 34 | return render_template('errors/403.html'), 403 35 | 36 | @app.errorhandler(500) 37 | def server_error(error): 38 | return render_template('errors/default.html'), 500 39 | 40 | return app 41 | -------------------------------------------------------------------------------- /app/assets.py: -------------------------------------------------------------------------------- 1 | from os.path import join, abspath, dirname 2 | from flask.ext.assetslite import Assets, Bundle 3 | from flask.ext.assetslite.filters import cssmin, uglifyjs 4 | 5 | # Static folder that all the paths below are relative from 6 | static_folder = join(abspath(dirname(__file__)), 'static') 7 | 8 | css = [ 9 | 'css/*.css', 10 | ] 11 | 12 | js = [ 13 | 'js/vendor/rye-0.1.0.js', 14 | ] 15 | 16 | 17 | def init(app): 18 | """ 19 | Initialise assets on the app. 20 | """ 21 | assets = Assets(app) 22 | assets.register('css', Bundle(css, output='__bundles/css/bundle.%s.css', 23 | filters=cssmin, static_folder=static_folder, 24 | cache_file='{0}/__bundles/css.cache'.format(static_folder))) 25 | 26 | assets.register('js', Bundle(js, output='__bundles/js/combined.%s.js', 27 | filters=uglifyjs, static_folder=static_folder, 28 | cache_file='{0}/__bundles/js.cache'.format(static_folder))) 29 | assets.build_all() 30 | 31 | return assets 32 | -------------------------------------------------------------------------------- /app/config/default.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | # No debugging by default; this is overriden dev config 4 | DEBUG = False 5 | 6 | # Override this; best to make it different for each environment 7 | SECRET_KEY = '' 8 | 9 | # Email address that emails originate from. Make sure it's real, you own it, 10 | # and SPF allows you to send from it. 11 | DEFAULT_MAIL_SENDER = 'vagrant@%s' % socket.getfqdn() 12 | 13 | # General email address for admins and errors 14 | ADMIN_RECIPIENTS = ['vagrant@localhost'] 15 | ERROR_EMAIL = None 16 | 17 | # Database connection string 18 | SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://@/app' 19 | -------------------------------------------------------------------------------- /app/config/dev.py: -------------------------------------------------------------------------------- 1 | # Turn on debugging 2 | DEBUG = True 3 | -------------------------------------------------------------------------------- /app/config/production.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | DEFAULT_MAIL_SENDER = 'www-data@%s' % socket.getfqdn() 4 | 5 | ADMIN_RECIPIENTS = ['devs@tobias.tv'] 6 | ERROR_EMAIL = 'devs@tobias.tv' 7 | 8 | # dd bs=1 count=32 if=/dev/random |pbcopy 9 | SECRET_KEY = '' 10 | -------------------------------------------------------------------------------- /app/db.py: -------------------------------------------------------------------------------- 1 | from flask.ext.sqlalchemy import SQLAlchemy as FlaskSQLAlchemyBase 2 | 3 | 4 | class SQLAlchemy(FlaskSQLAlchemyBase): 5 | 6 | def apply_driver_hacks(self, app, info, options): 7 | """ 8 | Extend Flask-SQLAlchemy's apply_driver_hacks function to apply 9 | our own hacks. 10 | """ 11 | super(SQLAlchemy, self).apply_driver_hacks(app, info, options) 12 | if info.drivername == 'postgresql+psycopg2': 13 | # Explicitly set client_encoding to utf8, as some systems default 14 | # to SQL_ASCII which gives unexpected behaviour for in psycopg2's 15 | # native unicode conversion. 16 | options['client_encoding'] = 'utf8' 17 | 18 | 19 | db = SQLAlchemy() 20 | 21 | def init(app): 22 | db.init_app(app) 23 | -------------------------------------------------------------------------------- /app/jinja.py: -------------------------------------------------------------------------------- 1 | from flask import request 2 | 3 | 4 | def init(app): 5 | @app.context_processor 6 | def debug(debug=app.debug): 7 | """ 8 | Notify templates that they're in debug mode 9 | """ 10 | return dict(debug=debug) 11 | 12 | @app.context_processor 13 | def request_global(): 14 | """ 15 | Make request available in templates 16 | """ 17 | return dict(request=request) 18 | -------------------------------------------------------------------------------- /app/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from logging.handlers import SMTPHandler 3 | 4 | 5 | def init(app): 6 | 7 | if app.config.get('ERROR_EMAIL'): 8 | 9 | if app.debug: 10 | logging.getLogger().setLevel(logging.DEBUG) 11 | else: 12 | log_format = logging.Formatter(''' 13 | --- 14 | Message type: %(levelname)s 15 | Location: %(pathname)s:%(lineno)d 16 | Module: %(module)s 17 | Function: %(funcName)s 18 | Time: %(asctime)s 19 | 20 | %(message)s 21 | 22 | ''') 23 | 24 | # Send errors via email 25 | mail_handler = SMTPHandler('127.0.0.1', 26 | app.config.get('DEFAULT_MAIL_SENDER'), 27 | app.config.get('ERROR_EMAIL'), 'Error') 28 | mail_handler.setLevel(logging.ERROR) 29 | mail_handler.setFormatter(log_format) 30 | 31 | # Also continue to log errors to stderr 32 | stream_handler = logging.StreamHandler() 33 | stream_handler.setFormatter(log_format) 34 | 35 | app.logger.addHandler(stream_handler) 36 | app.logger.addHandler(mail_handler) 37 | -------------------------------------------------------------------------------- /app/static/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } 428 | -------------------------------------------------------------------------------- /app/static/js/vendor/rye-0.1.0.js: -------------------------------------------------------------------------------- 1 | (function(global){ 2 | 3 | function Rye (selector, context) { 4 | if (!(this instanceof Rye)){ 5 | return new Rye(selector, context) 6 | } 7 | 8 | if (selector instanceof Rye){ 9 | return selector 10 | } 11 | 12 | var util = Rye.require('Util') 13 | 14 | if (typeof selector === 'string') { 15 | this.selector = selector 16 | this.elements = this.qsa(context, selector) 17 | 18 | } else if (selector instanceof Array) { 19 | this.elements = util.unique(selector.filter(util.isElement)) 20 | 21 | } else if (util.isNodeList(selector)) { 22 | this.elements = Array.prototype.slice.call(selector).filter(util.isElement) 23 | 24 | } else if (util.isElement(selector)) { 25 | this.elements = [selector] 26 | 27 | } else { 28 | this.elements = [] 29 | } 30 | 31 | this._update() 32 | } 33 | 34 | Rye.version = '0.1.0' 35 | 36 | // Minimalist module system 37 | var modules = {} 38 | Rye.require = function (module) { 39 | return modules[module] 40 | } 41 | Rye.define = function (module, fn) { 42 | modules[module] = fn.call(Rye.prototype) 43 | } 44 | 45 | // Export global object 46 | global.Rye = Rye 47 | 48 | })(window) 49 | 50 | ;Rye.define('Util', function(){ 51 | 52 | var _slice = Array.prototype.slice 53 | , _forEach = Array.prototype.forEach 54 | , _toString = Object.prototype.toString 55 | 56 | var uid = { 57 | current: 0 58 | , next: function(){ return ++this.current } 59 | } 60 | 61 | function each (obj, fn, context) { 62 | if (!obj) { 63 | return 64 | } 65 | if (obj.forEach === _forEach) { 66 | return obj.forEach(fn, context) 67 | } 68 | if (obj.length === +obj.length) { 69 | for (var i = 0; i < obj.length; i++) { 70 | fn.call(context || obj, obj[i], i, obj) 71 | } 72 | } else { 73 | var keys = Object.keys(obj) 74 | for (var i = 0; i < keys.length; i++) { 75 | var key = keys[i] 76 | fn.call(context || obj, obj[key], key, obj) 77 | } 78 | } 79 | } 80 | 81 | function extend (obj) { 82 | each(_slice.call(arguments, 1), function(source){ 83 | each(source, function(value, key){ 84 | obj[key] = value 85 | }) 86 | }) 87 | return obj 88 | } 89 | 90 | function inherits (child, parent) { 91 | extend(child, parent) 92 | function Ctor () { 93 | this.constructor = child 94 | } 95 | Ctor.prototype = parent.prototype 96 | child.prototype = new Ctor() 97 | child.__super__ = parent.prototype 98 | return child 99 | } 100 | 101 | function isElement (element) { 102 | return element && (element.nodeType === 1 || element.nodeType === 9) 103 | } 104 | 105 | function isNodeList (obj) { 106 | return obj && is(['nodelist', 'htmlcollection', 'htmlformcontrolscollection'], obj) 107 | } 108 | 109 | function unique (array) { 110 | return array.filter(function(item, idx){ 111 | return array.indexOf(item) == idx 112 | }) 113 | } 114 | 115 | function pluck (array, property) { 116 | return array.map(function(item){ 117 | return item[property] 118 | }) 119 | } 120 | 121 | function put (array, property, value) { 122 | return array.forEach(function(item, i){ 123 | array[i][property] = value 124 | }) 125 | } 126 | 127 | function prefix (key, obj) { 128 | var result 129 | , upcased = key[0].toUpperCase() + key.substring(1) 130 | , prefixes = ['moz', 'webkit', 'ms', 'o'] 131 | 132 | obj = obj || window 133 | 134 | if (result = obj[key]){ 135 | return result 136 | } 137 | 138 | // No pretty array methods here :( 139 | // http://jsperf.com/everywhile 140 | while(prefix = prefixes.shift()){ 141 | if (result = obj[prefix + upcased]){ 142 | break; 143 | } 144 | } 145 | return result 146 | } 147 | 148 | function _apply (context, fn, applyArgs, cutoff, fromLeft) { 149 | if (typeof fn === 'string') { 150 | fn = context[fn] 151 | } 152 | return function () { 153 | var args = _slice.call(arguments, 0, cutoff || Infinity) 154 | 155 | if (applyArgs) { 156 | args = fromLeft ? applyArgs.concat(args) : args.concat(applyArgs) 157 | } 158 | if (typeof context === 'number') { 159 | context = args[context] 160 | } 161 | 162 | return fn.apply(context || this, args) 163 | } 164 | } 165 | 166 | function applyRight (context, fn, applyArgs, cutoff) { 167 | return _apply(context, fn, applyArgs, cutoff) 168 | } 169 | 170 | function applyLeft (context, fn, applyArgs, cutoff) { 171 | return _apply(context, fn, applyArgs, cutoff, true) 172 | } 173 | 174 | function curry (fn) { 175 | return applyLeft(this, fn, _slice.call(arguments, 1)) 176 | } 177 | 178 | function getUid (element) { 179 | return element.rye_id || (element.rye_id = uid.next()) 180 | } 181 | 182 | function type (obj) { 183 | var ref = _toString.call(obj).match(/\s(\w+)\]$/) 184 | return ref && ref[1].toLowerCase() 185 | } 186 | 187 | function is (kind, obj) { 188 | return kind.indexOf(type(obj)) >= 0 189 | } 190 | 191 | return { 192 | each : each 193 | , extend : extend 194 | , inherits : inherits 195 | , isElement : isElement 196 | , isNodeList : isNodeList 197 | , unique : unique 198 | , pluck : pluck 199 | , put : put 200 | , prefix : prefix 201 | , applyRight : applyRight 202 | , applyLeft : applyLeft 203 | , curry : curry 204 | , getUid : getUid 205 | , type : type 206 | , is : is 207 | } 208 | 209 | }) 210 | 211 | ;Rye.define('Data', function(){ 212 | 213 | var util = Rye.require('Util') 214 | , data = {} 215 | 216 | function set (element, key, value) { 217 | var id = util.getUid(element) 218 | , obj = data[id] || (data[id] = {}) 219 | obj[key] = value 220 | } 221 | 222 | function get (element, key) { 223 | var obj = data[util.getUid(element)] 224 | if (key == null) { 225 | return obj 226 | } 227 | return obj && obj[key] 228 | } 229 | 230 | this.data = function (key, value) { 231 | if (value !== undefined) { 232 | this.each(function(element){ 233 | set(element, key, value) 234 | }) 235 | return this 236 | } 237 | 238 | if (this.elements.length === 1) { 239 | return get(this.elements[0], key) 240 | } else { 241 | return this.elements.map(function(element){ 242 | return get(element, key) 243 | }) 244 | } 245 | } 246 | 247 | 248 | return { 249 | set : set 250 | , get : get 251 | } 252 | }) 253 | ;Rye.define('Query', function(){ 254 | 255 | var util = Rye.require('Util') 256 | , _slice = Array.prototype.slice 257 | , selectorRE = /^([.#]?)([\w\-]+)$/ 258 | , selectorType = { 259 | '.': 'getElementsByClassName' 260 | , '#': 'getElementById' 261 | , '' : 'getElementsByTagName' 262 | , '_': 'querySelectorAll' 263 | } 264 | , dummyDiv = document.createElement('div') 265 | 266 | function matches(element, selector) { 267 | var matchesSelector, match 268 | if (!element || !util.isElement(element) || !selector) { 269 | return false 270 | } 271 | 272 | if (selector.nodeType) { 273 | return element === selector 274 | } 275 | 276 | if (selector instanceof Rye) { 277 | return selector.elements.some(function(selector){ 278 | return matches(element, selector) 279 | }) 280 | } 281 | 282 | if (element === document) { 283 | return false 284 | } 285 | 286 | matchesSelector = util.prefix('matchesSelector', dummyDiv) 287 | if (matchesSelector) { 288 | return matchesSelector.call(element, selector) 289 | } 290 | 291 | // fall back to performing a selector: 292 | if (!element.parentNode) { 293 | dummyDiv.appendChild(element) 294 | } 295 | match = qsa(element.parentNode, selector).indexOf(element) >= 0 296 | if (element.parentNode === dummyDiv) { 297 | dummyDiv.removeChild(element) 298 | } 299 | return match 300 | } 301 | 302 | function qsa (element, selector) { 303 | var method 304 | 305 | element = element || document 306 | 307 | // http://jsperf.com/getelementbyid-vs-queryselector/11 308 | if (!selector.match(selectorRE) || (RegExp.$1 === '#' && element !== document)) { 309 | method = selectorType._ 310 | } else { 311 | method = selectorType[RegExp.$1] 312 | selector = RegExp.$2 313 | } 314 | 315 | var result = element[method](selector) 316 | 317 | if (util.isNodeList(result)){ 318 | return _slice.call(result) 319 | } 320 | 321 | if (util.isElement(result)){ 322 | return [result] 323 | } 324 | 325 | return [] 326 | } 327 | 328 | // Walks the DOM tree using `method`, returns 329 | // when an element node is found 330 | function getClosestNode (element, method, selector) { 331 | do { 332 | element = element[method] 333 | } while (element && ((selector && !matches(element, selector)) || !util.isElement(element))) 334 | return element 335 | } 336 | 337 | // Creates a new Rye instance applying a filter if necessary 338 | function _create (elements, selector) { 339 | return selector == null ? new Rye(elements) : new Rye(elements).filter(selector) 340 | } 341 | 342 | this.qsa = qsa 343 | 344 | this.find = function (selector) { 345 | var elements 346 | if (this.length === 1) { 347 | elements = qsa(this.elements[0], selector) 348 | } else { 349 | elements = this.elements.reduce(function(elements, element){ 350 | return elements.concat(qsa(element, selector)) 351 | }, []) 352 | } 353 | return _create(elements) 354 | } 355 | 356 | this.filter = function (selector, inverse) { 357 | if (typeof selector === 'function') { 358 | var fn = selector 359 | return _create(this.elements.filter(function(element, index){ 360 | return fn.call(element, element, index) != (inverse || false) 361 | })) 362 | } 363 | if (selector && selector[0] === '!') { 364 | selector = selector.substr(1) 365 | inverse = true 366 | } 367 | return _create(this.elements.filter(function(element){ 368 | return matches(element, selector) != (inverse || false) 369 | })) 370 | } 371 | 372 | this.contains = function (selector) { 373 | var matches 374 | return _create(this.elements.reduce(function(elements, element){ 375 | matches = qsa(element, selector) 376 | return elements.concat(matches.length ? element : null) 377 | }, [])) 378 | } 379 | 380 | this.is = function (selector) { 381 | return this.length > 0 && this.filter(selector).length > 0 382 | } 383 | 384 | this.not = function (selector) { 385 | return this.filter(selector, true) 386 | } 387 | 388 | this.index = function (selector) { 389 | if (selector == null) { 390 | return this.parent().children().indexOf(this.elements[0]) 391 | } 392 | return this.indexOf(new Rye(selector).elements[0]) 393 | } 394 | 395 | this.add = function (selector, context) { 396 | var elements = selector 397 | if (typeof selector === 'string') { 398 | elements = new Rye(selector, context).elements 399 | } 400 | return this.concat(elements) 401 | } 402 | 403 | // Extract a list with the provided property for each value. 404 | // This works like underscore's pluck, with the added 405 | // getClosestNode() method to avoid picking up non-html nodes. 406 | this.pluckNode = function (property) { 407 | return this.map(function(element){ 408 | return getClosestNode(element, property) 409 | }) 410 | } 411 | 412 | this.next = function () { 413 | return _create(this.pluckNode('nextSibling')) 414 | } 415 | 416 | this.prev = function () { 417 | return _create(this.pluckNode('previousSibling')) 418 | } 419 | 420 | this.first = function () { 421 | return _create(this.get(0)) 422 | } 423 | 424 | this.last = function () { 425 | return _create(this.get(-1)) 426 | } 427 | 428 | this.siblings = function (selector) { 429 | var siblings = [] 430 | this.each(function(element){ 431 | _slice.call(element.parentNode.childNodes).forEach(function(child){ 432 | if (util.isElement(child) && child !== element){ 433 | siblings.push(child) 434 | } 435 | }) 436 | }) 437 | return _create(siblings, selector) 438 | } 439 | 440 | this.parent = function (selector) { 441 | return _create(this.pluck('parentNode'), selector) 442 | } 443 | 444 | // borrow from zepto 445 | this.parents = function (selector) { 446 | var ancestors = [] 447 | , elements = this.elements 448 | , fn = function (element) { 449 | if ((element = element.parentNode) && element !== document && ancestors.indexOf(element) < 0) { 450 | ancestors.push(element) 451 | return element 452 | } 453 | } 454 | 455 | while (elements.length > 0 && elements[0] !== undefined) { 456 | elements = elements.map(fn) 457 | } 458 | return _create(ancestors, selector) 459 | } 460 | 461 | this.closest = function (selector) { 462 | return this.map(function(element){ 463 | if (matches(element, selector)) { 464 | return element 465 | } 466 | return getClosestNode(element, 'parentNode', selector) 467 | }) 468 | } 469 | 470 | this.children = function (selector) { 471 | return _create(this.elements.reduce(function(elements, element){ 472 | var childrens = _slice.call(element.children) 473 | return elements.concat(childrens) 474 | }, []), selector) 475 | } 476 | 477 | 478 | return { 479 | matches : matches 480 | , qsa : qsa 481 | , getClosestNode : getClosestNode 482 | } 483 | 484 | }) 485 | 486 | ;Rye.define('Collection', function(){ 487 | 488 | var util = Rye.require('Util') 489 | , _slice = Array.prototype.slice 490 | , _concat = Array.prototype.concat 491 | 492 | this.get = function (index) { 493 | if (index == null) { 494 | return this.elements.slice() 495 | } 496 | return this.elements[index < 0 ? this.elements.length + index : index] 497 | } 498 | 499 | this.eq = function (index) { 500 | // We have to explicitly null the selection since .get() 501 | // returns the whole collection when called without arguments. 502 | if (index == null) { 503 | return new Rye() 504 | } 505 | return new Rye(this.get(index)) 506 | } 507 | 508 | // Methods that return a usable value 509 | ;['forEach', 'reduce', 'reduceRight', 'indexOf'].forEach(function(method){ 510 | this[method] = function (a, b, c, d) { 511 | return this.elements[method](a, b, c, d) 512 | } 513 | }.bind(this)) 514 | 515 | // Methods that return a list are turned into a Rye instance 516 | ;['map', 'sort'].forEach(function(method){ 517 | this[method] = function (a, b, c, d) { 518 | return new Rye(this.elements[method](a, b, c, d)) 519 | } 520 | }.bind(this)) 521 | 522 | this.each = function (fn) { 523 | this.elements.forEach(fn) 524 | return this 525 | } 526 | 527 | this.iterate = function(method, context){ 528 | return function(a, b, c, d){ 529 | return this.each(function(element){ 530 | method.call(context, element, a, b, c, d) 531 | }) 532 | } 533 | } 534 | 535 | this.push = function (item) { 536 | if (util.isElement(item)){ 537 | this.elements.push(item) 538 | this._update() 539 | return this.length - 1 540 | } else { 541 | return -1 542 | } 543 | } 544 | 545 | this.slice = function (start, end) { 546 | return new Rye(_slice.call(this.elements, start, end)) 547 | } 548 | 549 | // Concatenate two elements lists, do .unique() clean-up 550 | this.concat = function () { 551 | var args = _slice.call(arguments).map(function(arr){ 552 | return arr instanceof Rye ? arr.elements : arr 553 | }) 554 | return new Rye(_concat.apply(this.elements, args)) 555 | } 556 | 557 | this.pluck = function (property) { 558 | return util.pluck(this.elements, property) 559 | } 560 | 561 | this.put = function (property, value) { 562 | util.put(this.elements, property, value) 563 | return this 564 | } 565 | 566 | this._update = function () { 567 | this.length = this.elements.length 568 | } 569 | 570 | }) 571 | 572 | ;Rye.define('Manipulation', function(){ 573 | 574 | var util = Rye.require('Util') 575 | , query = Rye.require('Query') 576 | , _slice = Array.prototype.slice 577 | 578 | function getValue(element) { 579 | if (element.multiple) { 580 | return new Rye(element).find('option').filter(function(option) { 581 | return option.selected && !option.disabled 582 | }).pluck('value') 583 | } 584 | return element.value 585 | } 586 | 587 | function getAttribute(element, name) { 588 | if (name === 'value' && element.nodeName == 'INPUT') { 589 | return getValue(element) 590 | } 591 | return element.getAttribute(name) 592 | } 593 | 594 | function append (element, html) { 595 | if (typeof html === 'string') { 596 | element.insertAdjacentHTML('beforeend', html) 597 | } else { 598 | element.appendChild(html) 599 | } 600 | } 601 | 602 | function prepend (element, html) { 603 | var first 604 | if (typeof html === 'string') { 605 | element.insertAdjacentHTML('afterbegin', html) 606 | } else if (first = element.childNodes[0]){ 607 | element.insertBefore(html, first) 608 | } else { 609 | element.appendChild(html) 610 | } 611 | } 612 | 613 | function after (element, html) { 614 | var next 615 | if (typeof html === 'string') { 616 | element.insertAdjacentHTML('afterend', html) 617 | } else if (next = query.getClosestNode(element, 'nextSibling')) { 618 | element.parentNode.insertBefore(html, next) 619 | } else { 620 | element.parentNode.appendChild(html) 621 | } 622 | } 623 | 624 | function before (element, html) { 625 | if (typeof html === 'string') { 626 | element.insertAdjacentHTML('beforebegin', html) 627 | } else { 628 | element.parentNode.insertBefore(html, element) 629 | } 630 | } 631 | 632 | function proxyExport(fn, method) { 633 | // This function coerces the input into either a string or an array of elements, 634 | // then passes it on to the appropriate method, iterating if necessary. 635 | this[method] = function (obj) { 636 | 637 | if (typeof obj !== 'string'){ 638 | if (obj instanceof Rye) { 639 | obj = obj.elements 640 | } else if (util.isNodeList(obj)) { 641 | obj = _slice.call(obj) 642 | } 643 | // Also support arrays [el1, el2, ...] 644 | if (Array.isArray(obj)) { 645 | if (/prepend|before/.test(method)){ 646 | obj = _slice.call(obj, 0).reverse() 647 | } 648 | return obj.forEach(this[method].bind(this)) 649 | } 650 | } 651 | 652 | if (this.length === 1) { 653 | fn(this.elements[0], obj) 654 | } else { 655 | this.each(function(element, i){ 656 | var node = i > 0 ? obj.cloneNode(true) : obj 657 | fn(element, node) 658 | }) 659 | } 660 | return this 661 | } 662 | } 663 | 664 | // Patch methods, add to prototype 665 | util.each({ 666 | append : append 667 | , prepend : prepend 668 | , after : after 669 | , before : before 670 | }, proxyExport.bind(this)) 671 | 672 | 673 | this.text = function (text) { 674 | if (text == null) { 675 | return this.elements[0] && this.elements[0].textContent 676 | } 677 | return this.each(function(element){ 678 | element.textContent = text 679 | }) 680 | } 681 | 682 | this.html = function (html) { 683 | if (html == null) { 684 | return this.elements[0] && this.elements[0].innerHTML 685 | } 686 | return this.each(function(element){ 687 | element.innerHTML = html 688 | }) 689 | } 690 | 691 | this.empty = function () { 692 | return this.put('innerHTML', '') 693 | } 694 | 695 | this.clone = function () { 696 | return this.map(function(element){ 697 | return element.cloneNode(true) 698 | }) 699 | } 700 | 701 | this.remove = function () { 702 | return this.each(function(element){ 703 | if (element.parentNode) { 704 | element.parentNode.removeChild(element) 705 | } 706 | }) 707 | } 708 | 709 | this.val = function (value) { 710 | if (value == null) { 711 | return this.elements[0] && getValue(this.elements[0]) 712 | } 713 | return this.each(function(element){ 714 | element.value = value 715 | }) 716 | } 717 | 718 | this.attr = function (name, value) { 719 | if (typeof name === 'object'){ 720 | return this.each(function(element){ 721 | util.each(name, function(value, key){ 722 | element.setAttribute(key, value) 723 | }) 724 | }) 725 | } 726 | return typeof value === 'undefined' 727 | ? this.elements[0] && getAttribute(this.elements[0], name) 728 | : this.each(function(element){ 729 | element.setAttribute(name, value) 730 | }) 731 | } 732 | 733 | this.prop = function (name, value) { 734 | if (typeof name === 'object'){ 735 | return this.each(function(element){ 736 | util.each(name, function(value, key){ 737 | element[key] = value 738 | }) 739 | }) 740 | } 741 | return typeof value === 'undefined' 742 | ? this.elements[0] && this.elements[0][name] 743 | : this.put(name, value) 744 | } 745 | 746 | Rye.create = function (html) { 747 | var temp = document.createElement('div') 748 | , children 749 | 750 | temp.innerHTML = html 751 | 752 | children = _slice.call(temp.childNodes) 753 | children.forEach(function(node, i){ 754 | temp.removeChild(node) 755 | }) 756 | 757 | return new Rye(children) 758 | } 759 | 760 | return { 761 | getValue : getValue 762 | , getAttribute : getAttribute 763 | , append : append 764 | , prepend : prepend 765 | , after : after 766 | , before : before 767 | } 768 | 769 | }) 770 | ;Rye.define('Events', function(){ 771 | 772 | var util = Rye.require('Util') 773 | , query = Rye.require('Query') 774 | , _slice = Array.prototype.slice 775 | 776 | // General-purpose event emitter 777 | // ----------------------------- 778 | 779 | function EventEmitter () { 780 | this.events = {} 781 | this.context = null 782 | } 783 | 784 | // Adds a handler to the events list 785 | EventEmitter.prototype.addListener = function (event, handler) { 786 | var handlers = this.events[event] || (this.events[event] = []) 787 | handlers.push(handler) 788 | return this 789 | } 790 | 791 | // Add a handler that can only get called once 792 | EventEmitter.prototype.once = function (event, handler) { 793 | var self = this 794 | function suicide () { 795 | handler.apply(this, arguments) 796 | self.removeListener(event, suicide) 797 | } 798 | return this.addListener(event, suicide) 799 | } 800 | 801 | // Removes a handler from the events list 802 | EventEmitter.prototype.removeListener = function (event, handler) { 803 | var self = this 804 | , handlers = this.events[event] 805 | if (event === '*') { 806 | if (!handler) { 807 | this.events = {} 808 | } else { 809 | util.each(this.events, function(handlers, event){ 810 | self.removeListener(event, handler) 811 | }) 812 | } 813 | } else if (handler && handlers) { 814 | handlers.splice(handlers.indexOf(handler), 1) 815 | if (handlers.length === 0) { 816 | delete this.events[event] 817 | } 818 | } else { 819 | delete this.events[event] 820 | } 821 | return this 822 | } 823 | 824 | // Calls all handlers that match the event type 825 | EventEmitter.prototype.emit = function (event) { 826 | var handlers = this.events[event] 827 | , args = _slice.call(arguments, 1) 828 | , context = this.context || this 829 | 830 | if (handlers) { 831 | util.each(handlers, function(fn) { 832 | fn.apply(context, args) 833 | }) 834 | } 835 | return this 836 | } 837 | 838 | EventEmitter.prototype.proxy = function (event) { 839 | return util.applyLeft(this, this.emit, [event]) 840 | } 841 | 842 | // Utility methods 843 | // ----------------------------- 844 | 845 | var emitters = {} 846 | 847 | function getEmitter (element) { 848 | var id = util.getUid(element) 849 | return emitters[id] || (emitters[id] = new DOMEventEmitter(element)) 850 | } 851 | 852 | function getType (event) { 853 | var index = event.indexOf(' ') 854 | return index > 0 ? event.substr(0, index) : event 855 | } 856 | 857 | function getSelector (event) { 858 | var index = event.indexOf(' ') 859 | return index > 0 ? event.substr(index) : '' 860 | } 861 | 862 | function createEvent (type, properties) { 863 | if (typeof type != 'string') { 864 | type = type.type 865 | } 866 | var isMouse = ['click', 'mousedown', 'mouseup', 'mousemove'].indexOf(type) != -1 867 | , event = document.createEvent(isMouse ? 'MouseEvent' : 'Event') 868 | if (properties) { 869 | util.extend(event, properties) 870 | } 871 | event.initEvent(type, true, true) 872 | return event 873 | } 874 | 875 | // DOM event emitter 876 | // ----------------------------- 877 | 878 | /* 879 | Creates one event emitter per element, proxies DOM events to it. This way 880 | we can keep track of the functions so that they can be removed from the 881 | elements by reference when you call .removeListener() by event name. 882 | */ 883 | 884 | function DOMEventEmitter (element) { 885 | EventEmitter.call(this) 886 | this.element = element 887 | this.proxied = {} 888 | } 889 | 890 | util.inherits(DOMEventEmitter, EventEmitter) 891 | 892 | DOMEventEmitter.prototype._proxy = function (event) { 893 | return function (DOMEvent) { 894 | var selector = getSelector(event) 895 | , context = this.element 896 | // delegate behavior 897 | if (selector) { 898 | context = DOMEvent.target 899 | while (context && !query.matches(context, selector)) { 900 | context = context !== this.element && context.parentNode 901 | } 902 | if (!context || context == this.element) { 903 | return 904 | } 905 | } 906 | this.context = context 907 | this.emit(event, DOMEvent, this.element) 908 | }.bind(this) 909 | } 910 | 911 | DOMEventEmitter.prototype.proxy = function (event) { 912 | return this.proxied[event] || (this.proxied[event] = this._proxy(event)) 913 | } 914 | 915 | DOMEventEmitter.prototype.addListener = function (event, handler) { 916 | EventEmitter.prototype.addListener.call(this, event, handler) 917 | if (!this.proxied[event]) { 918 | this.element.addEventListener(getType(event), this.proxy(event), false) 919 | } 920 | return this 921 | } 922 | 923 | DOMEventEmitter.prototype.removeListener = function (event, handler) { 924 | if (event.indexOf('*') >= 0) { 925 | var self = this 926 | , re = new RegExp('^' + event.replace('*', '\\b')) 927 | // * : remove all events 928 | // type * : remove delegate events 929 | // type* : remove delegate and undelegate 930 | util.each(this.events, function(handlers, event){ 931 | if (re.test(event)) { 932 | self.removeListener(event, handler) 933 | } 934 | }) 935 | } else { 936 | var proxy = this.proxied[event] 937 | EventEmitter.prototype.removeListener.call(this, event, handler) 938 | if (!this.events[event] && proxy) { 939 | this.element.removeEventListener(getType(event), proxy, false) 940 | delete this.proxied[event] 941 | } 942 | } 943 | return this 944 | } 945 | 946 | function acceptMultipleEvents (method) { 947 | var _method = DOMEventEmitter.prototype[method] 948 | DOMEventEmitter.prototype[method] = function (event, handler) { 949 | var self = this 950 | if (typeof event !== 'string') { 951 | util.each(event, function(handler, event){ 952 | _method.call(self, event, handler) 953 | }) 954 | } else { 955 | _method.call(self, event, handler) 956 | } 957 | return self 958 | } 959 | } 960 | 961 | ;['addListener', 'once', 'removeListener'].forEach(acceptMultipleEvents) 962 | 963 | DOMEventEmitter.prototype.destroy = function () { 964 | return this.removeListener('*') 965 | } 966 | 967 | DOMEventEmitter.prototype.trigger = function (event, data) { 968 | if (!(event instanceof window.Event)) { 969 | event = createEvent(event) 970 | } 971 | event.data = data 972 | this.element.dispatchEvent(event) 973 | return this 974 | } 975 | 976 | // Exported methods 977 | // ----------------------------- 978 | 979 | var exports = {} 980 | 981 | function emitterProxy (method, element, event, handler) { 982 | getEmitter(element)[method](event, handler) 983 | } 984 | 985 | ;['addListener', 'removeListener', 'once', 'trigger'].forEach(function(method){ 986 | // Create a function proxy for the method 987 | var fn = util.curry(emitterProxy, method) 988 | // Exports module and rye methods 989 | exports[method] = fn 990 | this[method] = this.iterate(fn) 991 | }.bind(this)) 992 | 993 | // Aliases 994 | // ----------------------------- 995 | 996 | ;[EventEmitter.prototype, DOMEventEmitter.prototype, this].forEach(function(obj){ 997 | obj.on = obj.addListener 998 | }) 999 | 1000 | // Global event bus / pub-sub 1001 | // ----------------------------- 1002 | 1003 | var EE = new EventEmitter 1004 | 1005 | Rye.subscribe = EE.addListener.bind(EE) 1006 | Rye.unsubscribe = EE.removeListener.bind(EE) 1007 | Rye.publish = EE.emit.bind(EE) 1008 | 1009 | 1010 | return { 1011 | EventEmitter : EventEmitter 1012 | , DOMEventEmitter : DOMEventEmitter 1013 | , getEmitter : getEmitter 1014 | , createEvent : createEvent 1015 | , addListener : exports.addListener 1016 | , once : exports.once 1017 | , removeListener : exports.removeListener 1018 | , trigger : exports.trigger 1019 | } 1020 | }) 1021 | 1022 | ;Rye.define('Style', function(){ 1023 | 1024 | var util = Rye.require('Util') 1025 | , data = Rye.require('Data') 1026 | , _cssNumber = 'fill-opacity font-weight line-height opacity orphans widows z-index zoom'.split(' ') 1027 | 1028 | function getCSS (element, property) { 1029 | return element.style.getPropertyValue(property) 1030 | || window.getComputedStyle(element, null).getPropertyValue(property) 1031 | } 1032 | 1033 | function setCSS (element, property, value) { 1034 | // If a number was passed in, add 'px' to the (except for certain CSS properties) 1035 | if (typeof value == 'number' && _cssNumber.indexOf(property) === -1) { 1036 | value += 'px' 1037 | } 1038 | var action = (value === null || value === '') ? 'remove' : 'set' 1039 | element.style[action + 'Property'](property, '' + value) 1040 | return element 1041 | } 1042 | 1043 | function hasClass (element, name) { 1044 | name = name.trim() 1045 | return element.classList ? 1046 | element.classList.contains(name) 1047 | : (' ' + element.className + ' ').indexOf(' ' + name + ' ') !== -1 1048 | } 1049 | 1050 | function addClass (element, names) { 1051 | if (element.classList) { 1052 | names.replace(/\S+/g, function(name){ element.classList.add(name) }) 1053 | } else { 1054 | var classes = ' ' + element.className + ' ', name 1055 | names = names.trim().split(/\s+/) 1056 | while (name = names.shift()) { 1057 | if (classes.indexOf(' ' + name + ' ') === -1) { 1058 | classes += name + ' ' 1059 | } 1060 | } 1061 | element.className = classes.trim() 1062 | } 1063 | return element 1064 | } 1065 | 1066 | function removeClass (element, names) { 1067 | if (names === '*') { 1068 | element.className = '' 1069 | } else { 1070 | if (names instanceof RegExp) { 1071 | names = [names] 1072 | } else if (element.classList && names.indexOf('*') === -1) { 1073 | names.replace(/\S+/g, function(name){ element.classList.remove(name) }) 1074 | return 1075 | } else { 1076 | names = names.trim().split(/\s+/) 1077 | } 1078 | 1079 | var classes = ' ' + element.className + ' ', name 1080 | while (name = names.shift()) { 1081 | if (name.indexOf && name.indexOf('*') !== -1) { 1082 | name = new RegExp('\\s*\\b' + name.replace('*', '\\S*') + '\\b\\s*', 'g') 1083 | } 1084 | if (name instanceof RegExp) { 1085 | classes = classes.replace(name, ' ') 1086 | } else { 1087 | while (classes.indexOf(' ' + name + ' ') !== -1) { 1088 | classes = classes.replace(' ' + name + ' ', ' ') 1089 | } 1090 | } 1091 | } 1092 | element.className = classes.trim() 1093 | } 1094 | return element 1095 | } 1096 | 1097 | 1098 | this.show = this.iterate(function(element){ 1099 | setCSS(element, 'display', data.get(element, '_display') || 'block') 1100 | }) 1101 | 1102 | this.hide = this.iterate(function(element){ 1103 | var _display = getCSS(element, 'display') 1104 | if (_display !== 'none') { 1105 | data.set(element, '_display', _display) 1106 | } 1107 | setCSS(element, 'display', 'none') 1108 | }) 1109 | 1110 | this.css = function (property, value) { 1111 | if (value == null) { 1112 | if (typeof property == 'string') { 1113 | return this.elements[0] && getCSS(this.elements[0], property) 1114 | } 1115 | 1116 | return this.each(function(element){ 1117 | util.each(property, function(value, key){ 1118 | setCSS(element, key, value) 1119 | }) 1120 | }) 1121 | } 1122 | return this.each(function(element){ 1123 | setCSS(element, property, value) 1124 | }) 1125 | } 1126 | 1127 | this.hasClass = function (name) { 1128 | var result = false 1129 | this.each(function(element){ 1130 | result = result || hasClass(element, name) 1131 | }) 1132 | return !!result 1133 | } 1134 | 1135 | this.addClass = this.iterate(addClass) 1136 | 1137 | this.removeClass = this.iterate(removeClass) 1138 | 1139 | this.toggleClass = this.iterate(function(element, name, when){ 1140 | if (when == null) { 1141 | when = !hasClass(element, name) 1142 | } 1143 | (when ? addClass : removeClass)(element, name) 1144 | }) 1145 | 1146 | 1147 | return { 1148 | getCSS : getCSS 1149 | , setCSS : setCSS 1150 | , hasClass : hasClass 1151 | , addClass : addClass 1152 | , removeClass : removeClass 1153 | } 1154 | }) 1155 | 1156 | ;Rye.define('TouchEvents', function(){ 1157 | 1158 | var util = Rye.require('Util') 1159 | , events = Rye.require('Events') 1160 | , touch = {} 1161 | 1162 | // checks if it needed 1163 | function parentIfText (node) { 1164 | return 'tagName' in node ? node : node.parentNode 1165 | } 1166 | 1167 | function Gesture(props) { 1168 | util.extend(this, props) 1169 | Gesture.all.push(this) 1170 | } 1171 | Gesture.all = [] 1172 | Gesture.cancelAll = function () { 1173 | Gesture.all.forEach(function(instance){ 1174 | instance.cancel() 1175 | }) 1176 | touch = {} 1177 | } 1178 | Gesture.prototype.schedule = function () { 1179 | this.timeout = setTimeout(this._trigger.bind(this), this.delay) 1180 | } 1181 | Gesture.prototype._trigger = function () { 1182 | this.timeout = null 1183 | this.trigger() 1184 | } 1185 | Gesture.prototype.cancel = function () { 1186 | if (this.timeout) { 1187 | clearTimeout(this.timeout) 1188 | } 1189 | this.timeout = null 1190 | } 1191 | 1192 | if (events && ('ontouchstart' in window || window.mocha)) { 1193 | 1194 | var tap = new Gesture({ 1195 | delay: 0 1196 | , trigger: function () { 1197 | // cancelTouch cancels processing of single vs double taps for faster 'tap' response 1198 | var event = events.createEvent('tap') 1199 | event.cancelTouch = Gesture.cancelAll 1200 | events.trigger(touch.element, event) 1201 | 1202 | // trigger double tap immediately 1203 | if (touch.isDoubleTap) { 1204 | events.trigger(touch.element, 'doubletap') 1205 | touch = {} 1206 | 1207 | // trigger single tap after (x)ms of inactivity 1208 | } else { 1209 | singleTap.schedule() 1210 | } 1211 | } 1212 | }) 1213 | , singleTap = new Gesture({ 1214 | delay: 250 1215 | , trigger: function () { 1216 | events.trigger(touch.element, 'singletap') 1217 | touch = {} 1218 | } 1219 | }) 1220 | , longTap = new Gesture({ 1221 | delay: 750 1222 | , trigger: function () { 1223 | if (touch.last) { 1224 | events.trigger(touch.element, 'longtap') 1225 | touch = {} 1226 | } 1227 | } 1228 | }) 1229 | , swipe = new Gesture({ 1230 | delay: 0 1231 | , trigger: function () { 1232 | events.trigger(touch.element, 'swipe') 1233 | events.trigger(touch.element, 'swipe' + this.direction()) 1234 | touch = {} 1235 | } 1236 | , direction: function () { 1237 | if (Math.abs(touch.x1 - touch.x2) >= Math.abs(touch.y1 - touch.y2)) { 1238 | return touch.x1 - touch.x2 > 0 ? 'left' : 'right' 1239 | } 1240 | return touch.y1 - touch.y2 > 0 ? 'up' : 'down' 1241 | } 1242 | }) 1243 | 1244 | events.addListener(document.body, 'touchstart', function (event) { 1245 | var now = Date.now() 1246 | singleTap.cancel() 1247 | touch.element = parentIfText(event.touches[0].target) 1248 | touch.x1 = event.touches[0].pageX 1249 | touch.y1 = event.touches[0].pageY 1250 | if (touch.last && (now - touch.last) <= 250) { 1251 | touch.isDoubleTap = true 1252 | } 1253 | touch.last = now 1254 | longTap.schedule() 1255 | }) 1256 | 1257 | events.addListener(document.body, 'touchmove', function (event) { 1258 | longTap.cancel() 1259 | touch.x2 = event.touches[0].pageX 1260 | touch.y2 = event.touches[0].pageY 1261 | }) 1262 | 1263 | events.addListener(document.body, 'touchend', function () { 1264 | longTap.cancel() 1265 | 1266 | // swipe 1267 | if (Math.abs(touch.x1 - touch.x2) > 30 || Math.abs(touch.y1 - touch.y2) > 30) { 1268 | swipe.schedule() 1269 | // normal tap 1270 | } else if ('last' in touch) { 1271 | tap.schedule() 1272 | } 1273 | }) 1274 | 1275 | events.addListener(document.body, 'touchcancel', Gesture.cancelAll) 1276 | events.addListener(window, 'scroll', Gesture.cancelAll) 1277 | } 1278 | }) 1279 | 1280 | ;Rye.define('Request', function(){ 1281 | 1282 | var util = Rye.require('Util') 1283 | , manipulation = Rye.require('Manipulation') 1284 | , noop = function(){} 1285 | , escape = encodeURIComponent 1286 | , accepts = { 1287 | types : ['arraybuffer', 'blob', 'document', 'json', 'text'] 1288 | , json : 'application/json' 1289 | , xml : 'application/xml, text/xml' 1290 | , html : 'text/html, application/xhtml+xml' 1291 | , text : 'text/plain' 1292 | } 1293 | , defaults = { 1294 | method : 'GET' 1295 | , url : window.location.toString() 1296 | , async : true 1297 | , accepts : accepts 1298 | , callback : noop 1299 | , timeout : 0 1300 | // , headers : {} 1301 | // , contentType : null 1302 | // , data : null 1303 | // , responseType : null 1304 | // , headers : null 1305 | } 1306 | 1307 | function serialize (obj) { 1308 | var params = [] 1309 | ;(function fn (obj, scope) { 1310 | util.each(obj, function(value, key){ 1311 | value = obj[key] 1312 | if (scope) { 1313 | key = scope + '[' + (Array.isArray(obj) ? '' : key) + ']' 1314 | } 1315 | 1316 | if (util.is(['array', 'object'], value)) { 1317 | fn(value, key) 1318 | } else { 1319 | params.push(escape(key) + '=' + escape(value)) 1320 | } 1321 | }) 1322 | })(obj) 1323 | return params.join('&').replace(/%20/g, '+') 1324 | } 1325 | 1326 | function appendQuery (url, query) { 1327 | return (url + '&' + query).replace(/[&?]+/, '?') 1328 | } 1329 | 1330 | function parseData (options) { 1331 | if (options.data && (typeof options.data !== 'string')) { 1332 | options.data = serialize(options.data) 1333 | } 1334 | if (options.data && options.method === 'GET') { 1335 | options.url = appendQuery(options.url, options.data) 1336 | } 1337 | } 1338 | 1339 | function parseMime (mime) { 1340 | return mime && (mime.split('/')[1] || mime) 1341 | } 1342 | 1343 | function parseJSON (xhr) { 1344 | var data = xhr.response 1345 | // error of responseType: json 1346 | if (data === null) { 1347 | return new Error('Parser Error') 1348 | } 1349 | if (typeof data !== 'object') { 1350 | try { 1351 | data = JSON.parse(xhr.responseText) 1352 | } catch (err) { 1353 | return err 1354 | } 1355 | } 1356 | return data 1357 | } 1358 | 1359 | function parseXML (xhr) { 1360 | var data = xhr.responseXML 1361 | // parse xml to IE 9 1362 | if (data.xml && window.DOMParser) { 1363 | try { 1364 | var parser = new window.DOMParser() 1365 | data = parser.parseFromString(data.xml, 'text/xml') 1366 | } catch (err) { 1367 | return err 1368 | } 1369 | } 1370 | return data 1371 | } 1372 | 1373 | function request (options, callback) { 1374 | if (typeof options === 'string') { 1375 | options = { url: options } 1376 | } 1377 | if (!callback) { 1378 | callback = options.callback || noop 1379 | } 1380 | 1381 | var settings = util.extend({}, defaults, options) 1382 | , xhr = new window.XMLHttpRequest() 1383 | , mime = settings.accepts[settings.responseType] 1384 | , abortTimeout = null 1385 | , headers = {} 1386 | 1387 | settings.method = settings.method.toUpperCase() 1388 | parseData(settings) 1389 | 1390 | // sets request's accept and content type 1391 | if (mime) { 1392 | headers['Accept'] = mime 1393 | if (xhr.overrideMimeType) { 1394 | xhr.overrideMimeType(mime.split(',')[0]) 1395 | } 1396 | } 1397 | if (settings.contentType || ['POST', 'PUT'].indexOf(settings.method) >= 0) { 1398 | headers['Content-Type'] = settings.contentType || 'application/x-www-form-urlencoded' 1399 | } 1400 | util.extend(headers, settings.headers || {}) 1401 | 1402 | xhr.onreadystatechange = function(){ 1403 | var err, data 1404 | if (xhr.readyState != 4 || !xhr.status) { 1405 | return 1406 | } 1407 | xhr.onreadystatechange = noop 1408 | clearTimeout(abortTimeout) 1409 | 1410 | if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { 1411 | xhr.type = settings.responseType || xhr.responseType || parseMime(xhr.getResponseHeader('content-type')) 1412 | 1413 | switch (xhr.type) { 1414 | case 'json': 1415 | data = parseJSON(xhr) 1416 | break 1417 | case 'xml': 1418 | data = parseXML(xhr) 1419 | break 1420 | default: 1421 | data = xhr.responseText 1422 | } 1423 | 1424 | if (data instanceof Error) { 1425 | err = data, data = undefined 1426 | } 1427 | 1428 | } else { 1429 | err = new Error('Request failed') 1430 | } 1431 | callback.call(xhr, err, data, xhr) 1432 | } 1433 | 1434 | xhr.ontimeout = function(){ 1435 | callback.call(xhr, new Error('Timeout'), null, xhr) 1436 | } 1437 | 1438 | xhr.open(settings.method, settings.url, settings.async) 1439 | 1440 | // implements fallback to request's abort by timeout 1441 | if (!('timeout' in xhr) && settings.timeout > 0) { 1442 | abortTimeout = setTimeout(function(){ 1443 | xhr.onreadystatechange = noop 1444 | xhr.abort() 1445 | xhr.ontimeout() 1446 | }, settings.timeout) 1447 | } 1448 | 1449 | // exports settings to xhr and sets headers 1450 | util.each(settings, function(value, key) { 1451 | if (key !== 'responseType' || accepts.types.indexOf(value) >= 0) { 1452 | try { xhr[key] = value } catch (e) {} 1453 | } 1454 | }) 1455 | util.each(headers, function(value, name) { 1456 | xhr.setRequestHeader(name, value) 1457 | }) 1458 | 1459 | xhr.send(settings.data) 1460 | return xhr 1461 | } 1462 | 1463 | function requestProxy (method, options, callback) { 1464 | if (typeof options === 'string') { 1465 | options = { url: options } 1466 | } 1467 | options.method = method 1468 | return request(options, callback) 1469 | } 1470 | 1471 | var hideTypes = 'fieldset submit reset button image radio checkbox'.split(' ') 1472 | 1473 | this.serialize = function () { 1474 | var form = this.get(0) 1475 | , fields = {} 1476 | new Rye(form && form.elements).forEach(function(field){ 1477 | if (!field.disabled && ( 1478 | field.checked 1479 | || (field.type && hideTypes.indexOf(field.type) < 0) 1480 | ) 1481 | ) { 1482 | fields[field.name] = manipulation.getValue(field) 1483 | } 1484 | }) 1485 | return serialize(fields) 1486 | } 1487 | 1488 | // Exported methods 1489 | // ---------------- 1490 | 1491 | Rye.request = request 1492 | Rye.get = util.curry(requestProxy, 'GET') 1493 | Rye.post = util.curry(requestProxy, 'POST') 1494 | 1495 | // prevents to attach properties on request 1496 | var exports = request.bind({}) 1497 | 1498 | util.extend(exports, { 1499 | serialize : serialize 1500 | , appendQuery : appendQuery 1501 | , defaults : defaults 1502 | , get : util.curry(requestProxy, 'GET') 1503 | , post : util.curry(requestProxy, 'POST') 1504 | // https://github.com/mlbli/craft/blob/master/src/ajax.js#L77 1505 | }) 1506 | 1507 | return exports 1508 | 1509 | }) 1510 | -------------------------------------------------------------------------------- /app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% block title %}{% endblock %} 7 | 8 | 9 | 10 | 11 | {% for url in assets['css'].urls() %} 12 | 13 | {%- endfor %} 14 | 15 | 16 | 17 | 18 | 19 | {% block header %} 20 | {% endblock %} 21 | 22 | {% with messages = get_flashed_messages(with_categories=true) %} 23 | {% if messages %} 24 | {% for category, message in messages %} 25 |
{{ message }}
26 | {% endfor %} 27 | {% endif %} 28 | {% endwith %} 29 | 30 | {% block content %} 31 | {% endblock %} 32 | 33 | {% for url in assets['js'].urls() %} 34 | 35 | {%- endfor %} 36 | 37 | {% block script %} 38 | {% endblock %} 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/templates/errors/403.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

403 Forbidden

5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /app/templates/errors/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

404 Not Found

5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /app/templates/errors/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

500 Internal Server Error

5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /app/templates/main/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Hello, world!

5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /app/views.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, render_template 2 | 3 | blueprint = Blueprint('main', __name__, template_folder='templates', 4 | static_folder='static') 5 | 6 | @blueprint.route('/') 7 | def index(): 8 | return render_template('main/index.html') 9 | -------------------------------------------------------------------------------- /db/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | script_location = /srv/www/app/db 6 | 7 | # template used to generate migration files 8 | # file_template = %%(rev)s_%%(slug)s 9 | 10 | # set to 'true' to run the environment during 11 | # the 'revision' command, regardless of autogenerate 12 | # revision_environment = false 13 | 14 | # Logging configuration 15 | [loggers] 16 | keys = root,sqlalchemy,alembic 17 | 18 | [handlers] 19 | keys = console 20 | 21 | [formatters] 22 | keys = generic 23 | 24 | [logger_root] 25 | level = WARN 26 | handlers = console 27 | qualname = 28 | 29 | [logger_sqlalchemy] 30 | level = WARN 31 | handlers = 32 | qualname = sqlalchemy.engine 33 | 34 | [logger_alembic] 35 | level = INFO 36 | handlers = 37 | qualname = alembic 38 | 39 | [handler_console] 40 | class = StreamHandler 41 | args = (sys.stderr,) 42 | level = NOTSET 43 | formatter = generic 44 | 45 | [formatter_generic] 46 | format = %(levelname)-5.5s [%(name)s] %(message)s 47 | datefmt = %H:%M:%S 48 | -------------------------------------------------------------------------------- /db/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | import sys 3 | from os.path import dirname, abspath 4 | from alembic import context 5 | from sqlalchemy import engine_from_config, pool 6 | from logging.config import fileConfig 7 | 8 | sys.path.append(dirname(dirname(abspath(__file__)))) 9 | 10 | # Import app 11 | from app import create_app 12 | from app.db import db 13 | 14 | # this is the Alembic Config object, which provides 15 | # access to the values within the .ini file in use. 16 | config = context.config 17 | 18 | # Interpret the config file for Python logging. 19 | # This line sets up loggers basically. 20 | fileConfig(config.config_file_name) 21 | 22 | # Initialise Flask app 23 | app = create_app() 24 | 25 | # Set metadata for alembic 26 | config.set_main_option('sqlalchemy.url', app.config.get('SQLALCHEMY_DATABASE_URI')) 27 | target_metadata = db.metadata 28 | 29 | # other values from the config, defined by the needs of env.py, 30 | # can be acquired: 31 | # my_important_option = config.get_main_option("my_important_option") 32 | # ... etc. 33 | 34 | 35 | def run_migrations_offline(): 36 | """Run migrations in 'offline' mode. 37 | 38 | This configures the context with just a URL 39 | and not an Engine, though an Engine is acceptable 40 | here as well. By skipping the Engine creation 41 | we don't even need a DBAPI to be available. 42 | 43 | Calls to context.execute() here emit the given string to the 44 | script output. 45 | 46 | """ 47 | url = config.get_main_option("sqlalchemy.url") 48 | context.configure(url=url) 49 | 50 | with context.begin_transaction(): 51 | context.run_migrations() 52 | 53 | 54 | def run_migrations_online(): 55 | """Run migrations in 'online' mode. 56 | 57 | In this scenario we need to create an Engine 58 | and associate a connection with the context. 59 | 60 | """ 61 | engine = engine_from_config( 62 | config.get_section(config.config_ini_section), 63 | prefix='sqlalchemy.', 64 | poolclass=pool.NullPool) 65 | 66 | connection = engine.connect() 67 | context.configure( 68 | connection=connection, 69 | target_metadata=target_metadata 70 | ) 71 | 72 | try: 73 | with context.begin_transaction(): 74 | context.run_migrations() 75 | finally: 76 | connection.close() 77 | 78 | if context.is_offline_mode(): 79 | run_migrations_offline() 80 | else: 81 | run_migrations_online() 82 | -------------------------------------------------------------------------------- /db/script.py.mako: -------------------------------------------------------------------------------- 1 | """ 2 | ${message} 3 | 4 | Revision ID: ${up_revision} 5 | Revises: ${down_revision} 6 | Create Date: ${create_date} 7 | 8 | """ 9 | 10 | # revision identifiers, used by Alembic. 11 | revision = ${repr(up_revision)} 12 | down_revision = ${repr(down_revision)} 13 | 14 | from alembic import op 15 | import sqlalchemy as sa 16 | ${imports if imports else ""} 17 | 18 | def upgrade(): 19 | ${upgrades if upgrades else "pass"} 20 | 21 | 22 | def downgrade(): 23 | ${downgrades if downgrades else "pass"} 24 | -------------------------------------------------------------------------------- /fabfile/__init__.py: -------------------------------------------------------------------------------- 1 | from fabric.api import env 2 | from fabric.utils import abort 3 | from fabric.decorators import task 4 | from fabric.context_managers import settings 5 | 6 | # Load configuration 7 | import config 8 | 9 | # Fabfile modules 10 | import app 11 | import db 12 | import bootstrap as _bootstrap 13 | import deployment as code 14 | import puppet 15 | import servers 16 | import virtualenv 17 | 18 | 19 | @task 20 | def bootstrap(): 21 | """ 22 | Set up a new server (requires superuser credentials). 23 | """ 24 | # Very important that we only do this on remote machines and not locally. 25 | if not env.host_string: 26 | abort('You must specify a server to configure using -H.') 27 | 28 | # Use sudo if we're not logging on as root 29 | if env.user != 'root': 30 | env.sudo = True 31 | 32 | # Bootstrap stuff 33 | _bootstrap.software() 34 | _bootstrap.user() 35 | _bootstrap.project() 36 | 37 | # Initial code deploy 38 | code.deploy(warn=False) 39 | 40 | # Initial puppet run 41 | puppet.run() 42 | 43 | # Fix permissions 44 | _bootstrap.chown() 45 | 46 | # Ready to deploy now 47 | 48 | 49 | @task 50 | def build(): 51 | """ 52 | Execute build tasks. 53 | """ 54 | virtualenv.build() 55 | app.build() 56 | db.build() 57 | 58 | 59 | @task 60 | def run(): 61 | """ 62 | Run app in debug mode (for development). 63 | """ 64 | app.run() 65 | 66 | 67 | @task 68 | def test(): 69 | """Run tests.""" 70 | app.test() 71 | 72 | 73 | @task 74 | def deploy(): 75 | """ 76 | Deploy to remote environment. 77 | 78 | Deploys code from the current git branch to the remote server and reloads 79 | services so that the new code is in effect. 80 | 81 | The remote server to deploy to is automatically determined based on the 82 | currently checked-out git branch and matched to the configuration specified 83 | in fabfile/deploy/config.py. 84 | """ 85 | code.deploy() 86 | # Post-deploy tasks on the remote server 87 | with settings(host_string=servers.remote()): 88 | build() 89 | puppet.run() 90 | app.reload() 91 | -------------------------------------------------------------------------------- /fabfile/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | fabfile module containing application-specific tasks. 3 | """ 4 | from fabric.api import task 5 | from fabric.colors import cyan 6 | from fabfile.utils import do 7 | from fabfile.virtualenv import venv_path 8 | 9 | 10 | @task 11 | def build(): 12 | """ 13 | Run application build tasks. 14 | """ 15 | # Generate static assets. Note that we always build assets with the 16 | # production config because dev will never point to compiled files. 17 | print(cyan('\nBuilding static assets...')) 18 | do('mkdir -p app/static/__bundles/css') 19 | do('mkdir -p app/static/__bundles/js') 20 | do('export FLASK_CONFIG=config/production.py && %s/bin/python manage.py build' % venv_path) 21 | 22 | 23 | def run(): 24 | """Start app in debug mode (for development).""" 25 | do('export FLASK_CONFIG=config/dev.py && %s/bin/python manage.py runserver' % venv_path) 26 | 27 | 28 | def test(): 29 | """Run unit tests""" 30 | print(cyan('\nRunning tests...')) 31 | do('FLASK_CONFIG=config/test.py %s/bin/nosetests --exclude-dir-file=\'.noseexclude\' --with-yanc --with-spec --spec-color -q' % venv_path) 32 | 33 | 34 | def coverage(): 35 | """Generate test coverage report""" 36 | do('FLASK_CONFIG=config/test.py %s/bin/nosetests --exclude-dir-file=\'.noseexclude\' --with-cov --cov=app --cov-report=html' % venv_path) 37 | 38 | 39 | def start(): 40 | """Start app using init.""" 41 | do('sudo start uwsgi') 42 | 43 | 44 | def stop(): 45 | """Stop app using init.""" 46 | do('sudo stop uwsgi') 47 | 48 | 49 | def reload(): 50 | """Restart app using init.""" 51 | do('sudo reload uwsgi') 52 | -------------------------------------------------------------------------------- /fabfile/bootstrap.py: -------------------------------------------------------------------------------- 1 | """ 2 | fabfile module for prepping a server for deploy. 3 | """ 4 | from fabric.colors import cyan 5 | from fabric.context_managers import settings, hide 6 | from fabfile.utils import do 7 | 8 | 9 | def software(): 10 | """ 11 | Install required software. 12 | """ 13 | with settings(remote_path='/tmp'): 14 | 15 | # Install prerequisites 16 | print(cyan('\nInstalling software...')) 17 | do('apt-get -qq update') 18 | do('apt-get -qq install -y puppet git') 19 | 20 | 21 | def user(): 22 | """ 23 | Add and configure deploy user. 24 | """ 25 | with settings(remote_path='/tmp'): 26 | 27 | # Set up deploy user 28 | print(cyan('\nSetting up deploy user...')) 29 | with settings(hide('warnings'), warn_only=True): 30 | do('useradd tobias') 31 | do('[ -e /home/tobias ] || cp -r /etc/skel /home/tobias') 32 | # Copy authorized_keys from the current user into deploy user's home 33 | do('mkdir -p /home/tobias/.ssh') 34 | do('[ -e ~/.ssh/authorized_keys ] && cp ~/.ssh/authorized_keys /home/tobias/.ssh/') 35 | do('chown -R tobias:tobias /home/tobias') 36 | 37 | 38 | def project(): 39 | """ 40 | Set up project directory. 41 | """ 42 | with settings(remote_path='/tmp'): 43 | # Set up project directory 44 | print(cyan('\nSetting up project directory...')) 45 | do('mkdir -p /srv/www/app') 46 | 47 | 48 | def chown(): 49 | """ 50 | Fix project directory permissions after an initial deploy. 51 | """ 52 | with settings(remote_path='/tmp'): 53 | print(cyan('\nFixing permissions...')) 54 | do('chown -R tobias:tobias /srv/www/app') 55 | -------------------------------------------------------------------------------- /fabfile/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | fabfile bundle config. 3 | """ 4 | import sys 5 | from fabric.api import env 6 | from fabric.context_managers import settings 7 | from fabfile.git import branch 8 | 9 | # Attempt to detect branch from git HEAD 10 | with settings(warn_only=True): 11 | env.branch = branch() 12 | 13 | env.remote_path = '/srv/www/app' 14 | 15 | # Check to see if -u was specified at the command line 16 | for opt in sys.argv: 17 | if opt.startswith('-u') or opt.startswith('--user'): 18 | # Set a flag to signifiy -u was specified 19 | env.custom_user = True 20 | break 21 | 22 | # Use -u parameter if it was specified, otherwise default to deploy user 23 | if not env.get('custom_user', False): 24 | env.user = 'tobias' 25 | -------------------------------------------------------------------------------- /fabfile/db.py: -------------------------------------------------------------------------------- 1 | """ 2 | fabfile module for managing database-related tasks. 3 | """ 4 | from fabric.api import task 5 | from fabric.context_managers import settings, hide 6 | from fabric.colors import cyan 7 | from fabfile.utils import do 8 | from fabfile.virtualenv import venv_path 9 | 10 | DB = { 11 | 'name': 'app', 12 | 'user': 'www-data', 13 | } 14 | 15 | 16 | @task 17 | def build(): 18 | """Initialise and migrate database to latest version.""" 19 | print(cyan('\nUpdating database...')) 20 | 21 | # Ensure database exists 22 | if not _pg_db_exists(DB['name']): 23 | do('createdb -O \'%(user)s\' \'%(name)s\'' % DB) 24 | 25 | # Ensure versions folder exists 26 | do('mkdir -p db/versions') 27 | 28 | do('rm -f db/versions/*.pyc') 29 | 30 | # Run migrations 31 | do('%s/bin/alembic -c db/alembic.ini upgrade head' % venv_path) 32 | 33 | # Ensure www-data has permission on all objects 34 | do('psql -tAc \'GRANT ALL ON ALL TABLES IN SCHEMA public TO "%(user)s";\' %(name)s' % DB) 35 | do('psql -tAc \'GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO "%(user)s";\' %(name)s' % DB) 36 | do('psql -tAc \'ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO GROUP "%(user)s";\' %(name)s' % DB) 37 | do('psql -tAc \'ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO GROUP "%(user)s";\' %(name)s' % DB) 38 | 39 | 40 | @task 41 | def generate(message=None): 42 | """Generates a new Alembic revision based on DB changes.""" 43 | args = '' 44 | if message: 45 | args = '-m "%s"' % message 46 | do('%s/bin/alembic -c db/alembic.ini revision --autogenerate %s' % (venv_path, args)) 47 | 48 | 49 | def _pg_db_exists(database): 50 | """ 51 | Helper function to check if the postgresql database exists. 52 | """ 53 | with settings(hide('running', 'warnings'), warn_only=True): 54 | return do('psql -tAc "SELECT 1 FROM pg_database WHERE datname = \'%s\'" postgres |grep -q 1' % database, capture=True).succeeded 55 | -------------------------------------------------------------------------------- /fabfile/deployment.py: -------------------------------------------------------------------------------- 1 | """ 2 | fabfile module that handles deployment of the application to remote servers. 3 | """ 4 | import os 5 | from fabric.api import env, local, run, put 6 | from fabric.utils import abort 7 | from fabric.colors import cyan, yellow 8 | from fabric.contrib.console import confirm 9 | from fabric.context_managers import settings, hide 10 | from fabfile.servers import remote 11 | 12 | 13 | def deploy(warn=True): 14 | """ 15 | Deploy to remote environment. 16 | """ 17 | with settings(host_string=remote()): 18 | 19 | if warn: 20 | # Display confirmation 21 | print('\nYou are about to deploy current branch ' + yellow('%(branch)s' % env, bold=True) + ' to ' + yellow('%(host_string)s' % env, bold=True) + '.') 22 | if not confirm('Continue?', default=False): 23 | abort('User aborted') 24 | 25 | # git-pull 26 | local('git pull origin %(branch)s' % env) 27 | 28 | # Initialise remote git repo 29 | run('git init %(remote_path)s' % env) 30 | run('git --git-dir=%(remote_path)s/.git config receive.denyCurrentBranch ignore' % env) 31 | local('cat %s/deployment/hooks/post-receive.tpl | sed \'s/\$\$BRANCH\$\$/%s/g\' > /tmp/post-receive.tmp' % (os.path.dirname(__file__), env.branch)) 32 | put('/tmp/post-receive.tmp', '%(remote_path)s/.git/hooks/post-receive' % env) 33 | run('chmod +x %(remote_path)s/.git/hooks/post-receive' % env) 34 | 35 | # Add server to the local git repo config (as a git remote) 36 | with settings(hide('warnings'), warn_only=True): 37 | local('git remote rm %(branch)s' % env) 38 | local('git remote add %(branch)s ssh://%(user)s@%(host_string)s:%(port)s%(remote_path)s' % env) 39 | 40 | # Push to origin 41 | print(cyan('\nDeploying code...')) 42 | local('git push origin %(branch)s' % env) 43 | local('GIT_SSH=fabfile/deployment/ssh git push %(branch)s %(branch)s' % env) 44 | -------------------------------------------------------------------------------- /fabfile/deployment/hooks/post-receive.tpl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Git post-receive hook for deployment. 4 | # 5 | # daniel.simmons@tobias.tv 6 | # 2012-03-29 7 | # 8 | 9 | cd "$GIT_DIR/.." 10 | 11 | # These have to be unset, because the fab build (which is executed below) calls 12 | # git again, which will get confused if these variables are already set. 13 | unset GIT_WORK_TREE 14 | unset GIT_DIR 15 | 16 | # Check out correct branch 17 | git checkout $$BRANCH$$ 18 | 19 | # Update working tree to reflect new changes 20 | git reset --hard 21 | 22 | echo 23 | -------------------------------------------------------------------------------- /fabfile/deployment/ssh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -f "${0%/*}/keys/id_rsa" ]; then 3 | ssh -i "${0%/*}/keys/id_rsa" "$@" 4 | else 5 | ssh -i "${0%/*}/keys/id_rsa" "$@" 6 | fi 7 | -------------------------------------------------------------------------------- /fabfile/git.py: -------------------------------------------------------------------------------- 1 | """ 2 | fabfile module for common git commands - used as a helper by other fabfile 3 | modules. 4 | """ 5 | from fabric.api import local 6 | from fabric.context_managers import hide 7 | 8 | 9 | def branch(): 10 | """ 11 | Returns the name of the local branch that we're currently on. 12 | """ 13 | with hide('running'): 14 | result = local('git symbolic-ref -q HEAD', capture=True) 15 | try: 16 | return result.split('/')[2] 17 | except IndexError: 18 | return None 19 | -------------------------------------------------------------------------------- /fabfile/puppet.py: -------------------------------------------------------------------------------- 1 | """ 2 | fabfile module containing Puppet-related tasks. 3 | """ 4 | from fabfile.utils import do 5 | from fabric.colors import cyan 6 | from fabric.context_managers import settings 7 | from fabric.contrib.console import confirm 8 | from fabric.decorators import task 9 | from fabric.tasks import execute 10 | from fabric.utils import abort 11 | 12 | import servers 13 | 14 | 15 | def check_syntax(): 16 | """ 17 | Syntax check on Puppet config. 18 | """ 19 | print cyan('\nChecking puppet syntax...') 20 | do("find puppet -type f -name '*.pp' | xargs puppet parser validate") 21 | 22 | 23 | @task 24 | def remote(tag=None): 25 | """ 26 | Run Puppet on remote servers. 27 | """ 28 | if tag is None: 29 | if not confirm('Initiate Puppet run on all hosts?', default=False): 30 | abort('User aborted.') 31 | 32 | with settings(hosts=servers.remotes(tag=tag)): 33 | execute('code.deploy') 34 | execute('puppet.run') 35 | 36 | 37 | @task 38 | def run(debug=False): 39 | """ 40 | Apply Puppet manifest. 41 | """ 42 | check_syntax() 43 | 44 | print cyan('\nApplying puppet manifest...') 45 | if debug: 46 | debug = '--debug' 47 | else: 48 | debug = '' 49 | do('sudo /usr/bin/puppet apply puppet/manifests/default.pp ' 50 | '--modulepath=puppet/modules {0}'.format(debug)) 51 | -------------------------------------------------------------------------------- /fabfile/servers.json: -------------------------------------------------------------------------------- 1 | { 2 | "uat": "", 3 | "production": "" 4 | } 5 | -------------------------------------------------------------------------------- /fabfile/servers.py: -------------------------------------------------------------------------------- 1 | """ 2 | fabfile module that provides a helper for other modules to programmatically 3 | execute a task on the remote server (as specified by the git branch/server 4 | configuration mapping). 5 | """ 6 | import os 7 | import json 8 | from fabric.api import env, task 9 | from fabric.utils import abort 10 | 11 | # Read server config from JSON file. 12 | fp = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'servers.json') 13 | try: 14 | env.servers = json.load(open(fp, 'r')) 15 | except: 16 | env.servers = {} 17 | 18 | 19 | def remote(): 20 | """ 21 | Returns the current remote server (host_string). If there is none, look up 22 | the remote server from the current git branch and branch/server config. 23 | """ 24 | if env.host_string: 25 | # If host_string is already specified, use that. This allows the user to 26 | # override the target deploy server on the command-line. 27 | return env.host_string 28 | else: 29 | branch = env.branch 30 | if branch not in env.servers: 31 | abort('Branch does not correspond to server and no host specified.') 32 | return env.servers[branch] 33 | 34 | 35 | def save(): 36 | """ 37 | Write server config to JSON file. 38 | """ 39 | json.dump(env.servers, open(fp, 'w'), indent=4) 40 | -------------------------------------------------------------------------------- /fabfile/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | fabfile module that provides generic helper functions for other modules. 3 | """ 4 | import os 5 | from fabric.api import env, local, run as fab_run 6 | from fabric.context_managers import cd, lcd 7 | 8 | 9 | def do(cmd, capture=False, *args, **kwargs): 10 | """ 11 | Runs command locally or remotely depending on whether a remote host has 12 | been specified. 13 | """ 14 | if env.host_string or env.host or env.hosts: 15 | with cd(env.remote_path): 16 | if env.get('sudo', False): 17 | cmd = 'sudo %s' % cmd 18 | return fab_run(cmd, *args, **kwargs) 19 | else: 20 | # project root path is the default working directory 21 | path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 22 | if env['lcwd']: 23 | # lcd has already been invoked. If it's with a relative path, let's 24 | # make that relative to the project root 25 | if not env['lcwd'].startswith('/'): 26 | path = '%s/%s' % (path, env['lcwd']) 27 | else: 28 | # Honour the current lcd contact if it's an absolute path 29 | path = env['lcwd'] 30 | with lcd(path): 31 | # Add shell envs 32 | for name, val in env.shell_env.iteritems(): 33 | cmd = 'export {name}="{val}" && {cmd}'.format(name=name, 34 | val=val, 35 | cmd=cmd) 36 | # Add command prefixes to local commands 37 | for prefix in env.command_prefixes: 38 | cmd = '{prefix} && {cmd}'.format(prefix=prefix, cmd=cmd) 39 | return local(cmd, *args, capture=capture, **kwargs) 40 | -------------------------------------------------------------------------------- /fabfile/virtualenv.py: -------------------------------------------------------------------------------- 1 | """ 2 | fabfile module that handles virtualenv-related tasks. 3 | """ 4 | from fabric.context_managers import settings, hide 5 | from fabric.colors import cyan, red 6 | from fabric.utils import abort 7 | from fabfile.utils import do 8 | 9 | venv_path = '.venv' 10 | pip_cache_path = '.cache/pip' 11 | 12 | 13 | def build(): 14 | """Build or update the virtualenv.""" 15 | with settings(hide('stdout')): 16 | print(cyan('\nUpdating venv, installing packages...')) 17 | do('[ -e %s ] || virtualenv %s --distribute --no-site-packages' % (venv_path, venv_path)) 18 | do('mkdir -p %s' % pip_cache_path) 19 | # annoyingly, pip prints errors to stdout (instead of stderr), so we 20 | # have to check the return code and output only if there's an error. 21 | with settings(warn_only=True): 22 | pip = do('%s/bin/pip install --download-cache %s -r requirements.txt' % (venv_path, pip_cache_path), capture=True) 23 | if pip.failed: 24 | print(red(pip)) 25 | abort("pip exited with return code %i" % pip.return_code) 26 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from os.path import join 3 | from app import create_app 4 | from flask import current_app 5 | from flask.ext.script import Shell, Manager, Server 6 | 7 | manager = Manager(create_app) 8 | 9 | 10 | def _make_shell_context(): 11 | """ 12 | Shell context: import helper objects here. 13 | """ 14 | return dict(app=current_app) 15 | 16 | 17 | manager.add_option('--flask-config', dest='config', help='Specify Flask config file', required=False) 18 | manager.add_command('shell', Shell(make_context=_make_shell_context)) 19 | manager.add_command('runserver', Server(host='0.0.0.0')) 20 | 21 | 22 | @manager.command 23 | def build(): 24 | """ 25 | Build static assets. 26 | """ 27 | from app.assets import init 28 | assets = init(current_app) 29 | assets.build_all() 30 | 31 | 32 | if __name__ == '__main__': 33 | manager.run() 34 | -------------------------------------------------------------------------------- /puppet/manifests/bootstrap/apt-update.pp: -------------------------------------------------------------------------------- 1 | # 2 | # apt-update bootstrap 3 | # 4 | # Ubuntu vagrant boxes ship with US apt mirrors which are slow. Here 5 | # we overwrite them with the UK ones prior to running an apt-get update. 6 | # 7 | 8 | include apt 9 | 10 | class { 'apt::sources': 11 | mirror => 'http://gb.archive.ubuntu.com/ubuntu/', 12 | } 13 | -------------------------------------------------------------------------------- /puppet/manifests/config.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Global variables. 3 | # 4 | 5 | # User that is installed to facilitate app deploys 6 | $deploy_user = 'deploy' 7 | 8 | # Path to the application 9 | $app_path = '/srv/www/app' 10 | -------------------------------------------------------------------------------- /puppet/manifests/default.pp: -------------------------------------------------------------------------------- 1 | import 'config.pp' 2 | import 'lib/*.pp' 3 | 4 | # Import node config for different environments 5 | import 'environments/*.pp' 6 | 7 | # 8 | # Defaults. 9 | # 10 | node default { 11 | include cssmin 12 | include fabric 13 | include git 14 | include nginx 15 | include openssl 16 | include postfix 17 | include postgresql 18 | include puppet::sudoers 19 | include python::packages 20 | include rsyslog 21 | include sysctl 22 | include uglifyjs 23 | 24 | # Set timezone to Europe/London 25 | file { '/etc/localtime': 26 | source => '/usr/share/zoneinfo/Europe/London', 27 | # Restart syslog so log times are correct 28 | notify => Service[rsyslog], 29 | } 30 | 31 | # Create postgresql roles for the app and deploy user 32 | postgresql::role { 'www-data': 33 | ensure => present, 34 | } 35 | } 36 | 37 | 38 | # 39 | # Base class for externally hosted production nodes. 40 | # 41 | class site inherits default { 42 | include users::deploy 43 | 44 | # Firewall 45 | class { 'ferm': 46 | public_ports => [ 47 | 'http', 48 | 'https', 49 | ], 50 | } 51 | 52 | # Newrelic server monitoring 53 | class { 'newrelic::servermon': 54 | key => '3c11e611ab565cd0936ed88137ba555ca1500dc0', 55 | } 56 | 57 | # Give deploy user access to create and update the database 58 | postgresql::role { $::deploy_user: 59 | ensure => present, 60 | createdb => true, # This user creates the 61 | grant => 'www-data', # app database. 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /puppet/manifests/environments/production.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Production node(s). Rename or inherit from this. 3 | # 4 | node production inherits site { 5 | include uwsgi 6 | 7 | nginx::site { 'app': 8 | ssl_cert => '/etc/ssl/certs/your-cert-here.pem', 9 | ssl_cert_key => '/etc/ssl/private/your-key-here.key', 10 | } 11 | 12 | uwsgi::app { 'app': 13 | uwsgi_options => [ 14 | [ processes => $::processorcount ], 15 | [ module => 'app:create_app()' ], 16 | [ env => [ 17 | 'FLASK_CONFIG=config/production.py', 18 | ]], 19 | ], 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /puppet/manifests/environments/vagrant.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Standalone manifest - for dev Vagrant box. 3 | # 4 | node vagrant-ubuntu-trusty-64 inherits default { 5 | include vagrant 6 | include vagrant::pip 7 | 8 | postgresql::role { 'vagrant': 9 | ensure => present, 10 | createdb => true, 11 | grant => 'www-data', 12 | } 13 | 14 | nginx::site { 'app': 15 | ssl_cert => '/etc/ssl/certs/ssl-cert-snakeoil.pem', 16 | ssl_cert_key => '/etc/ssl/private/ssl-cert-snakeoil.key', 17 | } 18 | 19 | # Install uwsgi, but don't run automatically at boot 20 | class { 'uwsgi': 21 | ensure => stopped, 22 | } 23 | 24 | uwsgi::app { 'app': 25 | uwsgi_options => [ 26 | [ processes => $::processorcount ], 27 | [ module => 'app:create_app()' ], 28 | [ env => [ 29 | 'FLASK_CONFIG=config/dev.py', 30 | ]], 31 | ], 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /puppet/manifests/lib/line.pp: -------------------------------------------------------------------------------- 1 | define line($file, $line, $ensure = 'present') { 2 | case $ensure { 3 | default : { err ( "unknown ensure value ${ensure}" ) } 4 | present: { 5 | exec { "/bin/echo '${line}' >> '${file}'": 6 | unless => "/bin/grep -qFx '${line}' '${file}'" 7 | } 8 | } 9 | absent: { 10 | exec { "/bin/grep -vFx '${line}' '${file}' | /usr/bin/tee '${file}' >/dev/null 2>&1": 11 | onlyif => "/bin/grep -qFx '${line}' '${file}'" 12 | } 13 | } 14 | uncomment: { 15 | exec { "/bin/sed -i -e'/${line}/s/#\\+//' '${file}'": 16 | onlyif => "/bin/grep '${line}' '${file}' | /bin/grep '^#' | /usr/bin/wc -l" 17 | } 18 | } 19 | comment: { 20 | exec { "/bin/sed -i -e'/${line}/s/\\(.\\+\\)$/#\\1/' '${file}'": 21 | onlyif => "/usr/bin/test `/bin/grep '${line}' '${file}' | /bin/grep -v '^#' | /usr/bin/wc -l` -ne 0" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /puppet/modules/apt/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Ensure apt sources cache is up-to-date. 3 | # 4 | class apt { 5 | exec { 'apt-update': 6 | command => '/usr/bin/apt-get -qq update' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /puppet/modules/apt/manifests/sources.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Installs the default Ubuntu apt sources. 3 | # 4 | class apt::sources( 5 | $dist = 'trusty', 6 | $mirror = 'http://gb.archive.ubuntu.com/ubuntu/' 7 | ) { 8 | file { '/etc/apt/sources.list': 9 | content => template("apt/${dist}.list.tpl"), 10 | before => Exec['apt-update'], 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /puppet/modules/apt/templates/precise.list.tpl: -------------------------------------------------------------------------------- 1 | # 2 | 3 | # deb cdrom:[Ubuntu-Server 12.04 LTS _Precise Pangolin_ - Release amd64 (20120424.1)]/ dists/precise/main/binary-i386/ 4 | # deb cdrom:[Ubuntu-Server 12.04 LTS _Precise Pangolin_ - Release amd64 (20120424.1)]/ dists/precise/restricted/binary-i386/ 5 | # deb cdrom:[Ubuntu-Server 12.04 LTS _Precise Pangolin_ - Release amd64 (20120424.1)]/ precise main restricted 6 | 7 | # deb cdrom:[Ubuntu-Server 12.04 LTS _Precise Pangolin_ - Release amd64 (20120424.1)]/ dists/precise/main/binary-i386/ 8 | # deb cdrom:[Ubuntu-Server 12.04 LTS _Precise Pangolin_ - Release amd64 (20120424.1)]/ dists/precise/restricted/binary-i386/ 9 | # deb cdrom:[Ubuntu-Server 12.04 LTS _Precise Pangolin_ - Release amd64 (20120424.1)]/ precise main restricted 10 | 11 | # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to 12 | # newer versions of the distribution. 13 | deb <%= mirror %> precise main restricted 14 | deb-src <%= mirror %> precise main restricted 15 | 16 | ## Major bug fix updates produced after the final release of the 17 | ## distribution. 18 | deb <%= mirror %> precise-updates main restricted 19 | deb-src <%= mirror %> precise-updates main restricted 20 | 21 | ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu 22 | ## team. Also, please note that software in universe WILL NOT receive any 23 | ## review or updates from the Ubuntu security team. 24 | deb <%= mirror %> precise universe 25 | deb-src <%= mirror %> precise universe 26 | deb <%= mirror %> precise-updates universe 27 | deb-src <%= mirror %> precise-updates universe 28 | 29 | ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu 30 | ## team, and may not be under a free licence. Please satisfy yourself as to 31 | ## your rights to use the software. Also, please note that software in 32 | ## multiverse WILL NOT receive any review or updates from the Ubuntu 33 | ## security team. 34 | deb <%= mirror %> precise multiverse 35 | deb-src <%= mirror %> precise multiverse 36 | deb <%= mirror %> precise-updates multiverse 37 | deb-src <%= mirror %> precise-updates multiverse 38 | 39 | ## N.B. software from this repository may not have been tested as 40 | ## extensively as that contained in the main release, although it includes 41 | ## newer versions of some applications which may provide useful features. 42 | ## Also, please note that software in backports WILL NOT receive any review 43 | ## or updates from the Ubuntu security team. 44 | deb <%= mirror %> precise-backports main restricted universe multiverse 45 | deb-src <%= mirror %> precise-backports main restricted universe multiverse 46 | 47 | deb http://security.ubuntu.com/ubuntu precise-security main restricted 48 | deb-src http://security.ubuntu.com/ubuntu precise-security main restricted 49 | deb http://security.ubuntu.com/ubuntu precise-security universe 50 | deb-src http://security.ubuntu.com/ubuntu precise-security universe 51 | deb http://security.ubuntu.com/ubuntu precise-security multiverse 52 | deb-src http://security.ubuntu.com/ubuntu precise-security multiverse 53 | 54 | ## Uncomment the following two lines to add software from Canonical's 55 | ## 'partner' repository. 56 | ## This software is not part of Ubuntu, but is offered by Canonical and the 57 | ## respective vendors as a service to Ubuntu users. 58 | # deb http://archive.canonical.com/ubuntu precise partner 59 | # deb-src http://archive.canonical.com/ubuntu precise partner 60 | 61 | ## Uncomment the following two lines to add software from Ubuntu's 62 | ## 'extras' repository. 63 | ## This software is not part of Ubuntu, but is offered by third-party 64 | ## developers who want to ship their latest software. 65 | # deb http://extras.ubuntu.com/ubuntu precise main 66 | # deb-src http://extras.ubuntu.com/ubuntu precise main 67 | -------------------------------------------------------------------------------- /puppet/modules/apt/templates/trusty.list.tpl: -------------------------------------------------------------------------------- 1 | ## Note, this file is written by cloud-init on first boot of an instance 2 | ## modifications made here will not survive a re-bundle. 3 | ## if you wish to make changes you can: 4 | ## a.) add 'apt_preserve_sources_list: true' to /etc/cloud/cloud.cfg 5 | ## or do the same in user-data 6 | ## b.) add sources in /etc/apt/sources.list.d 7 | ## c.) make changes to template file /etc/cloud/templates/sources.list.tmpl 8 | # 9 | 10 | # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to 11 | # newer versions of the distribution. 12 | deb <%= @mirror %> trusty main 13 | deb-src <%= @mirror %> trusty main 14 | 15 | ## Major bug fix updates produced after the final release of the 16 | ## distribution. 17 | deb <%= @mirror %> trusty-updates main 18 | deb-src <%= @mirror %> trusty-updates main 19 | 20 | ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu 21 | ## team. Also, please note that software in universe WILL NOT receive any 22 | ## review or updates from the Ubuntu security team. 23 | deb <%= @mirror %> trusty universe 24 | deb-src <%= @mirror %> trusty universe 25 | deb <%= @mirror %> trusty-updates universe 26 | deb-src <%= @mirror %> trusty-updates universe 27 | 28 | ## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu 29 | ## team, and may not be under a free licence. Please satisfy yourself as to 30 | ## your rights to use the software. Also, please note that software in 31 | ## multiverse WILL NOT receive any review or updates from the Ubuntu 32 | ## security team. 33 | # deb <%= @mirror %> trusty multiverse 34 | # deb-src <%= @mirror %> trusty multiverse 35 | # deb <%= @mirror %> trusty-updates multiverse 36 | # deb-src <%= @mirror %> trusty-updates multiverse 37 | 38 | ## Uncomment the following two lines to add software from the 'backports' 39 | ## repository. 40 | ## N.B. software from this repository may not have been tested as 41 | ## extensively as that contained in the main release, although it includes 42 | ## newer versions of some applications which may provide useful features. 43 | ## Also, please note that software in backports WILL NOT receive any review 44 | ## or updates from the Ubuntu security team. 45 | # deb <%= @mirror %> trusty-backports main restricted universe multiverse 46 | # deb-src <%= @mirror %> trusty-backports main restricted universe multiverse 47 | 48 | ## Uncomment the following two lines to add software from Canonical's 49 | ## 'partner' repository. 50 | ## This software is not part of Ubuntu, but is offered by Canonical and the 51 | ## respective vendors as a service to Ubuntu users. 52 | # deb http://archive.canonical.com/ubuntu trusty partner 53 | # deb-src http://archive.canonical.com/ubuntu trusty partner 54 | 55 | deb http://security.ubuntu.com/ubuntu trusty-security main 56 | deb-src http://security.ubuntu.com/ubuntu trusty-security main 57 | deb http://security.ubuntu.com/ubuntu trusty-security universe 58 | deb-src http://security.ubuntu.com/ubuntu trusty-security universe 59 | # deb http://security.ubuntu.com/ubuntu trusty-security multiverse 60 | # deb-src http://security.ubuntu.com/ubuntu trusty-security multiverse 61 | -------------------------------------------------------------------------------- /puppet/modules/cssmin/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class cssmin { 2 | package {'cssmin': 3 | ensure => installed, 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /puppet/modules/fabric/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Ensure Fabric is installed. 3 | # 4 | # Fabric is used for app deployment. 5 | # 6 | class fabric { 7 | package { 'fabric': 8 | ensure => 'present', 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /puppet/modules/ferm/LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /puppet/modules/ferm/README: -------------------------------------------------------------------------------- 1 | 2 | Overview 3 | ======== 4 | 5 | This puppet module manages ferm and its rules. 6 | 7 | Variables 8 | ========= 9 | 10 | 11 | Classes 12 | ======= 13 | 14 | ferm 15 | ---- 16 | 17 | The ferm class performs all steps needed to the use of ferm such as package 18 | installation and configuration. Specific rules can be added later with 19 | ferm::rule or specific classes. 20 | 21 | 22 | Defines 23 | ======= 24 | 25 | ferm::rule 26 | ---------- 27 | 28 | Add a rule to the ferm rules.d directory 29 | 30 | Variables used : 31 | $host = false, 32 | $table="filter", 33 | $chain="INPUT", 34 | $rule, 35 | $description="", 36 | $prio="00", 37 | $notarule=false 38 | 39 | ferm::hook 40 | ---------- 41 | 42 | Add a hook to the ferm conf.d directory. 43 | 44 | Example: 45 | 46 | ferm::hook { 'conntrack_ftp': 47 | description => 'Module nf_conntrack_ftp pour proftpd', 48 | content_hook => 'modprobe nf_conntrack_ftp' 49 | } 50 | 51 | Licensing 52 | ========= 53 | 54 | This puppet module is licensed under the GPL version 3 or later. Redistribution 55 | and modification is encouraged. 56 | 57 | The GPL version 3 license text can be found in the "LICENSE" file accompanying 58 | this puppet module, or at the following URL: 59 | 60 | http://www.gnu.org/licenses/gpl-3.0.html 61 | -------------------------------------------------------------------------------- /puppet/modules/ferm/files/ferm.conf: -------------------------------------------------------------------------------- 1 | ## 2 | ## THIS FILE IS UNDER PUPPET CONTROL. DON'T EDIT IT HERE. 3 | ## 4 | 5 | # -*- shell-script -*- 6 | 7 | @include 'conf.d/'; 8 | 9 | domain (ip ip6) { 10 | table filter { 11 | 12 | chain INPUT { 13 | policy DROP; 14 | # connection tracking 15 | mod state state INVALID DROP; 16 | mod state state (ESTABLISHED RELATED) ACCEPT; 17 | 18 | # allow ping 19 | proto icmp ACCEPT; 20 | 21 | # allow local packet 22 | interface lo ACCEPT; 23 | } 24 | 25 | chain OUTPUT { 26 | policy ACCEPT; 27 | # connection tracking 28 | mod state state (ESTABLISHED RELATED) ACCEPT; 29 | } 30 | 31 | chain FORWARD { 32 | policy ACCEPT; 33 | # connection tracking 34 | mod state state INVALID DROP; 35 | mod state state (ESTABLISHED RELATED) ACCEPT; 36 | } 37 | } 38 | } 39 | 40 | @include 'rules.d/'; 41 | 42 | # vim:set et: 43 | -------------------------------------------------------------------------------- /puppet/modules/ferm/files/ferm.default: -------------------------------------------------------------------------------- 1 | # configuration for /etc/init.d/ferm 2 | 3 | # use iptables-restore for fast firewall initialization? 4 | FAST=yes 5 | 6 | # cache the output of ferm --lines in /var/cache/ferm? 7 | CACHE=yes 8 | 9 | # additional paramaters for ferm (like --def '$foo=bar') 10 | OPTIONS= 11 | 12 | # Enable ferm on bootup? 13 | ENABLED=yes 14 | 15 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/hook.pp: -------------------------------------------------------------------------------- 1 | define ferm::hook ( 2 | $description=undef, 3 | $content_hook=undef 4 | ) 5 | { 6 | file { "/etc/ferm/conf.d/hook_${name}": 7 | ensure => present, 8 | owner => root, 9 | group => root, 10 | mode => 0400, 11 | content => template('ferm/hook.erb'), 12 | notify => Service['ferm']; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class ferm { 2 | 3 | package { 'ferm': 4 | ensure => installed 5 | } 6 | 7 | file { 8 | '/etc/ferm/rules.d': 9 | ensure => directory, 10 | purge => true, 11 | owner => root, 12 | group => root, 13 | force => true, 14 | recurse => true, 15 | notify => Service['ferm'], 16 | require => Package['ferm']; 17 | '/etc/ferm': 18 | ensure => directory, 19 | owner => root, 20 | group => root, 21 | mode => 0755; 22 | '/etc/ferm/conf.d': 23 | ensure => directory, 24 | owner => root, 25 | group => root, 26 | require => Package['ferm']; 27 | '/etc/default/ferm': 28 | source => 'puppet:///modules/ferm/ferm.default', 29 | owner => root, 30 | group => root, 31 | require => Package['ferm'], 32 | notify => Service['ferm']; 33 | '/etc/ferm/ferm.conf': 34 | source => 'puppet:///modules/ferm/ferm.conf', 35 | owner => root, 36 | group => root, 37 | require => Package['ferm'], 38 | mode => 0400, 39 | notify => Service['ferm']; 40 | '/etc/ferm/conf.d/defs.conf': 41 | content => template('ferm/defs.conf.erb'), 42 | owner => root, 43 | group => root, 44 | require => Package['ferm'], 45 | mode => 0400, 46 | notify => Service['ferm']; 47 | } 48 | 49 | service { 'ferm': 50 | ensure => running, 51 | require => Package['ferm'] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/rule.pp: -------------------------------------------------------------------------------- 1 | define ferm::rule 2 | ( 3 | $host=false, 4 | $table='filter', 5 | $chain='INPUT', 6 | $rules, 7 | $description='', 8 | $prio='00', 9 | $notarule=false 10 | ) 11 | { 12 | file { "/etc/ferm/rules.d/${prio}_${name}": 13 | ensure => present, 14 | owner => root, 15 | group => root, 16 | mode => 0400, 17 | content => template('ferm/ferm-rule.erb'), 18 | notify => Service['ferm']; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/rule/custom.pp: -------------------------------------------------------------------------------- 1 | define ferm::rule::custom 2 | ( 3 | $content = '', 4 | $prio = "50", 5 | ) 6 | { 7 | file { "/etc/ferm/rules.d/${prio}_${name}": 8 | ensure => present, 9 | owner => root, 10 | group => root, 11 | mode => 0400, 12 | content => $content, 13 | notify => Service['ferm']; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/rules/bittorrent.pp: -------------------------------------------------------------------------------- 1 | class ferm::bittorrent { 2 | @ferm::rule { "bittorrent": 3 | description => "Allow smtp access", 4 | rule => "&SERVICE( ( tcp udp), 51413 )" 5 | } 6 | @ferm::rule { "bttrack": 7 | description => "Allow bttrack access", 8 | rule => "&SERVICE( tcp, 6969 )" 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/rules/dns.pp: -------------------------------------------------------------------------------- 1 | class ferm::dns { 2 | @ferm::rule { "dns": 3 | description => "Allow dns access", 4 | rule => "&SERVICE( (tcp udp), domain)" 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/rules/ftp.pp: -------------------------------------------------------------------------------- 1 | class ferm::ftp { 2 | @ferm::rule { "dsa-ftp": 3 | description => "Allow ftp access", 4 | rule => "&SERVICE(tcp, 21)" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/rules/git.pp: -------------------------------------------------------------------------------- 1 | class ferm::git { 2 | @ferm::rule { "git": 3 | description => "Allow git access", 4 | rule => "&SERVICE(tcp, 9418)" 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/rules/imaps.pp: -------------------------------------------------------------------------------- 1 | class ferm::imaps { 2 | @ferm::rule { "imaps": 3 | description => "Allow imaps access", 4 | rule => "&SERVICE( tcp, imaps)" 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/rules/jabber.pp: -------------------------------------------------------------------------------- 1 | class ferm::jabber { 2 | @ferm::rule { "jabber": 3 | description => "Allow jabber access", 4 | rule => "&SERVICE(tcp, (5222 5223 5269 5280))" 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/rules/rsync.pp: -------------------------------------------------------------------------------- 1 | class ferm::rsync { 2 | @ferm::rule { "dsa-rsync": 3 | description => "Allow rsync access", 4 | rule => "&SERVICE(tcp, 873)" 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/rules/smtp.pp: -------------------------------------------------------------------------------- 1 | class ferm::smtp { 2 | @ferm::rule { "smtp": 3 | description => "Allow smtp access", 4 | rule => "&SERVICE(tcp, smtp)" 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /puppet/modules/ferm/manifests/rules/www.pp: -------------------------------------------------------------------------------- 1 | class ferm::www { 2 | @ferm::rule { "www": 3 | description => "Allow www access", 4 | rule => "&SERVICE(tcp, (http https))" 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /puppet/modules/ferm/templates/defs.conf.erb: -------------------------------------------------------------------------------- 1 | ## 2 | ## THIS FILE IS UNDER PUPPET CONTROL. DON'T EDIT IT HERE. 3 | ## 4 | 5 | @def &SERVICE($proto, $port) = { 6 | proto $proto mod state state (NEW) dport $port ACCEPT; 7 | } 8 | 9 | @def &SERVICE_RANGE($proto, $port, $srange) = { 10 | proto $proto mod state state (NEW) dport $port @subchain "$port" { saddr @ipfilter($srange) ACCEPT; }" 11 | } 12 | 13 | @def &TCP_UDP_SERVICE($port) = { 14 | proto (tcp udp) mod state state (NEW) dport $port ACCEPT; 15 | } 16 | 17 | @def &TCP_UDP_SERVICE_RANGE($port, $srange) = { 18 | proto (tcp udp) mod state state (NEW) dport $port @subchain "$port" { saddr @ipfilter($srange) ACCEPT; }" 19 | } 20 | -------------------------------------------------------------------------------- /puppet/modules/ferm/templates/ferm-rule.erb: -------------------------------------------------------------------------------- 1 | ## 2 | ## THIS FILE IS UNDER PUPPET CONTROL. DON'T EDIT IT HERE. 3 | ## <%= description %> 4 | ## 5 | 6 | table <%= table %> { 7 | chain <%= chain %> { 8 | <% if host != false %>saddr <%= host %> { 9 | <% end -%> 10 | <% rules.each do |rule| %> <%= rule %>; 11 | <% end -%> 12 | <% if host != false %>}<% end -%> 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /puppet/modules/ferm/templates/hook.erb: -------------------------------------------------------------------------------- 1 | ## 2 | ## THIS FILE IS UNDER PUPPET CONTROL. DON'T EDIT IT HERE. 3 | ## <%= description %> 4 | ## 5 | 6 | @hook pre "<%= content_hook %>"; 7 | -------------------------------------------------------------------------------- /puppet/modules/git/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Ensure git is installed. 3 | # 4 | class git { 5 | package { 'git': 6 | ensure => installed, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /puppet/modules/newrelic/files/newrelic.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dansimau/flask-bootstrap/4a026d58f8722a008d22ce86bec3531850f64ba9/puppet/modules/newrelic/files/newrelic.gpg -------------------------------------------------------------------------------- /puppet/modules/newrelic/files/newrelic.list: -------------------------------------------------------------------------------- 1 | deb http://apt.newrelic.com/debian/ newrelic non-free 2 | -------------------------------------------------------------------------------- /puppet/modules/newrelic/manifests/servermon.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Ensure New Relic server monitoring daemon is installed and up-to-date. 3 | # 4 | class newrelic::servermon( $key = $::newrelic_key ) { 5 | # Add newrelic apt repo 6 | file { '/etc/apt/sources.list.d/newrelic.list': 7 | ensure => present, 8 | owner => root, 9 | group => root, 10 | mode => '0644', 11 | source => 'puppet:///modules/newrelic/newrelic.list' 12 | } 13 | file { '/etc/apt/trusted.gpg.d/newrelic.gpg': 14 | ensure => present, 15 | owner => root, 16 | group => root, 17 | mode => '0644', 18 | source => 'puppet:///modules/newrelic/newrelic.gpg', 19 | subscribe => File['/etc/apt/sources.list.d/newrelic.list'], 20 | } 21 | exec { 'newrelic-apt-refresh': 22 | command => '/usr/bin/apt-get update', 23 | subscribe => File['/etc/apt/trusted.gpg.d/newrelic.gpg'], 24 | refreshonly => true, 25 | } 26 | package { 'newrelic-sysmond': 27 | ensure => latest, 28 | subscribe => Exec['newrelic-apt-refresh'] 29 | } 30 | # Configuration 31 | file { '/etc/newrelic/nrsysmond.cfg': 32 | ensure => present, 33 | owner => root, 34 | group => newrelic, 35 | mode => '0640', 36 | content => template('newrelic/nrsysmond.cfg.tpl'), 37 | require => Package['newrelic-sysmond'], 38 | } 39 | service { 'newrelic-sysmond': 40 | ensure => running, 41 | subscribe => File['/etc/newrelic/nrsysmond.cfg'] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /puppet/modules/newrelic/templates/nrsysmond.cfg.tpl: -------------------------------------------------------------------------------- 1 | # 2 | # New Relic Server Monitor configuration file. 3 | # 4 | # Lines that begin with a # are comment lines and are ignored by the server 5 | # monitor. For those options that have command line equivalents, if the 6 | # option is specified on the command line it will over-ride any value set 7 | # in this file. 8 | # 9 | 10 | # 11 | # Option : license_key 12 | # Value : 40-character hexadecimal string provided by New Relic. This is 13 | # required in order for the server monitor to start. 14 | # Default: none 15 | # 16 | license_key=<%= key %> 17 | 18 | # 19 | # Option : loglevel 20 | # Value : Level of detail you want in the log file (as defined by the logfile 21 | # setting below. Valid values are (in increasing levels of verbosity): 22 | # error - show errors only 23 | # warning - show errors and warnings 24 | # info - show minimal additional information messages 25 | # verbose - show more detailed information messages 26 | # debug - show debug messages 27 | # verbosedebug - show very detailed debug messages 28 | # Default: error 29 | # Note : Can also be set with the -d command line option. 30 | # 31 | loglevel=info 32 | 33 | # 34 | # Option : logfile 35 | # Value : Name of the file where the server monitor will store it's log 36 | # messages. The amount of detail stored in this file is controlled 37 | # by the loglevel option (above). 38 | # Default: none. However it is highly recommended you set a value for this. 39 | # Note : Can also be set with the -l command line option. 40 | # 41 | logfile=/var/log/newrelic/nrsysmond.log 42 | 43 | # 44 | # Option : proxy 45 | # Value : The name and optional login credentials of the proxy server to use 46 | # for all communication with the New Relic collector. In its simplest 47 | # form this setting is just a hostname[:port] setting. The default 48 | # port if none is specified is 1080. If your proxy requires a user 49 | # name, use the syntax user@host[:port]. If it also requires a 50 | # password use the format user:password@host[:port]. For example: 51 | # fred:secret@proxy.mydomain.com:8181 52 | # Default: none (use a direct connection) 53 | # 54 | #proxy= 55 | 56 | # 57 | # Option : ssl 58 | # Value : Whether or not to use the Secure Sockets Layer (SSL) for all 59 | # communication with the New Relic collector. Possible values are 60 | # true/on or false/off. In certain rare cases you may need to modify 61 | # the SSL certificates settings below. 62 | # Default: false 63 | # 64 | #ssl=false 65 | 66 | # 67 | # Option : ssl_ca_bundle 68 | # Value : The name of a PEM-encoded Certificate Authority (CA) bundle to use 69 | # for SSL connections. This very rarely needs to be set. The monitor 70 | # will attempt to find the bundle in the most common locations. If 71 | # you need to use SSL and the monitor is unable to locate a CA bundle 72 | # then either set this value or the ssl_ca_path option below. 73 | # Default: /etc/ssl/certs/ca-certificates.crt or 74 | # /etc/pki/tls/certs/ca-bundle.crt 75 | # Note : Can also be set with the -b command line option. 76 | # 77 | #ssl_ca_bundle=/path/to/your/bundle.crt 78 | 79 | # 80 | # Option : ssl_ca_path 81 | # Value : If your SSL installation does not use CA bundles, but rather has a 82 | # directory with PEM-encoded Certificate Authority files, set this 83 | # option to the name of the directory that contains all the CA files. 84 | # Default: /etc/ssl/certs 85 | # Note : Can also be set with the -S command line option. 86 | # 87 | #ssl_ca_path=/etc/ssl/certs 88 | 89 | # 90 | # Option : pidfile 91 | # Value : Name of a file where the server monitoring daemon will store it's 92 | # process ID (PID). This is used by the startup and shutdown script 93 | # to determine if the monitor is already running, and to start it up 94 | # or shut it down. 95 | # Default: /tmp/nrsysmond.pid 96 | # Note : Can also be set with the -p command line option. 97 | # 98 | #pidfile=/var/run/newrelic/nrsysmond.pid 99 | 100 | # 101 | # Option : collector_host 102 | # Value : The name of the New Relic collector to connect to. This should only 103 | # ever be changed on advise from a New Relic support staff member. 104 | # The format is host[:port]. Using a port number of 0 means the default 105 | # port, which is 80 (if not using the ssl option - see below) or 443 106 | # if SSL is enabled. If the port is omitted the default value is used. 107 | # Default: collector.newrelic.com 108 | # 109 | #collector_host=collector.newrelic.com 110 | 111 | # 112 | # Option : timeout 113 | # Value : How long the monitor should wait to contact the collector host. If 114 | # the connection cannot be established in this period of time, the 115 | # monitor will progressively back off in 15-second increments, up to 116 | # a maximum of 300 seconds. Once the initial connection has been 117 | # established, this value is reset back to the value specified here 118 | # (or the default). This then sets the maximum time to wait for 119 | # a connection to the collector to report data. There is no back-off 120 | # once the original connection has been made. The value is in seconds. 121 | # Default: 30 122 | # 123 | #timeout=30 124 | 125 | -------------------------------------------------------------------------------- /puppet/modules/nginx/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Nginx 3 | # 4 | class nginx { 5 | include nginx::ssl 6 | 7 | package { 'nginx': 8 | ensure => installed, 9 | } 10 | # Disable default nginx site 11 | file { '/etc/nginx/sites-enabled/default': 12 | ensure => absent, 13 | before => Service[nginx] 14 | } 15 | service { 'nginx': 16 | ensure => running, 17 | require => Exec['nginx:ssl::generate-dhparams'], 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /puppet/modules/nginx/manifests/site.pp: -------------------------------------------------------------------------------- 1 | # 2 | # For defining nginx sites. 3 | # 4 | define nginx::site( 5 | $auth = false, 6 | $app_path = $::app_path, 7 | $ssl_cert = '/etc/ssl/certs/ssl-cert-snakeoil.pem', 8 | $ssl_cert_key = '/etc/ssl/private/ssl-cert-snakeoil.key', 9 | $redirects = false) 10 | { 11 | file { "/etc/nginx/sites-available/${name}": 12 | ensure => present, 13 | owner => root, 14 | group => root, 15 | mode => '0644', 16 | content => template("nginx/${name}.erb"), 17 | require => Package['nginx'], 18 | notify => Service['nginx'] 19 | } 20 | 21 | file { "/etc/nginx/sites-enabled/${name}": 22 | ensure => link, 23 | target => "/etc/nginx/sites-available/${name}", 24 | notify => Service['nginx'] 25 | } 26 | 27 | if $auth { 28 | file {"/etc/nginx/htpasswd-${name}": 29 | ensure => present, 30 | owner => root, 31 | group => root, 32 | mode => '0644', 33 | content => template('nginx/auth.erb'), 34 | require => Package['nginx'], 35 | } 36 | } 37 | 38 | # Ensure SSL key is secured 39 | file { $ssl_cert_key: 40 | owner => root, 41 | group => 'ssl-cert', 42 | mode => '0640', 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /puppet/modules/nginx/manifests/ssl.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Configure nginx for secure SSL by default. 3 | # 4 | class nginx::ssl { 5 | # Generate secure Diffie-Hellman params for PFS 6 | exec {'nginx:ssl::generate-dhparams': 7 | command => '/usr/bin/openssl dhparam -out /etc/ssl/private/dhparam.pem 2048 && chmod 0600 /etc/ssl/private/dhparam.pem &', 8 | creates => '/etc/ssl/private/dhparam.pem', 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /puppet/modules/nginx/templates/app.erb: -------------------------------------------------------------------------------- 1 | upstream <%= @name %>_uwsgi { 2 | server unix:/tmp/<%= @name %>.uwsgi.sock; 3 | } 4 | 5 | server { 6 | listen 80; 7 | rewrite ^/(.*) https://$host$request_uri? permanent; 8 | } 9 | 10 | server { 11 | listen 443 ssl; 12 | 13 | ssl_certificate <%= @ssl_cert %>; 14 | ssl_certificate_key <%= @ssl_cert_key %>; 15 | 16 | ssl_session_timeout 5m; 17 | ssl_session_cache shared:SSL:50m; 18 | ssl_dhparam /etc/ssl/private/dhparam.pem; 19 | 20 | ssl_protocols TLSv1.1 TLSv1.2; 21 | ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK'; 22 | ssl_prefer_server_ciphers on; 23 | 24 | ssl_stapling on; 25 | ssl_stapling_verify on; 26 | 27 | <%= scope.function_template(['nginx/uwsgi.erb']) %> 28 | 29 | } 30 | -------------------------------------------------------------------------------- /puppet/modules/nginx/templates/redirects.erb: -------------------------------------------------------------------------------- 1 | <% redirects.each do |source, dest| -%> 2 | server { 3 | listen 80; 4 | server_name <%= source %>; 5 | rewrite ^/(.*) http://<%= dest %>/$1 permanent; 6 | } 7 | <% end -%> 8 | -------------------------------------------------------------------------------- /puppet/modules/nginx/templates/uwsgi.erb: -------------------------------------------------------------------------------- 1 | client_max_body_size 4G; 2 | server_name _; 3 | 4 | root <%= @app_path %>; 5 | 6 | keepalive_timeout 5; 7 | 8 | # Enable gzip 9 | gzip on; 10 | gzip_vary on; 11 | gzip_disable "MSIE [1-6].(?!.*SV1)"; 12 | gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 13 | 14 | <%- if @auth -%> 15 | # password-protect site 16 | auth_basic "Restricted"; 17 | auth_basic_user_file htpasswd-<%= @name %>; 18 | <%- end -%> 19 | 20 | location /static { 21 | alias <%= @app_path %>/app/static; 22 | 23 | expires max; 24 | add_header Pragma cache; 25 | add_header cache-control public; 26 | } 27 | 28 | location / { 29 | uwsgi_pass <%= @name %>_uwsgi; 30 | include uwsgi_params; 31 | 32 | # Ensure HTTPS is set when it's supposed to be 33 | uwsgi_param HTTPS $https; 34 | } 35 | -------------------------------------------------------------------------------- /puppet/modules/openssl/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Ensures OpenSSL libraries are kept up-to-date. 3 | # 4 | # NOTE: This does not automatically restart services that rely on openssl. 5 | # Services still need to be restarted manually to ensure the latest version of 6 | # openssl is loaded. 7 | # 8 | class openssl { 9 | # Ensure openssl is latest. 10 | package { ['openssl', 'libssl1.0.0']: 11 | ensure => latest, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /puppet/modules/postfix/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class postfix { 2 | package { 'postfix': 3 | ensure => installed, 4 | } 5 | service { 'postfix': 6 | ensure => running, 7 | require => Package[postfix] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /puppet/modules/postgresql/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Ensure PostgreSQL is installed and running. 3 | # 4 | class postgresql { 5 | # postgresql-dev required for Python's psycopg2 6 | package { [ 'postgresql', 'postgresql-server-dev-all' ]: 7 | ensure => 'installed', 8 | } 9 | service { 'postgresql': 10 | ensure => running, 11 | require => Package[postgresql], 12 | } 13 | } 14 | 15 | # 16 | # Mechanism to create roles. 17 | # 18 | define postgresql::role( 19 | $ensure = 'present', 20 | $createdb = false, 21 | $createrole = false, 22 | $superuser = false, 23 | $grant = false 24 | ) { 25 | case $ensure { 26 | present: { 27 | 28 | if $createdb { $createdb_param = '-d' } else { $createdb_param = '-D' } 29 | if $createrole { $createrole_param = '-r' } else { $createrole_param = '-R' } 30 | if $superuser { $superuser_param = '-s' } else { $superuser_param = '-S' } 31 | 32 | exec { "postgresql::role::${name}::create": 33 | command => "/usr/bin/createuser ${createdb_param} ${createrole_param} ${superuser_param} \"${name}\"", 34 | user => postgres, 35 | unless => "/usr/bin/psql -tAc \"SELECT 1 FROM pg_roles WHERE rolname = \'${name}\'\" |grep -q 1", 36 | require => Service[postgresql], 37 | } 38 | if ( $grant != false ) { 39 | exec { "/usr/bin/psql -c \'GRANT \"${grant}\" TO \"${name}\";\'": 40 | user => postgres, 41 | unless => "/usr/bin/psql -tAc \"SELECT 1 FROM pg_authid p INNER JOIN pg_auth_members am ON (p.oid = am.roleid) INNER JOIN pg_authid m ON (am.member = m.oid) INNER JOIN pg_authid g ON (am.grantor = g.oid) WHERE p.rolname = '${grant}' AND m.rolname = '${name}';\" |grep -q 1", 42 | require => [ 43 | Service[postgresql], 44 | Exec["postgresql::role::${name}::create"], 45 | ], 46 | } 47 | } 48 | } 49 | absent: { 50 | exec { "postgresql::role::${name}::delete": 51 | command => "/usr/bin/dropuser \"${name}\"", 52 | user => postgres, 53 | onlyif => "/usr/bin/psql -tAc \"SELECT 1 FROM pg_roles WHERE rolname = \'${name}\'\" |grep -q 1", 54 | require => Service[postgresql], 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /puppet/modules/puppet/manifests/sudoers.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Ensures the deploy user can run Puppet via sudo. 3 | # 4 | class puppet::sudoers ( 5 | $app_path = $::app_path, 6 | $deploy_user = $::deploy_user 7 | ) { 8 | file { '/etc/sudoers.d/puppet': 9 | owner => root, 10 | group => root, 11 | mode => '0440', 12 | content => "${deploy_user} ALL = (root) NOPASSWD : /usr/bin/puppet apply --modulepath='${app_path}/modules' '${app_path}/puppet/manifests/site.pp'\n", 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /puppet/modules/python/manifests/packages.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Python packages required by the app. 3 | # 4 | class python::packages { 5 | package { [ 'python-virtualenv', 'python-dev', ]: 6 | ensure => installed 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /puppet/modules/rsyslog/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Ensure syslog is installed and running. 3 | # 4 | # Also provides the rsyslog Puppet service resource primarily so it can be 5 | # notified about system timezone changes (otherwise syslog timestamps are 6 | # wrong). 7 | # 8 | class rsyslog { 9 | package { 'rsyslog': 10 | ensure => installed, 11 | } 12 | service { 'rsyslog': 13 | ensure => running, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /puppet/modules/sysctl/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class sysctl { 2 | # This service is always stopped, but needs to be refreshed when a value 3 | # changes. 4 | exec { 'sysctl::reload': 5 | command => '/etc/init.d/procps start', 6 | refreshonly => true, 7 | } 8 | } 9 | 10 | define sysctl::parameter ( $value ) { 11 | file { "/etc/sysctl.d/60-${name}.conf": 12 | ensure => present, 13 | content => "${name} = ${value}\n", 14 | notify => Exec['sysctl::reload'], 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /puppet/modules/uglifyjs/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class uglifyjs { 2 | package { 'node-uglify': 3 | ensure => installed, 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /puppet/modules/users/manifests/deploy.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Set up deploy user. 3 | # 4 | class users::deploy($deploy_user = $::deploy_user) { 5 | 6 | user { $deploy_user: 7 | ensure => present, 8 | comment => 'App deployment user', 9 | managehome => true, 10 | shell => '/bin/bash', 11 | } 12 | 13 | # Grant sudo access 14 | file { "/etc/sudoers.d/${deploy_user}": 15 | owner => root, 16 | group => root, 17 | mode => '0440', 18 | content => "${deploy_user} ALL = (root) NOPASSWD : ALL\n" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /puppet/modules/uwsgi/files/etc/init/uwsgi.conf: -------------------------------------------------------------------------------- 1 | # Emperor uWSGI script 2 | 3 | description "uWSGI Emperor" 4 | start on runlevel [2345] 5 | stop on runlevel [06] 6 | 7 | exec /usr/local/bin/uwsgi --include /etc/uwsgi/uwsgi.ini 8 | -------------------------------------------------------------------------------- /puppet/modules/uwsgi/files/etc/logrotate.d/uwsgi: -------------------------------------------------------------------------------- 1 | "/var/log/uwsgi/*.log" "/var/log/uwsgi/*/*.log" { 2 | copytruncate 3 | daily 4 | rotate 5 5 | compress 6 | delaycompress 7 | missingok 8 | notifempty 9 | } 10 | -------------------------------------------------------------------------------- /puppet/modules/uwsgi/files/etc/uwsgi/uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | emperor = /etc/uwsgi/apps-enabled 3 | logto = /var/log/uwsgi/emperor.log 4 | uid = www-data 5 | gid = www-data 6 | -------------------------------------------------------------------------------- /puppet/modules/uwsgi/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Latest uWSGI, installed using pip. 3 | # 4 | class uwsgi ($ensure = 'running') { 5 | 6 | include uwsgi::tuning 7 | 8 | package { 'uwsgi': 9 | ensure => installed, 10 | name => 'uWSGI', 11 | provider => 'pip', 12 | } 13 | 14 | file { ['/etc/uwsgi', '/etc/uwsgi/apps-available', '/etc/uwsgi/apps-enabled']: 15 | ensure => directory, 16 | owner => root, 17 | group => root, 18 | mode => '0755', 19 | require => Package['uwsgi'], 20 | } 21 | 22 | file { '/etc/init/uwsgi.conf': 23 | ensure => file, 24 | owner => root, 25 | group => root, 26 | mode => '0644', 27 | source => 'puppet:///modules/uwsgi/etc/init/uwsgi.conf', 28 | require => Package['uwsgi'], 29 | notify => Service['uwsgi'], 30 | } 31 | 32 | file { '/etc/uwsgi/uwsgi.ini': 33 | ensure => file, 34 | owner => root, 35 | group => root, 36 | mode => '0644', 37 | source => 'puppet:///modules/uwsgi/etc/uwsgi/uwsgi.ini', 38 | require => File['/etc/uwsgi'], 39 | notify => Service['uwsgi'], 40 | } 41 | 42 | file { '/etc/logrotate.d/uwsgi': 43 | ensure => file, 44 | owner => root, 45 | group => root, 46 | mode => '0644', 47 | source => 'puppet:///modules/uwsgi/etc/logrotate.d/uwsgi', 48 | } 49 | 50 | file { '/etc/init.d/uwsgi': 51 | ensure => link, 52 | target => '/lib/init/upstart-job', 53 | owner => root, 54 | group => root, 55 | mode => '0644', 56 | } 57 | 58 | file { '/var/log/uwsgi': 59 | ensure => directory, 60 | owner => 'www-data', 61 | group => 'www-data', 62 | require => Package['uwsgi'], 63 | } 64 | 65 | service { 'uwsgi': 66 | ensure => $ensure, 67 | provider => upstart, 68 | enable => true, 69 | require => File['/etc/uwsgi/uwsgi.ini'], 70 | } 71 | } 72 | 73 | # 74 | # For defining uWSGI app instances. 75 | # 76 | define uwsgi::app($port = 8000, $uwsgi_options = []) { 77 | 78 | $settings = $::environment ? { 79 | undef => 'settings.dev', 80 | default => "settings.${::environment}", 81 | } 82 | 83 | $venv = $::venv ? { 84 | undef => '/srv/www/app/venv', 85 | default => $::venv, 86 | } 87 | 88 | file { "/etc/uwsgi/apps-available/${name}.ini": 89 | ensure => present, 90 | content => template('uwsgi/app.ini.erb'), 91 | notify => Service['uwsgi'], 92 | } 93 | 94 | file { "/var/log/uwsgi/${name}.log": 95 | ensure => present, 96 | } 97 | 98 | file { "/etc/uwsgi/apps-enabled/${name}.ini": 99 | ensure => link, 100 | target => "/etc/uwsgi/apps-available/${name}.ini", 101 | notify => Service['uwsgi'], 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /puppet/modules/uwsgi/manifests/tuning.pp: -------------------------------------------------------------------------------- 1 | # 2 | # uWSGI system tuning 3 | # 4 | class uwsgi::tuning { 5 | # Increase system max socket queue 6 | sysctl::parameter { 'net.core.somaxconn': 7 | value => 2048, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /puppet/modules/uwsgi/templates/app.ini.erb: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | socket = /tmp/<%= @name %>.uwsgi.sock 3 | chdir = /srv/www/app 4 | uid = www-data 5 | logto = /var/log/uwsgi/<%= @name %>.log 6 | reload-mercy = 30 7 | single-interpreter = true 8 | enable-threads = true 9 | virtualenv = <%= @venv %> 10 | listen = 2048 11 | 12 | <%- if @uwsgi_options -%> 13 | # Custom options 14 | <%- @uwsgi_options.each do | key, value | -%> 15 | <%- if value.kind_of?(Array) -%> 16 | <%- value.each do | value | -%> 17 | <%= key %>=<%= value %> 18 | <%- end -%> 19 | <%- else -%> 20 | <%= key %>=<%= value %> 21 | <%- end -%> 22 | <%- end -%> 23 | <%- end -%> 24 | -------------------------------------------------------------------------------- /puppet/modules/vagrant/files/pip/pip.conf: -------------------------------------------------------------------------------- 1 | [global] 2 | download_cache = /vagrant/.cache/pip 3 | -------------------------------------------------------------------------------- /puppet/modules/vagrant/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # 2 | # For developement environment. 3 | # 4 | class vagrant { 5 | 6 | # Set up app directory 7 | file { '/srv/www': 8 | ensure => directory, 9 | } 10 | file { '/srv/www/app': 11 | ensure => link, 12 | target => '/vagrant', 13 | } 14 | 15 | # Active the app venv on login 16 | line { 'line-venv-activate': 17 | ensure => present, 18 | file => '/home/vagrant/.bashrc', 19 | line => 'cd /vagrant && source .venv/bin/activate', 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /puppet/modules/vagrant/manifests/pip.pp: -------------------------------------------------------------------------------- 1 | class vagrant::pip { 2 | # Create pip.conf for vagrant 3 | file { '/home/vagrant/.pip': 4 | source => 'puppet:///modules/vagrant/pip', 5 | recurse => remote, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /puppet/modules/vagrant/manifests/postgresql.pp: -------------------------------------------------------------------------------- 1 | # 2 | # Create vagrant postgresql user for development. 3 | # 4 | class vagrant::postgresql { 5 | postgresql::role { 'vagrant': 6 | ensure => present, 7 | createdb => true, 8 | createrole => true, 9 | superuser => true, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Fabric 2 | Flask 3 | Flask-Script 4 | Flask-SQLAlchemy 5 | alembic 6 | git+git://github.com/tobiasandtobias/flask-assetslite.git@dev-cache#egg=Flask_AssetsLite 7 | psycopg2 8 | nose 9 | --------------------------------------------------------------------------------