Skip to content

Instantly share code, notes, and snippets.

@vsajip
Created May 14, 2022 12:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vsajip/3c6d6d269d9f406953bf34b450b033f4 to your computer and use it in GitHub Desktop.
Save vsajip/3c6d6d269d9f406953bf34b450b033f4 to your computer and use it in GitHub Desktop.
For Opalstack, configure Nginx and Apache proxy-port "private stack" applications to proxy to a static site with or without PHP.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2022 Red Dove Consultants Limited.
#
# License: Apache 2.0, see https://www.apache.org/licenses/LICENSE-2.0
#
# Gratefully based on work by Ryan Sanden and Sean Fulmer at
#
# https://github.com/rsanden/userspace-fpm-installer
#
import argparse
import functools
import io
import logging
import os
import re
import sys
DEBUGGING = 'PY_DEBUG' in os.environ
logger = logging.getLogger(__name__)
APACHE_CONF = '''
LoadModule mpm_event_module /usr/lib64/httpd/modules/mod_mpm_event.so
LoadModule unixd_module /usr/lib64/httpd/modules/mod_unixd.so
LoadModule authz_core_module /usr/lib64/httpd/modules/mod_authz_core.so
LoadModule proxy_module /usr/lib64/httpd/modules/mod_proxy.so
LoadModule proxy_fcgi_module /usr/lib64/httpd/modules/mod_proxy_fcgi.so
LoadModule mime_module /usr/lib64/httpd/modules/mod_mime.so
LoadModule dir_module /usr/lib64/httpd/modules/mod_dir.so
LoadModule alias_module /usr/lib64/httpd/modules/mod_alias.so
LoadModule autoindex_module /usr/lib64/httpd/modules/mod_autoindex.so
LoadModule log_config_module /usr/lib64/httpd/modules/mod_log_config.so
LoadModule rewrite_module /usr/lib64/httpd/modules/mod_rewrite.so
#LoadModule authz_host_module /usr/lib64/httpd/modules/mod_authz_host.so
#LoadModule authz_groupfile_module /usr/lib64/httpd/modules/mod_authz_groupfile.so
#LoadModule authz_owner_module /usr/lib64/httpd/modules/mod_authz_owner.so
#LoadModule authz_user_module /usr/lib64/httpd/modules/mod_authz_user.so
#LoadModule proxy_connect_module /usr/lib64/httpd/modules/mod_proxy_connect.so
#LoadModule proxy_http_module /usr/lib64/httpd/modules/mod_proxy_http.so
#LoadModule proxy_ftp_module /usr/lib64/httpd/modules/mod_proxy_ftp.so
#LoadModule remoteip_module /usr/lib64/httpd/modules/mod_remoteip.so
#LoadModule dav_module /usr/lib64/httpd/modules/mod_dav.so
#LoadModule dav_fs_module /usr/lib64/httpd/modules/mod_dav_fs.so
#LoadModule auth_basic_module /usr/lib64/httpd/modules/mod_auth_basic.so
#LoadModule auth_digest_module /usr/lib64/httpd/modules/mod_auth_digest.so
#LoadModule authn_file_module /usr/lib64/httpd/modules/mod_authn_file.so
#LoadModule cgid_module /usr/lib64/httpd/modules/mod_cgid.so
#LoadModule deflate_module /usr/lib64/httpd/modules/mod_deflate.so
#LoadModule setenvif_module /usr/lib64/httpd/modules/mod_setenvif.so
#LoadModule headers_module /usr/lib64/httpd/modules/mod_headers.so
#LoadModule include_module /usr/lib64/httpd/modules/mod_include.so
#LoadModule expires_module /usr/lib64/httpd/modules/mod_expires.so
#LoadModule env_module /usr/lib64/httpd/modules/mod_env.so
#LoadModule actions_module /usr/lib64/httpd/modules/mod_actions.so
#LoadModule negotiation_module /usr/lib64/httpd/modules/mod_negotiation.so
#LoadModule speling_module /usr/lib64/httpd/modules/mod_speling.so
#LoadModule access_compat_module /usr/lib64/httpd/modules/mod_access_compat.so
ServerName 127.0.0.1:%(port)s
ServerRoot %(prefix)s
DefaultRuntimeDir %(prefix)s/var/run
Listen 127.0.0.1:%(port)s
KeepAliveTimeout 3
KeepAlive Off
MaxConnectionsPerChild 5000
Timeout 60
PidFile %(prefix)s/var/run/httpd.pid
TypesConfig /etc/mime.types
LogLevel warn
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog %(prefix)s/log/apache_%(appnane)s_access.log combined
ErrorLog %(prefix)s/log/apache_%(appname)s_error.log
<VirtualHost 127.0.0.1:%(post)s>
ServerName %(domain)s
#ServerAlias www.%(domain)s
DocumentRoot %(targappdir)s
DirectoryIndex index.html index.htm index.cgi index.php
ProxyPreserveHost on
AddDefaultCharset utf-8
#if php
<Proxy "unix:/%(prefix)s/var/run/php-fpm.sock">
</Proxy>
#endif
Include /etc/httpd/conf.modules.d/autoindex.conf
<Directory %(targappdir)s>
AllowOverride all
<FilesMatch \.ht(access|passwd)>
Require all denied
</FilesMatch>
<FilesMatch (\.user\.ini|php\.ini)>
Require all denied
</FilesMatch>
#if php
<FilesMatch \.php$>
SetHandler "proxy:unix:%(prefix)s/var/run/php-fpm.sock|fcgi://localhost"
</FilesMatch>
#endif
</Directory>
</VirtualHost>
'''.strip()
NGINX_CONF = '''
pid %(prefix)s/var/run/nginx.pid;
error_log %(prefix)s/log/nginx_%(appname)s_error.log;
events {}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
client_body_temp_path %(prefix)s/tmp/client_body;
fastcgi_temp_path %(prefix)s/tmp/fastcgi_temp;
proxy_temp_path %(prefix)s/tmp/proxy_temp;
scgi_temp_path %(prefix)s/tmp/scgi_temp;
uwsgi_temp_path %(prefix)s/tmp/uwsgi_temp;
log_format main '$http_x_forwarded_for - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"';
access_log %(prefix)s/log/nginx_%(appname)s_access.log main;
server {
listen %(port)s;
server_name %(domain)s;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ //index.php?$args;
alias %(targappdir)s/;
#if php
location ~ \.php$ {
fastcgi_index index.php;
fastcgi_intercept_errors on;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:%(prefix)s/var/run/php-fpm.sock;
}
#endif
}
}
}
'''.strip()
FPM_CONF = '''
[global]
pid = %(prefix)s/var/run/php-fpm.pid
[www]
listen = %(prefix)s/var/run/php-fpm.sock
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
'''.strip()
APACHE_START = '''
#!/bin/bash
PREFIX="%(prefix)s"
#if php
$PREFIX/bin/php-fpm --prefix "$PREFIX"
#endif
$PREFIX/bin/httpd -d "$PREFIX"
'''.strip()
APACHE_STOP = '''
#!/bin/bash
PREFIX="%(prefix)s"
#if php
kill $(cat "$PREFIX/var/run/php-fpm.pid") &> /dev/null
#endif
kill $(cat "$PREFIX/var/run/httpd.pid") &> /dev/null
'''.strip()
NGINX_START = '''
#!/bin/bash
PREFIX="%(prefix)s"
#if php
$PREFIX/bin/php-fpm --prefix "$PREFIX"
#endif
$PREFIX/bin/nginx -c "$PREFIX/conf/nginx.conf" -p "$PREFIX" -e "$PREFIX/log/nginx_%(appname)s_error.log" 2>/dev/null
'''.strip()
NGINX_STOP = '''
#!/bin/bash
PREFIX="%(prefix)s"
#if php
kill $(cat "$PREFIX/var/run/php-fpm.pid") &> /dev/null
#endif
kill $(cat "$PREFIX/var/run/nginx.pid") &> /dev/null
'''.strip()
RESTART = '''
#!/bin/bash
PREFIX="%(prefix)s"
"$PREFIX/bin/stop"
sleep 3
"$PREFIX/bin/start"
'''.strip()
SERVER_TYPES = ('apache', 'nginx')
PHP_VERSIONS = {
'5.6': '/opt/remi/php56/root/usr/sbin/php-fpm',
'7.3': '/usr/sbin/php-fpm',
'7.4': '/opt/remi/php74/root/usr/sbin/php-fpm',
'8.0': '/opt/remi/php80/root/usr/sbin/php-fpm',
'8.1': '/opt/remi/php81/root/usr/sbin/php-fpm',
}
PHP_COND = re.compile(r'(#if php\n(.*)#endif)\n', re.MULTILINE | re.DOTALL)
def create_dirs(options):
prefix = os.path.expanduser('~/apps/%s' % options.appname)
dirs = ('bin', 'conf', 'etc', 'log', 'tmp', 'var/run')
if options.php:
dirs += ('lib',)
for d in dirs:
p = os.path.join(prefix, d)
if not os.path.exists(p):
if options.dry_run:
print('mkdir -p %s' % p)
else:
os.makedirs(p)
def get_context(options):
return {
'appname': options.appname,
'prefix': os.path.expanduser('~/apps/%s' % options.appname),
'port': options.port,
'domain': options.domain,
'targappdir': os.path.expanduser('~/apps/%s' % options.target),
}
def process_output(options, s, p):
if options.dry_run:
print('\ncat << "EOF" > %s' % p)
print('%s\nEOF' % s)
else:
with io.open(p, 'w', encoding='utf-8') as f:
f.write(s)
def generate_php_conf(options):
context = get_context(options)
d = os.path.join(context['prefix'], 'lib')
fn = os.path.join(d, 'php.ini')
if options.dry_run:
print('touch %s' % fn)
else:
with io.open(fn, 'w', encoding='utf-8'):
pass
s = FPM_CONF % context
p = os.path.join(context['prefix'], 'etc', 'php-fpm.conf')
process_output(options, s, p)
def conditional_replacer(want_php, m):
if want_php:
return m.groups()[1]
else:
return ''
replacer = None
def generate_apache_conf(options):
context = get_context(options)
p = os.path.join(context['prefix'], 'conf', 'httpd.conf')
s = PHP_COND.sub(replacer, APACHE_CONF % context)
process_output(options, s, p)
def generate_nginx_conf(options):
context = get_context(options)
p = os.path.join(context['prefix'], 'conf', 'nginx.conf')
s = PHP_COND.sub(replacer, NGINX_CONF % context)
process_output(options, s, p)
def generate_scripts(options):
if options.server == 'apache':
templates = (APACHE_START, APACHE_STOP)
else:
templates = (NGINX_START, NGINX_STOP)
context = get_context(options)
for i, t in enumerate(templates):
sname = 'start' if i == 0 else 'stop'
p = os.path.expanduser('~/apps/%s/bin/%s' % (options.appname, sname))
# import pdb; pdb.set_trace()
s = PHP_COND.sub(replacer, t % context)
process_output(options, s, p)
if options.dry_run:
print('chmod 755 %s' % p)
else:
os.chmod(p, 0o755)
s = RESTART % context
p = os.path.expanduser('~/apps/%s/bin/restart' % options.appname)
process_output(options, s, p)
if options.dry_run:
print('chmod 755 %s' % p)
else:
os.chmod(p, 0o755)
def make_link(options, src, dst):
if os.path.islink(dst):
return
if options.dry_run:
print('ln -s "%s" "%s"' % (src, dst))
else:
os.symlink(src, dst)
def process(options):
global replacer
replacer = functools.partial(conditional_replacer, options.php)
create_dirs(options)
d = os.path.expanduser('~/apps/%s/bin' % options.appname)
fn = 'httpd' if options.server == 'apache' else 'nginx'
p = os.path.join(d, fn)
make_link(options, '/usr/sbin/%s' % fn, p)
if options.php:
php = PHP_VERSIONS[options.php]
p = os.path.join(d, 'php-fpm')
make_link(options, php, p)
generate_php_conf(options)
if options.server == 'apache':
generate_apache_conf(options)
else:
generate_nginx_conf(options)
generate_scripts(options)
def check_port(s):
if not s.isdigit():
raise argparse.ArgumentTypeError('%r is not a valid port value' % s)
result = int(s)
if result < 1024:
raise argparse.ArgumentTypeError('%r is not a valid port value (too low)' % result)
return result
def main():
fn = os.path.basename(__file__)
fn = os.path.splitext(fn)[0]
adhf = argparse.ArgumentDefaultsHelpFormatter
ap = argparse.ArgumentParser(formatter_class=adhf, prog=fn,
description='Set up a private stack on Opalstack for Apache/nginx/PHP')
aa = ap.add_argument
aa('appname', metavar='APPNAME',
help='Name of the proxy-port application to use for the private stack')
aa('port', metavar='PORT', type=check_port, help='The port for the proxy-port application')
aa('target', metavar='TARGET',
help='Name of the target application (can be PHP or purely static)')
aa('domain', metavar='DOMAIN',
help='Name of the domain for the private stack')
aa('--server', default='nginx', choices=SERVER_TYPES, help='Server type for private stack')
aa('--php', choices=sorted(PHP_VERSIONS), help='PHP version for PHP-FPM')
aa('--dry-run','-n', default=False, action='store_true', help='Do a dry run only (prints equivalent Bash commands, which you can edit to customize)')
options = ap.parse_args()
process(options)
if __name__ == '__main__':
try:
rc = main()
except KeyboardInterrupt:
rc = 2
except Exception as e:
if DEBUGGING:
s = ' %s:' % type(e).__name__
else:
s = ''
sys.stderr.write('Failed:%s %s\n' % (s, e))
if DEBUGGING: import traceback; traceback.print_exc()
rc = 1
sys.exit(rc)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment