├── LICENSE ├── Makefile ├── README.md ├── nginxify.py └── usr.sh /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nate Ferrero 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. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | groupadd -f nginxify 3 | make _install 4 | 5 | install-mac-os-x: 6 | rm -rf /bin/nginxify 7 | cp nginxify.py /bin/nginxify 8 | mkdir -p /var/log/nginx 9 | @echo "Installed! Use: sudo python /bin/nginxify" 10 | 11 | _install: 12 | rm -rf /bin/nginxify 13 | cp nginxify.py /bin/nginxify 14 | chown root:nginxify /bin/nginxify 15 | chmod g+x /bin/nginxify 16 | chmod +x /bin/nginxify 17 | 18 | rm -rf /usr/bin/nginxify 19 | cp usr.sh /usr/bin/nginxify 20 | chown root:nginxify /usr/bin/nginxify 21 | chmod g+x /usr/bin/nginxify 22 | chmod +x /usr/bin/nginxify 23 | 24 | mkdir -p /etc/nginx/sites-enabled 25 | 26 | @echo "Successfully installed! Try: nginxify" 27 | 28 | uninstall: 29 | rm -rf /bin/nginxify 30 | rm -rf /usr/bin/nginxify 31 | 32 | @echo "Successfully uninstalled!" 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nginxify 2 | ======== 3 | 4 | Nginxify parses JSON configurations of NGINX config files and creates the appropriate representations in configuration. 5 | 6 | ## NGINX Config File Location 7 | 8 | The default generated configuration file is located at `/etc/nginx/sites-enabled/nginxify.conf`, if this is not included by default, for example on Mac OS X, you will need to include this file in your `nginx.conf` file using the [include](http://nginx.org/en/docs/ngx_core_module.html#include) directive. 9 | 10 | ## Usage 11 | 12 | Create a file called `.nginx` in your user home directory (`/home/username/.nginx` or `/Users/username/.nginx` on Mac OS X), and populate according to the following JSON. Each user can have their own `.nginx` file, and they will be merged in the order returned by the `glob` package in Python. 13 | 14 | Be sure to [install NGINX](http://wiki.nginx.org/Install) before continuing. 15 | 16 | **Important:** All paths are relative to the home directory containing the `.nginx` file, as such do not begin paths with a `/` or `./` - all projects should be under your home directory. You can use projects that are not within your home directory, but those must be proxied to a local port. 17 | 18 | ```js 19 | { 20 | "my.domain.com": { 21 | "paths": { 22 | "/": "projects/my_domain/public", // this is a directory relative to your home directory, 23 | "/testapp": 3000 // This will proxy all requests to a server running on port 3000 24 | } 25 | }, 26 | 27 | "other.domain.com": { 28 | "paths": { 29 | "/foo": "another/folder/path" 30 | } 31 | }, 32 | 33 | "another.domain.com": { 34 | "paths": { 35 | "/": ["sphp", "path/to/sphp-project"] 36 | } 37 | }, 38 | 39 | "php.domain.com": { 40 | "paths": { 41 | "/": ["php", "path/to/php-project"] 42 | } 43 | } 44 | } 45 | ``` 46 | #### Static 47 | 48 | Static site path config has a string path relative to your home folder. 49 | 50 | #### Proxy Pass 51 | 52 | Proxy pass to a port with an integer port number as the path config. 53 | 54 | #### PHP 55 | 56 | PHP using [PHP-FPM](http://php-fpm.org/) is supported with the path config `["php", "path/to/project"]`. 57 | 58 | **Important:** You must configure `php-fpm.conf` to listen on a unix socket with the following line: `listen = /var/run/php5-fpm.sock`. 59 | 60 | #### SimplifiedPHP 61 | 62 | [SimplifiedPHP](https://github.com/NateFerrero/simplified-php) using [PHP-FPM](http://php-fpm.org/) is supported with the path config `["sphp", "path/to/project"]`. 63 | 64 | **Important:** You must configure `php-fpm.conf` to listen on a unix socket with the following line: `listen = /var/run/php5-fpm.sock`. 65 | 66 | **Important:** You must install the `simplified-php` project at `/usr/share/simplified-php`, this can be confirmed by running `cat /usr/share/simplified-php/simplified.php` - if the file is displayed, you should be good to go. 67 | 68 | ## Running Nginxify 69 | 70 | Every time you change your `.nginx` file, you must run Nginxify to update the generated NGINX configuration file. 71 | 72 | #### Linux 73 | 74 | On Linux, when you have completed editing the configuration file, and after nginxify has been installed (see below), run the `nginxify` command in a shell. 75 | 76 | #### Mac OS X 77 | 78 | On Mac OS X, run `sudo python /bin/nginxify && sudo nginx -s reload` to regenerate the configuration file. 79 | 80 | ## Installation - Mac OS X 81 | 82 | ```bash 83 | git clone git@github.com:NateFerrero/nginxify.git 84 | cd nginxify 85 | sudo make install-mac-os-x 86 | ``` 87 | 88 | For PHP, be sure to fix permissions for /var/run socket. 89 | 90 | ## Installation - Linux 91 | 92 | ```bash 93 | git clone git@github.com:NateFerrero/nginxify.git 94 | cd nginxify 95 | sudo make install 96 | ``` 97 | 98 | ## Allowing users to run `nginxify` without sudo 99 | 100 | Add the following to /etc/sudoers using `visudo`: 101 | 102 | ```bash 103 | # Nginxify 104 | %nginxify ALL=NOPASSWD: /bin/nginxify, /etc/init.d/nginx reload 105 | ``` 106 | 107 | Add a user to the nginxify group: 108 | 109 | ```bash 110 | sudo usermod -a -G nginxify the_username 111 | ``` 112 | 113 | Keep in mind that the user will have to open a new shell to use the new group permissions. 114 | -------------------------------------------------------------------------------- /nginxify.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python2 2 | # Author: Nate Ferrero 3 | import os 4 | import json 5 | import glob 6 | 7 | master = {} 8 | 9 | all = "" 10 | 11 | locblock = """ 12 | location {location} << 13 | {type} {value}; 14 | 15 | {config} 16 | >>""" 17 | 18 | error_block = """error_page {code} {value}; 19 | """ 20 | 21 | generic_block = """{key} {value}; 22 | """ 23 | 24 | proxy_upgrade_block = """ 25 | proxy_http_version 1.1; 26 | proxy_set_header Upgrade $http_upgrade; 27 | proxy_set_header Connection "Upgrade"; 28 | """ 29 | 30 | php_fpm_block = """ 31 | location ~ \.php$ << 32 | try_files $uri =404; 33 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 34 | # With php5-fpm: 35 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 36 | fastcgi_pass unix:/var/run/php5-fpm.sock; 37 | fastcgi_index index.php; 38 | include fastcgi_params; 39 | >>""" 40 | 41 | sphp_fpm_block = """ 42 | location ~ \.php$ << 43 | try_files $uri =404; 44 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 45 | # With php5-fpm: 46 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 47 | fastcgi_pass unix:/var/run/php5-fpm.sock; 48 | fastcgi_index index.php; 49 | include fastcgi_params; 50 | fastcgi_param PHP_VALUE "auto_prepend_file=/usr/share/simplified-php/simplified.php \n auto_append_file=/usr/share/simplified-php/simplified.php"; 51 | >>""" 52 | 53 | http_https = """ 54 | server << 55 | listen 80; 56 | server_name {name}; 57 | access_log /var/log/nginx/{user}-{xname}-insecure-access.log; 58 | error_log /var/log/nginx/{user}-{xname}-insecure-error.log; 59 | 60 | location / << 61 | rewrite ^ https://$http_host$request_uri? permanent; 62 | >> 63 | >>""" 64 | 65 | ssl_block = """ 66 | ssl_certificate {sslpath}{xname}.crt; 67 | ssl_certificate_key {sslpath}{xname}.key; 68 | ssl_client_certificate {sslpath}cacert.pem; 69 | ssl_verify_client off; 70 | ssl_ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS; 71 | ssl_prefer_server_ciphers on; 72 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 73 | ssl_session_cache shared:SSL:10m; 74 | """ 75 | 76 | block = """ 77 | server << 78 | listen {listen}; 79 | server_name {name}; 80 | {ssl} 81 | 82 | client_max_body_size 1024M; 83 | index index.html; 84 | allow all; 85 | autoindex on; 86 | 87 | gzip on; 88 | gzip_http_version 1.1; 89 | gzip_vary on; 90 | gzip_comp_level 6; 91 | gzip_proxied any; 92 | gzip_types text/plain text/css application/json application/javascript application/x-javascript text/javascript text/xml application/xml application/rss+xml application/atom+xml application/rdf+xml; 93 | 94 | access_log /var/log/nginx/{user}-{xname}-access.log; 95 | error_log /var/log/nginx/{user}-{xname}-error.log; 96 | {locations} 97 | >>""" 98 | 99 | if os.path.exists('/Users'): 100 | pattern = '/Users/*/.nginx' 101 | 102 | elif os.path.exists('/home'): 103 | pattern = '/home/*/.nginx' 104 | 105 | else: 106 | raise OSError('No suitable search path found') 107 | 108 | for path in glob.glob(pattern): 109 | print path 110 | with open(path) as file: 111 | user = os.path.basename(os.path.dirname(path)) 112 | print '[nginxify] Loading nginx config for user: {}'.format(user) 113 | print ' ' 114 | 115 | domains = json.load(file) 116 | alnum = 'abcdefghijklmnopqrstuvwxyz0123456789-_' 117 | alnum += alnum.upper() 118 | 119 | for (name, conf) in domains.items(): 120 | sub = {'name': name, 'user': user} 121 | sub['xname'] = ''.join([x if x in alnum else '-' for x in name]) 122 | sub['sslpath'] = os.path.dirname(path) + '/.ssl/' 123 | 124 | # HTTPS: SSL is enabled 125 | if 'ssl' in conf: 126 | sub['listen'] = '443 ssl spdy' 127 | ssl = { 128 | 'header': 'add_header Alternate-Protocol 443:npn-spdy/3', 129 | 'block': ssl_block.format(**sub) 130 | } 131 | sub['ssl'] = ' {header};\n{block}'.format(**ssl) 132 | 133 | sslcrt = '{sslpath}{xname}.crt'.format(**sub) 134 | sslkey = '{sslpath}{xname}.key'.format(**sub) 135 | sslpem = '{sslpath}cacert.pem'.format(**sub) 136 | 137 | failpath = None 138 | for sslf in [sslcrt, sslkey, sslpem]: 139 | if not os.path.exists(sslf): 140 | failpath = sslf 141 | break 142 | 143 | if failpath: 144 | print '[nginxify] SSL certificate must be present at ' + failpath 145 | print ' [FAIL] skipping ' + name 146 | print ' ' 147 | continue 148 | 149 | all += http_https.format(**sub).replace('<<', '{').replace('>>', '}') 150 | 151 | # HTTP: No SSL 152 | else: 153 | sub['listen'] = '80' 154 | sub['ssl'] = '' 155 | 156 | # Handle locations 157 | sub['locations'] = '' 158 | for (location, value) in conf['paths'].items(): 159 | loc = { 160 | 'location': location, 161 | 'config': '' 162 | } 163 | 164 | # Advanced configuration with {default: ..., 404: 'index.html'} etc. 165 | if isinstance(value, dict): 166 | setup = value 167 | value = setup.get('default', None) 168 | 169 | for key, val in setup.items(): 170 | if key == 'default': 171 | continue 172 | 173 | # Error pages 174 | try: 175 | code = int(key) 176 | if code > 200: 177 | loc['config'] += error_block.format(code=code, value=val) 178 | continue 179 | except: 180 | pass 181 | 182 | # Try Files etc 183 | if isinstance(key, basestring): 184 | loc['config'] += generic_block.format(key=key, value=val) 185 | 186 | # Static files 187 | if isinstance(value, basestring): 188 | loc['type'] = 'alias' 189 | loc['value'] = os.path.dirname(path) + '/' + value.replace('..', '.') 190 | if not loc['value'].endswith('/'): 191 | loc['value'] += '/' 192 | 193 | # None 194 | elif value is None: 195 | loc['type'] = '# No' 196 | loc['value'] = 'default set' 197 | 198 | # Simple [type, value] configuration 199 | elif isinstance(value, list): 200 | if len(value) != 2: 201 | print '[error] List must contain 2 elements exactly' 202 | continue 203 | 204 | _type, root = value 205 | loc['type'] = _type 206 | loc['value'] = os.path.dirname(path) + '/' + root.replace('..', '.') 207 | if not loc['value'].endswith('/'): 208 | loc['value'] += '/' 209 | 210 | if loc['type'] == 'php': 211 | sub['locations'] += '\nroot {};'.format(loc['value']) 212 | sub['locations'] += '\nindex index.php index.html;' 213 | sub['locations'] += php_fpm_block 214 | 215 | elif loc['type'] == 'sphp': 216 | sub['locations'] += '\nroot {};'.format(loc['value']) 217 | sub['locations'] += '\nindex index.php index.html;' 218 | sub['locations'] += sphp_fpm_block 219 | 220 | else: 221 | print '[error] Invalid location type {}'.format(_type) 222 | 223 | continue 224 | 225 | # Proxy to port 226 | elif isinstance(value, int): 227 | loc['type'] = 'proxy_pass' 228 | loc['value'] = 'http://localhost:{}'.format(value) 229 | if location != '/': 230 | loc['config'] += generic_block.format( 231 | key="rewrite", 232 | value="^{}(.*)$ $1 break".format(location) 233 | ) 234 | loc['config'] += proxy_upgrade_block 235 | 236 | # Invalid 237 | else: 238 | print '[error] invalid location format for: ' + location 239 | continue 240 | 241 | sub['locations'] += locblock.format(**loc) 242 | 243 | # Format block 244 | sub['locations'] = sub['locations'].replace('\n', '\n ') 245 | all += '\n' + block.format(**sub).replace('<<', '{').replace('>>', '}') 246 | 247 | print ' ... done!' 248 | print ' ' 249 | 250 | print '[nginxify] Writing nginx configuration file' 251 | 252 | with open('/etc/nginx/sites-enabled/nginxify.conf', 'w') as f: 253 | f.write(all) 254 | 255 | print ' ... done!' 256 | print ' ' 257 | -------------------------------------------------------------------------------- /usr.sh: -------------------------------------------------------------------------------- 1 | # /bin/bash 2 | sudo /bin/nginxify 3 | sudo /etc/init.d/nginx reload 4 | --------------------------------------------------------------------------------