diff --git a/.github/workflows/proxy-tests.yml b/.github/workflows/proxy-tests.yml index 36df6b22078..45d5a59a4e7 100644 --- a/.github/workflows/proxy-tests.yml +++ b/.github/workflows/proxy-tests.yml @@ -27,11 +27,6 @@ jobs: which luarocks sudo /home/runner/work/Blot/Blot/.luarocks/bin/luarocks install lua-resty-auto-ssl - - name: generate self-signed SSL certs - run: | - mkdir -p /home/runner/work/Blot/Blot/config/openresty/data - openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /home/runner/work/Blot/Blot/config/openresty/data/selfsigned.key -out /home/runner/work/Blot/Blot/config/openresty/data/selfsigned.crt -subj "/C=US/ST=Oregon/L=Portland/O=Company Name/OU=Org/CN=localhost" - - name: Set up Redis uses: shogo82148/actions-setup-redis@v1 with: @@ -70,9 +65,15 @@ jobs: node config/openresty/build-config.js cat config/openresty/data/openresty.conf + # run this after building config since building config will overwrite the data directory + - name: generate self-signed SSL certs + run: | + mkdir -p /home/runner/work/Blot/Blot/config/openresty/data + openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /home/runner/work/Blot/Blot/config/openresty/data/selfsigned.key -out /home/runner/work/Blot/Blot/config/openresty/data/selfsigned.crt -subj "/C=US/ST=Oregon/L=Portland/O=Company Name/OU=Org/CN=localhost" + - name: run openresty run: | - sudo /usr/local/openresty/bin/openresty -c /home/runner/work/Blot/Blot/config/openresty/default.conf + sudo /usr/local/openresty/bin/openresty -c /home/runner/work/Blot/Blot/config/openresty/data/openresty.conf - name: check openresty is running on port 80 run: | diff --git a/config/openresty/build-config.js b/config/openresty/build-config.js index 1772cad3651..d5d26a3c4e7 100644 --- a/config/openresty/build-config.js +++ b/config/openresty/build-config.js @@ -1,54 +1,54 @@ -const mustache = require('mustache') -const config = require('config') -const fs = require('fs-extra') +const mustache = require("mustache"); +const config = require("config"); +const fs = require("fs-extra"); -const NODE_SERVER_IP = process.env.NODE_SERVER_IP -const REDIS_IP = process.env.REDIS_IP +const NODE_SERVER_IP = process.env.NODE_SERVER_IP; +const REDIS_IP = process.env.REDIS_IP; -const OUTPUT = __dirname + '/data' -const CONFIG_DIRECTORY = __dirname + '/conf' +const OUTPUT = __dirname + "/data"; +const CONFIG_DIRECTORY = __dirname + "/conf-new"; -const template = fs.readFileSync(`${CONFIG_DIRECTORY}/server.conf`, 'utf8') -const partials = {} +const template = fs.readFileSync(`${CONFIG_DIRECTORY}/server.conf`, "utf8"); +const partials = {}; const locals = { blot_directory: config.blot_directory, // development: config.environment === "development", - host: 'blot.im', + host: "blot.im", disable_http2: process.env.DISABLE_HTTP2, node_ip: NODE_SERVER_IP, node_port: config.port, redis: { host: REDIS_IP }, reverse_proxy_ip: process.env.PUBLIC_IP, - user: process.env.OPENRESTY_USER || 'ec2-user', + user: process.env.OPENRESTY_USER || "ec2-user", config_directory: - process.env.OPENRESTY_CONFIG_DIRECTORY || '/home/ec2-user/openresty', + process.env.OPENRESTY_CONFIG_DIRECTORY || "/home/ec2-user/openresty", // if you change the cache directory, you must also update the // script mount-instance-store.sh - cache_directory: process.env.OPENRESTY_CACHE_DIRECTORY || '/var/www/cache', + cache_directory: process.env.OPENRESTY_CACHE_DIRECTORY || "/var/www/cache", ssl_certificate: - process.env.SSL_CERTIFICATE || '/etc/ssl/private/letsencrypt-domain.pem', + process.env.SSL_CERTIFICATE || "/etc/ssl/private/letsencrypt-domain.pem", ssl_certificate_key: - process.env.SSL_CERTIFICATE_KEY || '/etc/ssl/private/letsencrypt-domain.key' -} + process.env.SSL_CERTIFICATE_KEY || "/etc/ssl/private/letsencrypt-domain.key" +}; -if (!NODE_SERVER_IP) throw new Error('NODE_SERVER_IP not set') -if (!REDIS_IP) throw new Error('REDIS_IP not set') +if (!NODE_SERVER_IP) throw new Error("NODE_SERVER_IP not set"); +if (!REDIS_IP) throw new Error("REDIS_IP not set"); -fs.emptyDirSync(OUTPUT) +fs.emptyDirSync(OUTPUT); -fs.copySync(`${__dirname}/html`, `${__dirname}/data/html`) +fs.copySync(`${__dirname}/html`, `${__dirname}/data/html`); fs.readdirSync(CONFIG_DIRECTORY).forEach(file => { // copy lua files to data directory so they are available to nginx - if (file.endsWith('.lua')) { - const lua = fs.readFileSync(`${CONFIG_DIRECTORY}/${file}`, 'utf8') - const result = mustache.render(lua, locals) - fs.outputFileSync(`${OUTPUT}/${file}`, result) + if (file.endsWith(".lua")) { + const lua = fs.readFileSync(`${CONFIG_DIRECTORY}/${file}`, "utf8"); + const result = mustache.render(lua, locals); + fs.outputFileSync(`${OUTPUT}/${file}`, result); } - if (!file.endsWith('.conf')) return - partials[file] = fs.readFileSync(CONFIG_DIRECTORY + '/' + file, 'utf8') -}) + if (!file.endsWith(".conf")) return; + partials[file] = fs.readFileSync(CONFIG_DIRECTORY + "/" + file, "utf8"); +}); const warning = ` @@ -65,8 +65,8 @@ const warning = ` # !!!!!!!!!!! WARNING !!!!!!!!!!! # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -` +`; -const result = mustache.render(template, locals, partials) +const result = mustache.render(template, locals, partials); -fs.outputFileSync(__dirname + '/data/openresty.conf', warning + result) +fs.outputFileSync(__dirname + "/data/openresty.conf", warning + result); diff --git a/config/openresty/conf-new/add.lua b/config/openresty/conf-new/add.lua new file mode 100644 index 00000000000..da45261e700 --- /dev/null +++ b/config/openresty/conf-new/add.lua @@ -0,0 +1,29 @@ +package.path = package.path .. ";{{{config_directory}}}/?.lua" + +local md5 = require "md5" + +if ngx ~= nil then + local proxy_cache_keys_by_host = ngx.shared.proxy_cache_keys_by_host + + local host = ngx.var.host + local request_uri = ngx.var.request_uri + local scheme = ngx.var.scheme + + local cache_key = scheme .. "://" .. host .. request_uri + local cache_key_md5 = md5.sumhexa(cache_key) + + -- the cache file path is in the following format: + -- $x/$y/$cache_key_md5 + -- where x is the last character of the cache_key_md5 + -- and y are the two characters before that + local cache_file_path = cache_key_md5:sub(-1) .. "/" .. cache_key_md5:sub(-3,-2) .. "/" .. cache_key_md5 + local already_stored = proxy_cache_keys_by_host:get(cache_file_path) + + if (already_stored == nil) then + ngx.log(ngx.NOTICE,host .. " " .. cache_file_path.. "adding to dictionary") + proxy_cache_keys_by_host:rpush(host, cache_file_path) + proxy_cache_keys_by_host:set(cache_file_path, true) + else + ngx.log(ngx.NOTICE,host .. " " .. cache_file_path.. "already stored" ) + end +end diff --git a/config/openresty/conf-new/auto-ssl-init.conf b/config/openresty/conf-new/auto-ssl-init.conf new file mode 100644 index 00000000000..082cd094c43 --- /dev/null +++ b/config/openresty/conf-new/auto-ssl-init.conf @@ -0,0 +1,276 @@ +# The "auto_ssl" shared dict should be defined with enough storage space to +# hold your certificate data. 1MB of storage holds certificates for +# approximately 100 separate domains. Note that this should not cause an +# error if there are too many domains, just that nginx will have to look +# up the certificate in the database which is slower. +lua_shared_dict auto_ssl 100m; + +# The "auto_ssl" shared dict is used to temporarily store various settings +# like the secret used by the hook server on port 8999. Do not change or +# omit it. +lua_shared_dict auto_ssl_settings 64k; + +# A DNS resolver must be defined for OCSP stapling to function. +# +# This example uses Google's DNS server. You may want to use your system's +# default DNS servers, which can be found in /etc/resolv.conf. If your network +# is not IPv6 compatible, you may wish to disable IPv6 results by using the +# "ipv6=off" flag (like "resolver 8.8.8.8 ipv6=off"). +# https://github.com/auto-ssl/lua-resty-auto-ssl/issues/12#issuecomment-259402817 +# found by running cat /etc/resolv.conf and looking for the nameserver value +resolver 8.8.8.8 ipv6=off; + +# Initial setup tasks. +init_by_lua_block { + + + local redis = require "resty.redis" + + local redis_options = { host = "{{redis.host}}", port = 6379 , prefix = "ssl" } + + local function get_redis_instance(redis_options) + + local instance = ngx.ctx.auto_ssl_redis_instance + + if instance then + return instance + end + + instance = redis:new() + + local ok, err + + if redis_options["socket"] then + ok, err = instance:connect(redis_options["socket"]) + else + ok, err = instance:connect(redis_options["host"], redis_options["port"]) + end + + if not ok then + return false, err + end + + if redis_options["auth"] then + ok, err = instance:auth(redis_options["auth"]) + if not ok then + return false, err + end + end + + ngx.ctx.auto_ssl_redis_instance = instance + return instance + end + + auto_ssl = (require "resty.auto-ssl").new() + + auto_ssl:set("redis", redis_options) + + -- Certificates are stored in redis + auto_ssl:set("storage_adapter", "resty.auto-ssl.storage_adapters.redis") + + -- This function determines whether the incoming domain + -- should automatically issue a new SSL certificate. + -- I need to set domain:blot.im to foo in the database so that + -- the allow_domain function works as expected even though + -- it's not technically a user's domain + auto_ssl:set("allow_domain", function(domain) + + local certstorage = auto_ssl.storage + + local fullchain_pem, privkey_pem = certstorage:get_cert(domain) + + -- If we have this cert in the memory cache + -- then return it without checking redis to save time + if fullchain_pem then + return true + end + + local redis_instance, instance_err = get_redis_instance(redis_options) + + if instance_err then + return nil, instance_err + end + + local res, err = redis_instance:get('domain:' .. domain) + + if res == ngx.null then + return false + end + + return true + end) + + auto_ssl:init() + + -- now we rehydrate the cache + + local cache_directory = "{{{cache_directory}}}" + + -- this file will read the contents of the cache directory and store them in + -- the lua shared dict proxy_cache_keys_by_host so we can purge them later + + -- I'm not sure why we need to deduplicate the keys?-- + local function deduplicate_key_list_by_host (host, shared_dict) + + ngx.log(ngx.NOTICE, "deduplicate_key_list_by_host: " .. host) + + local deduplicated_key_list = {} + + -- we lpop from the list until we get nil + local key = shared_dict:lpop(host) + + while key do + + if (deduplicated_key_list[key] == nil) then + ngx.log(ngx.NOTICE, "unique key: " .. key) + table.insert(deduplicated_key_list, key) + deduplicated_key_list[key] = true + else + ngx.log(ngx.NOTICE, "duplicate key: " .. key) + end + + key = shared_dict:lpop(host) + end + + -- reinsert the keys into the list + for _, key in ipairs(deduplicated_key_list) do + ngx.log(ngx.NOTICE, "reinserting key: " .. key) + shared_dict:rpush(host, key) + end + end + + local function extractHostFromCacheFile (cache_file_path) + -- emit a warning with the cache_file_path + ngx.log(ngx.NOTICE, "extracting host from cache_file_path: " .. cache_file_path) + + -- we need to read the first line of the cache file to get the host + -- the first line is in the format: + -- KEY: http://example.com/xyz/abc + local file = io.open(cache_file_path, "r") + local first_line = file:read() + + -- keep reading the file until we get a line that starts with KEY + while (first_line ~= nil and string.match(first_line, "^KEY:") == nil) do + first_line = file:read() + end + + if (first_line == nil) then + ngx.log(ngx.NOTICE, "uri is nil") + return nil + end + + local uri = string.match(first_line, "KEY: (.*)") + + if (uri == nil) then + ngx.log(ngx.NOTICE, "uri is nil") + return nil + end + + -- the protocol is included in the uri so we need to remove it + local uri_without_protocol = string.match(uri, "://(.*)") + + -- the host is the first part of the uri, up to question mark or slash if present + local host = string.match(uri_without_protocol, "([^/?]+)") + + if (host == nil) then + ngx.log(ngx.NOTICE, "host is nil") + return nil + end + + file:close() + + ngx.log(ngx.NOTICE, "found host from cache_file_path: " .. host) + + return host + end + + -- list all the items in the cache directory + local function readdirectory(directory) + local i, t, popen = 0, {}, io.popen + local pfile = popen('find "'..directory..'" -maxdepth 1') + for line in pfile:lines() do + i = i + 1 + -- the line starts with the directory so we need to remove that + local filename = string.match(line, directory .. "/(.*)") + -- skip the first line which is the directory itself + if (i > 1 and filename ~= nil) then + table.insert(t, filename) + end + end + pfile:close() + return t + end + + if ngx ~= nil then + + -- local purged_files = purge_host(ngx.var.arg_host) + local proxy_cache_keys_by_host = ngx.shared.proxy_cache_keys_by_host + + -- first we list all the top level directories in the cache directory + local top_level_directories = readdirectory(cache_directory) + + -- store a list of hosts + local hosts = {} + + -- then for each directory we list all the files in that directory + for _, top_level_directory in ipairs(top_level_directories) do + + local top_level_directory_path = cache_directory .. "/" .. top_level_directory + local second_level_directories = readdirectory(top_level_directory_path) + + ngx.log(ngx.NOTICE, "reading top level directory: " .. top_level_directory_path) + + for _, second_level_directory in ipairs(second_level_directories) do + local second_level_directory_path = top_level_directory_path .. "/" .. second_level_directory + local files = readdirectory(second_level_directory_path) + + for _, file in ipairs(files) do + local cache_file_path = top_level_directory .. "/" .. second_level_directory .. "/" .. file + local host = extractHostFromCacheFile(second_level_directory_path .. "/" .. file) + + if (host ~= nil) then + -- add the host to the list of hosts + if (hosts[host] == nil) then + ngx.log(ngx.NOTICE, "found host: " .. host) + table.insert(hosts, host) + hosts[host] = true + end + + ngx.log(ngx.NOTICE, "found cache file path: " .. cache_file_path) + proxy_cache_keys_by_host:set(cache_file_path, true) + proxy_cache_keys_by_host:rpush(host, cache_file_path) + end + + + end + end + end + + for _, host in ipairs(hosts) do + deduplicate_key_list_by_host(host, proxy_cache_keys_by_host) + end + end + +} + +init_worker_by_lua_block { + auto_ssl:init_worker() +} + + +# Internal server running on port 8999 for handling certificate tasks. +server { + + listen 127.0.0.1:8999; + + # Increase the body buffer size, to ensure the internal POSTs can always + # parse the full POST contents into memory. + client_body_buffer_size 128k; + client_max_body_size 128k; + + location / { + content_by_lua_block { + auto_ssl:hook_server() + } + } +} \ No newline at end of file diff --git a/config/openresty/conf-new/auto-ssl.conf b/config/openresty/conf-new/auto-ssl.conf new file mode 100644 index 00000000000..c83474b62f8 --- /dev/null +++ b/config/openresty/conf-new/auto-ssl.conf @@ -0,0 +1,30 @@ + +# I found this shit on the internet for getting a +# good score with the SSL evaluator tool. Don't understand. +ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +ssl_prefer_server_ciphers on; +ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + +# Hopefully improves performance +ssl_session_cache shared:SSL:10m; + +# Dynamic handler for issuing or returning certs for SNI domains. +ssl_certificate_by_lua_block { + auto_ssl:ssl_certificate() +} + +# You must still define a static ssl_certificate file for nginx to start. +# The SSL certificate we use is generated by https://github.com/kshcherban/acme-nginx +# for the "*.blot.im" and "blot.im" domains. This allows us to do an infinite +# number of subdomains for previewing templates. The paths to these files are +# hardcoded in the source for kshcherban/acme-nginx. I wrote more about this +# process in notes/wildcard-ssl +ssl_certificate {{{ssl_certificate}}}; +ssl_certificate_key {{{ssl_certificate_key}}}; + +# Endpoint used for performing domain verification with Let's Encrypt. +location /.well-known/acme-challenge/ { + content_by_lua_block { + auto_ssl:challenge_server() + } +} \ No newline at end of file diff --git a/config/openresty/conf-new/blot-blogs.conf b/config/openresty/conf-new/blot-blogs.conf new file mode 100644 index 00000000000..79842a229ee --- /dev/null +++ b/config/openresty/conf-new/blot-blogs.conf @@ -0,0 +1,27 @@ +root /; + +# This is used to determine whether the server is handling +# requests, don't remove it unless you change monit.rc too! +location = /health { + return 200; +} + +error_page 502 /502.html; + +# send the text '502 Bad Gateway' to the client +location = /error-proxy-502.html { + client_max_body_size 1M; + root {{{config_directory}}}/html; +} + +location / { + {{> reverse-proxy-cache.conf}} +} + +# This is used to prevent people from accessing the git repositories in user folders +# We basically pass these requests directly to the node server where they are treated +# as 404s. This needs to be in both blot-blog.conf and blot-site.conf because it seems +# all requests hit the blot-site.conf cache layer first? +location ^~ /.git { + {{> reverse-proxy.conf}} +} diff --git a/config/openresty/conf-new/blot-site.conf b/config/openresty/conf-new/blot-site.conf new file mode 100644 index 00000000000..3d8b636d094 --- /dev/null +++ b/config/openresty/conf-new/blot-site.conf @@ -0,0 +1,36 @@ +# This is where we send server-sent events +# which need a long timeout +location = /status { + client_max_body_size 1M; + proxy_read_timeout 24h; + {{> reverse-proxy.conf}} +} + +location = /health { + return 200; +} + +# redirect for /cdn/XYZ to cdn.blot.im/XYZ +location ~ ^/cdn/(.*)$ { + return 301 https://cdn.blot.im/$1; +} + +location = /redis-health { + {{> reverse-proxy.conf}} +} + +# bypass cache for these +location /dashboard { + {{> reverse-proxy.conf}} +} + +# bypass cache for these +location /clients { + client_max_body_size 1000M; + {{> reverse-proxy.conf}} +} + + +location / { + {{> reverse-proxy-cache.conf}} +} \ No newline at end of file diff --git a/config/openresty/conf-new/http.conf b/config/openresty/conf-new/http.conf new file mode 100644 index 00000000000..2dbb4923285 --- /dev/null +++ b/config/openresty/conf-new/http.conf @@ -0,0 +1,31 @@ +log_format access_log_format '[$time_local] $request_id $status $request_time $request_length:$bytes_sent $scheme://$host$request_uri cache=$sent_http_blot_cache proxy_cache=$upstream_cache_status'; + +error_log {{{config_directory}}}/error.log info; +access_log {{{config_directory}}}/access.log access_log_format; + +# Hide the nginx version in the server header +server_tokens off; + +# Added to set the content-type charset header +# for text files served by NGINX +charset utf-8; + +{{> static-file.conf}} + +init_by_lua_file {{{config_directory}}}/init.lua; + +# Make sure this directory exists +# It does not need to be owned by root +proxy_cache_path {{{cache_directory}}} levels=1:2 keys_zone=PROXY_CACHE:10m inactive=1y max_size=10g use_temp_path=off; + +lua_shared_dict proxy_cache_keys_by_host 50m; + +upstream blot_node { + server {{node_ip}}:{{node_port}}; + keepalive 64; +} + +map $http_accept_encoding $gz { + default ""; + ~*.*gzip.* gzip; +} \ No newline at end of file diff --git a/config/openresty/conf-new/init.lua b/config/openresty/conf-new/init.lua new file mode 100644 index 00000000000..3a90465267a --- /dev/null +++ b/config/openresty/conf-new/init.lua @@ -0,0 +1,201 @@ +local cache_directory = "{{{cache_directory}}}" + +-- this file will read the contents of the cache directory and store them in +-- the lua shared dict proxy_cache_keys_by_host so we can purge them later + +-- I'm not sure why we need to deduplicate the keys?-- +local function deduplicate_key_list_by_host (host, shared_dict) + + ngx.log(ngx.NOTICE, "deduplicate_key_list_by_host: " .. host) + + local deduplicated_key_list = {} + + -- we lpop from the list until we get nil + local key = shared_dict:lpop(host) + + while key do + + if (deduplicated_key_list[key] == nil) then + ngx.log(ngx.NOTICE, "unique key: " .. key) + table.insert(deduplicated_key_list, key) + deduplicated_key_list[key] = true + else + ngx.log(ngx.NOTICE, "duplicate key: " .. key) + end + + key = shared_dict:lpop(host) + end + + -- reinsert the keys into the list + for _, key in ipairs(deduplicated_key_list) do + ngx.log(ngx.NOTICE, "reinserting key: " .. key) + shared_dict:rpush(host, key) + end +end + +local function extractHostFromCacheFile (cache_file_path) + -- emit a warning with the cache_file_path + ngx.log(ngx.NOTICE, "extracting host from cache_file_path: " .. cache_file_path) + + -- we need to read the first line of the cache file to get the host + -- the first line is in the format: + -- KEY: http://example.com/xyz/abc + local file = io.open(cache_file_path, "r") + local first_line = file:read() + local uri = string.match(first_line, "KEY: (.*)") + + -- the protocol is included in the uri so we need to remove it + local uri_without_protocol = string.match(uri, "://(.*)") + + -- the host is the first part of the uri, up to question mark or slash if present + local host = string.match(uri_without_protocol, "([^/?]+)") + + file:close() + + ngx.log(ngx.NOTICE, "found host from cache_file_path: " .. host) + + return host +end + +-- list all the items in the cache directory +local function readdirectory(directory) + local i, t, popen = 0, {}, io.popen + local pfile = popen('find "'..directory..'" -maxdepth 1') + for line in pfile:lines() do + i = i + 1 + -- the line starts with the directory so we need to remove that + local filename = string.match(line, directory .. "/(.*)") + -- skip the first line which is the directory itself + if (i > 1 and filename ~= nil) then + table.insert(t, filename) + end + end + pfile:close() + return t +end + +if ngx ~= nil then + + -- local purged_files = purge_host(ngx.var.arg_host) + local proxy_cache_keys_by_host = ngx.shared.proxy_cache_keys_by_host + + -- first we list all the top level directories in the cache directory + local top_level_directories = readdirectory(cache_directory) + + -- store a list of hosts + local hosts = {} + + -- then for each directory we list all the files in that directory + for _, top_level_directory in ipairs(top_level_directories) do + + local top_level_directory_path = cache_directory .. "/" .. top_level_directory + local second_level_directories = readdirectory(top_level_directory_path) + + ngx.log(ngx.NOTICE, "reading top level directory: " .. top_level_directory_path) + + for _, second_level_directory in ipairs(second_level_directories) do + local second_level_directory_path = top_level_directory_path .. "/" .. second_level_directory + local files = readdirectory(second_level_directory_path) + + for _, file in ipairs(files) do + local cache_file_path = top_level_directory .. "/" .. second_level_directory .. "/" .. file + local host = extractHostFromCacheFile(second_level_directory_path .. "/" .. file) + + -- add the host to the list of hosts + if (hosts[host] == nil) then + ngx.log(ngx.NOTICE, "found host: " .. host) + table.insert(hosts, host) + hosts[host] = true + end + + ngx.log(ngx.NOTICE, "found cache file path: " .. cache_file_path) + proxy_cache_keys_by_host:set(cache_file_path, true) + proxy_cache_keys_by_host:rpush(host, cache_file_path) + end + end + end + + for _, host in ipairs(hosts) do + deduplicate_key_list_by_host(host, proxy_cache_keys_by_host) + end +end + + + +local redis = require "resty.redis" + +local redis_options = { host = "{{redis.host}}", port = 6379 , prefix = "ssl" } + +local function get_redis_instance(redis_options) + + local instance = ngx.ctx.auto_ssl_redis_instance + + if instance then + return instance + end + + instance = redis:new() + + local ok, err + + if redis_options["socket"] then + ok, err = instance:connect(redis_options["socket"]) + else + ok, err = instance:connect(redis_options["host"], redis_options["port"]) + end + + if not ok then + return false, err + end + + if redis_options["auth"] then + ok, err = instance:auth(redis_options["auth"]) + if not ok then + return false, err + end + end + + ngx.ctx.auto_ssl_redis_instance = instance + return instance +end + +auto_ssl = (require "resty.auto-ssl").new() + +auto_ssl:set("redis", redis_options) + +-- Certificates are stored in redis +auto_ssl:set("storage_adapter", "resty.auto-ssl.storage_adapters.redis") + +-- This function determines whether the incoming domain +-- should automatically issue a new SSL certificate. +-- I need to set domain:blot.im to foo in the database so that +-- the allow_domain function works as expected even though +-- it's not technically a user's domain +auto_ssl:set("allow_domain", function(domain) + + local certstorage = auto_ssl.storage + + local fullchain_pem, privkey_pem = certstorage:get_cert(domain) + + -- If we have this cert in the memory cache + -- then return it without checking redis to save time + if fullchain_pem then + return true + end + + local redis_instance, instance_err = get_redis_instance(redis_options) + + if instance_err then + return nil, instance_err + end + + local res, err = redis_instance:get('domain:' .. domain) + + if res == ngx.null then + return false + end + + return true +end) + +auto_ssl:init() diff --git a/config/openresty/conf-new/initial.conf b/config/openresty/conf-new/initial.conf new file mode 100644 index 00000000000..2663b7b7ae5 --- /dev/null +++ b/config/openresty/conf-new/initial.conf @@ -0,0 +1,17 @@ +user {{user}} {{user}}; ## Default: nobody + +worker_processes auto; +worker_cpu_affinity auto; + +# Defines the scheduling priority for worker processes like it is done by the nice command: +# a negative number means higher priority. Allowed range normally varies from -20 to 20. +worker_priority -20; + +# Sets the limit of the maximum number of open files (RLIMIT_NOFILE) +# for worker processes +worker_rlimit_nofile 10000; + +events { + worker_connections 10000; + multi_accept on; +} \ No newline at end of file diff --git a/config/openresty/conf-new/md5.lua b/config/openresty/conf-new/md5.lua new file mode 100644 index 00000000000..ac836d943a4 --- /dev/null +++ b/config/openresty/conf-new/md5.lua @@ -0,0 +1,427 @@ +local md5 = { + _VERSION = "md5.lua 1.1.0", + _DESCRIPTION = "MD5 computation in Lua (5.1-3, LuaJIT)", + _URL = "https://github.com/kikito/md5.lua", + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2013 Enrique GarcĂ­a Cota + Adam Baldwin + hanzao + Equi 4 Software + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]] + } + + -- bit lib implementions + + local char, byte, format, rep, sub = + string.char, string.byte, string.format, string.rep, string.sub + local bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift + + local ok, bit = pcall(require, 'bit') + local ok_ffi, ffi = pcall(require, 'ffi') + if ok then + bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift = bit.bor, bit.band, bit.bnot, bit.bxor, bit.rshift, bit.lshift + else + ok, bit = pcall(require, 'bit32') + + if ok then + + bit_not = bit.bnot + + local tobit = function(n) + return n <= 0x7fffffff and n or -(bit_not(n) + 1) + end + + local normalize = function(f) + return function(a,b) return tobit(f(tobit(a), tobit(b))) end + end + + bit_or, bit_and, bit_xor = normalize(bit.bor), normalize(bit.band), normalize(bit.bxor) + bit_rshift, bit_lshift = normalize(bit.rshift), normalize(bit.lshift) + + else + + local function tbl2number(tbl) + local result = 0 + local power = 1 + for i = 1, #tbl do + result = result + tbl[i] * power + power = power * 2 + end + return result + end + + local function expand(t1, t2) + local big, small = t1, t2 + if(#big < #small) then + big, small = small, big + end + -- expand small + for i = #small + 1, #big do + small[i] = 0 + end + end + + local to_bits -- needs to be declared before bit_not + + bit_not = function(n) + local tbl = to_bits(n) + local size = math.max(#tbl, 32) + for i = 1, size do + if(tbl[i] == 1) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + return tbl2number(tbl) + end + + -- defined as local above + to_bits = function (n) + if(n < 0) then + -- negative + return to_bits(bit_not(math.abs(n)) + 1) + end + -- to bits table + local tbl = {} + local cnt = 1 + local last + while n > 0 do + last = n % 2 + tbl[cnt] = last + n = (n-last)/2 + cnt = cnt + 1 + end + + return tbl + end + + bit_or = function(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + for i = 1, #tbl_m do + if(tbl_m[i]== 0 and tbl_n[i] == 0) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + + return tbl2number(tbl) + end + + bit_and = function(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + for i = 1, #tbl_m do + if(tbl_m[i]== 0 or tbl_n[i] == 0) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + + return tbl2number(tbl) + end + + bit_xor = function(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + for i = 1, #tbl_m do + if(tbl_m[i] ~= tbl_n[i]) then + tbl[i] = 1 + else + tbl[i] = 0 + end + end + + return tbl2number(tbl) + end + + bit_rshift = function(n, bits) + local high_bit = 0 + if(n < 0) then + -- negative + n = bit_not(math.abs(n)) + 1 + high_bit = 0x80000000 + end + + local floor = math.floor + + for i=1, bits do + n = n/2 + n = bit_or(floor(n), high_bit) + end + return floor(n) + end + + bit_lshift = function(n, bits) + if(n < 0) then + -- negative + n = bit_not(math.abs(n)) + 1 + end + + for i=1, bits do + n = n*2 + end + return bit_and(n, 0xFFFFFFFF) + end + end + end + + -- convert little-endian 32-bit int to a 4-char string + local lei2str + -- function is defined this way to allow full jit compilation (removing UCLO instruction in LuaJIT) + if ok_ffi then + local ct_IntType = ffi.typeof("int[1]") + lei2str = function(i) return ffi.string(ct_IntType(i), 4) end + else + lei2str = function (i) + local f=function (s) return char( bit_and( bit_rshift(i, s), 255)) end + return f(0)..f(8)..f(16)..f(24) + end + end + + + + -- convert raw string to big-endian int + local function str2bei(s) + local v=0 + for i=1, #s do + v = v * 256 + byte(s, i) + end + return v + end + + -- convert raw string to little-endian int + local str2lei + + if ok_ffi then + local ct_constcharptr = ffi.typeof("const char*") + local ct_constintptr = ffi.typeof("const int*") + str2lei = function(s) + local int = ct_constcharptr(s) + return ffi.cast(ct_constintptr, int)[0] + end + else + str2lei = function(s) + local v=0 + for i = #s,1,-1 do + v = v*256 + byte(s, i) + end + return v + end + end + + + -- cut up a string in little-endian ints of given size + local function cut_le_str(s) + return { + str2lei(sub(s, 1, 4)), + str2lei(sub(s, 5, 8)), + str2lei(sub(s, 9, 12)), + str2lei(sub(s, 13, 16)), + str2lei(sub(s, 17, 20)), + str2lei(sub(s, 21, 24)), + str2lei(sub(s, 25, 28)), + str2lei(sub(s, 29, 32)), + str2lei(sub(s, 33, 36)), + str2lei(sub(s, 37, 40)), + str2lei(sub(s, 41, 44)), + str2lei(sub(s, 45, 48)), + str2lei(sub(s, 49, 52)), + str2lei(sub(s, 53, 56)), + str2lei(sub(s, 57, 60)), + str2lei(sub(s, 61, 64)), + } + end + + -- An MD5 mplementation in Lua, requires bitlib (hacked to use LuaBit from above, ugh) + -- 10/02/2001 jcw@equi4.com + + local CONSTS = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, + 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 + } + + local f=function (x,y,z) return bit_or(bit_and(x,y),bit_and(-x-1,z)) end + local g=function (x,y,z) return bit_or(bit_and(x,z),bit_and(y,-z-1)) end + local h=function (x,y,z) return bit_xor(x,bit_xor(y,z)) end + local i=function (x,y,z) return bit_xor(y,bit_or(x,-z-1)) end + local z=function (ff,a,b,c,d,x,s,ac) + a=bit_and(a+ff(b,c,d)+x+ac,0xFFFFFFFF) + -- be *very* careful that left shift does not cause rounding! + return bit_or(bit_lshift(bit_and(a,bit_rshift(0xFFFFFFFF,s)),s),bit_rshift(a,32-s))+b + end + + local function transform(A,B,C,D,X) + local a,b,c,d=A,B,C,D + local t=CONSTS + + a=z(f,a,b,c,d,X[ 0], 7,t[ 1]) + d=z(f,d,a,b,c,X[ 1],12,t[ 2]) + c=z(f,c,d,a,b,X[ 2],17,t[ 3]) + b=z(f,b,c,d,a,X[ 3],22,t[ 4]) + a=z(f,a,b,c,d,X[ 4], 7,t[ 5]) + d=z(f,d,a,b,c,X[ 5],12,t[ 6]) + c=z(f,c,d,a,b,X[ 6],17,t[ 7]) + b=z(f,b,c,d,a,X[ 7],22,t[ 8]) + a=z(f,a,b,c,d,X[ 8], 7,t[ 9]) + d=z(f,d,a,b,c,X[ 9],12,t[10]) + c=z(f,c,d,a,b,X[10],17,t[11]) + b=z(f,b,c,d,a,X[11],22,t[12]) + a=z(f,a,b,c,d,X[12], 7,t[13]) + d=z(f,d,a,b,c,X[13],12,t[14]) + c=z(f,c,d,a,b,X[14],17,t[15]) + b=z(f,b,c,d,a,X[15],22,t[16]) + + a=z(g,a,b,c,d,X[ 1], 5,t[17]) + d=z(g,d,a,b,c,X[ 6], 9,t[18]) + c=z(g,c,d,a,b,X[11],14,t[19]) + b=z(g,b,c,d,a,X[ 0],20,t[20]) + a=z(g,a,b,c,d,X[ 5], 5,t[21]) + d=z(g,d,a,b,c,X[10], 9,t[22]) + c=z(g,c,d,a,b,X[15],14,t[23]) + b=z(g,b,c,d,a,X[ 4],20,t[24]) + a=z(g,a,b,c,d,X[ 9], 5,t[25]) + d=z(g,d,a,b,c,X[14], 9,t[26]) + c=z(g,c,d,a,b,X[ 3],14,t[27]) + b=z(g,b,c,d,a,X[ 8],20,t[28]) + a=z(g,a,b,c,d,X[13], 5,t[29]) + d=z(g,d,a,b,c,X[ 2], 9,t[30]) + c=z(g,c,d,a,b,X[ 7],14,t[31]) + b=z(g,b,c,d,a,X[12],20,t[32]) + + a=z(h,a,b,c,d,X[ 5], 4,t[33]) + d=z(h,d,a,b,c,X[ 8],11,t[34]) + c=z(h,c,d,a,b,X[11],16,t[35]) + b=z(h,b,c,d,a,X[14],23,t[36]) + a=z(h,a,b,c,d,X[ 1], 4,t[37]) + d=z(h,d,a,b,c,X[ 4],11,t[38]) + c=z(h,c,d,a,b,X[ 7],16,t[39]) + b=z(h,b,c,d,a,X[10],23,t[40]) + a=z(h,a,b,c,d,X[13], 4,t[41]) + d=z(h,d,a,b,c,X[ 0],11,t[42]) + c=z(h,c,d,a,b,X[ 3],16,t[43]) + b=z(h,b,c,d,a,X[ 6],23,t[44]) + a=z(h,a,b,c,d,X[ 9], 4,t[45]) + d=z(h,d,a,b,c,X[12],11,t[46]) + c=z(h,c,d,a,b,X[15],16,t[47]) + b=z(h,b,c,d,a,X[ 2],23,t[48]) + + a=z(i,a,b,c,d,X[ 0], 6,t[49]) + d=z(i,d,a,b,c,X[ 7],10,t[50]) + c=z(i,c,d,a,b,X[14],15,t[51]) + b=z(i,b,c,d,a,X[ 5],21,t[52]) + a=z(i,a,b,c,d,X[12], 6,t[53]) + d=z(i,d,a,b,c,X[ 3],10,t[54]) + c=z(i,c,d,a,b,X[10],15,t[55]) + b=z(i,b,c,d,a,X[ 1],21,t[56]) + a=z(i,a,b,c,d,X[ 8], 6,t[57]) + d=z(i,d,a,b,c,X[15],10,t[58]) + c=z(i,c,d,a,b,X[ 6],15,t[59]) + b=z(i,b,c,d,a,X[13],21,t[60]) + a=z(i,a,b,c,d,X[ 4], 6,t[61]) + d=z(i,d,a,b,c,X[11],10,t[62]) + c=z(i,c,d,a,b,X[ 2],15,t[63]) + b=z(i,b,c,d,a,X[ 9],21,t[64]) + + return bit_and(A+a,0xFFFFFFFF),bit_and(B+b,0xFFFFFFFF), + bit_and(C+c,0xFFFFFFFF),bit_and(D+d,0xFFFFFFFF) + end + + ---------------------------------------------------------------- + + local function md5_update(self, s) + self.pos = self.pos + #s + s = self.buf .. s + for ii = 1, #s - 63, 64 do + local X = cut_le_str(sub(s,ii,ii+63)) + assert(#X == 16) + X[0] = table.remove(X,1) -- zero based! + self.a,self.b,self.c,self.d = transform(self.a,self.b,self.c,self.d,X) + end + self.buf = sub(s, math.floor(#s/64)*64 + 1, #s) + return self + end + + local function md5_finish(self) + local msgLen = self.pos + local padLen = 56 - msgLen % 64 + + if msgLen % 64 > 56 then padLen = padLen + 64 end + + if padLen == 0 then padLen = 64 end + + local s = char(128) .. rep(char(0),padLen-1) .. lei2str(bit_and(8*msgLen, 0xFFFFFFFF)) .. lei2str(math.floor(msgLen/0x20000000)) + md5_update(self, s) + + assert(self.pos % 64 == 0) + return lei2str(self.a) .. lei2str(self.b) .. lei2str(self.c) .. lei2str(self.d) + end + + ---------------------------------------------------------------- + + function md5.new() + return { a = CONSTS[65], b = CONSTS[66], c = CONSTS[67], d = CONSTS[68], + pos = 0, + buf = '', + update = md5_update, + finish = md5_finish } + end + + function md5.tohex(s) + return format("%08x%08x%08x%08x", str2bei(sub(s, 1, 4)), str2bei(sub(s, 5, 8)), str2bei(sub(s, 9, 12)), str2bei(sub(s, 13, 16))) + end + + function md5.sum(s) + return md5.new():update(s):finish() + end + + function md5.sumhexa(s) + return md5.tohex(md5.sum(s)) + end + + return md5 \ No newline at end of file diff --git a/config/openresty/conf-new/purge.conf b/config/openresty/conf-new/purge.conf new file mode 100644 index 00000000000..db789bfc5ea --- /dev/null +++ b/config/openresty/conf-new/purge.conf @@ -0,0 +1,7 @@ +location = /purge { + if ($request_method = PURGE) { + # if you edit this path also update proxy_cache_path in http.conf + set $lua_cache_directory "{{{cache_directory}}}"; + content_by_lua_file {{{config_directory}}}/purge.lua; + } +} \ No newline at end of file diff --git a/config/openresty/conf-new/purge.lua b/config/openresty/conf-new/purge.lua new file mode 100644 index 00000000000..e5945aeaddd --- /dev/null +++ b/config/openresty/conf-new/purge.lua @@ -0,0 +1,55 @@ +-- Delete nginx cached assets associated with a host using a PURGE request +-- + +local function file_exists(filepath) + local f = io.open(filepath, "r") + if f~=nil then io.close(f) return true else return false end +end + +local function purge(filepath) + if (file_exists(filepath)) then + os.remove(filepath) + end +end + + +if ngx ~= nil then + -- local purged_files = purge_host(ngx.var.arg_host) + local proxy_cache_keys_by_host = ngx.shared.proxy_cache_keys_by_host + + local message = "purging..." + + -- extract a list of hosts from ngx.var.args which looks something like: "host=127.0.0.1&host=example.com" + local hosts = {} + + -- prevent an error if the args are nil + if (ngx.var.args == nil) then + ngx.say("please pass a host to purge") + ngx.exit(ngx.OK) + end + + for host in string.gmatch(ngx.var.args, "host=([^&]+)") do + table.insert(hosts, host) + end + + for _, host in ipairs(hosts) do + local cached_filename = proxy_cache_keys_by_host:lpop(host) + local number_of_cache_keys = tostring(proxy_cache_keys_by_host:llen(host)) + + message = message .. "\n host: " .. host .. " number_of_cache_keys: " .. number_of_cache_keys + + while cached_filename do + proxy_cache_keys_by_host:delete(cached_filename) + local cache_file_path = ngx.var.lua_cache_directory .. "/" .. cached_filename + + purge(cache_file_path) + + -- message = message .. "\n purged: " .. cache_file_path + cached_filename = proxy_cache_keys_by_host:lpop(host) + end + + end + + ngx.say(message) + ngx.exit(ngx.OK) +end \ No newline at end of file diff --git a/config/openresty/conf-new/reverse-proxy-cache.conf b/config/openresty/conf-new/reverse-proxy-cache.conf new file mode 100644 index 00000000000..f1d58dce7d7 --- /dev/null +++ b/config/openresty/conf-new/reverse-proxy-cache.conf @@ -0,0 +1,13 @@ +open_file_cache off; + +proxy_cache PROXY_CACHE; +proxy_cache_valid 200 301 302 304 1y; +proxy_cache_key $scheme://$host$request_uri; +proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; + +add_header 'Blot-Cache' $upstream_cache_status always; + +{{> reverse-proxy.conf}} + +# This stores that we have cached a given response +log_by_lua_file {{{config_directory}}}/add.lua; \ No newline at end of file diff --git a/config/openresty/conf-new/reverse-proxy.conf b/config/openresty/conf-new/reverse-proxy.conf new file mode 100644 index 00000000000..798c96b2e95 --- /dev/null +++ b/config/openresty/conf-new/reverse-proxy.conf @@ -0,0 +1,23 @@ +add_header 'Blot-Server' '{{reverse_proxy_ip}}'; + +proxy_pass http://blot_node; +proxy_http_version 1.1; + +proxy_set_header Host $host; +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $scheme; +proxy_set_header X-Request-ID $request_id; +proxy_set_header Upgrade $http_upgrade; +proxy_set_header Connection 'upgrade'; + +# This seemed to fix an issue with MP4 demo video +# ERR_CONTENT_DECODING_FAILED +# All gzipping should be handled by NGINX, rather than node +proxy_set_header Accept-Encoding ""; + +gzip on; +gzip_comp_level 6; +gzip_proxied any; + +proxy_redirect off; \ No newline at end of file diff --git a/config/openresty/default.conf b/config/openresty/conf-new/server.conf similarity index 100% rename from config/openresty/default.conf rename to config/openresty/conf-new/server.conf diff --git a/config/openresty/conf-new/static-file.conf b/config/openresty/conf-new/static-file.conf new file mode 100644 index 00000000000..d5303f6e507 --- /dev/null +++ b/config/openresty/conf-new/static-file.conf @@ -0,0 +1,163 @@ +# Taken from +# https://github.com/h5bp/server-configs-nginx/blob/master/mime.types + +types { + + # Data interchange + + application/atom+xml atom; + application/json json map topojson; + application/ld+json jsonld; + application/rss+xml rss; + application/vnd.geo+json geojson; + application/xml rdf xml; + + + # JavaScript + + # Normalize to standard type. + # https://tools.ietf.org/html/rfc4329#section-7.2 + application/javascript js jsgzip; + + + # Manifest files + + application/manifest+json webmanifest; + application/x-web-app-manifest+json webapp; + text/cache-manifest appcache; + + + # Media files + + audio/midi mid midi kar; + audio/mp4 aac f4a f4b m4a; + audio/mpeg mp3; + audio/ogg oga ogg opus; + audio/x-realaudio ra; + audio/x-wav wav; + image/bmp bmp; + image/gif gif; + image/jpeg jpeg jpg; + image/jxr jxr hdp wdp; + image/png png; + image/svg+xml svg svggzip; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/webp webp; + image/x-jng jng; + video/3gpp 3gp 3gpp; + video/mp4 f4p f4v m4v mp4; + video/mpeg mpeg mpg; + video/ogg ogv; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-mng mng; + video/x-ms-asf asf asx; + video/x-ms-wmv wmv; + video/x-msvideo avi; + + # Serving `.ico` image files with a different media type + # prevents Internet Explorer from displaying then as images: + # https://github.com/h5bp/html5-boilerplate/commit/37b5fec090d00f38de64b591bcddcb205aadf8ee + + image/x-icon cur ico; + + + # Microsoft Office + + application/msword doc; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx; + application/vnd.openxmlformats-officedocument.presentationml.presentation pptx; + + + # Web fonts + + application/font-woff woff; + application/font-woff2 woff2; + application/vnd.ms-fontobject eot; + + # Browsers usually ignore the font media types and simply sniff + # the bytes to figure out the font type. + # https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern + # + # However, Blink and WebKit based browsers will show a warning + # in the console if the following font types are served with any + # other media types. + + application/x-font-ttf ttc ttf; + font/opentype otf; + + + # Other + + application/java-archive ear jar war; + application/mac-binhex40 hqx; + application/octet-stream bin deb dll dmg exe img iso msi msm msp safariextz; + application/pdf pdf; + application/postscript ai eps ps; + application/rtf rtf; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/vnd.wap.wmlc wmlc; + application/x-7z-compressed 7z; + application/x-bb-appworld bbaw; + application/x-bittorrent torrent; + application/x-chrome-extension crx; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-opera-extension oex; + application/x-perl pl pm; + application/x-pilot pdb prc; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert crt der pem; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xslt+xml xsl; + application/zip zip; + text/css css cssgzip; + text/csv csv; + text/html htm html shtml htmlgzip; + text/markdown md; + text/mathml mml; + text/plain txt; + text/vcard vcard vcf; + text/vnd.rim.location.xloc xloc; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/vtt vtt; + text/x-component htc; + +} + +open_file_cache max=1000 inactive=3600s; +open_file_cache_valid 60s; +open_file_cache_min_uses 1; +open_file_cache_errors on; + +# do we get better performance if we disable this feature +# rather than resending the entire file? +# if_modified_since off; + +sendfile on; +sendfile_max_chunk 1m; + +tcp_nopush on; +tcp_nodelay on; + +gzip on; +gzip_types text/plain text/xml text/css + text/comma-separated-values + application/javascript + text/javascript application/x-javascript + application/atom+xml; \ No newline at end of file