diff --git a/.buildinfo b/.buildinfo index deb2aeb..42702cb 100644 --- a/.buildinfo +++ b/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: b29d8ecfd0099551dc86d61c1fd86fab +config: a4bba1f98e0467e98e20dbc81feea1b1 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/_sources/aws/AWS Config Files.md.txt b/_sources/aws/AWS Config Files.md.txt new file mode 100644 index 0000000..85f2d9f --- /dev/null +++ b/_sources/aws/AWS Config Files.md.txt @@ -0,0 +1,1130 @@ +## Specify 7 Docker Config Example + +### Example defaults.env + +``` +DATABASE_HOST=10.133.58.98 +DATABASE_PORT=3306 +MASTER_NAME=master +MASTER_PASSWORD=master_password +SECRET_KEY=secret_key +ASSET_SERVER_URL=https://assets1.specifycloud.org/web_asset_store.xml +ASSET_SERVER_KEY=asset_server_key +REPORT_RUNNER_HOST=10.133.58.98 +REPORT_RUNNER_PORT=8080 +CELERY_BROKER_URL=redis://redis/0 +CELERY_RESULT_BACKEND=redis://redis/1 +LOG_LEVEL=WARNING +SP7_DEBUG=false +``` + +### Example docker-compose.yml + +```yml +version: '3.7' + +services: + + + + sp7demofish-eu: + image: specifyconsortium/specify7-service:v7 + command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"] + init: true + restart: unless-stopped + volumes: + - "specify6801:/opt/Specify:ro" + - "static-files-sp7demofish-eu:/volumes/static-files" + + env_file: defaults.env + environment: + - DATABASE_NAME=sp7demofish_eu + - ASSET_SERVER_COLLECTION=sp7demofish_eu + + + - ASSET_SERVER_URL=https://demo-assets.specifycloud.org/web_asset_store.xml + + + + sp7demofish-eu-worker: + image: specifyconsortium/specify7-service:v7 + command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q sp7demofish_eu + init: true + restart: unless-stopped + volumes: + - "specify6801:/opt/Specify:ro" + - "static-files-sp7demofish-eu:/volumes/static-files" + env_file: defaults.env + environment: + - DATABASE_NAME=sp7demofish_eu + - ASSET_SERVER_COLLECTION=sp7demofish_eu + + + - ASSET_SERVER_URL=https://demo-assets.specifycloud.org/web_asset_store.xml + + + + + rjb-madrid: + image: specifyconsortium/specify7-service:v7 + command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"] + init: true + restart: unless-stopped + volumes: + - "specify6803:/opt/Specify:ro" + - "static-files-rjb-madrid:/volumes/static-files" + + env_file: defaults.env + environment: + - DATABASE_NAME=rjb_madrid + - ASSET_SERVER_COLLECTION=rjb_madrid + + + - ASSET_SERVER_URL= + + - ASSET_SERVER_COLLECTION=Fanerogamia + + + + rjb-madrid-worker: + image: specifyconsortium/specify7-service:v7 + command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q rjb_madrid + init: true + restart: unless-stopped + volumes: + - "specify6803:/opt/Specify:ro" + - "static-files-rjb-madrid:/volumes/static-files" + env_file: defaults.env + environment: + - DATABASE_NAME=rjb_madrid + - ASSET_SERVER_COLLECTION=rjb_madrid + + + - ASSET_SERVER_URL= + + - ASSET_SERVER_COLLECTION=Fanerogamia + + + + + mcnb: + image: specifyconsortium/specify7-service:v7 + command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"] + init: true + restart: unless-stopped + volumes: + - "specify6803:/opt/Specify:ro" + - "static-files-mcnb:/volumes/static-files" + + env_file: defaults.env + environment: + - DATABASE_NAME=mcnb + - ASSET_SERVER_COLLECTION=mcnb + + + mcnb-worker: + image: specifyconsortium/specify7-service:v7 + command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q mcnb + init: true + restart: unless-stopped + volumes: + - "specify6803:/opt/Specify:ro" + - "static-files-mcnb:/volumes/static-files" + env_file: defaults.env + environment: + - DATABASE_NAME=mcnb + - ASSET_SERVER_COLLECTION=mcnb + + + + herb-rbge: + image: specifyconsortium/specify7-service:v7 + command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"] + init: true + restart: unless-stopped + volumes: + - "specify6803:/opt/Specify:ro" + - "static-files-herb-rbge:/volumes/static-files" + + - "./settings/herb-rbge-settings.py:/opt/specify7/settings/local_specify_settings.py:ro" + + env_file: defaults.env + environment: + - DATABASE_NAME=herb_rbge + - ASSET_SERVER_COLLECTION=herb_rbge + + + herb-rbge-worker: + image: specifyconsortium/specify7-service:v7 + command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q herb_rbge + init: true + restart: unless-stopped + volumes: + - "specify6803:/opt/Specify:ro" + - "static-files-herb-rbge:/volumes/static-files" + env_file: defaults.env + environment: + - DATABASE_NAME=herb_rbge + - ASSET_SERVER_COLLECTION=herb_rbge + + + + sandbox-rbge: + image: specifyconsortium/specify7-service:issue_388 + command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"] + init: true + restart: unless-stopped + volumes: + - "specify6803:/opt/Specify:ro" + - "static-files-sandbox-rbge:/volumes/static-files" + + - "./settings/sandbox-rbge-settings.py:/opt/specify7/settings/local_specify_settings.py:ro" + + env_file: defaults.env + environment: + - DATABASE_NAME=sandbox_rbge + - ASSET_SERVER_COLLECTION=sandbox_rbge + + + sandbox-rbge-worker: + image: specifyconsortium/specify7-service:issue_388 + command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q sandbox_rbge + init: true + restart: unless-stopped + volumes: + - "specify6803:/opt/Specify:ro" + - "static-files-sandbox-rbge:/volumes/static-files" + env_file: defaults.env + environment: + - DATABASE_NAME=sandbox_rbge + - ASSET_SERVER_COLLECTION=sandbox_rbge + + + + cryoarks-test: + image: specifyconsortium/specify7-service:v7 + command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"] + init: true + restart: unless-stopped + volumes: + - "specify6801:/opt/Specify:ro" + - "static-files-cryoarks-test:/volumes/static-files" + + env_file: defaults.env + environment: + - DATABASE_NAME=cryoarks_test + - ASSET_SERVER_COLLECTION=cryoarks_test + + + cryoarks-test-worker: + image: specifyconsortium/specify7-service:v7 + command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q cryoarks_test + init: true + restart: unless-stopped + volumes: + - "specify6801:/opt/Specify:ro" + - "static-files-cryoarks-test:/volumes/static-files" + env_file: defaults.env + environment: + - DATABASE_NAME=cryoarks_test + - ASSET_SERVER_COLLECTION=cryoarks_test + + + + eurl: + image: specifyconsortium/specify7-service:v7 + command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"] + init: true + restart: unless-stopped + volumes: + - "specify6803:/opt/Specify:ro" + - "static-files-eurl:/volumes/static-files" + + env_file: defaults.env + environment: + - DATABASE_NAME=eurl + - ASSET_SERVER_COLLECTION=eurl + + + eurl-worker: + image: specifyconsortium/specify7-service:v7 + command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q eurl + init: true + restart: unless-stopped + volumes: + - "specify6803:/opt/Specify:ro" + - "static-files-eurl:/volumes/static-files" + env_file: defaults.env + environment: + - DATABASE_NAME=eurl + - ASSET_SERVER_COLLECTION=eurl + + + + + specify6800: + image: specifyconsortium/specify6-service:6.8.00 + volumes: + - "specify6800:/volumes/Specify" + + specify6801: + image: specifyconsortium/specify6-service:6.8.01 + volumes: + - "specify6801:/volumes/Specify" + + specify6803: + image: specifyconsortium/specify6-service:6.8.03 + volumes: + - "specify6803:/volumes/Specify" + + + nginx: + image: nginx + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + + - "static-files-sp7demofish-eu:/volumes/static-files-sp7demofish-eu:ro" + + - "static-files-rjb-madrid:/volumes/static-files-rjb-madrid:ro" + + - "static-files-mcnb:/volumes/static-files-mcnb:ro" + + - "static-files-herb-rbge:/volumes/static-files-herb-rbge:ro" + + - "static-files-sandbox-rbge:/volumes/static-files-sandbox-rbge:ro" + + - "static-files-cryoarks-test:/volumes/static-files-cryoarks-test:ro" + + - "static-files-eurl:/volumes/static-files-eurl:ro" + + + + - "specify6800:/volumes/specify6800:ro" + + - "specify6801:/volumes/specify6801:ro" + + - "specify6803:/volumes/specify6803:ro" + + + - "./nginx.conf:/etc/nginx/conf.d/default.conf:ro" + - "/etc/letsencrypt:/etc/letsencrypt:ro" + - "/etc/ssl/certs/dhparam.pem:/etc/ssl/certs/dhparam.pem:ro" + - "/var/www:/var/www:ro" + + redis: + restart: unless-stopped + image: redis:6.0 + +volumes: + + specify6800: + + specify6801: + + specify6803: + + + + static-files-sp7demofish-eu: + + static-files-rjb-madrid: + + static-files-mcnb: + + static-files-herb-rbge: + + static-files-sandbox-rbge: + + static-files-cryoarks-test: + + static-files-eurl: +``` + +### Example nginx.conf + +``` +server { + listen 80; + server_name sp7demofish-eu.*; + + # The LetsEncrypt pass-though. + location /.well-known/ { + root /var/www/sp7demofish-eu/; + } + + + location / { + return 301 https://$host$request_uri; + } +} + +server { + # This stanza defines the HTTPS end point. + listen 443 ssl; + server_name sp7demofish-eu.*; + + ssl_certificate /etc/letsencrypt/live/sp7demofish-eu.specifycloud.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/sp7demofish-eu.specifycloud.org/privkey.pem; + + # from https://cipherli.st/ + # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + ssl_ecdh_curve secp384r1; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + # Disable preloading HSTS for now. You can use the commented out header line that includes + # the "preload" directive if you understand the implications. + #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # The LetsEncrypt pass-though. I'm not sure if this is needed + # on HTTPS side, but I'm including it just in case. + location /.well-known/ { + root /var/www/sp7demofish-eu/; + } + + + root /usr/share/nginx; + + location /static/ { + root /volumes; + rewrite ^/static/config/(.*)$ /specify6801/config/$1 break; + rewrite ^/static/depository/(.*)$ /static-files-sp7demofish-eu/depository/$1 break; + rewrite ^/static/(.*)$ /static-files-sp7demofish-eu/frontend-static/$1 break; + } + + location / { + resolver 127.0.0.11 valid=30s; + set $backend "http://sp7demofish-eu:8000"; + proxy_pass $backend; + 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_read_timeout 600s; + client_max_body_size 0; + } +} + + +server { + listen 80; + server_name rjb-madrid.*; + + # The LetsEncrypt pass-though. + location /.well-known/ { + root /var/www/rjb-madrid/; + } + + + location / { + return 301 https://$host$request_uri; + } +} + +server { + # This stanza defines the HTTPS end point. + listen 443 ssl; + server_name rjb-madrid.*; + + ssl_certificate /etc/letsencrypt/live/rjb-madrid.specifycloud.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/rjb-madrid.specifycloud.org/privkey.pem; + + # from https://cipherli.st/ + # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + ssl_ecdh_curve secp384r1; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + # Disable preloading HSTS for now. You can use the commented out header line that includes + # the "preload" directive if you understand the implications. + #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # The LetsEncrypt pass-though. I'm not sure if this is needed + # on HTTPS side, but I'm including it just in case. + location /.well-known/ { + root /var/www/rjb-madrid/; + } + + + root /usr/share/nginx; + + location /static/ { + root /volumes; + rewrite ^/static/config/(.*)$ /specify6803/config/$1 break; + rewrite ^/static/depository/(.*)$ /static-files-rjb-madrid/depository/$1 break; + rewrite ^/static/(.*)$ /static-files-rjb-madrid/frontend-static/$1 break; + } + + location / { + resolver 127.0.0.11 valid=30s; + set $backend "http://rjb-madrid:8000"; + proxy_pass $backend; + 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_read_timeout 600s; + client_max_body_size 0; + } +} + + +server { + listen 80; + server_name mcnb.*; + + # The LetsEncrypt pass-though. + location /.well-known/ { + root /var/www/mcnb/; + } + + + location / { + return 301 https://$host$request_uri; + } +} + +server { + # This stanza defines the HTTPS end point. + listen 443 ssl; + server_name mcnb.*; + + ssl_certificate /etc/letsencrypt/live/mcnb.specifycloud.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/mcnb.specifycloud.org/privkey.pem; + + # from https://cipherli.st/ + # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + ssl_ecdh_curve secp384r1; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + # Disable preloading HSTS for now. You can use the commented out header line that includes + # the "preload" directive if you understand the implications. + #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # The LetsEncrypt pass-though. I'm not sure if this is needed + # on HTTPS side, but I'm including it just in case. + location /.well-known/ { + root /var/www/mcnb/; + } + + + root /usr/share/nginx; + + location /static/ { + root /volumes; + rewrite ^/static/config/(.*)$ /specify6803/config/$1 break; + rewrite ^/static/depository/(.*)$ /static-files-mcnb/depository/$1 break; + rewrite ^/static/(.*)$ /static-files-mcnb/frontend-static/$1 break; + } + + location / { + resolver 127.0.0.11 valid=30s; + set $backend "http://mcnb:8000"; + proxy_pass $backend; + 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_read_timeout 600s; + client_max_body_size 0; + } +} + + +server { + listen 80; + server_name herb-rbge.*; + + # The LetsEncrypt pass-though. + location /.well-known/ { + root /var/www/herb-rbge/; + } + + + location / { + return 301 https://$host$request_uri; + } +} + +server { + # This stanza defines the HTTPS end point. + listen 443 ssl; + server_name herb-rbge.*; + + ssl_certificate /etc/letsencrypt/live/herb-rbge.specifycloud.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/herb-rbge.specifycloud.org/privkey.pem; + + # from https://cipherli.st/ + # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + ssl_ecdh_curve secp384r1; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + # Disable preloading HSTS for now. You can use the commented out header line that includes + # the "preload" directive if you understand the implications. + #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # The LetsEncrypt pass-though. I'm not sure if this is needed + # on HTTPS side, but I'm including it just in case. + location /.well-known/ { + root /var/www/herb-rbge/; + } + + + root /usr/share/nginx; + + location /static/ { + root /volumes; + rewrite ^/static/config/(.*)$ /specify6803/config/$1 break; + rewrite ^/static/depository/(.*)$ /static-files-herb-rbge/depository/$1 break; + rewrite ^/static/(.*)$ /static-files-herb-rbge/frontend-static/$1 break; + } + + location / { + resolver 127.0.0.11 valid=30s; + set $backend "http://herb-rbge:8000"; + proxy_pass $backend; + 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_read_timeout 600s; + client_max_body_size 0; + } +} + + +server { + listen 80; + server_name sandbox-rbge.*; + + # The LetsEncrypt pass-though. + location /.well-known/ { + root /var/www/sandbox-rbge/; + } + + + location / { + return 301 https://$host$request_uri; + } +} + +server { + # This stanza defines the HTTPS end point. + listen 443 ssl; + server_name sandbox-rbge.*; + + ssl_certificate /etc/letsencrypt/live/sandbox-rbge.specifycloud.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/sandbox-rbge.specifycloud.org/privkey.pem; + + # from https://cipherli.st/ + # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + ssl_ecdh_curve secp384r1; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + # Disable preloading HSTS for now. You can use the commented out header line that includes + # the "preload" directive if you understand the implications. + #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # The LetsEncrypt pass-though. I'm not sure if this is needed + # on HTTPS side, but I'm including it just in case. + location /.well-known/ { + root /var/www/sandbox-rbge/; + } + + + root /usr/share/nginx; + + location /static/ { + root /volumes; + rewrite ^/static/config/(.*)$ /specify6803/config/$1 break; + rewrite ^/static/depository/(.*)$ /static-files-sandbox-rbge/depository/$1 break; + rewrite ^/static/(.*)$ /static-files-sandbox-rbge/frontend-static/$1 break; + } + + location / { + resolver 127.0.0.11 valid=30s; + set $backend "http://sandbox-rbge:8000"; + proxy_pass $backend; + 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_read_timeout 600s; + client_max_body_size 0; + } +} + + +server { + listen 80; + server_name cryoarks-test.*; + + # The LetsEncrypt pass-though. + location /.well-known/ { + root /var/www/cryoarks-test/; + } + + + + root /usr/share/nginx; + + location /static/ { + root /volumes; + rewrite ^/static/config/(.*)$ /specify6801/config/$1 break; + rewrite ^/static/depository/(.*)$ /static-files-cryoarks-test/depository/$1 break; + rewrite ^/static/(.*)$ /static-files-cryoarks-test/frontend-static/$1 break; + } + + location / { + resolver 127.0.0.11 valid=30s; + set $backend "http://cryoarks-test:8000"; + proxy_pass $backend; + 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_read_timeout 600s; + client_max_body_size 0; + } +} + + +server { + listen 80; + server_name eurl.*; + + # The LetsEncrypt pass-though. + location /.well-known/ { + root /var/www/eurl/; + } + + + location / { + return 301 https://$host$request_uri; + } +} + +server { + # This stanza defines the HTTPS end point. + listen 443 ssl; + server_name eurl.*; + + ssl_certificate /etc/letsencrypt/live/eurl.specifycloud.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/eurl.specifycloud.org/privkey.pem; + + # from https://cipherli.st/ + # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + ssl_ecdh_curve secp384r1; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + # Disable preloading HSTS for now. You can use the commented out header line that includes + # the "preload" directive if you understand the implications. + #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # The LetsEncrypt pass-though. I'm not sure if this is needed + # on HTTPS side, but I'm including it just in case. + location /.well-known/ { + root /var/www/eurl/; + } + + + root /usr/share/nginx; + + location /static/ { + root /volumes; + rewrite ^/static/config/(.*)$ /specify6803/config/$1 break; + rewrite ^/static/depository/(.*)$ /static-files-eurl/depository/$1 break; + rewrite ^/static/(.*)$ /static-files-eurl/frontend-static/$1 break; + } + + location / { + resolver 127.0.0.11 valid=30s; + set $backend "http://eurl:8000"; + proxy_pass $backend; + 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_read_timeout 600s; + client_max_body_size 0; + } +} +``` + +## Asset Server + +Using the GitHub repo at https://github.com/specify/web-asset-server + +### Example settings.py file + +``` +# Sample Specify web asset server settings. + +# Turns on bottle.py debugging, module reloading and printing some +# information to console. +DEBUG = False + +# This secret key is used to generate authentication tokens for requests. +# The same key must be set in the Web Store Attachment Preferences in Specify. +# A good source for key value is: https://www.grc.com/passwords.htm +# Set KEY to None to disable security. This is NOT recommended since doing so +# will allow anyone on the internet to use the attachment server to store +# arbitrary files. +KEY = 'tnhercbrhtktanehul.dukb' + +# Auth token timestamp must be within this many seconds of server time +# in order to be considered valid. This prevents replay attacks. +# Set to None to disable time validation. +TIME_TOLERANCE = 600 + +# Set this to True to require authentication for downloads in addition +# to uploads and deletes. Static file access, if enabled, is not +# affected by this setting. +REQUIRE_KEY_FOR_GET = False + +# This is required for use with the Web Portal. +# Enables the 'getfileref' and '/static/...' URLs. +ALLOW_STATIC_FILE_ACCESS = True + +# These values are interpolated into the web_asset_store.xml resource +# so the client knows how to talk to the server. +HOST = 'assets1.specifycloud.org' +PORT = 8080 + +SERVER_NAME = HOST +SERVER_PORT = PORT + +# Port the development test server should listen on. +DEVELOPMENT_PORT = PORT + +# Map collection names to directories. Set to None to store +# everything in the same originals and thumbnail directories. This is +# recommended unless some provision is made to allow attachments for +# items scoped above collections to be found. + +COLLECTION_DIRS = { + # 'COLLECTION_NAME': 'DIRECTORY_NAME', + 'beaty': 'beaty', + 'ncslgfungi': 'ncslgfungi', + 'ocpc': 'ocpc', + 'stlawu': 'stlawu', + 'asnhcmammals': 'asnhcmammals', + 'dwuspiders': 'dwuspiders', + + # 'nha': 'nha', + # 'cornellmammals': 'cornellmammals', + # 'cornellherps': 'cornellherps', + # 'cornellfishes': 'cornellfishes', + 'uprm_invcol': 'uprm_invcol', + 'nybg_vanuatu': 'nybg_vanuatu', + 'demobirds': 'demobirds', + 'emoryherbarium': 'emoryherbarium', + 'uw_geo_collections': 'uw_geo_collections', + 'keherbariumvascular': 'keherbariumvascular', + # 'cornellbirds': 'cornellbirds', + # 'critters': 'critters', + 'williherb': 'williherb', + 'sbccprofantmuseum': 'sbccprofantmuseum', + 'josc': 'josc', + 'ccnhm': 'ccnhm', + 'lsumz_mammals': 'lsumz_mammals', + # 'pripaleo': 'pripaleo', + 'Invertebrate Paleontology': 'pripaleo', + 'usgs_nmnhpaleontologycollections': 'usgs_nmnhpaleontologycollections', + 'vimsfish': 'vimsfish', + 'vimsworkshop': 'vimsworkshop', + 'ilstu': 'ilstu', + 'wespalcoll': 'wespalcoll', + 'royfungi': 'royfungi', + 'isua': 'isua', + 'chagasecohealth': 'chagasecohealth', + 'plu': 'plu', + 'uconnverts': 'uconnverts', + 'kelichens': 'kelichens', + 'lakeforestbiology': 'lakeforestbiology', + 'mariamitchell': 'mariamitchell', + 'gree': 'gree', + 'pennstatefishmuseum': 'pennstatefishmuseum', + 'dukebryo': 'dukebryo', + 'bishopmuseum': 'bishopmuseum', + 'mndragonfly': 'mndragonfly', + 'rbge': 'rbge', + 'cudiatoms': 'cudiatoms', + 'fwri': 'fwri', + 'sdinverts': 'sdinverts', + 'shellmuseum': 'shellmuseum', + 'ansppaleo': 'ansppaleo', + 'osuorton': 'osuorton', + 'ciscollections': 'ciscollections', + 'cuic': 'cuic', + 'lsumz_fishes': 'lsumz_fishes', + 'calvertmarinemuseum': 'calvertmarinemuseum', + + 'mcnb': 'mcnb', + 'Invertebrate non-arthropod': 'mcnb', + + 'lsumz_herps': 'lsumz_herps', + 'uwfc': 'uwfc', + + 'Oregon State Ichthyology Collection': 'os', + 'os': 'os', + 'unitec': 'unitec', + 'herb_rbge': 'herb_rbge', + 'Herbarium RBGE': 'herb_rbge', + 'sandbox_rbge': 'sandbox_rbge', + + 'nbm_mnb': 'nbm_mnb', + "Amphibian Reptile Sightings": 'nbm_mnb', + + 'purdueherbaria': 'purdueherbaria', + 'montrealinsectarium': 'montrealinsectarium', + + 'workshop': 'workshop', + 'ptrm': 'ptrm', + 'herb_umass': 'herb_umass', + 'unsm_vp': 'unsm_vp', + 'morpalo': 'morpalo', + 'aafc_aac': 'aafc_aac', + 'spnhc': 'spnhc' +} + +# COLLECTION_DIRS = None + +# Base directory for all attachments. +BASE_DIR = '/home/specify/attachments' + +# Originals and thumbnails are stored in separate directories. +THUMB_DIR = 'thumbnails' +ORIG_DIR = 'originals' + +# Set of mime types that the server will try to thumbnail. +CAN_THUMBNAIL = {'image/jpeg', 'image/gif', 'image/png', 'image/tiff', 'application/pdf'} + +# What HTTP server to use for stand-alone operation. +SERVER = 'paste' # Requires python-paste package. Fast, and seems to work good. +#SERVER = 'wsgiref' # For testing. Requires no extra packages. +``` + +### Possible Dockerfile for ECS Build (Unfinished) + +```dockerfile +FROM arm64v8/ubuntu:18.04 AS run-asset-server + +RUN apt-get update && apt-get -y install --no-install-recommends \ + ghostscript \ + imagemagick \ + python3.8 \ + python3.8-dev \ + python3.8-pip \ + python3-venv \ + git \ + nginx \ + certbot \ + awscli \ + s3fs \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN groupadd -g 999 specify && \ + useradd -r -u 999 -g specify specify + +RUN mkdir -p /home/specify && chown specify.specify /home/specify + +USER specify +WORKDIR /home/specify + +COPY --chown=specify:specify requirements.txt . + +RUN python3.8 -m venv ve && ve/bin/pip install --no-cache-dir -r requirements.txt + +COPY --chown=specify:specify *.py views ./ + +RUN mkdir -p /home/specify/attachments/ + +RUN echo \ + "import os" \ + "\nSERVER = 'paste'" \ + "\nSERVER_NAME = os.environ['SERVER_NAME']" \ + "\nSERVER_PORT = int(os.getenv('SERVER_PORT', 8080))" \ + "\nKEY = os.environ['ATTACHMENT_KEY']" \ + "\nDEBUG = os.getenv('DEBUG_MODE', 'false').lower() == 'true'" \ + >> settings.py + +# Configure AWS +RUN aws configure set aws_access_key_id "aws_access_key_id" && \ +aws configure set aws_secret_access_key "aws_secret_access_key" && \ +aws configure set default.region us-east-1 && \ +aws configure set default.output json + +# S3 Mounting +RUN s3fs specify-cloud /home/specify/attachments/ + +EXPOSE 8080 +CMD ve/bin/python server.py +``` + +### Asset nginx web-server config + +``` +server { + # HTTP access is needed for Specify 6. It will not work with HTTPS. + listen 80 default_server; + server_name assets1.specifycloud.org; + client_max_body_size 0; + + # The LetsEncrypt certificate mechanism places a nonce + # challenge at this location to prove we have control of the + # domain. Mapping it to a location in the filesystem allows us + # to easily use their auto renew system. + location /.well-known/ { + root /var/www/; + } + + # The web_asset_store.xml resource must be proxied to the + # actual server so that it gets the correct timestamp headers. + # We do a string substitution on the response to make the links + # it defines point to this proxy. + location = /web_asset_store.xml { + proxy_pass http://localhost:8080/web_asset_store.xml; + sub_filter 'http://assets1.specifycloud.org:8080' 'http://assets1.specifycloud.org'; + sub_filter_once off; + sub_filter_types text/xml; + } + + # All other requests are passed to the actual asset server + # unchanged. + location / { + proxy_pass http://localhost:8080/; + } +} + +server { + # This stanza defines the HTTPS end point. + listen 443 ssl default_server; + server_name assets1.specifycloud.org; + client_max_body_size 0; + + ssl_certificate /etc/letsencrypt/live/assets1.specifycloud.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/assets1.specifycloud.org/privkey.pem; + + # from https://cipherli.st/ + # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + ssl_ecdh_curve secp384r1; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + # Disable preloading HSTS for now. You can use the commented out header line that includes + # the "preload" directive if you understand the implications. + #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # The LetsEncrypt pass-though. I'm not sure if this is needed + # on HTTPS side, but I'm including it just in case. + location /.well-known/ { + root /var/www/; + } + + # This is the same as the above, except the links get rewritten + # to use HTTPS in addition to changing the port. + location = /web_asset_store.xml { + proxy_pass http://localhost:8080/web_asset_store.xml; + sub_filter 'http://assets1.specifycloud.org:8080' 'https://assets1.specifycloud.org'; + sub_filter_once off; + sub_filter_types text/xml; + } + + # Everything else is just passed through. + location / { + proxy_pass http://localhost:8080/; + } +} +``` \ No newline at end of file diff --git a/_sources/aws/AWS Infrastructure Notes.md.txt b/_sources/aws/AWS Infrastructure Notes.md.txt new file mode 100644 index 0000000..e69de29 diff --git a/_sources/aws/AWS Specify Asset Server Setup.md.txt b/_sources/aws/AWS Specify Asset Server Setup.md.txt new file mode 100644 index 0000000..09c12e4 --- /dev/null +++ b/_sources/aws/AWS Specify Asset Server Setup.md.txt @@ -0,0 +1,395 @@ +## EC2 Non-Dockerized Build +```bash +#!/bin/bash + +sudo apt update; +sudo apt upgrade -y; +sudo apt-get -y install --no-install-recommends \ + python3-venv \ + python3.8 \ + python3.8-dev \ + python3-pip \ + imagemagick \ + ghostscript \ + git \ + nginx \ + certbot \ + authbind \ + s3fs \ + awscli; + +# python 3.6 +#sudo apt update +#sudo apt install build-essential checkinstall zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev; +#wget https://www.python.org/ftp/python/3.6.15/Python-3.6.15.tgz; +#tar -xf Python-3.6.15.tgz; +#cd Python-3.6.15 && ./configure --enable-optimizations; +#make -j$(nproc); +#sudo make altinstall; +#python3.6 --version; + +# python 3.6 install with apt +sudo apt install software-properties-common; +sudo add-apt-repository ppa:deadsnakes/ppa; +sudo apt update; +sudo apt install python3.6; +sudo apt-get install python3.6-distutils; + +# install pip3.6 +#wget https://bootstrap.pypa.io/pip/3.6/get-pip.py; +python3.6 -m venv --without-pip ve; +source ve/bin/activate; +wget https://bootstrap.pypa.io/get-pip.py; +#wget https://bootstrap.pypa.io/pip/3.5/get-pip.py +#deactivate; + +# activate python3.6 venv +sudo apt install -y python3-virtualenv; +python3.6 -m venv myenv; +source myenv/bin/activate; +pip install --no-cache-dir -r requirements.txt; +#deactivate; + +# TLS dependencies +sudo apt-get -y install --no-install-recommends \ + certbot \ + python3-certbot-nginx \ + software-properties-common; + +# Configure AWS +aws configure set aws_access_key_id "ACCESS_KEY"; +aws configure set aws_secret_access_key "ACCESS_KEY_SECRET"; +aws configure set default.region us-east-1; +aws configure set default.output json; + +# Import attachment files +#mkdir attachments; +#aws s3 cp s3://specify-cloud/assets-server/attachments/ ~/attachments --recursive; + +# S3 Mounting +mkdir attachments; +s3fs specify-cloud /assets-server/attachments/; + +# Clone asset server repo +git clone https://github.com/specify/web-asset-server.git; +cd ~/web-asset-server; +git checkout arm-build; + +# Build python web asset server +python3.8 -m venv ve; +sudo ve/bin/pip install --no-cache-dir -r requirements.txt +#sudo pip install -r requirements.txt; + +# Port config +# not needed when running with nginx +#sudo apt-get install authbind; +#touch 80; +#chmod u+x 80; +#sudo mv 80 /etc/authbind/byport; + +# Create SystemD service +sudo cat > /etc/systemd/system/web-asset-server.service << EOF +[Unit] +Description=Specify Web Asset Server +Wants=network.target + +[Service] +User=ubuntu +WorkingDirectory=/home/ubuntu/web-asset-server +ExecStart=/home/ubuntu/web-asset-server/ve/bin/python /home/ubuntu/web-asset-server/server.py +Restart=always + +[Install] +WantedBy=multi-user.target + +EOF + +sudo systemctl daemon-reload; +sudo systemctl enable web-asset-server.service; +sudo systemctl start web-asset-server.service; +sudo systemctl status web-asset-server.service; + +# nginx +# sudo vim etc/nginx/sites-enabled/assets.conf +sudo rm -f /etc/nginx/sites-enabled/default; +sudo nginx -t; +sudo /etc/init.d/nginx reload; + +# S3 Mounting +mount -o discard,defaults,noatime /dev/disk/by-id/scsi-0DO_Volume_volume-nyc1-01 /mnt/volume-nyc1-01 + +# TODO: EFS Mounting + +# Certbot TLS config +sudo mkdir /var/www/.well-known; +sudo certbot --nginx -d assets-test.specifycloud.org -d assets-test.specifycloud.org; +sudo ls -la /etc/letsencrypt/live/assets-test.specifycloud.org; +#openssl dhparam -out /etc/nginx/dhparam.pem 4096; +sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096; #2048 or 1024 +sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 1024; +# add https server config to nginx assets. + +# Edit +``` + +/etc/systemd/system/web-asset-server.service -> +``` +[Unit] +Description=Specify Web Asset Server +Wants=network.target + +[Service] +User=ubuntu +WorkingDirectory=/home/ubuntu/web-asset-server +ExecStart=/usr/bin/ubuntu/web-asset-server/ve/usr/bin/python /home/ubuntu/web-asset-server/server.py +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +settings.py -> +```python +# Sample Specify web asset server settings. + +# Turns on bottle.py debugging, module reloading and printing some +# information to console. +DEBUG = True + +# This secret key is used to generate authentication tokens for requests. +# The same key must be set in the Web Store Attachment Preferences in Specify. +# A good source for key value is: https://www.grc.com/passwords.htm +# Set KEY to None to disable security. This is NOT recommended since doing so +# will allow anyone on the internet to use the attachment server to store +# arbitrary files. +#KEY = 'tnhercbrhtktanehul.dukb' +KEY = 'test_attachment_key' + +# Auth token timestamp must be within this many seconds of server time +# in order to be considered valid. This prevents replay attacks. +# Set to None to disable time validation. +TIME_TOLERANCE = 600 + +# Set this to True to require authentication for downloads in addition +# to uploads and deletes. Static file access, if enabled, is not +# affected by this setting. +REQUIRE_KEY_FOR_GET = False + +# This is required for use with the Web Portal. +# Enables the 'getfileref' and '/static/...' URLs. +ALLOW_STATIC_FILE_ACCESS = True + +# These values are interpolated into the web_asset_store.xml resource +# so the client knows how to talk to the server. +#HOST = 'localhost' +HOST = 'assets-test.specifycloud.org' +PORT = 8080 +#PORT = 80 + +SERVER_NAME = HOST +SERVER_PORT = PORT + +# Port the development test server should listen on. +DEVELOPMENT_PORT = PORT + +# Map collection names to directories. Set to None to store +# everything in the same originals and thumbnail directories. This is +# recommended unless some provision is made to allow attachments for +# items scoped above collections to be found. + +# COLLECTION_DIRS = { +# # 'COLLECTION_NAME': 'DIRECTORY_NAME', +# 'KUFishvoucher': 'Ichthyology', +# 'KUFishtissue': 'Ichthyology', +# } + +COLLECTION_DIRS = { + 'herb_rbge': 'herb_rbge', + 'KUFishvoucher': 'sp7demofish', + 'KUFishtissue': 'sp7demofish', +} + +# Base directory for all attachments. +#BASE_DIR = '/home/specify/attachments/' +BASE_DIR = '/home/ubuntu/attachments/' + +# Originals and thumbnails are stored in separate directories. +THUMB_DIR = 'thumbnails' +ORIG_DIR = 'originals' + +# Set of mime types that the server will try to thumbnail. +CAN_THUMBNAIL = {'image/jpeg', 'image/gif', 'image/png', 'image/tiff', 'application/pdf'} + +# What HTTP server to use for stand-alone operation. +# SERVER = 'paste' # Requires python-paste package. Fast, and seems to work good. +SERVER = 'wsgiref' # For testing. Requires no extra packages. +``` + +/etc/nginx/sites-enabled/assets.conf from the aasets1.specifycloud.org- -> +``` +# Nginx configuration for supplying an HTTPS end point for the web +# asset server. The asset server is running on the same system +# (demo-assets.specifycloud.org) on port 8080 meaning it can run +# without root privileges and without using authbind. Nginx proxies +# HTTP requests on port 80 and HTTPS requests on port 443 to the +# underlying asset server. It also rewrites the web_asset_store.xml +# response to cause subsequent request to go through the proxy. + +server { + # HTTP access is needed for Specify 6. It will not work with HTTPS. + listen 80 default_server; + server_name assets1.specifycloud.org; + client_max_body_size 0; + + # The LetsEncrypt certificate mechanism places a nonce + # challenge at this location to prove we have control of the + # domain. Mapping it to a location in the filesystem allows us + # to easily use their auto renew system. + location /.well-known/ { + root /var/www/; + } + + # The web_asset_store.xml resource must be proxied to the + # actual server so that it gets the correct timestamp headers. + # We do a string substitution on the response to make the links + # it defines point to this proxy. + location = /web_asset_store.xml { + proxy_pass http://localhost:8080/web_asset_store.xml; + sub_filter 'http://assets1.specifycloud.org:8080' 'http://assets1.specifycloud.org'; + sub_filter_once off; + sub_filter_types text/xml; + } + + # All other requests are passed to the actual asset server + # unchanged. + location / { + proxy_pass http://localhost:8080/; + } +} + +server { + # This stanza defines the HTTPS end point. + listen 443 ssl default_server; + server_name assets1.specifycloud.org; + client_max_body_size 0; + + ssl_certificate /etc/letsencrypt/live/assets1.specifycloud.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/assets1.specifycloud.org/privkey.pem; + + # from https://cipherli.st/ + # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + ssl_ecdh_curve secp384r1; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + # Disable preloading HSTS for now. You can use the commented out header line that includes + # the "preload" directive if you understand the implications. + #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # The LetsEncrypt pass-though. I'm not sure if this is needed + # on HTTPS side, but I'm including it just in case. + location /.well-known/ { + root /var/www/; + } + + # This is the same as the above, except the links get rewritten + # to use HTTPS in addition to changing the port. + location = /web_asset_store.xml { + proxy_pass http://localhost:8080/web_asset_store.xml; + sub_filter 'http://assets1.specifycloud.org:8080' 'https://assets1.specifycloud.org'; + sub_filter_once off; + sub_filter_types text/xml; + } + + # Everything else is just passed through. + location / { + proxy_pass http://localhost:8080/; + } +} +``` + +/etc/letsencrypt/renewal/assets1.specifycloud.org.conf -> +``` +# renew_before_expiry = 30 days +cert = /etc/letsencrypt/live/assets1.specifycloud.org/cert.pem +privkey = /etc/letsencrypt/live/assets1.specifycloud.org/privkey.pem +chain = /etc/letsencrypt/live/assets1.specifycloud.org/chain.pem +fullchain = /etc/letsencrypt/live/assets1.specifycloud.org/fullchain.pem +version = 1.9.0 +archive_dir = /etc/letsencrypt/archive/assets1.specifycloud.org + +# Options and defaults used in the renewal process +[renewalparams] +authenticator = webroot +account = a563615cc912ed3d7a3edfede09d6760 +post_hook = systemctl reload nginx +server = https://acme-v02.api.letsencrypt.org/directory +[[webroot_map]] +assets1.specifycloud.org = /var/www +``` + +/etc/ssl/certs/dhparam.pem from assets1.specofycloud.org-> +``` +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEAlcFKsIuFylwX47jxqbNT0wSVD6ifznsMcti8f7T+zaQQNr84IYIM +pNTT9E6SrVkkJg2u1nGScNqj5lArXvrda6zL66T8WmkFFrGfNW7RYCQ3vpg6BpGs +dJ3+HtWYDNoMbeCrDyMz1DDfX/3OWblTTZRbjpvn/tEgTAn3DexP/QkE9E2c1AUX +Mf/07vWpZ7giemaNgaME3fHDKyReNhTpfg1eDKypUUhEmr+PJmWQ9LQBc12LyXOP +DaFwAJUrqwEqrQP5fEQdOMdh522RwuD2/fPeXTukQHI8gUuMjk652aeLOcn1Ufhy +/KbbV6TJi7wS5F3HVaNXGOLMsHq+CywOCwIBAg== +-----END DH PARAMETERS----- +``` + + +swiss asset server password: xD5dakesktkxceb + + +## EC2 Non-docker build shell script + +```bash +#!/bin/bash + +sudo apt update; +sudo apt upgrade -y; +sudo apt-get -y install --no-install-recommends \ + python3-venv \ + python3.8 \ + python3.8-dev \ + python3-pip \ + imagemagick \ + ghostscript \ + git \ + nginx \ + certbot \ + authbind \ + s3fs \ + awscli; + +# Clone asset server repo +git clone https://github.com/specify/web-asset-server.git; +cd ~/web-asset-server; +git checkout arm-build; + +# python 3.6 install with apt +sudo apt install -y software-properties-common; +sudo add-apt-repository ppa:deadsnakes/ppa; +sudo apt update; +sudo apt install -y python3.6; +sudo apt-get install -y python3.6-distutils; +pip install --no-cache-dir -r requirements.txt; + +``` + +## Docker Build diff --git a/_sources/aws/Add New User Insance to Specify Cloud.md.txt b/_sources/aws/Add New User Insance to Specify Cloud.md.txt new file mode 100644 index 0000000..e403eaa --- /dev/null +++ b/_sources/aws/Add New User Insance to Specify Cloud.md.txt @@ -0,0 +1,65 @@ +Example for UNSM_VP + +1. Create Database + 1. look through the sql file for issues and do test upload to local database + 2. mysql -umaster -p'master' -e "create database unsm_vp;" + 3. mysql -umaster -p'master' unsm_vp < unsm_vp.sql + 4. may need to run `grant all privileges on eurl.* to 'master'@'%';` if master doesn't have access `flush privileges;` +2. DNS Registtration: + 1. Login to Dreamhost, select Websites -> Manage Websites + 2. For specifycloud.org, select DNS + 3. Add CNAME record in the style of the other users. + 4. `unsm-vp` points to `na-specify7-1.specifycloud.org.` + 4. Wait at least 10 minutes for domain to circulate. + 5. For the ku servers, request the dns CNAME record to bitech@ku.edu +3. Config + 1. Add to spcloudserver.json + 2. Make sure to add https: false + 3. su specify -c make + 4. docker-compose up -d + 5. docker-compose restart nginx (actually just reload is fine here: docker exec -it specifycloud_nginx_1 nginx -s reload) + 6. check url +4. Add SSL + 1. mkdir /var/www/unsm-vp + 2. certbot --webroot -w /var/www/unsm-vp -d unsm-vp.specifycloud.org certonly + 3. certbot certificates + 4. Remove https: false from spcloudserver.json + 5. su specify -c make + 6. docker-compose up -d + 7. docker-compose restart (maybe just reload instead) + 8. check url + 9. note: after an ssl certificate renewal -> docker exec -it specifycloud_nginx_1 nginx -s reload + 1. For automatic nginx reloading on certificate renewal create /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh `#!/bin/bash docker exec -it specifycloud_nginx_1 nginx -s reload` + 2. crontab -e; and then add the line "0 3 * * 0,2,4,6 docker exec specifycloud_nginx_1 nginx -s reload" + 3. crontab -l to list cronjobs +5. Database Backup + 1. ssh into biprdsp6ap.cc.ku.edu + 2. sudo su - spcloudbackup + 3. Add unsm_vp into the file /home/spcloudbackup/backup_specify_cloud.py +6. Asset Server + 1. ssh into asset + 2. Add unsm_vp directory in attachments directory 'su specify -c "mkdir attachments/unsm_vp"' + 3. Add unsm_vp to /home/specify/new-asset-server/settings.py + 4. systemctl restart web-asset-server.service +7. Updown + 1. Add url: unsm-vp.specifycloud.org/context/system_info.json + 2. Add alias: unsm-vp + + +Restarting the Database droplet, handle mariadb failing to restart: +- mysqld --tc-heuristic-recover=ROLLBACK +- systemctl start mariadb.service + +Fixing an instance by restarting it: +```bash +sudo docker exec -it specifycloud_nginx_1 nginx -s reload; +sudo docker stop client client-worker; +sudo docker compose up -d; +sudo docker exec -it specifycloud_nginx_1 nginx -s reload; +``` + +Add ssh key: +```bash +vim .ssh/authorized_keys +sudo systemctl reload sshd +``` diff --git a/_sources/aws/Asset Server Config.md.txt b/_sources/aws/Asset Server Config.md.txt new file mode 100644 index 0000000..aafe166 --- /dev/null +++ b/_sources/aws/Asset Server Config.md.txt @@ -0,0 +1,197 @@ +Here is an asset server configuration example with the latest image `specifyconsortium/specify-asset-service:connection_fix`. +I spun up a server with the following config and got it working `assets-docker.specifycloud.org/web_asset_store.xml` +Make sure to configure your dns record to the IP address of your server. + +docker-compose.yml -> +``` +version: '3.7' +services: + nginx: + image: nginx:alpine + ports: + - "80:80" + + volumes: + - "./nginx.conf:/etc/nginx/conf.d/default.conf:ro" + + asset-server: + restart: unless-stopped + image: specifyconsortium/specify-asset-service:connection_fix + init: true + volumes: + # Store all attachments outside the container, in a separate volume + # - "attachments:/home/specify/attachments" + - "/home/ubuntu/attachments:/home/specify/attachments" + environment: + # Replace this with the URL at which asset server would be publicly available + SERVER_NAME: assets-docker.specifycloud.org + SERVER_PORT: 8080 + # SERVER: paste + ATTACHMENT_KEY: qwertyasdfghzxcvbnlmnop + DEBUG_MODE: false + COLLECTION_DIRS: > + { + 'sp7demofish':'sp7demofish', + 'KUFishvoucher':'KUFishvoucher', + 'KUFishtissue':'KUFishtissue' + } + BASE_DIR: /home/ubuntu/attachments + +volumes: + attachments: # the asset-servers attachment files +``` + +nginx.conf -> +``` +server { + listen 80 default_server; + server_name assets-docker.specifycloud.org; + client_max_body_size 0; + + location /.well-known/ { + root /var/www/assets-docker/; + } + + location = /web_asset_store.xml { + proxy_pass http://asset-server:8080/web_asset_store.xml; + sub_filter 'http://assets-docker.specifycloud.org:8080' 'http://assets-docker.specifycloud.org'; + sub_filter_once off; + sub_filter_types text/xml; + } + + location / { + proxy_pass http://asset-server:8080/; + } +} +``` + +Then to get the asset server working with https and connected with Specify7, I created certificates with certbot, and then used the follow config + +certbot bash commands -> +``` +# Make sure the nginx server is running +sudo mkdir /var/www; +sudo mkdir /var/www/assets-docker; +sudo certbot --webroot -w /var/www/assets-docker -d assets-docker.specifycloud.org certonly; +``` + +docker-compose.yml -> +``` +version: '3.7' +services: + nginx: + image: nginx:alpine + ports: + - "80:80" + - "443:443" + + volumes: + - "./nginx.conf:/etc/nginx/conf.d/default.conf:ro" + + asset-server: + restart: unless-stopped + image: specifyconsortium/specify-asset-service:connection_fix + init: true + volumes: + # Store all attachments outside the container, in a separate volume + # - "attachments:/home/specify/attachments" + - "/home/ubuntu/attachments:/home/specify/attachments" + environment: + # Replace this with the URL at which asset server would be publicly available + SERVER_NAME: assets-docker.specifycloud.org + SERVER_PORT: 8080 + # SERVER: paste + ATTACHMENT_KEY: qwertyasdfghzxcvbnlmnop + DEBUG_MODE: false + HTTPS: true + COLLECTION_DIRS: > + { + 'sp7demofish':'sp7demofish', + 'KUFishvoucher':'KUFishvoucher', + 'KUFishtissue':'KUFishtissue' + } + BASE_DIR: /home/ubuntu/attachments + +volumes: + attachments: # the asset-servers attachment files +``` + +nginx.conf -> +``` +server { + listen 80 default_server; + server_name assets-docker.specifycloud.org; + client_max_body_size 0; + + location /.well-known/ { + root /var/www/assets-docker/; + } + + location = /web_asset_store.xml { + proxy_pass http://asset-server:8080/web_asset_store.xml; + sub_filter 'http://assets-docker.specifycloud.org:8080' 'http://assets-docker.specifycloud.org'; + sub_filter_once off; + sub_filter_types text/xml; + } + + location / { + proxy_pass http://asset-server:8080/; + } +} + +server { + # This stanza defines the HTTPS end point. + listen 443 ssl default_server; + server_name assets-docker.specifycloud.org; + client_max_body_size 0; + + ssl_certificate /etc/letsencrypt/live/assets-docker.specifycloud.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/assets-docker.specifycloud.org/privkey.pem; + + # from https://cipherli.st/ + # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + ssl_ecdh_curve secp384r1; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + # Disable preloading HSTS for now. You can use the commented out header line that includes + # the "preload" directive if you understand the implications. + #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # The LetsEncrypt pass-though. I'm not sure if this is needed + # on HTTPS side, but I'm including it just in case. + location /.well-known/ { + root /var/www/assets-docker/; + } + + # This is the same as the above, except the links get rewritten + # to use HTTPS in addition to changing the port. + location = /web_asset_store.xml { + proxy_pass http://asset-server:8080/web_asset_store.xml; + sub_filter 'http://assets-docker.specifycloud.org:8080' 'https://assets-docker.specifycloud.org'; + sub_filter_once off; + sub_filter_types text/xml; + } + + # Everything else is just passed through. + location / { + proxy_pass http://asset-server:8080/; + } +} +``` + +Make sure to set `- ASSET_SERVER_URL=https://assets-docker.specifycloud.org/web_asset_store.xml` in your specify7 and specify7-worker docker containers + +Let me know if the new image and config works for you! diff --git a/_sources/aws/Asset Server Setup.md.txt b/_sources/aws/Asset Server Setup.md.txt new file mode 100644 index 0000000..34a98da --- /dev/null +++ b/_sources/aws/Asset Server Setup.md.txt @@ -0,0 +1,329 @@ +## EC2 Non-Dockerized Build +```bash +#!/bin/bash + +sudo apt update; +sudo apt upgrade -y; +sudo apt-get -y install --no-install-recommends \ + python3-venv \ + python3.8 \ + python3.8-dev \ + python3-pip \ + imagemagick \ + ghostscript \ + git \ + nginx \ + certbot \ + authbind \ + s3fs \ + awscli; + +# python 3.6 install with apt +sudo apt install software-properties-common; +sudo add-apt-repository ppa:deadsnakes/ppa; +sudo apt update; +sudo apt install python3.6; +sudo apt-get install python3.6-distutils; + +# install pip3.6 +#wget https://bootstrap.pypa.io/pip/3.6/get-pip.py; +python3.6 -m venv --without-pip ve; +source ve/bin/activate; +wget https://bootstrap.pypa.io/get-pip.py; +#wget https://bootstrap.pypa.io/pip/3.5/get-pip.py + +# activate python3.6 venv +sudo apt install python3-virtualenv; +python3.6 -m venv myenv; +source myenv/bin/activate; +pip install --no-cache-dir -r requirements.txt; + +# TLS dependencies +sudo apt-get -y install --no-install-recommends \ + certbot \ + python3-certbot-nginx \ + software-properties-common; + +# Import attachment files +#mkdir attachments; +#aws s3 cp s3://specify-cloud/assets-server/attachments/ ~/attachments --recursive; + +# S3 Mounting +mkdir attachments; +s3fs specify-cloud /assets-server/attachments/; + +# Clone asset server repo +git clone https://github.com/specify/web-asset-server.git; +cd ~/web-asset-server; +git checkout arm-build; + +# Build python web asset server +python3.8 -m venv ve; +sudo ve/bin/pip install --no-cache-dir -r requirements.txt +#sudo pip install -r requirements.txt; + +# Port config if needed +# not needed when running with nginx +#sudo apt-get install authbind; +#touch 80; +#chmod u+x 80; +#sudo mv 80 /etc/authbind/byport; + +# Create SystemD service +sudo cat > /etc/systemd/system/web-asset-server.service << EOF +[Unit] +Description=Specify Web Asset Server +Wants=network.target + +[Service] +User=ubuntu +WorkingDirectory=/home/ubuntu/web-asset-server +ExecStart=/home/ubuntu/web-asset-server/ve/bin/python /home/ubuntu/web-asset-server/server.py +Restart=always + +[Install] +WantedBy=multi-user.target + +EOF + +sudo systemctl daemon-reload; +sudo systemctl enable web-asset-server.service; +sudo systemctl start web-asset-server.service; +sudo systemctl status web-asset-server.service; + +# nginx +# sudo vim etc/nginx/sites-enabled/assets.conf +sudo rm -f /etc/nginx/sites-enabled/default; +sudo nginx -t; +sudo /etc/init.d/nginx reload; + +# S3 Mounting +mount -o discard,defaults,noatime /dev/disk/by-id/scsi-0DO_Volume_volume-nyc1-01 /mnt/volume-nyc1-01 + +# Certbot TLS config +sudo mkdir /var/www/.well-known; +sudo certbot --nginx -d assets-test.specifycloud.org -d assets-test.specifycloud.org; +sudo ls -la /etc/letsencrypts/live/assets-test.specifycloud.org; +sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096; #2048 or 1024 +sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 1024; +# add https server config to nginx assets.conf +``` + +/etc/systemd/system/web-asset-server.service -> +``` +[Unit] +Description=Specify Web Asset Server +Wants=network.target + +[Service] +User=ubuntu +WorkingDirectory=/home/ubuntu/web-asset-server +ExecStart=/usr/bin/ubuntu/web-asset-server/ve/usr/bin/python /home/ubuntu/web-asset-server/server.py +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +settings.py -> +```python +# Sample Specify web asset server settings. + +# Turns on bottle.py debugging, module reloading and printing some +# information to console. +DEBUG = True + +# This secret key is used to generate authentication tokens for requests. +# The same key must be set in the Web Store Attachment Preferences in Specify. +# A good source for key value is: https://www.grc.com/passwords.htm +# Set KEY to None to disable security. This is NOT recommended since doing so +# will allow anyone on the internet to use the attachment server to store +# arbitrary files. +KEY = 'test_attachment_key' + +# Auth token timestamp must be within this many seconds of server time +# in order to be considered valid. This prevents replay attacks. +# Set to None to disable time validation. +TIME_TOLERANCE = 600 + +# Set this to True to require authentication for downloads in addition +# to uploads and deletes. Static file access, if enabled, is not +# affected by this setting. +REQUIRE_KEY_FOR_GET = False + +# This is required for use with the Web Portal. +# Enables the 'getfileref' and '/static/...' URLs. +ALLOW_STATIC_FILE_ACCESS = True + +# These values are interpolated into the web_asset_store.xml resource +# so the client knows how to talk to the server. +#HOST = 'localhost' +HOST = 'assets-test.specifycloud.org' +PORT = 8080 +#PORT = 80 + +SERVER_NAME = HOST +SERVER_PORT = PORT + +# Port the development test server should listen on. +DEVELOPMENT_PORT = PORT + +# Map collection names to directories. Set to None to store +# everything in the same originals and thumbnail directories. This is +# recommended unless some provision is made to allow attachments for +# items scoped above collections to be found. + +# COLLECTION_DIRS = { +# # 'COLLECTION_NAME': 'DIRECTORY_NAME', +# 'KUFishvoucher': 'Ichthyology', +# 'KUFishtissue': 'Ichthyology', +# } + +COLLECTION_DIRS = { + 'herb_rbge': 'herb_rbge', + 'KUFishvoucher': 'sp7demofish', + 'KUFishtissue': 'sp7demofish', +} + +# Base directory for all attachments. +#BASE_DIR = '/home/specify/attachments/' +BASE_DIR = '/home/ubuntu/attachments/' + +# Originals and thumbnails are stored in separate directories. +THUMB_DIR = 'thumbnails' +ORIG_DIR = 'originals' + +# Set of mime types that the server will try to thumbnail. +CAN_THUMBNAIL = {'image/jpeg', 'image/gif', 'image/png', 'image/tiff', 'application/pdf'} + +# What HTTP server to use for stand-alone operation. +# SERVER = 'paste' # Requires python-paste package. Fast, and seems to work good. +SERVER = 'wsgiref' # For testing. Requires no extra packages. +``` + +/etc/nginx/sites-enabled/assets.conf from the aasets1.specifycloud.org- -> +``` +# Nginx configuration for supplying an HTTPS end point for the web +# asset server. The asset server is running on the same system +# (demo-assets.specifycloud.org) on port 8080 meaning it can run +# without root privileges and without using authbind. Nginx proxies +# HTTP requests on port 80 and HTTPS requests on port 443 to the +# underlying asset server. It also rewrites the web_asset_store.xml +# response to cause subsequent request to go through the proxy. + +server { + # HTTP access is needed for Specify 6. It will not work with HTTPS. + listen 80 default_server; + server_name assets1.specifycloud.org; + client_max_body_size 0; + + # The LetsEncrypt certificate mechanism places a nonce + # challenge at this location to prove we have control of the + # domain. Mapping it to a location in the filesystem allows us + # to easily use their auto renew system. + location /.well-known/ { + root /var/www/; + } + + # The web_asset_store.xml resource must be proxied to the + # actual server so that it gets the correct timestamp headers. + # We do a string substitution on the response to make the links + # it defines point to this proxy. + location = /web_asset_store.xml { + proxy_pass http://localhost:8080/web_asset_store.xml; + sub_filter 'http://assets1.specifycloud.org:8080' 'http://assets1.specifycloud.org'; + sub_filter_once off; + sub_filter_types text/xml; + } + + # All other requests are passed to the actual asset server + # unchanged. + location / { + proxy_pass http://localhost:8080/; + } +} + +server { + # This stanza defines the HTTPS end point. + listen 443 ssl default_server; + server_name assets1.specifycloud.org; + client_max_body_size 0; + + ssl_certificate /etc/letsencrypt/live/assets1.specifycloud.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/assets1.specifycloud.org/privkey.pem; + + # from https://cipherli.st/ + # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; + ssl_ecdh_curve secp384r1; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + resolver 8.8.8.8 8.8.4.4 valid=300s; + resolver_timeout 5s; + # Disable preloading HSTS for now. You can use the commented out header line that includes + # the "preload" directive if you understand the implications. + #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains"; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + + ssl_dhparam /etc/ssl/certs/dhparam.pem; + + # The LetsEncrypt pass-though. I'm not sure if this is needed + # on HTTPS side, but I'm including it just in case. + location /.well-known/ { + root /var/www/; + } + + # This is the same as the above, except the links get rewritten + # to use HTTPS in addition to changing the port. + location = /web_asset_store.xml { + proxy_pass http://localhost:8080/web_asset_store.xml; + sub_filter 'http://assets1.specifycloud.org:8080' 'https://assets1.specifycloud.org'; + sub_filter_once off; + sub_filter_types text/xml; + } + + # Everything else is just passed through. + location / { + proxy_pass http://localhost:8080/; + } +} +``` + +/etc/letsencrypt/renewal/assets1.specifycloud.org.conf -> +``` +# renew_before_expiry = 30 days +cert = /etc/letsencrypt/live/assets1.specifycloud.org/cert.pem +privkey = /etc/letsencrypt/live/assets1.specifycloud.org/privkey.pem +chain = /etc/letsencrypt/live/assets1.specifycloud.org/chain.pem +fullchain = /etc/letsencrypt/live/assets1.specifycloud.org/fullchain.pem +version = 1.9.0 +archive_dir = /etc/letsencrypt/archive/assets1.specifycloud.org + +# Options and defaults used in the renewal process +[renewalparams] +authenticator = webroot +account = a563615cc912ed3d7a3edfede09d6760 +post_hook = systemctl reload nginx +server = https://acme-v02.api.letsencrypt.org/directory +[[webroot_map]] +assets1.specifycloud.org = /var/www +``` + +/etc/ssl/certs/dhparam.pem from assets1.specofycloud.org-> +``` +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEAlcFKsIuFylwX47jxqbNT0wSVD6ifznsMcti8f7T+zaQQNr84IYIM +pNTT9E6SrVkkJg2u1nGScNqj5lArXvrda6zL66T8WmkFFrGfNW7RYCQ3vpg6BpGs +dJ3+HtWYDNoMbeCrDyMz1DDfX/3OWblTTZRbjpvn/tEgTAn3DexP/QkE9E2c1AUX +Mf/07vWpZ7giemaNgaME3fHDKyReNhTpfg1eDKypUUhEmr+PJmWQ9LQBc12LyXOP +DaFwAJUrqwEqrQP5fEQdOMdh522RwuD2/fPeXTukQHI8gUuMjk652aeLOcn1Ufhy +/KbbV6TJi7wS5F3HVaNXGOLMsHq+CywOCwIBAg== +-----END DH PARAMETERS----- +``` \ No newline at end of file diff --git a/_sources/aws/Client Migration Notes.md.txt b/_sources/aws/Client Migration Notes.md.txt new file mode 100644 index 0000000..39311d1 --- /dev/null +++ b/_sources/aws/Client Migration Notes.md.txt @@ -0,0 +1,18 @@ +The migration from Digital Ocean to Amazon Web Services has our hosting provider will increase Specify's reliability and security. + +Since MySQL 5.7 is now deprecated, we are now using MariaDB v10.11. In the future we hope to upgrade to PostgreSQL + +The database backups will be further improved by storing daily backups for a month + +For connecting Specify6 to the database via ssh, two things have changed, there will be no root login to the server, and the IP address for the database. The Linux user name will be the same as in your institution's url, but with underscore `_` replacing dashes `-` + +Here are the new database IPs (they have been updated in the wiki as well https://github.com/specify/specify7/wiki/Specify-6-Remote-Access): +NA: `172.31.96.36` +EU: `172.31.16.73` +CA: `172.31.35.249` + +Here is an example +On Linux/Mac`ssh -N -L3307:172.31.96.36:3306 institution_id@na-db-1.specifycloud.org` +On Windows PuTTY target `C:\Program Files\PuTTY\putty.exe" -ssh -i C:\users\your_user\private_key_.ppk institution_id@eu-db-1.specifycloud.org -L 3307:172.31.16.73:3306 -N` + +For now, you will log into the database as `master` with the same previous passwords, but we will soon be creating database user for each institution. \ No newline at end of file diff --git a/_sources/aws/EC2 & RDS Specify7 Setup.md.txt b/_sources/aws/EC2 & RDS Specify7 Setup.md.txt new file mode 100644 index 0000000..dfac7b4 --- /dev/null +++ b/_sources/aws/EC2 & RDS Specify7 Setup.md.txt @@ -0,0 +1,212 @@ +These are the detailed instructions to get Specify7 up and running on an EC2 instance. This is a temporary solution for deployment, with an ECS solution coming in the future. These instruction require access to the `specify/docker-compositions` private repo. For this kind of AWS deployment, it is easiest to use the `specifycloud` deployment, even with just one client instance. I used the `specifycloud` deployment for my AWS EC2 instance, but if you want to include things like the asset server, you can create a separate instance of the asset server deployment (instructions here https://github.com/specify/web-asset-server). You could also try to use the `all-in-one` deployment instead. + +Specify7 was not originally designed to be cloud native, but since I have joined the team and picked up the project, I have been working on developing a modern cloud native solution. I hope to soon have a set of CDK scripts that will make deployment to ECS, S3, and RDS simple and scalable. + +## Spin Up EC2 Instance + +- For the Amazon Machine Image (AMI), choose the default Ubuntu Server (Ubuntu Server 22.04 LTS) with the 64-bit x86 architecture. +- For the instance type, start with the t3.small or t3.medium. Upgrade to a better instance if needed. +- In Network Settings, make sure to allow HTTP and HTTPS traffic. +- Setup your Key Pair for logging in. +- Launch Instance. + +## S3 Setup + +Create an S3 bucket for storing some static files. + +You can either upload the GitHub repo to s3, or you can clone the repo directly in the EC2 instance. If you are cloning inside the EC2 instance, note that you will need a GitHub access token to clone the private repo, instructions here https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token. Make sure when configuring the token that you allow it to read from private repos, otherwise cloning will not work. + +Upload your SQL file to the S3 bucket for the initial database upload. + +There are two config files that you need to create for specifycloud: + +spcloudservers.json -> +```json +{ + "servers": { + "archireef": { + "sp7": "edge", + "sp6": "specify6803", + "https": false, + "database": "archireef" + } + }, + "decommissioned": [], + "sp6versions": { + "specify6803": "6.8.03" + } +} +``` +Your need to edit the version of Specify6 for your database's schema version (latest is 6.8.03). For sp7, input the GitHub tag that you want to use in the repo (latest is always edge). For your first run, keep https set to false. Change it to true only after you have setup SSL/TLS. The database value is the name of your database. + +defaults.env -> +``` +DATABASE_HOST= +DATABASE_PORT=3306 +MASTER_NAME=master +MASTER_PASSWORD= +SECRET_KEY=temp +ASSET_SERVER_URL=https://assets1.specifycloud.org/web_asset_store.xml +ASSET_SERVER_KEY= +REPORT_RUNNER_HOST=10.132.218.32 +REPORT_RUNNER_PORT=8080 +CELERY_BROKER_URL=redis://redis/0 +CELERY_RESULT_BACKEND=redis://redis/1 +LOG_LEVEL=WARNING +SP7_DEBUG=false +``` +You will only need to edit the DATABASE_HOST, MASTER_PASSWORD, ASSET_SERVER_URL, and ASSET_SERVER_KEY + +## Configure Specify7 + +Once the EC2 instance is up and running, ssh into the EC2 instance. +- Download your AWS access key +- `chmod 600 YOUR-AWS-ACCESS-KEY.pem` +- On the EC2 Instance webpage, click connect, choose ssh, and copy the ssh command. Make sure you are in the directory with the access key. +- You can try using AWS CloudShell if you don't want to ssh from your local machine. + +Here are the commands to run to setup Specify7. Note that for the AWS access key, you can use the AWS Secrets manager if you prefer. Also note that after some of these commands, SystemD services will restart. When that happens, just press enter once or twice to you get back to the shell. + +```bash +# Avoid services restarting during apt updates +sudo sed -i "s/#\$nrconf{kernelhints} = -1;/\$nrconf{kernelhints} = -1;/g" /etc/needrestart/needrestart.conf; + +# Run apt installs +sudo apt update; +sudo apt upgrade -y; +sudo apt install -y apt-transport-https ca-certificates git gh curl software-properties-common wget python3-pip awscli mysql-client j2cli; + +# Configure AWS +aws configure set aws_access_key_id "YOUR_AWS_ACCESS_KEY_ID"; +aws configure set aws_secret_access_key "YOUR_AWS_ACCESS_KEY_SECRET"; +aws configure set default.region us-east-1; +aws configure set default.output json; + +# Install Docker +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg; +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null; +sudo apt update; +apt-cache policy docker-ce; +sudo apt install -y docker-ce; +docker --version; # Check to make sure it installed correctly + +# Install docker compose +mkdir .docker; +mkdir .docker/cli-plugins; +curl -L "https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-linux-$(uname -m)" -o ~/.docker/cli-plugins/docker-compose; +chmod +x ~/.docker/cli-plugins/docker-compose; +docker compose version; # Check to make sure it installed correctly + +# Copy files from S3, only need to copy the file that you need +aws s3 cp s3://specify-cloud/repo-snapshots/docker-compositions/ ./ --recursive; +aws s3 cp s3://specify-cloud/config/github-access-key.txt ./; +aws s3 cp s3://specify-cloud/config/spcloudservers.json ./specifycloud/; +aws s3 cp s3://specify-cloud/config/defaults.env ./specifycloud/; + +# Optional, need to clone if you did not copy repo from S3 +# Clone private repo, need to use github cli +gh auth login --with-token < github-access-key.txt; +gh auth setup-git; +gh repo clone https://github.com/specify/docker-compositions.git; +``` + +## Spin Up RDS Instance + +- For choosing the RDS engine type, I have been experimenting with using Aurora MySQL 5.7 to handle dynamic scaling, but the default option for Specify7 on AWS RDS is MariaDB 10.6.10. +- For Templates, I would suggest starting with Dev/Test instead of production. +- Setup Master password. +- For testing, I would suggest using either the `db.t3.medium` or `db.t3.large` instances. You might feel the need to upgrade to the `db.m5.large` instance or something similar. +- Decide on storage size to accommodate your data size. The general purpose SSD storage option should suffice, but you can try the provisioned IOPS SSD option if needed. +- Important, make sure in the connectivity options, select "Connect to an EC2 compute resource" and add your EC2 instance. +- Create database. + +## Upload Data + +Get the database host from the AWS RDS webpage. In the connectivity tab of your RDS instance, copy the test marked 'Endpoint'. Then run the following commands to upload your data to the database. + +```bash +# Database setup +cd specifycloud; +mkdir seed-databases; +aws s3 cp s3://specify-cloud/seed-database/archireef.sql ./seed-databases/; +mysql --host specify-cloud-swiss-demo-database-1.c9qlnkmf2lfl.eu-central-2.rds.amazonaws.com --port 3306 -u master -p -e "create database archireef;"; +mysql --host specify-cloud-swiss-demo-database-1.c9qlnkmf2lfl.eu-central-2.rds.amazonaws.com --port 3306 -u master -p specify < ./seed-databases/archireef.sql; +rm -f ./seed-databases/archireef.sql; +``` + +## Deploy Specify7 + +Here are the remaining commands on the EC2 instance to setup the database connection and start running Specify7. Replace the S3 and RDS urls with your own. + +```bash +# Run Specify Network +cd specifycloud; +make; +sudo docker compose up -d; +``` + +Go to the AWS console webpage and navigate to the EC2 instance page. For your instance, select the networking tab, then copy the the public IPv4 DNS address and open if in you r browser. Note to change the url from https to http if you haven't setup SSL/TLS yet (https notes in the Concluding notes section). + +## Docker Container Dependencies + +Containers: +- specify7 + - django application of specify7 + - depends on connections to the webpack, specify7-worker, redis, asset-server, nginx, and report-runner containers + - depends on files being created by the specify6 container +- webpack + - holds static files for the front-end + - the nginx server depends on this container + - independent container +- specify7-worker + - python celery worker that runs long running tasks for specify7 + - depends on connection to specify7 container +- redis + - acts as the job queue broker between the specify7 and specify7-worker containers + - could possible be replaced by AWS SQS in the future + - depends on connections to both the specify7 and specify7-worker containers +- asset-server + - serves the assets to the specify7 container + - independent container +- specify6 + - generates files and database schema version for specify7 + - the conainter stops after the files are created + - independent container + - this container can be removed if the static files are moved to S3 and then copied into the specify7 container on startup +- nginx + - webserver for specify7 django application + - depends on connection to specify7 and webpack containers + - could possibly be replaced with AWS CloudFront +- report-runner + - java application the generates reports for specify7 + - independent container + +## Concluding Notes + +You can use 'AWS Certificate Manager' or the tool 'CertBot' for setting up HTTPS SSL/TLS certificates. Who will need to setup a domain name through AWS Route 53. You will probably want to setup an elastic ip address for the EC2 instance through AWS as well. +Here are the instructions for using certbot: +```bash +sudo apt install -y certbot python3-certbot-apache; +sudo mkdir /var/www/archireef; +sudo certbot --webroot -w /var/www/archireef -d certonly; +certbot certificates; +vim spcloudservers.json # change line to `https: true` +make; +sudo docker compose up -d; +sudo docker exec -it specifycloud_nginx_1 nginx -s reload; # Maybe needed +``` + +You might want to extend the time of your ssh connection: +SSH Client: vim ~/.ssh/config +``` +Host * + ServerAliveInterval 20 + #TCPKeepAlive no +``` +SSH Server: sudo vim /etc/ssh/sshd_config +``` +ClientAliveInterval 1200 +ClientAliveCountMax 3 +``` +Then run `sudo systemctl reload sshd` + diff --git a/_sources/aws/Hybrid Asset Server Setup Instructions.md.txt b/_sources/aws/Hybrid Asset Server Setup Instructions.md.txt new file mode 100644 index 0000000..07ee41e --- /dev/null +++ b/_sources/aws/Hybrid Asset Server Setup Instructions.md.txt @@ -0,0 +1,83 @@ + +Instructions to setup an asset server with + +```bash +#!/bin/bash + +sudo apt update; +sudo apt upgrade -y; +sudo apt-get -y install --no-install-recommends \ + python3-venv \ + python3.8 \ + python3.8-dev \ + python3-pip \ + imagemagick \ + ghostscript \ + git \ + nginx \ + certbot \ + authbind \ + s3fs \ + awscli; + +# python 3.6 install with apt +sudo apt install software-properties-common; +sudo add-apt-repository ppa:deadsnakes/ppa; +sudo apt update; +sudo apt install python3.6; +sudo apt-get install python3.6-distutils; + +# install pip3.6 +#wget https://bootstrap.pypa.io/pip/3.6/get-pip.py; +python3.6 -m venv --without-pip ve; +source ve/bin/activate; +wget https://bootstrap.pypa.io/get-pip.py; +#wget https://bootstrap.pypa.io/pip/3.5/get-pip.py + +# activate python3.6 venv +sudo apt install python3-virtualenv; +python3.6 -m venv myenv; +source myenv/bin/activate; +pip install --no-cache-dir -r requirements.txt; + +# TLS dependencies +sudo apt-get -y install --no-install-recommends \ + certbot \ + python3-certbot-nginx \ + software-properties-common; + +# Import attachment files +#mkdir attachments; +#aws s3 cp s3://specify-cloud/assets-server/attachments/ ~/attachments --recursive; + +# S3 Mounting +mkdir attachments; +s3fs specify-cloud /assets-server/attachments/; + +# Clone asset server repo +git clone https://github.com/specify/web-asset-server.git; +cd ~/web-asset-server; +git checkout arm-build; + +# Build python web asset server +python3.8 -m venv ve; +sudo ve/bin/pip install --no-cache-dir -r requirements.txt +#sudo pip install -r requirements.txt; + +# Port config if needed +# not needed when running with nginx +#sudo apt-get install authbind; +#touch 80; +#chmod u+x 80; +#sudo mv 80 /etc/authbind/byport; + + + +# Certbot TLS config +sudo mkdir /var/www/.well-known; +sudo certbot --nginx -d assets-test.specifycloud.org -d assets-test.specifycloud.org; +sudo ls -la /etc/letsencrypts/live/assets-test.specifycloud.org; +sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096; #2048 or 1024 +sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 1024; +# add https server config to nginx assets.conf +``` diff --git a/_sources/aws/KU IT Notes.md.txt b/_sources/aws/KU IT Notes.md.txt new file mode 100644 index 0000000..378f4c7 --- /dev/null +++ b/_sources/aws/KU IT Notes.md.txt @@ -0,0 +1,104 @@ +## New certificate on biprdsp7wbdb.cc.ku.edu server + +Form to request new certificate: https://kuit.service-now.com/nav_to.do?uri=%2Fcom.glideapp.servicecatalog_cat_item_view.do%3Fv%3D1%26sysparm_id%3D78fee42fdb2a8850162673e1ba96195b%26sysparm_link_parent%3D322911f41bec6490cf2d337e034bcb23%26sysparm_catalog%3De0d08b13c3330100c8b837659bba8fb4%26sysparm_catalog_view%3Dcatalog_default%26sysparm_view%3Dcatalog_default + +generate CSR string: +```bash +openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr +``` + +with configuration: +``` +Country Name (2 letter code) [XX]:US +State or Province Name (full name) []:Kansas +Locality Name (eg, city) [Default City]:Lawrence +Organization Name (eg, company) [Default Company Ltd]:University of Kansas +Organizational Unit Name (eg, section) []:Specify +Common Name (eg, your name or your server's hostname) []:biimages.biodiversity.ku.edu +Email Address []:alec.white@ku.edu +A challenge password []: +An optional company name []: +``` + +verify configuration with `openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr` with output +``` +C = US, ST = Kansas, L = Lawrence, O = University of Kansas, OU = Specify, CN = biimages.biodiversity.ku.edu, emailAddress = alec.white@ku.edu +``` + +after receiving new certificate files +``` +biimages_biodiversity_ku_edu.cer +biimages_biodiversity_ku_edu_cert.cer +biimages.biodiversity.ku.edu.conf +biimages_biodiversity_ku_edu.crt +biimages_biodiversity_ku_edu_interm.cer +biimages_biodiversity_ku_edu.p7b +biimages_biodiversity_ku_edu.pem +``` + +generate 'fullchain.pem' file with concatenation +```bash +cat biimages_biodiversity_ku_edu.pem biimages_biodiversity_ku_edu_interm.cer > fullchain.pem +``` + +then run commands to copy files into proper locations (make sure the number is incremented ex. 40) +```bash +sudo cp server.key /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/privkey40.pem +sudo cp biimages_biodiversity_ku_edu.pem /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/cert40.pem; +sudo cp biimages_biodiversity_ku_edu_interm.cer /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/chain40.pem; +sudo cp fullchain.pem /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/fullchain40.pem; +``` + +then create symbolic links to where the nginx file looks for SSL files +```bash +sudo ln -sf /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/fullchain40.pem /etc/letsencrypt/live/biimages.biodiversity.ku.edu/fullchain.pem; +sudo ln -sf /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/privkey40.pem /etc/letsencrypt/live/biimages.biodiversity.ku.edu/privkey.pem; +sudo ln -sf /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/chain40.pem /etc/letsencrypt/live/biimages.biodiversity.ku.edu/chain.pem; +sudo ln -sf /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/cert40.pem /etc/letsencrypt/live/biimages.biodiversity.ku.edu/cert.pem; +``` + +here are the line in the '/etc/nginx/conf.d/web-asset-server.conf' nginx file +``` +server_name biimages.biodiversity.ku.edu; +ssl_certificate /etc/letsencrypt/live/biimages.biodiversity.ku.edu/fullchain.pem; +ssl_certificate_key /etc/letsencrypt/live/biimages.biodiversity.ku.edu/privkey.pem; +``` + +verify the key and cert are correct by making sure their hashes are the same +```bash +sudo openssl x509 -noout -modulus -in /etc/letsencrypt/live/biimages.biodiversity.ku.edu/cert.pem | openssl md5 +sudo openssl rsa -noout -modulus -in /etc/letsencrypt/live/biimages.biodiversity.ku.edu/privkey.pem | openssl md5 +``` + +restart nginx +```bash +sudo systemctl restart web-asset-server.service +sudo systemctl status web-asset-server.service +``` + +## web-portal certificate + +here are the lines in the `/etc/nginx/conf.d/webportal-nginx.conf` nginx file +``` +server_name collections.biodiversity.ku.edu; +ssl_certificate /home/specify/keystore/collections_biodiversity_ku_edu_cert.cer; +ssl_certificate_key /home/specify/keystore/collections_biodiversity_ku_edu.key; +``` + +```bash +cat collections_biodiversity_ku_edu.pem collections_biodiversity_ku_edu_interm.cer > fullchain.pem +``` + +```bash +sudo cp collections_biodiversity_ku_edu_cert.cer /home/specify/keystore/cert.pem +sudo cp ~/webportal-keys/webportal_server.key /home/specify/keystore/privkey.pem +sudo cp ~/webportal-keys/fullchain.pem /home/specify/keystore/fullchain.pem +``` + +```bash +sudo chown specify:bi-sp7access cert.pem; +sudo chown specify:bi-sp7access privkey.pem; +sudo chown specify:bi-sp7access fullchain.pem; +``` + + diff --git a/_sources/aws/Setup AWS Specify Cloud.md.txt b/_sources/aws/Setup AWS Specify Cloud.md.txt new file mode 100644 index 0000000..6c04b76 --- /dev/null +++ b/_sources/aws/Setup AWS Specify Cloud.md.txt @@ -0,0 +1,440 @@ +### Setup Aurora MySQL Database + +### Setup EC2 Server +EC2 Parameters: +- ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-arm64-server-20220131 +ami-0770bf1d6ae61c858 +Initial Commands: +```bash +#!/bin/bash + +sudo apt-get update; +sudo apt upgrade; +sudo apt install -y make python3-pip + +#sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose mysql-client; +#sudo apt install docker.io + +# Install Docker +sudo apt install -y apt-transport-https ca-certificates curl software-properties-common; +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg; +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null; +sudo apt update; +sudo apt -y install docker-ce; +docker --version; +sudo systemctl status docker; + +#sudo systemctl start docker.service +#sudo systemctl enable docker.service +#sudo systemctl status docker.service + +# Install docker compose +mkdir .docker +mkdir .docker/cli-plugins +curl -L "https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-linux-$(uname -m)" -o ~/.docker/cli-plugins/docker-compose; +chmod +x ~/.docker/cli-plugins/docker-compose; +docker compose version; + +# Install awscli +sudo apt -y install awscli; +aws configure; # See aws credentials at bottom of notes + +# Setup mysql datbase connection +sudo apt install -y mysql-client; + +# Copy files from S3 +aws s3 cp s3://specify-cloud/repo-snapshots/docker-compositions/ ./ --recursive + +# python setup +sudo apt install -y python3-pip; +python3 -m pip install j2cli; + +# Create config files for specifycloud +cd specifycloud; +vim spcloudservers.json +vim defaults.env +sudo apt install j2cli +make + +# ssh client-server alive settings +sudo echo -e "ClientAliveInterval 1200\nClientAliveCountMax 3" >> /etc/ssh/sshd_config +sudo systemctl reload sshd; + +#docker pull specifyconsortium/specify7-service:edge + +# su specify -c make +docker-compose up -d +``` + +SSH Client: vim ~/.ssh/config +``` +Host * + ServerAliveInterval 20 + #TCPKeepAlive no +``` +SSH Server: sudo vim /etc/ssh/sshd_config +``` +ClientAliveInterval 1200 +ClientAliveCountMax 3 +``` +Then run `sudo systemctl reload sshd` + +spcloudservers.json -> +```json +{ + "servers": { + "freshfish": { + "sp7": "edge", + "sp6": "specify6803", + "https": false, + "env": { + "ASSET_SERVER_URL": "https://demo-assets.specifycloud.org/web_asset_store.xml", + "ANONYMOUS_USER": "sp7demofish" + } + } + }, + "decommissioned": [], + "sp6versions": { + "specify6800": "6.8.00", + "specify6801": "6.8.01", + "specify6802": "6.8.02", + "specify6803": "6.8.03" + } +} +``` +defaults.env -> +``` +DATABASE_HOST=specifycloud-dev-database-1-instance-1.cqvncffkwz9t.us-east-1.rds.amazonaws.com +DATABASE_PORT=3306 +MASTER_NAME=master +MASTER_PASSWORD=mastermaster +SECRET_KEY=bogus +ASSET_SERVER_URL=https://assets1.specifycloud.org/web_asset_store.xml +ASSET_SERVER_KEY=tnhercbrhtktanehul.dukb +REPORT_RUNNER_HOST=10.132.218.32 +REPORT_RUNNER_PORT=8080 +CELERY_BROKER_URL=redis://redis/0 +CELERY_RESULT_BACKEND=redis://redis/1 +LOG_LEVEL=WARNING +SP7_DEBUG=false +``` + + +### Info Misc. +aws credentials: +- username: `specify.user` +- password: `Specify-Cloud-aws-user` +- access key: ACCESS_KEY +- secret access key: ACCESS_KEY_SECRET +- default region: us-east-1 +- default output format: json + +AWS EC2 User data: +```bash +# Avoid services restarting during apt upgrade +sudo sed -i "s/#\$nrconf{kernelhints} = -1;/\$nrconf{kernelhints} = -1;/g" /etc/needrestart/needrestart.conf; +sudo sed -i "s/#\$nrconf{restart} = 'i';/\$nrconf{restart} = 'a';/g" /etc/needrestart/needrestart.conf; + +# Run apt installs +sudo apt update; +sudo apt upgrade -y; +sudo apt install -y apt-transport-https ca-certificates git gh curl software-properties-common wget python3-pip awscli mysql-client j2cli; + +# Configure AWS +aws configure set aws_access_key_id "ACCESS_KEY"; +aws configure set aws_secret_access_key "ACCESS_KEY_SECRET"; +aws configure set default.region us-east-1; +aws configure set default.output json; + +# Install Docker +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg; +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null; +sudo apt update; +apt-cache policy docker-ce; +sudo apt install -y docker-ce; +docker --version; + +# Install docker compose +mkdir .docker; +mkdir .docker/cli-plugins; +curl -L "https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-linux-$(uname -m)" -o ~/.docker/cli-plugins/docker-compose; +chmod +x ~/.docker/cli-plugins/docker-compose; +docker compose version; + +# Python setup +# python3 -m pip install j2cli; +# export PATH=$PATH:/home/ubuntu/.local/bin; +# sudo apt install j2cli; + +# Copy files from S3 +aws s3 cp s3://specify-cloud/repo-snapshots/docker-compositions/ ./ --recursive; +aws s3 cp s3://specify-cloud/repo-snapshots/spcloudservers.json ./specifycloud/; +aws s3 cp s3://specify-cloud/repo-snapshots/defaults.env ./specifycloud/; + +# Database setup +mkdir seed-databases; +aws s3 cp s3://specify-cloud/seed-database/specify.sql ./seed-databases/; +mysql --host specify-cloud-swiss-demo-database-1.c9qlnkmf2lfl.eu-central-2.rds.amazonaws.com --port 3306 -u master -p'mastermaster' -e "create database specify;"; +mysql --host specify-cloud-swiss-demo-database-1.c9qlnkmf2lfl.eu-central-2.rds.amazonaws.com --port 3306 -u master -p'mastermaster' specify < ./seed-databases/specify.sql; +rm -f ./seed-databases/specify.sql; + +# Configure Specify Network +cd specifycloud; +touch spcloudservers.json; +touch defaults.env; + +# Run Specify Network +make; +sudo docker compose up -d; + +# Certbot setup +sudo apt install certbot python3-certbot-apache; +sudo mkdir /var/www/sp7demofish; + +# Github clone private repo +ssh-keygen -t ed25519 -C "acwhite211@gmail.com"; +#ssh-keygen -t rsa -b 4096 -C "acwhite211@gmail.com"; + +# git clone repos +git clone https://github.com/specify/specify7.git; +git clone https://github.com/specify/specify6.git; +git clone https://github.com/specify/report-runner-service.git; +#git clone https://github.com/specify/web-asset-server.git; + +# Install nginx +sudo apt install -y nginx openjdk-8-jdk maven ant; +sudo ufw allow 'Nginx HTTP'; +sudo ufw status; +sudo update-alternatives --set java /usr/lib/jvm/java-8-openjdk-arm64/jre/bin/java; + +# Build without docker +cd specify6; +ant compile-nonmac; + +``` + +Database Prices: +- db.r5.large - 2 vCPUs - 16 gb ram - $0.24 per hour = $173.00 per month +- db.m5.large - 4vCPUs - 16 gb ram - $0.171 per hour = $123.10 per month +- db.t3.medium - 2vCPUs - 4 gb ram - $0.068 per hour = $49.00 per month +- db.t3.large - 2vCPUs - 8 gb ram - $0.136 per hour = $97.92 per month +- db.t3.xlarge - 4vCPUs - 16 gb ram - $0.272 per hour = $195.80 per month +- +db.t4g.medium - 2vCPUs - 4 gb ram - $0.065 per hour = $46.80 per month +- db.t4g.large - 2vCPUs - 8 gb ram - $0.129 per hour = $92.88 per month +Aurora v2 Prices: +- 1 ACU - 2 vCPUs - 2 gb ram - $0.12 per ACU hour = $86.40 per ACU month +Aurora v1 Prices: +- 1 ACU - 2 vCPUs - 2 gb ram - $0.06 per ACU hour = $43.29 per ACU month +EC2 Prices: +- t4g.nano - 2vCPUs - 0.5 gb ram - $0.0042 per hour = $3.02 per month +- t4g.micro - 2vCPUs - 1 gb ram - $0.0084 per hour = $6.05 per month +- t4g.small - 2vCPUs - 2 gb ram - $0.0168 per hour = $12.10 per month +- +t4g.medium - 2vCPUs - 4 gb ram - $0.0336 per hour = $24.19 per month +- t4g.large - 2vCPUs - 8 gb ram - $0.0672 per hour = $48.38 per month +- t4g.xlarge - 4vCPUs - 16 gb ram - $0.1344 per hour = $96.77 per month +- m7g.medium - 1vCPUs - 4 gb ram - $0.0408 per hour = $29.38 per month +- m7g.large - 2vCPUs - 8 gb ram - $0.0816 per hour = $58.75 per month +- m7g.xlarge - 4vCPUs - 16 gb ram - $0.2232 per hour = $160.70 per month +Fargate Prices (Linux/ARM): +- On Demand - $0.03238 per vCPU per hour and $0.00356 per GB per hour +- Spot - $0.01279585 per vCPU per hour and $0.00140508 per GB per hour +- Ephemeral Storage - $0.000111 per storage GB per hour +- 1 On-Demand vCPU = $23.31 per month +- 1 On-Demand GB ram = $2.56 per month +- 0.25 On-Demand vCPU & 0.5 GB ram On-Demand = 5.82 + 1.28 = $7.10 per month +- 1 Spot vCPU = $9.21 per month +- 1 Spot GB ram = $1.01 per month +- 1 On-Demand with Savings Plan vCPU = $12.59 per month +- 1 On-Demand with Savings Plan GB ram = $1.38 per month +- ex. 1 cpu and 1 gb = $10.22 per month +- ex. 2 cpus and 8 gb = $26.52 per month +- ex. 8 cpus and 16 gb = $89.89 per month +- ex. 16 cpus and 32 gb = $179.78 per month +Notes: +- m7g is general purpose using graviton 3 +- t4g is general purpose using graviton 2 +- for Fargate, memory and storage are cheep, it's the vCPUs that get expensive + +NA Server: +- 45 clients * 2 = 90 django containers +- digital ocean 4vCPUs 8 GB memory + - cpu usage nominal at 25% with spikes to 40% + - memory usage nominal at 90% +- 45 / 0.25 vCPU = 11.25 +- 45 * 0.5 GB = 22.5 +- 10 containers per task definition +- So 9 task definitions needed for django +- vimsfish might need more than 0.5 GB +CA Server: +- 8 clients +- digital ocean 1vCPUs 2 GB memory + - cpu usage nominal at 8% with spikes to 80% + - memory usage nominal at 85% +- beaty might need more than 0.5 GB +EU Server: +- 9 clients +- digital ocean 1vCPUs 2 GB memory + - cpu usage nominal at 6% with spikes to 72% + - memory usage nominal at 80% +- herb_rbge might need more than 0.5 GB + +So maybe 1vCPU and 0.5 GB of memory will be enough to handle each django container. Most are fine with 0.5 GB, only a few will go over with the django and worker containers combined. + +Price Option Comparison +t4g.medium +- on-demand +- spot +- 12 month reserved instance +- 36 month reserved instance + + +### Specify Network Extract + +Specify Network EC2 instance: +```bash +sudo apt update; +sudo apt upgrade -y; +sudo apt install -y wget awscli unzip; +aws configure set aws_access_key_id "ACCESS_KEY"; +aws configure set aws_secret_access_key "ACCESS_KEY_SECRET"; +aws configure set default.region us-east-1; +aws configure set default.output json; +mkdir gbif; +mkdir gbif/download; +mkdir gbif/extract; +cd gbif/download; +aws s3 cp s3://specify-network/gbif/0146304-230224095556074.zip ./; +wget $GBIF_URL; +unzip 0146304-230224095556074.zip -d ../extract/; +cd ../extract/; +mv ./0146304-230224095556074.csv ./gbif.csv +aws s3 cp gbif.csv s3://specify-network-dev/gbif_test/gbif_extract/; +``` + +specify aws github ssh key: +id_ed25519.pub -> +``` +sh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKHq3lVhZ4U8j0Derpm37wgUPLGLgQtim77M68m+XNWL acwhite211@gmail.com +``` +id_ed25519 -> +``` +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCh6t5VYWeFPI9A3q6Zt+8IFDyxi4ELYpu+zOvJvlzViwAAAJj4E1iO+BNY +jgAAAAtzc2gtZWQyNTUxOQAAACCh6t5VYWeFPI9A3q6Zt+8IFDyxi4ELYpu+zOvJvlzViw +AAAEDi0KTenAzeyomMyaqOBd8APyQjcL3YU7tXMMMrit8bjaHq3lVhZ4U8j0Derpm37wgU +PLGLgQtim77M68m+XNWLAAAAFGFjd2hpdGUyMTFAZ21haWwuY29tAQ== +-----END OPENSSH PRIVATE KEY----- +``` + +MariaDB version: 10.3.38-MariaDB-0ubuntu0.20.04.1-log +AWS DB password: dancing-taco-magic-rainbow-vibes +AWS DB password: dance-taco-magic-rainbow-vibe + +Install Ubuntu EC2 instance all in one with no docker +```bash +#!/bin/bash + +# Avoid services restarting during apt upgrade +sudo sed -i "s/#\$nrconf{kernelhints} = -1;/\$nrconf{kernelhints} = -1;/g" /etc/needrestart/needrestart.conf; +sudo sed -i "s/#\$nrconf{restart} = 'i';/\$nrconf{restart} = 'a';/g" /etc/needrestart/needrestart.conf; + +# Run apt installs +sudo apt update; +sudo apt upgrade -y; +sudo apt install -y --no-install-recommends \ + apt-transport-https ca-certificates git curl software-properties-common wget \ + python3-pip awscli mysql-client j2cli nginx openjdk-8-jdk maven ant gcc make \ + openldap-devel \ + #nodejs npm \ + python3-venv \ + #python3.8 python3.8-dev \ + redis unzip \ + apache2 \ + libapache2-mod-wsgi-py3; + +# Install nodejs 18 +cd ~; +#curl -sL https://deb.nodesource.com/setup_18.x -o nodesource_setup.sh; +#sudo bash nodesource_setup.sh; +#sudo apt install -y nodejs; +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash; +source ~/.bashrc; +nvm install v18; +nvm use 18; +nvm alias default 18; +node -v; + +# Install python 3.8 +sudo add-apt-repository -y ppa:deadsnakes/ppa; +sudo apt update; +sudo apt install -y python3.8 python3.8-dev; + +# Git clone repos +git clone https://github.com/specify/specify7.git; +git clone https://github.com/specify/specify6.git; +git clone https://github.com/specify/report-runner-service.git; + +# Setup database +aws s3 cp s3://specify-cloud/seed-database/sp7demofish.sql ./specify7/seed-database/; + +# Setup specify6 +#cd ~/specify6; +#sudo update-alternatives --set java /usr/lib/jvm/java-8-openjdk-arm64/jre/bin/java; +#wget https://update.specifysoftware.org/6803/Specify_unix_64.sh; +#sh Specify_unix_64.sh -q -dir ./Specify6.8.03; +#sudo ln -s $(pwd)/Specify6.8.03 /opt/Specify; +sudo ln -s $(pwd)/specify6 /opt/Specify; + +# Setup specify7 +cd ~/specify7; +#git checkout tags/v7.8.10 +python3.8 -m venv specify7/ve; +specify7/ve/bin/pip install wheel; +specify7/ve/bin/pip install --upgrade -r specify7/requirements.txt; + +# Run specify dev +cd ~/specify7; +source ve/bin/activate; +make runserver; + +# Setup specify-worker +cd ~/specify7; +#ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q specify; +celery -A specifyweb worker -l INFO --concurrency=1; + +# Setup apache +# sudo apt install -y apache2 libapache2-mod-wsgi-py3 + +# Setup nginx +#sudo apt install -y nginx openjdk-8-jdk maven ant; +sudo ufw allow 'Nginx HTTP'; +sudo ufw status; +``` + +Using the Amazon arm54 centos image: +```bash +#!/bin/bash + +sudo yum upgrade; +sudo yum install -y \ + git gcc \ + openldap-devel \ + #mariadb-devel \ + mariadb105-devel.aarch64 \ + nodjs npm \ + #java-11-openjdk-headless \ + #java-11-amazon-corretto-headless.aarch64 \ + java-1.8.0-amazon-corretto.aarch64 java-1.8.0-amazon-corretto-devel.aarch64 \ + #python38-virtualenv \ + #python38 python38u-devel \ + redis6 unzip +sudo dnf install mariadb105; +sudo dnf install openldap-servers; + +# Specify 7 +python3 -m venv specify7/ve; +specify7/ve/bin/python3 -m pip install --upgrade pip; +specify7/ve/bin/pip install wheel; +specify7/ve/bin/pip install --upgrade -r specify7/requirements.txt +``` diff --git a/_sources/aws/Specify Cloud Graviton Setup.md.txt b/_sources/aws/Specify Cloud Graviton Setup.md.txt new file mode 100644 index 0000000..0c6aeb4 --- /dev/null +++ b/_sources/aws/Specify Cloud Graviton Setup.md.txt @@ -0,0 +1,408 @@ +Commands for running on ubuntu 20.04 arm64 +See `~/git/specify-aws-specify7-mosti-in-one-lite/docker-entrypoint.sh` for latest +```bash +#!/bin/bash + +sudo apt update; +sudo apt upgrade -y; +sudo add-apt-repository ppa:openjdk-r/ppa; # repo for jdk-8 +curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -; +sudo apt -y install --no-install-recommends \ + build-essential \ + git \ + libldap2-dev \ + libmariadbclient-dev \ + libsasl2-dev \ + nodejs \ + npm \ + python3-venv \ + python3.8 \ + python3.8-dev \ + redis \ + unzip \ + openjdk-8-jdk \ + maven \ + ant \ + awscli \ + mysql-client \ + nginx \ + certbot \ + python3-certbot-nginx; +node -v; +#sudo apt install -y j2cli; +sudo apt install -y nginx; +#sudo apt install -y apache2 libapache2-mod-wsgi-py3; +sudo apt install -y mysql-client-core-8.0; +sudo apt clean; + +# Configure AWS +aws configure set aws_access_key_id "ACCESS_KEY"; +aws configure set aws_secret_access_key "ACCESS_KEY_SECRET"; +aws configure set default.region us-east-1; +aws configure set default.output json; + +# Specify6 +wget https://update.specifysoftware.org/6803/Specify_unix_64.sh; +sh Specify_unix_64.sh -q -dir ./Specify6.8.03; +sudo ln -s $(pwd)/Specify6.8.03 /opt/Specify; + +# Specify7 +git clone https://github.com/specify/specify7.git; +mkdir ~/wb_upload_logs; +mkdir ~/specify_depository; +#cd specify7 +#git checkout tags/v7.8.6 + +# Specify settings config +cd ~/specify7; +echo 'export DOMAIN_NAME=ec2-3-87-116-210.compute-1.amazonaws.com' >> ~/.bashrc; +echo 'export DATABASE_HOST=specify-cloud-aurora-v2-test-database-1-instance-1.cqvncffkwz9t.us-east-1.rds.amazonaws.com' >> ~/.bashrc; +echo 'export DATABASE_PORT=3306' >> ~/.bashrc; +echo 'export DATABASE_NAME=sp7demofish' >> ~/.bashrc; +echo 'export MASTER_NAME=master' >> ~/.bashrc; +echo 'export MASTER_PASSWORD=dance-taco-magic-rainbow-vibe' >> ~/.bashrc; +echo 'export WEB_ATTACHMENT_URL=https://assets1.specifycloud.org/web_asset_store.xml' >> ~/.bashrc; +echo 'export WEB_ATTACHMENT_KEY=tnhercbrhtktanehul.dukb' >> ~/.bashrc; +echo 'export WEB_ATTACHMENT_COLLECTION=sp7demofish' >> ~/.bashrc; +echo 'export REPORT_RUNNER_HOST=10.133.58.98' >> ~/.bashrc; +echo 'export REPORT_RUNNER_PORT=8080' >> ~/.bashrc; +source ~/.bashrc; +sed -i "s/DATABASE_HOST = 'SpecifyDB'/DATABASE_HOST = '${DATABASE_HOST}'/g" specifyweb/settings/specify_settings.py; +sed -i "s/DATABASE_PORT = ''/DATABASE_PORT = '${DATABASE_PORT}'/g" specifyweb/settings/specify_settings.py; +sed -i "s/DATABASE_NAME = 'SpecifyDB'/DATABASE_NAME = '$DATABASE_NAME'/g" specifyweb/settings/specify_settings.py; +sed -i "s/MASTER_NAME = 'MasterUser'/MASTER_NAME = '$MASTER_NAME'/g" specifyweb/settings/specify_settings.py; +sed -i "s/MASTER_PASSWORD = 'MasterPassword'/MASTER_PASSWORD = '$MASTER_PASSWORD'/g" specifyweb/settings/specify_settings.py; +sed -i "s|WEB_ATTACHMENT_URL = None|WEB_ATTACHMENT_URL = '$WEB_ATTACHMENT_URL'|g" specifyweb/settings/specify_settings.py; +sed -i "s/WEB_ATTACHMENT_KEY = None/WEB_ATTACHMENT_KEY = '$WEB_ATTACHMENT_KEY'/g" specifyweb/settings/specify_settings.py; +sed -i "s/WEB_ATTACHMENT_COLLECTION = None/WEB_ATTACHMENT_COLLECTION = '$WEB_ATTACHMENT_COLLECTION'/g" specifyweb/settings/specify_settings.py; +sed -i "s/REPORT_RUNNER_HOST = ''/REPORT_RUNNER_HOST = '$REPORT_RUNNER_HOST'/g" specifyweb/settings/specify_settings.py; +sed -i "s/REPORT_RUNNER_PORT = ''/REPORT_RUNNER_PORT = '$REPORT_RUNNER_PORT'/g" specifyweb/settings/specify_settings.py; +sed -i "s/home\/specify/home\/ubuntu/g" specifyweb/settings/specify_settings.py; + +# Setup Specify7 python environment +cd ~/specify7; +python3.8 -m venv ./ve; +./ve/bin/pip install wheel; +./ve/bin/pip install --upgrade -r ./requirements.txt; +ve/bin/pip install --no-cache-dir gunicorn; + +# Database setup +aws s3 cp s3://specify-cloud/seed-database/sp7demofish.sql ~/specify7/seed-database/; +mysql --host $DATABASE_HOST -u $MASTER_NAME -p"${MASTER_PASSWORD}" -e "create database ${DATABASE_NAME};"; +mysql --host $DATABASE_HOST -u $MASTER_NAME -p"${MASTER_PASSWORD}" $DATABASE_NAME < ~/specify7/seed-database/sp7demofish.sql; + +# Build Specify7 +cd specify7; +source ve/bin/activate; +make; +#make runserver; +#ve/bin/pip install gunicorn +#ve/bin/gunicorn -w 3 -b 0.0.0.0.8000 -t 300 specifyweb_wsgi; +sudo ln -s $(pwd)/specify7 /opt/specify7; + +# Specify7 worker +cd ~/specify7; +celery -A specifyweb worker -l INFO --concurrency=1 &; + +# Webserver setup +mkdir ~/media; +sed -i "s/MEDIA_ROOT = ''/MEDIA_ROOT = '\/home\/ubuntu\/media'/g" ~/specify7/specifyweb/settings/__init__.py; +sed -i "s/MEDIA_URL = ''/MEDIA_URL = 'http:\/\/${DOMAIN_NAME}\/media'/g" ~/specify7/specifyweb/settings/__init__.py; + +# Nginx webserver +#sudo ufw allow 'Nginx HTTP'; +#sudo ufw status; +sed -i "s/server_name localhost/server_name sp7demofish/g" ~/specify7/nginx.conf; +sudo cp ~/specify7/nginx.conf /etc/nginx/sites-available/specify7; +sudo ln -s /etc/nginx/sites-available/specify7 /etc/nginx/sites-enabled/; +sudo nginx -c ~/specify7/nginx.conf; + +# Apache webserver +sed "s/\$servername/$DOMAIN_NAME/g" ~/specify7/specifyweb_apache.conf; +sudo rm /etc/apache2/sites-enabled/000-default.conf; +sudo ln -s $(pwd)/specify7/specifyweb_apache.conf /etc/apache2/sites-enabled/; +sudo systemctl restart apache2.service; +#sudo invoke-rc.d apache2 restart; + +# TLS/SSL +sudo certbot --nginx -d your_domain; +sudo ufw allow 'Nginx Full'; +sudo ufw delete allow 'Nginx HTTP'; +``` + +bash script for setting env varibales and specify7 setting configs: +```bash +#!/bin/bash + +sed -i "s/DATABASE_NAME = 'SpecifyDB'/DATABASE_NAME = ''/g" specifyweb/settings/specify_settings.py; + +update_setting() { + local setting_key="$1" + local setting_value="$2" + local file_path="specifyweb/settings/specify_settings.py" + + sed -i "s/${setting_key} = ''/${setting_key} = '${setting_value}'/g" "$file_path" +} + +cat <> ~/.bashrc +export DATABASE_HOST=specify-cloud-aurora-test-database-1-instance-1.cqvncffkwz9t.us-east-1.rds.amazonaws.com +export DATABASE_PORT=3306 +export DATABASE_NAME=sp7demofish +export MASTER_NAME=master +export MASTER_PASSWORD=mastermaster +export WEB_ATTACHMENT_URL=https://assets1.specifycloud.org/web_asset_store.xml +export WEB_ATTACHMENT_KEY=tnhercbrhtktanehul.dukb +export WEB_ATTACHMENT_COLLECTION=sp7demofish +export REPORT_RUNNER_HOST=10.133.58.98 +export REPORT_RUNNER_PORT=8080 +EOT + +source ~/.bashrc; + +update_setting "DATABASE_HOST" "$DATABASE_HOST" +update_setting "DATABASE_PORT" "$DATABASE_PORT" +update_setting "DATABASE_NAME" "$DATABASE_NAME" +update_setting "MASTER_NAME" "$MASTER_NAME" +update_setting "MASTER_PASSWORD" "$MASTER_PASSWORD" +update_setting "WEB_ATTACHMENT_URL" "$WEB_ATTACHMENT_URL" +update_setting "WEB_ATTACHMENT_KEY" "$WEB_ATTACHMENT_KEY" +update_setting "WEB_ATTACHMENT_COLLECTION" "$WEB_ATTACHMENT_COLLECTION" +update_setting "REPORT_RUNNER_HOST" "$REPORT_RUNNER_HOST" +update_setting "REPORT_RUNNER_PORT" "$REPORT_RUNNER_PORT" + +``` + +nginx.conf -> +``` +server { + listen 80; + server_name ec2-54-162-114-41.compute-1.amazonaws.com; + root /usr/share/nginx; + client_max_body_size 128M; + + # serve static files directly + location /static/ { + #client_max_body_size 0; + root /volumes; + #rewrite ^/static/config/(.*)$ /specify6/config/$1 break; + #rewrite ^/static/depository/(.*)$ /static-files/depository/$1 break; + #rewrite ^/static/js/(.*)$ /webpack-output/$1 break; + #rewrite ^/static/(.*)$ /static-files/frontend-static/$1 break; + rewrite ^/static/config/(.*)$ /home/ubuntu/specify6.8.03/config/$1 break; + rewrite ^/static/depository/(.*)$ /home/ubuntu/static-files/depository/$1 break; + #rewrite ^/static/js/(.*)$ /webpack-output/$1 break; + rewrite ^/static/(.*)$ /static-files/frontend-static/$1 break; + } + + # proxy these urls to the asset server + location ~ ^/(fileget|fileupload|filedelete|getmetadata|testkey|web_asset_store.xml) { + client_max_body_size 0; + resolver 127.0.0.11 valid=30s; + set $backend "http://asset-server:8080"; + proxy_pass $backend; + 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 everything else to specify 7 + location / { + client_max_body_size 400M; + client_body_buffer_size 400M; + client_body_timeout 120; + resolver 127.0.0.11 valid=30s; + set $backend "http://specify7:8000"; + proxy_pass $backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` + +simple nginx.conf -> +``` +server { + listen 80; + server_name ec2-54-162-114-41.compute-1.amazonaws.com; + location / { + # django running in uWSGI + uwsgi_pass unix:///run/uwsgi/app/django/socket; + include uwsgi_params; + uwsgi_read_timeout 300s; + client_max_body_size 32m; + } + location /static/ { + # static files + alias /home/ubuntu/static/; # ending slash is required + } + location /media/ { + # media files, uploaded by users + alias /home/ubuntu/media/; # ending slash is required + } +} + +``` + +specifyweb_apache.conf -> +``` + + # Grant access to the Specify directories. + + Options +FollowSymLinks -Indexes -MultiViews + Require all granted + + + + Options +FollowSymLinks -Indexes -MultiViews + Require all granted + + + + Options +FollowSymLinks -Indexes -MultiViews + Require all granted + + + + Options +FollowSymLinks -Indexes -MultiViews + Require all granted + + + # Alias the following to the location set in specifyweb/settings/local_specify_settings.py + Alias /static/depository /home/ubuntu/specify_depository + + # Alias the following to the Specify6 installation + /config + Alias /static/config /opt/Specify/config + + # Alias the following to the Specify7 installation + /specifyweb/frontend/static + Alias /static /opt/specify7/specifyweb/frontend/static + + # Set the user and group you want the Specify 7 python process to run as. + # The python-home points to the location of the python libraries in the + # virtualenv you established. If not using a virtualenv, leave off the + # python-home parameter. + WSGIDaemonProcess ec2-3-87-116-210.compute-1.amazonaws.com user=ubuntu group=ubuntu python-home=/opt/specify7/ve + WSGIProcessGroup ec2-3-87-116-210.compute-1.amazonaws.com + + # Alias the following to the Specify7 installation + /specifyweb.wsgi + WSGIScriptAlias / /opt/specify7/specifyweb.wsgi + + ErrorLog /var/log/apache2/error.log + # # Possible values include: debug, info, notice, warn, error, crit, + # # alert, emerg. + # LogLevel warn + + CustomLog /var/log/apache2/access.log combined + +``` + +default apache +``` + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + #ServerName www.example.com + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + +``` + +Webpack notes: +```bash +webpack 5.73.0 compiled with 2 warnings in 104175 ms +make[1]: Leaving directory '/home/ubuntu/specify7/specifyweb/frontend/js_src' +``` + +spcloud nginx config notes: +`rewrite ^/static/depository/(.*)$ /static-files-sp7demofish-eu/depository/$1 break;` +check out /static-files-sp7demofish-eu/depository/ +example from spcloud nginx.conf using http -> +``` +server { + listen 80; + server_name cryoarks-test.*; + + # The LetsEncrypt pass-though. + location /.well-known/ { + root /var/www/cryoarks-test/; + } + + + + root /usr/share/nginx; + + location /static/ { + root /volumes; + rewrite ^/static/config/(.*)$ /specify6801/config/$1 break; + rewrite ^/static/depository/(.*)$ /static-files-cryoarks-test/depository/$1 break; + rewrite ^/static/(.*)$ /static-files-cryoarks-test/frontend-static/$1 break; + } + + location / { + resolver 127.0.0.11 valid=30s; + set $backend "http://cryoarks-test:8000"; + proxy_pass $backend; + 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_read_timeout 600s; + client_max_body_size 0; + } +} +``` +then edited for aws ec2 web server -> +``` +server { + listen 80; + server_name ec2-54-162-114-41.compute-1.amazonaws.com; + + # The LetsEncrypt pass-though. + #location /.well-known/ { + # root /var/www/cryoarks-test/; + #} + + root /usr/share/nginx; + + location /static/ { + root /volumes; + rewrite ^/static/config/(.*)$ /specify6803/config/$1 break; + rewrite ^/static/depository/(.*)$ /static-files-cryoarks-test/depository/$1 break; + rewrite ^/static/(.*)$ /static-files-cryoarks-test/frontend-static/$1 break; + } + + location / { + resolver 127.0.0.11 valid=30s; + set $backend "http://cryoarks-test:8000"; + proxy_pass $backend; + 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_read_timeout 600s; + client_max_body_size 0; + } +} +``` \ No newline at end of file diff --git a/_sources/aws/Specify7 ECS most-in-one.md.txt b/_sources/aws/Specify7 ECS most-in-one.md.txt new file mode 100644 index 0000000..0d2c59c --- /dev/null +++ b/_sources/aws/Specify7 ECS most-in-one.md.txt @@ -0,0 +1,213 @@ +## Image +create a one client pod with specify7, specify7-worker, webpack, nginx, and maybe specify6 +excludes mariadb, redis, asset-server +```Dockerfile +FROM arm64v8/ubuntu:20.04 + +# Set environment variables +ENV DATABASE_HOST=localhost +ENV DATABASE_PORT=3306 +ENV MASTER_NAME=master +ENV MASTER_PASSWORD=master +ENV SECRET_KEY=bogus +ENV ASSET_SERVER_URL=https://assets-test.specifycloud.org/web_asset_store.xml +ENV ASSET_SERVER_KEY=tnhercbrhtktanehul.dukb +ENV REPORT_RUNNER_HOST=report-runner +ENV REPORT_RUNNER_PORT=8080 +ENV CELERY_BROKER_URL=redis://redis/0 +ENV CELERY_RESULT_BACKEND=redis://redis/1 +ENV LOG_LEVEL=WARNING +ENV SP7_DEBUG=true +ENV SP6_VERSION=6.8.03 +ENV SP6_VERSION_STR=6803 +# ENV SP6_VERSION_STR="${SP6_VERSION//.}" +# ENV SP6_VERSION_STR=$(echo "$SP6_VERSION" | tr -d '.') + +##################################################################### + +RUN apt-get update && \ + apt upgrade -y +RUN apt-get -y install --no-install-recommends \ + git \ + build-essential \ + libldap2-dev \ + libmariadbclient-dev \ + libsasl2-dev \ + nodejs \ + npm \ + python3-venv \ + python3.8 \ + python3.8-dev \ + redis \ + unzip \ + openjdk-8-jdk \ + maven \ + ant \ + awscli \ + mysql-client \ + nginx \ + certbot \ + python3-certbot-nginx; +RUN apt clean + +##################################################################### + +# Download repos +RUN wget https://update.specifysoftware.org/${SP6_VERSION_STR}/Specify_unix_64.sh +RUN git clone https://github.com/specify/specify7.git +RUN git clone https://github.com/specify/report-runner-service.git + +##################################################################### + +# Webpack +RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash; +RUN export NVM_DIR="$HOME/.nvm"; +RUN [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"; # This loads nvm +RUN nvm install node; +RUN cd ~/specify7/specifyweb/frontend/js_src; +RUN npm ci; +RUN mkdir dist; +RUN npx webpack --mode production; + +##################################################################### + +# Specify7 +RUN cd ~/specify7; +RUN make; + +COPY specify7-django.service /etc/systemd/system/ +COPY nginx.conf /home/ubuntu/specify7/nginx.conf + +##################################################################### + +# Report-runner-service +COPY report-fonts.jar /home/ubuntu/report-runner-service + +RUN mkdir -p /tmp/build + +COPY pom.xml /tmp/build + +RUN cd ~/report-runner-service +RUN mvn compile && mvn war:exploded + +COPY src /tmp/build/src +RUN mvn compile && mvn war:exploded + +##################################################################### + +# nginx webserver +RUN sed -i "s/server_name localhost/server_name sp7demofish/g" ~/specify7/nginx.conf; +RUN rm -f /etc/nginx/sites-available/default; +RUN sudo cp ~/specify7/nginx.conf /etc/nginx/sites-available/specify7; +RUN sudo ln -s /etc/nginx/sites-available/specify7 /etc/nginx/sites-enabled/; + +##################################################################### + +# Networking ports +EXPOSE 80 # nginx port +EXPOSE 8000 # django gunicorn port +EXPOSE 8080 # report runner port +# EXPOSE 3000 # Django Debug +# EXPOSE 8888 # Debugging service (ptvsd) + +##################################################################### + +COPY ./docker-entrypoint.sh ~/ +CMD ["/bin/bash", "~/docker-entrypoint.sh"] + +``` + +docker-entrypoint.sh +```bash +#!/bin/bash + +# Set environment variables +export DOMAIN_NAME=$(hostname) +if ! [[ -v CLIENT_NAME ]]; then export CLIENT_NAME=sp7demofish; fi +if ! [[ -v DATABASE_NAME ]]; then export DATABASE_NAME=sp7demofish; fi +if ! [[ -v DATABASE_HOST ]]; then export DATABASE_HOST=localhost; fi +if ! [[ -v DATABASE_PORT ]]; then export DATABASE_PORT=3306; fi +if ! [[ -v MASTER_NAME ]]; then export MASTER_NAME=master; fi +if ! [[ -v MASTER_PASSWORD ]]; then export MASTER_PASSWORD=master; fi +if ! [[ -v SECRET_KEY ]]; then export SECRET_KEY=bogus; fi +if ! [[ -v ASSET_SERVER_URL ]]; then export ASSET_SERVER_URL=https://assets-test.specifycloud.org/web_asset_store.xml; fi +if ! [[ -v ASSET_SERVER_KEY ]]; then export ASSET_SERVER_KEY=tnhercbrhtktanehul.dukb; fi +if ! [[ -v REPORT_RUNNER_HOST ]]; then export REPORT_RUNNER_HOST=localhost; fi +if ! [[ -v REPORT_RUNNER_PORT ]]; then export REPORT_RUNNER_PORT=8080; fi +if ! [[ -v CELERY_BROKER_URL ]]; then export CELERY_BROKER_URL=redis://redis/0; fi +if ! [[ -v CELERY_RESULT_BACKEND ]]; then export CELERY_RESULT_BACKEND=redis://redis/1; fi +if ! [[ -v LOG_LEVEL ]]; then export LOG_LEVEL=WARNING; fi +if ! [[ -v SP7_DEBUG ]]; then export SP7_DEBUG=true; fi +if ! [[ -v SP6_VERSION ]]; then export SP6_VERSION=6.8.03; fi +if ! [[ -v SP6_VERSION_STR ]]; then export SP6_VERSION_STR="${SP6_VERSION//.}"; fi +if ! [[ -v WORKER_COUNT ]]; then export WORKER_COUNT=4; fi + +echo 'export DOMAIN_NAME=$DOMAIN_NAME' >> ~/.bashrc; +echo 'export CLIENT_NAME=$CLIENT_NAME' >> ~/.bashrc; +echo 'export DATABASE_NAME=$DATABASE_NAME' >> ~/.bashrc; +echo 'export DATABASE_HOST=$DATABASE_HOST' >> ~/.bashrc; +echo 'export DATABASE_PORT=$DATABASE_PORT' >> ~/.bashrc; +echo 'export MASTER_NAME=$MASTER_NAME' >> ~/.bashrc; +echo 'export MASTER_PASSWORD=$MASTER_PASSWORD' >> ~/.bashrc; +echo 'export SECRET_KEY=$SECRET_KEY' >> ~/.bashrc; +echo 'export ASSET_SERVER_URL=$ASSET_SERVER_URL' >> ~/.bashrc; +echo 'export ASSET_SERVER_KEY=$ASSET_SERVER_KEY' >> ~/.bashrc; +echo 'export REPORT_RUNNER_HOST=$REPORT_RUNNER_HOST' >> ~/.bashrc; +echo 'export REPORT_RUNNER_PORT=$REPORT_RUNNER_PORT' >> ~/.bashrc; +echo 'export CELERY_BROKER_URL=$CELERY_BROKER_URL' >> ~/.bashrc; +echo 'export CELERY_RESULT_BACKEND=$CELERY_RESULT_BACKEND' >> ~/.bashrc; +echo 'export LOG_LEVEL=$LOG_LEVEL' >> ~/.bashrc; +echo 'export SP7_DEBUG=$SP7_DEBUG' >> ~/.bashrc; +echo 'export SP6_VERSION=$SP6_VERSION' >> ~/.bashrc; +echo 'export SP6_VERSION_STR=$SP6_VERSION_STR' >> ~/.bashrc; +echo 'export WORKER_COUNT=$WORKER_COUNT' >> ~/.bashrc; + +# Specify7 Django +cp specifyweb.wsgi specifyweb_wsgi.py; +sudo systemctl enable specify7-django.service; +sudo systemctl start specify7-django.service; + +# Specify7 worker +cd ~/specify7; +celery -A specifyweb worker -l INFO --concurrency=1 &; + +# Report runner + + +# nginx webserver +sudo systemctl daemon-reload; +sudo systemctl restart nginx; + +``` + +specify7-django.service +```service +[Unit] +Description=Specify 7 Django Server +Wants=network.target + +[Service] +User=ubuntu +WorkingDirectory=/home/ubuntu/specify7 +ExecStart=/home/ubuntu/specify7/ve/bin/gunicorn -w 3 -b 0.0.0.0:8000 -t 300 specifyweb_wsgi +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +report-runner.service +```service +[Unit] +Description=Specify Report Runner Service +Wants=network.target +ConditionPathExists=/home/ubuntu/report-runner-service + +[Service] +User=ubuntu +WorkingDirectory=/home/ubuntu/report-runner-service +ExecStart=/usr/bin/mvn jetty:run + +[Install] +Alias=ireportrunner.service +``` \ No newline at end of file diff --git a/_sources/aws/Useful Bash Commands.md.txt b/_sources/aws/Useful Bash Commands.md.txt new file mode 100644 index 0000000..4309386 --- /dev/null +++ b/_sources/aws/Useful Bash Commands.md.txt @@ -0,0 +1,131 @@ +sftp +```bash +sftp -i alec_specify_ssh_key ubuntu@ec2-52-206-2-67.compute-1.amazonaws.com; +pwd; +put /Users/alecwhite/git/specify-aws/report-fonts.jar ./; +ls; +exit; +``` + +background & foreground tasks +```bash + +``` + +check storage +```bash +df -H +du -sh * +``` + +rsync +```bash +rsync -avz -e "ssh -i ~/specify/keys/specify-aws-ssh.pem" ~/git/specify-aws/specify7-cluster/ ubuntu@ec2-52-206-2-67.compute-1.amazonaws.com:/home/ubuntu/specify7-cluster/ + +``` + +docker images view architecture and OS +```bash +for img in $(docker image ls -q); do echo $img; docker image inspect $img | jq '.[0] | {image: .RepoTags[0], os: .Os, arch: .Architecture}'; done +``` + +run a django unit test through docker +```bash +sudo docker exec -it specify7-specify7-1 bash -c "ve/bin/python3 manage.py test specifyweb.notifications.tests.NotificationsTests" +``` + +git stash specify files +```bash +git stash push -m "stash-name" file1.txt file2.txt +git stash list +git stash apply stash^(0) +``` + +add user +```bash +adduser --disabled-password --gecos "" specify; +su - specify; +``` + +docker build and push for multiple architectures +```bash +export DOCKER_CLI_EXPERIMENTAL=enabled +docker buildx create --name mybuilder --use # only do once +docker buildx inspect mybuilder --bootstrap + +docker buildx build --platform linux/amd64,linux/arm64 -t specifyconsortium/specify-asset-service:connection_fix . --push + +docker buildx use default # don't think needs to be done +``` + +create linux user for ssh login and database access +```bash +#!/bin/bash + +# Ask for the new username +read -p "Enter the new username: " username + +# Create the new user without a password +sudo adduser --disabled-password --gecos "" $username + +# Restrict the user with rbash +sudo usermod -s /bin/rbash $username + +# Create a bin directory for the user for restricted commands +mkdir /home/$username/bin + +# Symlink the MySQL client to the user's bin so they can use it +ln -s /usr/bin/mysql /home/$username/bin/mysql + +# Restrict access to the user's home directory +sudo chown $username:$username /home/$username +sudo chmod 700 /home/$username + +# Set up .ssh directory for SSH key-based authentication +mkdir /home/$username/.ssh +chmod 700 /home/$username/.ssh + +# Ask for the user's public SSH key and add it to the authorized_keys file +read -p "Enter the new user's public SSH key: " ssh_key +echo "$ssh_key" > /home/$username/.ssh/authorized_keys +chmod 600 /home/$username/.ssh/authorized_keys +chown $username:$username /home/$username/.ssh/authorized_keys + +mysql -u root -p +CREATE USER 'dbuser'@'localhost' IDENTIFIED BY 'DBUSER_PASSWORD'; +GRANT SELECT, INSERT, UPDATE, DELETE ON your_database_name.* TO 'dbuser'@'localhost'; +FLUSH PRIVILEGES; +EXIT; + +``` + +view live formatted nginx logs example +```bash +docker logs specifycloud-nginx-1 --tail 1000 --since 10m --follow | \ +grep -v updown | grep -v notification | grep specifycloud.org | \ +awk '{ split($4,time,"["); print time[2], "-", $6, $7, $8, $9, $10, $11; }' +``` + +Add swap memory +```bash +sudo fallocate -l 4G /swapfile; +# sudo dd if=/dev/zero of=/swapfile bs=1024 count=4096k; # if fallocate is not available +sudo chmod 600 /swapfile; +sudo mkswap /swapfile; # make swap file +sudo swapon /swapfile; # enable swap file +echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab; # make change permanent + +# check swap +sudo swapon --show; +free -h; + +# swappiness defauls to 10, 100 is very argessive use, 0 is use only will absolutley necessary +cat /proc/sys/vm/swappiness; +sudo sysctl vm.swappiness=10; +sudo echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf; sudo sysctl -p; # make chane permanent + +# increase swap size +sudo swapoff -v /swapfile # turn off +sudo fallocate -l 8G /swapfile; # resize +sudo mkswap /swapfile; sudo swapon /swapfile; # turn on +``` \ No newline at end of file diff --git a/_sources/aws/VS Code Django Unit Test Debugging Notes.md.txt b/_sources/aws/VS Code Django Unit Test Debugging Notes.md.txt new file mode 100644 index 0000000..99879b7 --- /dev/null +++ b/_sources/aws/VS Code Django Unit Test Debugging Notes.md.txt @@ -0,0 +1,123 @@ +VS Code Django Unit Test Debugging Notes: + +Might need to add permissions to the test db for the master user: +`mysql --host 127.0.0.1 -u root -p'root' -e "GRANT ALL PRIVILEGES ON test_cuic.* TO 'master'@'%';"` +`GRANT ALL PRIVILEGES ON test_.* TO 'master'@'%';` + +https://devpress.csdn.net/python/62fe07607e66823466192fa3.html + +```sh +ve/bin/pip install pytest pytest-django +``` + +```sh +ve/bin/pip install hypothesis==6.4.0 hypothesis-jsonschema==0.19.0 +``` + +specifyweb/pytest.ini -> +```sh +[pytest] +DJANGO_SETTINGS_MODULE=specifyweb.settings +python_files=*test*.py testparsing.py +addopts = --ignore=specifyweb/specify/selenium_tests.py +``` + +.vscode/settings.json -> +```sh +{ + "python.pythonPath": "ve/bin/python", + "python.testing.pytestArgs": [ + "specifyweb", + "-s", + "-vv" + ], + "python.testing.pytestEnabled": true, + "python.testing.nosetestsEnabled": false, + "python.testing.unittestEnabled": false +} +``` + +.env -> not needed +```sh +PYTHONPATH=specifyweb/ +``` + +manage.py -> +paste between "os.environ.setdefault" ... "try: from django.core.management" +```python +from django.conf import settings + + if settings.DEBUG: + if os.environ.get('RUN_MAIN') or os.environ.get('WERKZEUG_RUN_MAIN'): + import debugpy + debugpy.listen(("0.0.0.0", 3000)) + print('Attached!') +``` + +requirement-testing.txt -> +```txt +debugpy==1.6.5 +pytest==7.2.1 +pytest-django==4.5.2 +``` + +docker-compose.yml -> +```yml +ports: + - 3000:3000 # Django Debug + - 8888:8888 # debugging service (ptvsd) + +SECRET_KEY=bogus +``` + +Dockerfile -> +```Dockerfile +COPY --chown=specify:specify requirements-testing.txt /home/specify/ + +RUN python3.8 -m venv ve \ + && ve/bin/pip install --no-cache-dir -r /home/specify/requirements-testing.txt \ + && ve/bin/pip install --no-cache-dir -r /home/specify/requirements.txt +``` + +For the non-container instance of vscode, the debugger is used at runtime with the front-end: + +.vscode/launch.json -> +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Django", + "type": "python", + "request": "attach", + "pathMappings": [ + { + "localRoot": "${workspaceFolder}/specifyweb", + "remoteRoot": "/opt/specify7/specifyweb" + } + ], + "port": 3000, + "host": "127.0.0.1", + } + ] + } +``` + +.vscode/settings.json -> +```json +{ + "python.testing.unittestArgs": [ + "-v", + "-s", + "./specifyweb", + "-p", + "*test*.py" + ], + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true, + "python.linting.mypyEnabled": true, + "python.linting.enabled": true +} +``` + + diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 0000000..8141580 --- /dev/null +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/_static/alabaster.css b/_static/alabaster.css deleted file mode 100644 index e3174bf..0000000 --- a/_static/alabaster.css +++ /dev/null @@ -1,708 +0,0 @@ -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: Georgia, serif; - font-size: 17px; - background-color: #fff; - color: #000; - margin: 0; - padding: 0; -} - - -div.document { - width: 940px; - margin: 30px auto 0 auto; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 220px; -} - -div.sphinxsidebar { - width: 220px; - font-size: 14px; - line-height: 1.5; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #fff; - color: #3E4349; - padding: 0 30px 0 30px; -} - -div.body > .section { - text-align: left; -} - -div.footer { - width: 940px; - margin: 20px auto 30px auto; - font-size: 14px; - color: #888; - text-align: right; -} - -div.footer a { - color: #888; -} - -p.caption { - font-family: inherit; - font-size: inherit; -} - - -div.relations { - display: none; -} - - -div.sphinxsidebar { - max-height: 100%; - overflow-y: auto; -} - -div.sphinxsidebar a { - color: #444; - text-decoration: none; - border-bottom: 1px dotted #999; -} - -div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; -} - -div.sphinxsidebarwrapper { - padding: 18px 10px; -} - -div.sphinxsidebarwrapper p.logo { - padding: 0; - margin: -10px 0 0 0px; - text-align: center; -} - -div.sphinxsidebarwrapper h1.logo { - margin-top: -10px; - text-align: center; - margin-bottom: 5px; - text-align: left; -} - -div.sphinxsidebarwrapper h1.logo-name { - margin-top: 0px; -} - -div.sphinxsidebarwrapper p.blurb { - margin-top: 0; - font-style: normal; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: Georgia, serif; - color: #444; - font-size: 24px; - font-weight: normal; - margin: 0 0 5px 0; - padding: 0; -} - -div.sphinxsidebar h4 { - font-size: 20px; -} - -div.sphinxsidebar h3 a { - color: #444; -} - -div.sphinxsidebar p.logo a, -div.sphinxsidebar h3 a, -div.sphinxsidebar p.logo a:hover, -div.sphinxsidebar h3 a:hover { - border: none; -} - -div.sphinxsidebar p { - color: #555; - margin: 10px 0; -} - -div.sphinxsidebar ul { - margin: 10px 0; - padding: 0; - color: #000; -} - -div.sphinxsidebar ul li.toctree-l1 > a { - font-size: 120%; -} - -div.sphinxsidebar ul li.toctree-l2 > a { - font-size: 110%; -} - -div.sphinxsidebar input { - border: 1px solid #CCC; - font-family: Georgia, serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox input[type="text"] { - width: 160px; -} - -div.sphinxsidebar .search > div { - display: table-cell; -} - -div.sphinxsidebar hr { - border: none; - height: 1px; - color: #AAA; - background: #AAA; - - text-align: left; - margin-left: 0; - width: 50%; -} - -div.sphinxsidebar .badge { - border-bottom: none; -} - -div.sphinxsidebar .badge:hover { - border-bottom: none; -} - -/* To address an issue with donation coming after search */ -div.sphinxsidebar h3.donation { - margin-top: 10px; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: Georgia, serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #DDD; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #EAEAEA; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - margin: 20px 0px; - padding: 10px 30px; - background-color: #EEE; - border: 1px solid #CCC; -} - -div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { - background-color: #FBFBFB; - border-bottom: 1px solid #fafafa; -} - -div.admonition p.admonition-title { - font-family: Georgia, serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight { - background-color: #fff; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.warning { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.danger { - background-color: #FCC; - border: 1px solid #FAA; - -moz-box-shadow: 2px 2px 4px #D52C2C; - -webkit-box-shadow: 2px 2px 4px #D52C2C; - box-shadow: 2px 2px 4px #D52C2C; -} - -div.error { - background-color: #FCC; - border: 1px solid #FAA; - -moz-box-shadow: 2px 2px 4px #D52C2C; - -webkit-box-shadow: 2px 2px 4px #D52C2C; - box-shadow: 2px 2px 4px #D52C2C; -} - -div.caution { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.attention { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.important { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.note { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.tip { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.hint { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.seealso { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.topic { - background-color: #EEE; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt, code { - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; -} - -.hll { - background-color: #FFC; - margin: 0 -12px; - padding: 0 12px; - display: block; -} - -img.screenshot { -} - -tt.descname, tt.descclassname, code.descname, code.descclassname { - font-size: 0.95em; -} - -tt.descname, code.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #EEE; - -webkit-box-shadow: 2px 2px 4px #EEE; - box-shadow: 2px 2px 4px #EEE; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #EEE; - -webkit-box-shadow: 2px 2px 4px #EEE; - box-shadow: 2px 2px 4px #EEE; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #EEE; - background: #FDFDFD; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.field-list p { - margin-bottom: 0.8em; -} - -/* Cloned from - * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 - */ -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -table.footnote td.label { - width: .1px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -dl { - margin-left: 0; - margin-right: 0; - margin-top: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - margin: 0 0 0 30px; - padding: 0; -} - -ul, ol { - /* Matches the 30px from the narrow-screen "li > ul" selector below */ - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - background: #EEE; - padding: 7px 30px; - margin: 15px 0px; - line-height: 1.3em; -} - -div.viewcode-block:target { - background: #ffd; -} - -dl pre, blockquote pre, li pre { - margin-left: 0; - padding-left: 30px; -} - -tt, code { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, code.xref, a tt { - background-color: #FBFBFB; - border-bottom: 1px solid #fff; -} - -a.reference { - text-decoration: none; - border-bottom: 1px dotted #004B6B; -} - -/* Don't put an underline on images */ -a.image-reference, a.image-reference:hover { - border-bottom: none; -} - -a.reference:hover { - border-bottom: 1px solid #6D4100; -} - -a.footnote-reference { - text-decoration: none; - font-size: 0.7em; - vertical-align: top; - border-bottom: 1px dotted #004B6B; -} - -a.footnote-reference:hover { - border-bottom: 1px solid #6D4100; -} - -a:hover tt, a:hover code { - background: #EEE; -} - - -@media screen and (max-width: 870px) { - - div.sphinxsidebar { - display: none; - } - - div.document { - width: 100%; - - } - - div.documentwrapper { - margin-left: 0; - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - } - - div.bodywrapper { - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - margin-left: 0; - } - - ul { - margin-left: 0; - } - - li > ul { - /* Matches the 30px from the "ul, ol" selector above */ - margin-left: 30px; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .bodywrapper { - margin: 0; - } - - .footer { - width: auto; - } - - .github { - display: none; - } - - - -} - - - -@media screen and (max-width: 875px) { - - body { - margin: 0; - padding: 20px 30px; - } - - div.documentwrapper { - float: none; - background: #fff; - } - - div.sphinxsidebar { - display: block; - float: none; - width: 102.5%; - margin: 50px -30px -20px -30px; - padding: 10px 20px; - background: #333; - color: #FFF; - } - - div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, - div.sphinxsidebar h3 a { - color: #fff; - } - - div.sphinxsidebar a { - color: #AAA; - } - - div.sphinxsidebar p.logo { - display: none; - } - - div.document { - width: 100%; - margin: 0; - } - - div.footer { - display: none; - } - - div.bodywrapper { - margin: 0; - } - - div.body { - min-height: 0; - padding: 0; - } - - .rtd_doc_footer { - display: none; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .footer { - width: auto; - } - - .github { - display: none; - } -} - - -/* misc. */ - -.revsys-inline { - display: none!important; -} - -/* Hide ugly table cell borders in ..bibliography:: directive output */ -table.docutils.citation, table.docutils.citation td, table.docutils.citation th { - border: none; - /* Below needed in some edge cases; if not applied, bottom shadows appear */ - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - - -/* relbar */ - -.related { - line-height: 30px; - width: 100%; - font-size: 0.9rem; -} - -.related.top { - border-bottom: 1px solid #EEE; - margin-bottom: 20px; -} - -.related.bottom { - border-top: 1px solid #EEE; -} - -.related ul { - padding: 0; - margin: 0; - list-style: none; -} - -.related li { - display: inline; -} - -nav#rellinks { - float: right; -} - -nav#rellinks li+li:before { - content: "|"; -} - -nav#breadcrumbs li+li:before { - content: "\00BB"; -} - -/* Hide certain items when printing */ -@media print { - div.related { - display: none; - } -} \ No newline at end of file diff --git a/_static/basic.css b/_static/basic.css index 4157edf..30fee9d 100644 --- a/_static/basic.css +++ b/_static/basic.css @@ -222,7 +222,7 @@ table.modindextable td { /* -- general body styles --------------------------------------------------- */ div.body { - min-width: inherit; + min-width: 360px; max-width: 800px; } diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 0000000..c718cee --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 0000000..19a446a --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/custom.css b/_static/custom.css deleted file mode 100644 index 2a924f1..0000000 --- a/_static/custom.css +++ /dev/null @@ -1 +0,0 @@ -/* This file intentionally left blank. */ diff --git a/_static/jquery.js b/_static/jquery.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/html5shiv.min.js b/_static/js/html5shiv.min.js new file mode 100644 index 0000000..cd1c674 --- /dev/null +++ b/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/_static/js/theme.js b/_static/js/theme.js new file mode 100644 index 0000000..1fddb6e --- /dev/null +++ b/_static/js/theme.js @@ -0,0 +1 @@ +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t + + + + + + Specify 7 Docker Config Example — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Specify 7 Docker Config Example

+
+

Example defaults.env

+
DATABASE_HOST=10.133.58.98
+DATABASE_PORT=3306
+MASTER_NAME=master
+MASTER_PASSWORD=master_password
+SECRET_KEY=secret_key
+ASSET_SERVER_URL=https://assets1.specifycloud.org/web_asset_store.xml
+ASSET_SERVER_KEY=asset_server_key
+REPORT_RUNNER_HOST=10.133.58.98
+REPORT_RUNNER_PORT=8080
+CELERY_BROKER_URL=redis://redis/0
+CELERY_RESULT_BACKEND=redis://redis/1
+LOG_LEVEL=WARNING
+SP7_DEBUG=false
+
+
+
+
+

Example docker-compose.yml

+
version: '3.7'
+
+services:
+
+
+
+  sp7demofish-eu:
+    image: specifyconsortium/specify7-service:v7
+    command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"]
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6801:/opt/Specify:ro"
+      - "static-files-sp7demofish-eu:/volumes/static-files"
+
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=sp7demofish_eu
+      - ASSET_SERVER_COLLECTION=sp7demofish_eu
+
+
+      - ASSET_SERVER_URL=https://demo-assets.specifycloud.org/web_asset_store.xml
+
+
+
+  sp7demofish-eu-worker:
+    image: specifyconsortium/specify7-service:v7
+    command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q sp7demofish_eu
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6801:/opt/Specify:ro"
+      - "static-files-sp7demofish-eu:/volumes/static-files"
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=sp7demofish_eu
+      - ASSET_SERVER_COLLECTION=sp7demofish_eu
+
+
+      - ASSET_SERVER_URL=https://demo-assets.specifycloud.org/web_asset_store.xml
+
+
+
+
+  rjb-madrid:
+    image: specifyconsortium/specify7-service:v7
+    command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"]
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6803:/opt/Specify:ro"
+      - "static-files-rjb-madrid:/volumes/static-files"
+
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=rjb_madrid
+      - ASSET_SERVER_COLLECTION=rjb_madrid
+
+
+      - ASSET_SERVER_URL=
+
+      - ASSET_SERVER_COLLECTION=Fanerogamia
+
+
+
+  rjb-madrid-worker:
+    image: specifyconsortium/specify7-service:v7
+    command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q rjb_madrid
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6803:/opt/Specify:ro"
+      - "static-files-rjb-madrid:/volumes/static-files"
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=rjb_madrid
+      - ASSET_SERVER_COLLECTION=rjb_madrid
+
+
+      - ASSET_SERVER_URL=
+
+      - ASSET_SERVER_COLLECTION=Fanerogamia
+
+
+
+
+  mcnb:
+    image: specifyconsortium/specify7-service:v7
+    command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"]
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6803:/opt/Specify:ro"
+      - "static-files-mcnb:/volumes/static-files"
+
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=mcnb
+      - ASSET_SERVER_COLLECTION=mcnb
+
+
+  mcnb-worker:
+    image: specifyconsortium/specify7-service:v7
+    command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q mcnb
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6803:/opt/Specify:ro"
+      - "static-files-mcnb:/volumes/static-files"
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=mcnb
+      - ASSET_SERVER_COLLECTION=mcnb
+
+
+
+  herb-rbge:
+    image: specifyconsortium/specify7-service:v7
+    command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"]
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6803:/opt/Specify:ro"
+      - "static-files-herb-rbge:/volumes/static-files"
+
+      - "./settings/herb-rbge-settings.py:/opt/specify7/settings/local_specify_settings.py:ro"
+
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=herb_rbge
+      - ASSET_SERVER_COLLECTION=herb_rbge
+
+
+  herb-rbge-worker:
+    image: specifyconsortium/specify7-service:v7
+    command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q herb_rbge
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6803:/opt/Specify:ro"
+      - "static-files-herb-rbge:/volumes/static-files"
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=herb_rbge
+      - ASSET_SERVER_COLLECTION=herb_rbge
+
+
+
+  sandbox-rbge:
+    image: specifyconsortium/specify7-service:issue_388
+    command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"]
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6803:/opt/Specify:ro"
+      - "static-files-sandbox-rbge:/volumes/static-files"
+
+      - "./settings/sandbox-rbge-settings.py:/opt/specify7/settings/local_specify_settings.py:ro"
+
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=sandbox_rbge
+      - ASSET_SERVER_COLLECTION=sandbox_rbge
+
+
+  sandbox-rbge-worker:
+    image: specifyconsortium/specify7-service:issue_388
+    command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q sandbox_rbge
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6803:/opt/Specify:ro"
+      - "static-files-sandbox-rbge:/volumes/static-files"
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=sandbox_rbge
+      - ASSET_SERVER_COLLECTION=sandbox_rbge
+
+
+
+  cryoarks-test:
+    image: specifyconsortium/specify7-service:v7
+    command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"]
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6801:/opt/Specify:ro"
+      - "static-files-cryoarks-test:/volumes/static-files"
+
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=cryoarks_test
+      - ASSET_SERVER_COLLECTION=cryoarks_test
+
+
+  cryoarks-test-worker:
+    image: specifyconsortium/specify7-service:v7
+    command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q cryoarks_test
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6801:/opt/Specify:ro"
+      - "static-files-cryoarks-test:/volumes/static-files"
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=cryoarks_test
+      - ASSET_SERVER_COLLECTION=cryoarks_test
+
+
+
+  eurl:
+    image: specifyconsortium/specify7-service:v7
+    command: ["ve/bin/gunicorn", "-w", "1", "--threads", "5", "-b", "0.0.0.0:8000", "-t", "300", "specifyweb_wsgi"]
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6803:/opt/Specify:ro"
+      - "static-files-eurl:/volumes/static-files"
+
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=eurl
+      - ASSET_SERVER_COLLECTION=eurl
+
+
+  eurl-worker:
+    image: specifyconsortium/specify7-service:v7
+    command: ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q eurl
+    init: true
+    restart: unless-stopped
+    volumes:
+      - "specify6803:/opt/Specify:ro"
+      - "static-files-eurl:/volumes/static-files"
+    env_file: defaults.env
+    environment:
+      - DATABASE_NAME=eurl
+      - ASSET_SERVER_COLLECTION=eurl
+
+
+
+
+  specify6800:
+    image: specifyconsortium/specify6-service:6.8.00
+    volumes:
+      - "specify6800:/volumes/Specify"
+
+  specify6801:
+    image: specifyconsortium/specify6-service:6.8.01
+    volumes:
+      - "specify6801:/volumes/Specify"
+
+  specify6803:
+    image: specifyconsortium/specify6-service:6.8.03
+    volumes:
+      - "specify6803:/volumes/Specify"
+
+
+  nginx:
+    image: nginx
+    restart: unless-stopped
+    ports:
+      - "80:80"
+      - "443:443"
+    volumes:
+
+      - "static-files-sp7demofish-eu:/volumes/static-files-sp7demofish-eu:ro"
+
+      - "static-files-rjb-madrid:/volumes/static-files-rjb-madrid:ro"
+
+      - "static-files-mcnb:/volumes/static-files-mcnb:ro"
+
+      - "static-files-herb-rbge:/volumes/static-files-herb-rbge:ro"
+
+      - "static-files-sandbox-rbge:/volumes/static-files-sandbox-rbge:ro"
+
+      - "static-files-cryoarks-test:/volumes/static-files-cryoarks-test:ro"
+
+      - "static-files-eurl:/volumes/static-files-eurl:ro"
+
+
+
+      - "specify6800:/volumes/specify6800:ro"
+
+      - "specify6801:/volumes/specify6801:ro"
+
+      - "specify6803:/volumes/specify6803:ro"
+
+
+      - "./nginx.conf:/etc/nginx/conf.d/default.conf:ro"
+      - "/etc/letsencrypt:/etc/letsencrypt:ro"
+      - "/etc/ssl/certs/dhparam.pem:/etc/ssl/certs/dhparam.pem:ro"
+      - "/var/www:/var/www:ro"
+
+  redis:
+    restart: unless-stopped
+    image: redis:6.0
+
+volumes:
+
+  specify6800:
+
+  specify6801:
+
+  specify6803:
+
+
+
+  static-files-sp7demofish-eu:
+
+  static-files-rjb-madrid:
+
+  static-files-mcnb:
+
+  static-files-herb-rbge:
+
+  static-files-sandbox-rbge:
+
+  static-files-cryoarks-test:
+
+  static-files-eurl:
+
+
+
+
+

Example nginx.conf

+
server {
+    listen 80;
+    server_name sp7demofish-eu.*;
+
+    # The LetsEncrypt pass-though.
+    location /.well-known/ {
+             root /var/www/sp7demofish-eu/;
+    }
+
+
+    location / {
+        return 301 https://$host$request_uri;
+    }
+}
+
+server {
+    # This stanza defines the HTTPS end point.
+    listen 443 ssl;
+    server_name sp7demofish-eu.*;
+
+    ssl_certificate /etc/letsencrypt/live/sp7demofish-eu.specifycloud.org/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/sp7demofish-eu.specifycloud.org/privkey.pem;
+
+    # from https://cipherli.st/
+    # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+    ssl_prefer_server_ciphers on;
+    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+    ssl_ecdh_curve secp384r1;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_tickets off;
+    ssl_stapling on;
+    ssl_stapling_verify on;
+    resolver 8.8.8.8 8.8.4.4 valid=300s;
+    resolver_timeout 5s;
+    # Disable preloading HSTS for now.  You can use the commented out header line that includes
+    # the "preload" directive if you understand the implications.
+    #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
+    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
+    add_header X-Frame-Options DENY;
+    add_header X-Content-Type-Options nosniff;
+
+    ssl_dhparam /etc/ssl/certs/dhparam.pem;
+
+    # The LetsEncrypt pass-though. I'm not sure if this is needed
+    # on HTTPS side, but I'm including it just in case.
+    location /.well-known/ {
+             root /var/www/sp7demofish-eu/;
+    }
+
+
+    root /usr/share/nginx;
+
+    location /static/ {
+        root /volumes;
+        rewrite ^/static/config/(.*)$ /specify6801/config/$1 break;
+        rewrite ^/static/depository/(.*)$ /static-files-sp7demofish-eu/depository/$1 break;
+        rewrite ^/static/(.*)$ /static-files-sp7demofish-eu/frontend-static/$1 break;
+    }
+
+    location / {
+        resolver 127.0.0.11 valid=30s;
+        set $backend "http://sp7demofish-eu:8000";
+        proxy_pass $backend;
+        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_read_timeout 600s;
+	client_max_body_size 0;
+    }
+}
+
+
+server {
+    listen 80;
+    server_name rjb-madrid.*;
+
+    # The LetsEncrypt pass-though.
+    location /.well-known/ {
+             root /var/www/rjb-madrid/;
+    }
+
+
+    location / {
+        return 301 https://$host$request_uri;
+    }
+}
+
+server {
+    # This stanza defines the HTTPS end point.
+    listen 443 ssl;
+    server_name rjb-madrid.*;
+
+    ssl_certificate /etc/letsencrypt/live/rjb-madrid.specifycloud.org/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/rjb-madrid.specifycloud.org/privkey.pem;
+
+    # from https://cipherli.st/
+    # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+    ssl_prefer_server_ciphers on;
+    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+    ssl_ecdh_curve secp384r1;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_tickets off;
+    ssl_stapling on;
+    ssl_stapling_verify on;
+    resolver 8.8.8.8 8.8.4.4 valid=300s;
+    resolver_timeout 5s;
+    # Disable preloading HSTS for now.  You can use the commented out header line that includes
+    # the "preload" directive if you understand the implications.
+    #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
+    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
+    add_header X-Frame-Options DENY;
+    add_header X-Content-Type-Options nosniff;
+
+    ssl_dhparam /etc/ssl/certs/dhparam.pem;
+
+    # The LetsEncrypt pass-though. I'm not sure if this is needed
+    # on HTTPS side, but I'm including it just in case.
+    location /.well-known/ {
+             root /var/www/rjb-madrid/;
+    }
+
+
+    root /usr/share/nginx;
+
+    location /static/ {
+        root /volumes;
+        rewrite ^/static/config/(.*)$ /specify6803/config/$1 break;
+        rewrite ^/static/depository/(.*)$ /static-files-rjb-madrid/depository/$1 break;
+        rewrite ^/static/(.*)$ /static-files-rjb-madrid/frontend-static/$1 break;
+    }
+
+    location / {
+        resolver 127.0.0.11 valid=30s;
+        set $backend "http://rjb-madrid:8000";
+        proxy_pass $backend;
+        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_read_timeout 600s;
+	client_max_body_size 0;
+    }
+}
+
+
+server {
+    listen 80;
+    server_name mcnb.*;
+
+    # The LetsEncrypt pass-though.
+    location /.well-known/ {
+             root /var/www/mcnb/;
+    }
+
+
+    location / {
+        return 301 https://$host$request_uri;
+    }
+}
+
+server {
+    # This stanza defines the HTTPS end point.
+    listen 443 ssl;
+    server_name mcnb.*;
+
+    ssl_certificate /etc/letsencrypt/live/mcnb.specifycloud.org/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/mcnb.specifycloud.org/privkey.pem;
+
+    # from https://cipherli.st/
+    # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+    ssl_prefer_server_ciphers on;
+    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+    ssl_ecdh_curve secp384r1;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_tickets off;
+    ssl_stapling on;
+    ssl_stapling_verify on;
+    resolver 8.8.8.8 8.8.4.4 valid=300s;
+    resolver_timeout 5s;
+    # Disable preloading HSTS for now.  You can use the commented out header line that includes
+    # the "preload" directive if you understand the implications.
+    #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
+    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
+    add_header X-Frame-Options DENY;
+    add_header X-Content-Type-Options nosniff;
+
+    ssl_dhparam /etc/ssl/certs/dhparam.pem;
+
+    # The LetsEncrypt pass-though. I'm not sure if this is needed
+    # on HTTPS side, but I'm including it just in case.
+    location /.well-known/ {
+             root /var/www/mcnb/;
+    }
+
+
+    root /usr/share/nginx;
+
+    location /static/ {
+        root /volumes;
+        rewrite ^/static/config/(.*)$ /specify6803/config/$1 break;
+        rewrite ^/static/depository/(.*)$ /static-files-mcnb/depository/$1 break;
+        rewrite ^/static/(.*)$ /static-files-mcnb/frontend-static/$1 break;
+    }
+
+    location / {
+        resolver 127.0.0.11 valid=30s;
+        set $backend "http://mcnb:8000";
+        proxy_pass $backend;
+        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_read_timeout 600s;
+	client_max_body_size 0;
+    }
+}
+
+
+server {
+    listen 80;
+    server_name herb-rbge.*;
+
+    # The LetsEncrypt pass-though.
+    location /.well-known/ {
+             root /var/www/herb-rbge/;
+    }
+
+
+    location / {
+        return 301 https://$host$request_uri;
+    }
+}
+
+server {
+    # This stanza defines the HTTPS end point.
+    listen 443 ssl;
+    server_name herb-rbge.*;
+
+    ssl_certificate /etc/letsencrypt/live/herb-rbge.specifycloud.org/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/herb-rbge.specifycloud.org/privkey.pem;
+
+    # from https://cipherli.st/
+    # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+    ssl_prefer_server_ciphers on;
+    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+    ssl_ecdh_curve secp384r1;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_tickets off;
+    ssl_stapling on;
+    ssl_stapling_verify on;
+    resolver 8.8.8.8 8.8.4.4 valid=300s;
+    resolver_timeout 5s;
+    # Disable preloading HSTS for now.  You can use the commented out header line that includes
+    # the "preload" directive if you understand the implications.
+    #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
+    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
+    add_header X-Frame-Options DENY;
+    add_header X-Content-Type-Options nosniff;
+
+    ssl_dhparam /etc/ssl/certs/dhparam.pem;
+
+    # The LetsEncrypt pass-though. I'm not sure if this is needed
+    # on HTTPS side, but I'm including it just in case.
+    location /.well-known/ {
+             root /var/www/herb-rbge/;
+    }
+
+
+    root /usr/share/nginx;
+
+    location /static/ {
+        root /volumes;
+        rewrite ^/static/config/(.*)$ /specify6803/config/$1 break;
+        rewrite ^/static/depository/(.*)$ /static-files-herb-rbge/depository/$1 break;
+        rewrite ^/static/(.*)$ /static-files-herb-rbge/frontend-static/$1 break;
+    }
+
+    location / {
+        resolver 127.0.0.11 valid=30s;
+        set $backend "http://herb-rbge:8000";
+        proxy_pass $backend;
+        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_read_timeout 600s;
+	client_max_body_size 0;
+    }
+}
+
+
+server {
+    listen 80;
+    server_name sandbox-rbge.*;
+
+    # The LetsEncrypt pass-though.
+    location /.well-known/ {
+             root /var/www/sandbox-rbge/;
+    }
+
+
+    location / {
+        return 301 https://$host$request_uri;
+    }
+}
+
+server {
+    # This stanza defines the HTTPS end point.
+    listen 443 ssl;
+    server_name sandbox-rbge.*;
+
+    ssl_certificate /etc/letsencrypt/live/sandbox-rbge.specifycloud.org/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/sandbox-rbge.specifycloud.org/privkey.pem;
+
+    # from https://cipherli.st/
+    # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+    ssl_prefer_server_ciphers on;
+    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+    ssl_ecdh_curve secp384r1;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_tickets off;
+    ssl_stapling on;
+    ssl_stapling_verify on;
+    resolver 8.8.8.8 8.8.4.4 valid=300s;
+    resolver_timeout 5s;
+    # Disable preloading HSTS for now.  You can use the commented out header line that includes
+    # the "preload" directive if you understand the implications.
+    #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
+    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
+    add_header X-Frame-Options DENY;
+    add_header X-Content-Type-Options nosniff;
+
+    ssl_dhparam /etc/ssl/certs/dhparam.pem;
+
+    # The LetsEncrypt pass-though. I'm not sure if this is needed
+    # on HTTPS side, but I'm including it just in case.
+    location /.well-known/ {
+             root /var/www/sandbox-rbge/;
+    }
+
+
+    root /usr/share/nginx;
+
+    location /static/ {
+        root /volumes;
+        rewrite ^/static/config/(.*)$ /specify6803/config/$1 break;
+        rewrite ^/static/depository/(.*)$ /static-files-sandbox-rbge/depository/$1 break;
+        rewrite ^/static/(.*)$ /static-files-sandbox-rbge/frontend-static/$1 break;
+    }
+
+    location / {
+        resolver 127.0.0.11 valid=30s;
+        set $backend "http://sandbox-rbge:8000";
+        proxy_pass $backend;
+        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_read_timeout 600s;
+	client_max_body_size 0;
+    }
+}
+
+
+server {
+    listen 80;
+    server_name cryoarks-test.*;
+
+    # The LetsEncrypt pass-though.
+    location /.well-known/ {
+             root /var/www/cryoarks-test/;
+    }
+
+
+
+    root /usr/share/nginx;
+
+    location /static/ {
+        root /volumes;
+        rewrite ^/static/config/(.*)$ /specify6801/config/$1 break;
+        rewrite ^/static/depository/(.*)$ /static-files-cryoarks-test/depository/$1 break;
+        rewrite ^/static/(.*)$ /static-files-cryoarks-test/frontend-static/$1 break;
+    }
+
+    location / {
+        resolver 127.0.0.11 valid=30s;
+        set $backend "http://cryoarks-test:8000";
+        proxy_pass $backend;
+        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_read_timeout 600s;
+	client_max_body_size 0;
+    }
+}
+
+
+server {
+    listen 80;
+    server_name eurl.*;
+
+    # The LetsEncrypt pass-though.
+    location /.well-known/ {
+             root /var/www/eurl/;
+    }
+
+
+    location / {
+        return 301 https://$host$request_uri;
+    }
+}
+
+server {
+    # This stanza defines the HTTPS end point.
+    listen 443 ssl;
+    server_name eurl.*;
+
+    ssl_certificate /etc/letsencrypt/live/eurl.specifycloud.org/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/eurl.specifycloud.org/privkey.pem;
+
+    # from https://cipherli.st/
+    # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+    ssl_prefer_server_ciphers on;
+    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+    ssl_ecdh_curve secp384r1;
+    ssl_session_cache shared:SSL:10m;
+    ssl_session_tickets off;
+    ssl_stapling on;
+    ssl_stapling_verify on;
+    resolver 8.8.8.8 8.8.4.4 valid=300s;
+    resolver_timeout 5s;
+    # Disable preloading HSTS for now.  You can use the commented out header line that includes
+    # the "preload" directive if you understand the implications.
+    #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
+    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
+    add_header X-Frame-Options DENY;
+    add_header X-Content-Type-Options nosniff;
+
+    ssl_dhparam /etc/ssl/certs/dhparam.pem;
+
+    # The LetsEncrypt pass-though. I'm not sure if this is needed
+    # on HTTPS side, but I'm including it just in case.
+    location /.well-known/ {
+             root /var/www/eurl/;
+    }
+
+
+    root /usr/share/nginx;
+
+    location /static/ {
+        root /volumes;
+        rewrite ^/static/config/(.*)$ /specify6803/config/$1 break;
+        rewrite ^/static/depository/(.*)$ /static-files-eurl/depository/$1 break;
+        rewrite ^/static/(.*)$ /static-files-eurl/frontend-static/$1 break;
+    }
+
+    location / {
+        resolver 127.0.0.11 valid=30s;
+        set $backend "http://eurl:8000";
+        proxy_pass $backend;
+        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_read_timeout 600s;
+	client_max_body_size 0;
+    }
+}
+
+
+
+
+
+

Asset Server

+

Using the GitHub repo at https://github.com/specify/web-asset-server

+
+

Example settings.py file

+
# Sample Specify web asset server settings.
+
+# Turns on bottle.py debugging, module reloading and printing some
+# information to console.
+DEBUG = False
+
+# This secret key is used to generate authentication tokens for requests.
+# The same key must be set in the Web Store Attachment Preferences in Specify.
+# A good source for key value is: https://www.grc.com/passwords.htm
+# Set KEY to None to disable security. This is NOT recommended since doing so
+# will allow anyone on the internet to use the attachment server to store
+# arbitrary files.
+KEY = 'tnhercbrhtktanehul.dukb'
+
+# Auth token timestamp must be within this many seconds of server time
+# in order to be considered valid. This prevents replay attacks.
+# Set to None to disable time validation.
+TIME_TOLERANCE = 600
+
+# Set this to True to require authentication for downloads in addition
+# to uploads and deletes.  Static file access, if enabled, is not
+# affected by this setting.
+REQUIRE_KEY_FOR_GET = False
+
+# This is required for use with the Web Portal.
+# Enables the 'getfileref' and '/static/...' URLs.
+ALLOW_STATIC_FILE_ACCESS = True
+
+# These values are interpolated into the web_asset_store.xml resource
+# so the client knows how to talk to the server.
+HOST = 'assets1.specifycloud.org'
+PORT = 8080
+
+SERVER_NAME = HOST
+SERVER_PORT = PORT
+
+# Port the development test server should listen on.
+DEVELOPMENT_PORT = PORT
+
+# Map collection names to directories.  Set to None to store
+# everything in the same originals and thumbnail directories.  This is
+# recommended unless some provision is made to allow attachments for
+# items scoped above collections to be found.
+
+COLLECTION_DIRS = {
+    # 'COLLECTION_NAME': 'DIRECTORY_NAME',
+    'beaty': 'beaty',
+    'ncslgfungi': 'ncslgfungi',
+    'ocpc': 'ocpc',
+    'stlawu': 'stlawu',
+    'asnhcmammals': 'asnhcmammals',
+    'dwuspiders': 'dwuspiders',
+
+    # 'nha': 'nha',
+    # 'cornellmammals': 'cornellmammals',
+    # 'cornellherps': 'cornellherps',
+    # 'cornellfishes': 'cornellfishes',
+    'uprm_invcol': 'uprm_invcol',
+    'nybg_vanuatu': 'nybg_vanuatu',
+    'demobirds': 'demobirds',
+    'emoryherbarium': 'emoryherbarium',
+    'uw_geo_collections': 'uw_geo_collections',
+    'keherbariumvascular': 'keherbariumvascular',
+    # 'cornellbirds': 'cornellbirds',
+    # 'critters': 'critters',
+    'williherb': 'williherb',
+    'sbccprofantmuseum': 'sbccprofantmuseum',
+    'josc': 'josc',
+    'ccnhm': 'ccnhm',
+    'lsumz_mammals': 'lsumz_mammals',
+    # 'pripaleo': 'pripaleo',
+    'Invertebrate Paleontology': 'pripaleo',
+    'usgs_nmnhpaleontologycollections': 'usgs_nmnhpaleontologycollections',
+    'vimsfish': 'vimsfish',
+    'vimsworkshop': 'vimsworkshop',
+    'ilstu': 'ilstu',
+    'wespalcoll': 'wespalcoll',
+    'royfungi': 'royfungi',
+    'isua': 'isua',
+    'chagasecohealth': 'chagasecohealth',
+    'plu': 'plu',
+    'uconnverts': 'uconnverts',
+    'kelichens': 'kelichens',
+    'lakeforestbiology': 'lakeforestbiology',
+    'mariamitchell': 'mariamitchell',
+    'gree': 'gree',
+    'pennstatefishmuseum': 'pennstatefishmuseum',
+    'dukebryo': 'dukebryo',
+    'bishopmuseum': 'bishopmuseum',
+    'mndragonfly': 'mndragonfly',
+    'rbge': 'rbge',
+    'cudiatoms': 'cudiatoms',
+    'fwri': 'fwri',
+    'sdinverts': 'sdinverts',
+    'shellmuseum': 'shellmuseum',
+    'ansppaleo': 'ansppaleo',
+    'osuorton': 'osuorton',
+    'ciscollections': 'ciscollections',
+    'cuic': 'cuic',
+    'lsumz_fishes': 'lsumz_fishes',
+    'calvertmarinemuseum': 'calvertmarinemuseum',
+
+    'mcnb': 'mcnb',
+    'Invertebrate non-arthropod': 'mcnb',
+
+    'lsumz_herps': 'lsumz_herps',
+    'uwfc': 'uwfc',
+
+    'Oregon State Ichthyology Collection': 'os',
+    'os': 'os',
+    'unitec': 'unitec',
+    'herb_rbge': 'herb_rbge',
+    'Herbarium RBGE': 'herb_rbge',
+    'sandbox_rbge': 'sandbox_rbge',
+
+    'nbm_mnb': 'nbm_mnb',
+    "Amphibian Reptile Sightings": 'nbm_mnb',
+
+    'purdueherbaria': 'purdueherbaria',
+    'montrealinsectarium': 'montrealinsectarium',
+
+    'workshop': 'workshop',
+    'ptrm': 'ptrm',
+    'herb_umass': 'herb_umass',
+    'unsm_vp': 'unsm_vp',
+    'morpalo': 'morpalo',
+    'aafc_aac': 'aafc_aac',
+    'spnhc': 'spnhc'
+}
+
+# COLLECTION_DIRS = None
+
+# Base directory for all attachments.
+BASE_DIR = '/home/specify/attachments'
+
+# Originals and thumbnails are stored in separate directories.
+THUMB_DIR = 'thumbnails'
+ORIG_DIR = 'originals'
+
+# Set of mime types that the server will try to thumbnail.
+CAN_THUMBNAIL = {'image/jpeg', 'image/gif', 'image/png', 'image/tiff', 'application/pdf'}
+
+# What HTTP server to use for stand-alone operation.
+SERVER = 'paste' # Requires python-paste package. Fast, and seems to work good.
+#SERVER = 'wsgiref'  # For testing. Requires no extra packages.
+
+
+
+
+

Possible Dockerfile for ECS Build (Unfinished)

+
FROM arm64v8/ubuntu:18.04 AS run-asset-server
+
+RUN apt-get update && apt-get -y install --no-install-recommends \
+        ghostscript \
+        imagemagick \
+        python3.8 \
+        python3.8-dev \
+        python3.8-pip \
+        python3-venv \
+        git \
+        nginx \
+        certbot \
+        awscli \
+        s3fs \
+        && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+RUN groupadd -g 999 specify && \
+        useradd -r -u 999 -g specify specify
+
+RUN mkdir -p /home/specify && chown specify.specify /home/specify
+
+USER specify
+WORKDIR /home/specify
+
+COPY --chown=specify:specify requirements.txt .
+
+RUN python3.8 -m venv ve && ve/bin/pip install --no-cache-dir -r requirements.txt
+
+COPY --chown=specify:specify *.py views ./
+
+RUN mkdir -p /home/specify/attachments/
+
+RUN echo \
+        "import os" \
+        "\nSERVER = 'paste'" \
+        "\nSERVER_NAME = os.environ['SERVER_NAME']" \
+        "\nSERVER_PORT = int(os.getenv('SERVER_PORT', 8080))" \
+        "\nKEY = os.environ['ATTACHMENT_KEY']" \
+        "\nDEBUG = os.getenv('DEBUG_MODE', 'false').lower() == 'true'" \
+        >> settings.py
+
+# Configure AWS
+RUN aws configure set aws_access_key_id "aws_access_key_id" && \
+aws configure set aws_secret_access_key "aws_secret_access_key" && \
+aws configure set default.region us-east-1 && \
+aws configure set default.output json
+
+# S3 Mounting
+RUN s3fs specify-cloud /home/specify/attachments/
+
+EXPOSE 8080
+CMD ve/bin/python server.py
+
+
+
+
+

Asset nginx web-server config

+
server {
+       # HTTP access is needed for Specify 6. It will not work with HTTPS.
+       listen 80 default_server;
+       server_name assets1.specifycloud.org;
+       client_max_body_size 0;
+
+       # The LetsEncrypt certificate mechanism places a nonce
+       # challenge at this location to prove we have control of the
+       # domain. Mapping it to a location in the filesystem allows us
+       # to easily use their auto renew system.
+       location /.well-known/ {
+                root /var/www/;
+       }
+
+       # The web_asset_store.xml resource must be proxied to the
+       # actual server so that it gets the correct timestamp headers.
+       # We do a string substitution on the response to make the links
+       # it defines point to this proxy.
+       location = /web_asset_store.xml {
+                proxy_pass http://localhost:8080/web_asset_store.xml;
+                sub_filter 'http://assets1.specifycloud.org:8080' 'http://assets1.specifycloud.org';
+                sub_filter_once off;
+                sub_filter_types text/xml;
+       }
+
+       # All other requests are passed to the actual asset server
+       # unchanged.
+       location / {
+                proxy_pass http://localhost:8080/;
+       }
+}
+
+server {
+       # This stanza defines the HTTPS end point.
+       listen 443 ssl default_server;
+       server_name assets1.specifycloud.org;
+       client_max_body_size 0;
+
+       ssl_certificate /etc/letsencrypt/live/assets1.specifycloud.org/fullchain.pem;
+       ssl_certificate_key /etc/letsencrypt/live/assets1.specifycloud.org/privkey.pem;
+
+       # from https://cipherli.st/
+       # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+
+       ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+       ssl_prefer_server_ciphers on;
+       ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+       ssl_ecdh_curve secp384r1;
+       ssl_session_cache shared:SSL:10m;
+       ssl_session_tickets off;
+       ssl_stapling on;
+       ssl_stapling_verify on;
+       resolver 8.8.8.8 8.8.4.4 valid=300s;
+       resolver_timeout 5s;
+       # Disable preloading HSTS for now.  You can use the commented out header line that includes
+       # the "preload" directive if you understand the implications.
+       #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
+       add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
+       add_header X-Frame-Options DENY;
+       add_header X-Content-Type-Options nosniff;
+
+       ssl_dhparam /etc/ssl/certs/dhparam.pem;
+
+       # The LetsEncrypt pass-though. I'm not sure if this is needed
+       # on HTTPS side, but I'm including it just in case.
+       location /.well-known/ {
+                root /var/www/;
+       }
+
+       # This is the same as the above, except the links get rewritten
+       # to use HTTPS in addition to changing the port.
+       location = /web_asset_store.xml {
+                proxy_pass http://localhost:8080/web_asset_store.xml;
+                sub_filter 'http://assets1.specifycloud.org:8080' 'https://assets1.specifycloud.org';
+                sub_filter_once off;
+                sub_filter_types text/xml;
+       }
+
+       # Everything else is just passed through.
+       location / {
+                proxy_pass http://localhost:8080/;
+       }
+}
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/AWS Infrastructure Notes.html b/aws/AWS Infrastructure Notes.html new file mode 100644 index 0000000..bc27825 --- /dev/null +++ b/aws/AWS Infrastructure Notes.html @@ -0,0 +1,102 @@ + + + + + + + <no title> — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ + + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/AWS Specify Asset Server Setup.html b/aws/AWS Specify Asset Server Setup.html new file mode 100644 index 0000000..64464b2 --- /dev/null +++ b/aws/AWS Specify Asset Server Setup.html @@ -0,0 +1,494 @@ + + + + + + + EC2 Non-Dockerized Build — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

EC2 Non-Dockerized Build

+
#!/bin/bash
+
+sudo apt update;
+sudo apt upgrade -y;
+sudo apt-get -y install --no-install-recommends \
+  python3-venv \
+  python3.8 \
+  python3.8-dev \
+  python3-pip \
+  imagemagick \
+  ghostscript \
+  git \
+  nginx \
+  certbot \
+  authbind \
+  s3fs \
+  awscli;
+
+# python 3.6
+#sudo apt update
+#sudo apt install build-essential checkinstall zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev;
+#wget https://www.python.org/ftp/python/3.6.15/Python-3.6.15.tgz;
+#tar -xf Python-3.6.15.tgz;
+#cd Python-3.6.15 && ./configure --enable-optimizations;
+#make -j$(nproc);
+#sudo make altinstall;
+#python3.6 --version;
+
+# python 3.6 install with apt
+sudo apt install software-properties-common;
+sudo add-apt-repository ppa:deadsnakes/ppa;
+sudo apt update;
+sudo apt install python3.6;
+sudo apt-get install python3.6-distutils;
+
+# install pip3.6
+#wget https://bootstrap.pypa.io/pip/3.6/get-pip.py;
+python3.6 -m venv --without-pip ve;
+source ve/bin/activate;
+wget https://bootstrap.pypa.io/get-pip.py;
+#wget https://bootstrap.pypa.io/pip/3.5/get-pip.py
+#deactivate;
+
+# activate python3.6 venv
+sudo apt install -y python3-virtualenv;
+python3.6 -m venv myenv;
+source myenv/bin/activate;
+pip install --no-cache-dir -r requirements.txt;
+#deactivate;
+
+# TLS dependencies
+sudo apt-get -y install --no-install-recommends \
+	certbot \
+	python3-certbot-nginx \
+	software-properties-common;
+
+# Configure AWS
+aws configure set aws_access_key_id "ACCESS_KEY";
+aws configure set aws_secret_access_key "ACCESS_KEY_SECRET";
+aws configure set default.region us-east-1;
+aws configure set default.output json;
+
+# Import attachment files
+#mkdir attachments;
+#aws s3 cp s3://specify-cloud/assets-server/attachments/ ~/attachments --recursive;
+
+# S3 Mounting
+mkdir attachments;
+s3fs specify-cloud /assets-server/attachments/;
+
+# Clone asset server repo
+git clone https://github.com/specify/web-asset-server.git;
+cd ~/web-asset-server;
+git checkout arm-build;
+
+# Build python web asset server
+python3.8 -m venv ve;
+sudo ve/bin/pip install --no-cache-dir -r requirements.txt
+#sudo pip install -r requirements.txt;
+
+# Port config
+# not needed when running with nginx
+#sudo apt-get install authbind;
+#touch 80;
+#chmod u+x 80;
+#sudo mv 80 /etc/authbind/byport;
+
+# Create SystemD service
+sudo cat > /etc/systemd/system/web-asset-server.service << EOF
+[Unit]
+Description=Specify Web Asset Server
+Wants=network.target
+
+[Service]
+User=ubuntu
+WorkingDirectory=/home/ubuntu/web-asset-server
+ExecStart=/home/ubuntu/web-asset-server/ve/bin/python /home/ubuntu/web-asset-server/server.py
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
+
+EOF
+
+sudo systemctl daemon-reload;
+sudo systemctl enable web-asset-server.service;
+sudo systemctl start web-asset-server.service;
+sudo systemctl status web-asset-server.service;
+
+# nginx
+# sudo vim etc/nginx/sites-enabled/assets.conf
+sudo rm -f /etc/nginx/sites-enabled/default;
+sudo nginx -t;
+sudo /etc/init.d/nginx reload;
+
+# S3 Mounting
+mount -o discard,defaults,noatime /dev/disk/by-id/scsi-0DO_Volume_volume-nyc1-01 /mnt/volume-nyc1-01
+
+# TODO: EFS Mounting
+
+# Certbot TLS config
+sudo mkdir /var/www/.well-known;
+sudo certbot --nginx -d assets-test.specifycloud.org -d assets-test.specifycloud.org;
+sudo ls -la /etc/letsencrypt/live/assets-test.specifycloud.org;
+#openssl dhparam -out /etc/nginx/dhparam.pem 4096;
+sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096; #2048 or 1024
+sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 1024;
+# add https server config to nginx assets.
+
+# Edit 
+
+
+

/etc/systemd/system/web-asset-server.service ->

+
[Unit]
+Description=Specify Web Asset Server
+Wants=network.target
+
+[Service]
+User=ubuntu
+WorkingDirectory=/home/ubuntu/web-asset-server
+ExecStart=/usr/bin/ubuntu/web-asset-server/ve/usr/bin/python /home/ubuntu/web-asset-server/server.py
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
+
+
+

settings.py ->

+
# Sample Specify web asset server settings.
+
+# Turns on bottle.py debugging, module reloading and printing some
+# information to console.
+DEBUG = True
+
+# This secret key is used to generate authentication tokens for requests.
+# The same key must be set in the Web Store Attachment Preferences in Specify.
+# A good source for key value is: https://www.grc.com/passwords.htm
+# Set KEY to None to disable security. This is NOT recommended since doing so
+# will allow anyone on the internet to use the attachment server to store
+# arbitrary files.
+#KEY = 'tnhercbrhtktanehul.dukb'
+KEY = 'test_attachment_key'
+
+# Auth token timestamp must be within this many seconds of server time
+# in order to be considered valid. This prevents replay attacks.
+# Set to None to disable time validation.
+TIME_TOLERANCE = 600
+
+# Set this to True to require authentication for downloads in addition
+# to uploads and deletes.  Static file access, if enabled, is not
+# affected by this setting.
+REQUIRE_KEY_FOR_GET = False
+
+# This is required for use with the Web Portal.
+# Enables the 'getfileref' and '/static/...' URLs.
+ALLOW_STATIC_FILE_ACCESS = True
+
+# These values are interpolated into the web_asset_store.xml resource
+# so the client knows how to talk to the server.
+#HOST = 'localhost'
+HOST = 'assets-test.specifycloud.org'
+PORT = 8080
+#PORT = 80
+
+SERVER_NAME = HOST
+SERVER_PORT = PORT
+
+# Port the development test server should listen on.
+DEVELOPMENT_PORT = PORT
+
+# Map collection names to directories.  Set to None to store
+# everything in the same originals and thumbnail directories.  This is
+# recommended unless some provision is made to allow attachments for
+# items scoped above collections to be found.
+
+# COLLECTION_DIRS = {
+#     # 'COLLECTION_NAME': 'DIRECTORY_NAME',
+#     'KUFishvoucher': 'Ichthyology',
+#     'KUFishtissue': 'Ichthyology',
+# }
+
+COLLECTION_DIRS = {
+    'herb_rbge': 'herb_rbge',
+    'KUFishvoucher': 'sp7demofish',
+    'KUFishtissue': 'sp7demofish',
+}
+
+# Base directory for all attachments.
+#BASE_DIR = '/home/specify/attachments/'
+BASE_DIR = '/home/ubuntu/attachments/'
+
+# Originals and thumbnails are stored in separate directories.
+THUMB_DIR = 'thumbnails'
+ORIG_DIR = 'originals'
+
+# Set of mime types that the server will try to thumbnail.
+CAN_THUMBNAIL = {'image/jpeg', 'image/gif', 'image/png', 'image/tiff', 'application/pdf'}
+
+# What HTTP server to use for stand-alone operation.
+# SERVER = 'paste' # Requires python-paste package. Fast, and seems to work good.
+SERVER = 'wsgiref'  # For testing. Requires no extra packages.
+
+
+

/etc/nginx/sites-enabled/assets.conf from the aasets1.specifycloud.org- ->

+
# Nginx configuration for supplying an HTTPS end point for the web
+# asset server. The asset server is running on the same system
+# (demo-assets.specifycloud.org) on port 8080 meaning it can run
+# without root privileges and without using authbind. Nginx proxies
+# HTTP requests on port 80 and HTTPS requests on port 443 to the
+# underlying asset server. It also rewrites the web_asset_store.xml
+# response to cause subsequent request to go through the proxy.
+
+server {
+       # HTTP access is needed for Specify 6. It will not work with HTTPS.
+       listen 80 default_server;
+       server_name assets1.specifycloud.org;
+       client_max_body_size 0;
+
+       # The LetsEncrypt certificate mechanism places a nonce
+       # challenge at this location to prove we have control of the
+       # domain. Mapping it to a location in the filesystem allows us
+       # to easily use their auto renew system.
+       location /.well-known/ {
+                root /var/www/;
+       }
+
+       # The web_asset_store.xml resource must be proxied to the
+       # actual server so that it gets the correct timestamp headers.
+       # We do a string substitution on the response to make the links
+       # it defines point to this proxy.
+       location = /web_asset_store.xml {
+                proxy_pass http://localhost:8080/web_asset_store.xml;
+                sub_filter 'http://assets1.specifycloud.org:8080' 'http://assets1.specifycloud.org';
+                sub_filter_once off;
+                sub_filter_types text/xml;
+       }
+
+       # All other requests are passed to the actual asset server
+       # unchanged.
+       location / {
+                proxy_pass http://localhost:8080/;
+       }
+}
+
+server {
+       # This stanza defines the HTTPS end point.
+       listen 443 ssl default_server;
+       server_name assets1.specifycloud.org;
+       client_max_body_size 0;
+
+       ssl_certificate /etc/letsencrypt/live/assets1.specifycloud.org/fullchain.pem;
+       ssl_certificate_key /etc/letsencrypt/live/assets1.specifycloud.org/privkey.pem;
+
+       # from https://cipherli.st/
+       # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+
+       ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+       ssl_prefer_server_ciphers on;
+       ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+       ssl_ecdh_curve secp384r1;
+       ssl_session_cache shared:SSL:10m;
+       ssl_session_tickets off;
+       ssl_stapling on;
+       ssl_stapling_verify on;
+       resolver 8.8.8.8 8.8.4.4 valid=300s;
+       resolver_timeout 5s;
+       # Disable preloading HSTS for now.  You can use the commented out header line that includes
+       # the "preload" directive if you understand the implications.
+       #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
+       add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
+       add_header X-Frame-Options DENY;
+       add_header X-Content-Type-Options nosniff;
+
+       ssl_dhparam /etc/ssl/certs/dhparam.pem;
+
+       # The LetsEncrypt pass-though. I'm not sure if this is needed
+       # on HTTPS side, but I'm including it just in case.
+       location /.well-known/ {
+                root /var/www/;
+       }
+
+       # This is the same as the above, except the links get rewritten
+       # to use HTTPS in addition to changing the port.
+       location = /web_asset_store.xml {
+                proxy_pass http://localhost:8080/web_asset_store.xml;
+                sub_filter 'http://assets1.specifycloud.org:8080' 'https://assets1.specifycloud.org';
+                sub_filter_once off;
+                sub_filter_types text/xml;
+       }
+
+       # Everything else is just passed through.
+       location / {
+                proxy_pass http://localhost:8080/;
+       }
+}
+
+
+

/etc/letsencrypt/renewal/assets1.specifycloud.org.conf ->

+
# renew_before_expiry = 30 days
+cert = /etc/letsencrypt/live/assets1.specifycloud.org/cert.pem
+privkey = /etc/letsencrypt/live/assets1.specifycloud.org/privkey.pem
+chain = /etc/letsencrypt/live/assets1.specifycloud.org/chain.pem
+fullchain = /etc/letsencrypt/live/assets1.specifycloud.org/fullchain.pem
+version = 1.9.0
+archive_dir = /etc/letsencrypt/archive/assets1.specifycloud.org
+
+# Options and defaults used in the renewal process
+[renewalparams]
+authenticator = webroot
+account = a563615cc912ed3d7a3edfede09d6760
+post_hook = systemctl reload nginx
+server = https://acme-v02.api.letsencrypt.org/directory
+[[webroot_map]]
+assets1.specifycloud.org = /var/www
+
+
+

/etc/ssl/certs/dhparam.pem from assets1.specofycloud.org->

+
-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEAlcFKsIuFylwX47jxqbNT0wSVD6ifznsMcti8f7T+zaQQNr84IYIM
+pNTT9E6SrVkkJg2u1nGScNqj5lArXvrda6zL66T8WmkFFrGfNW7RYCQ3vpg6BpGs
+dJ3+HtWYDNoMbeCrDyMz1DDfX/3OWblTTZRbjpvn/tEgTAn3DexP/QkE9E2c1AUX
+Mf/07vWpZ7giemaNgaME3fHDKyReNhTpfg1eDKypUUhEmr+PJmWQ9LQBc12LyXOP
+DaFwAJUrqwEqrQP5fEQdOMdh522RwuD2/fPeXTukQHI8gUuMjk652aeLOcn1Ufhy
+/KbbV6TJi7wS5F3HVaNXGOLMsHq+CywOCwIBAg==
+-----END DH PARAMETERS-----
+
+
+

swiss asset server password: xD5dakesktkxceb

+
+
+

EC2 Non-docker build shell script

+
#!/bin/bash
+
+sudo apt update;
+sudo apt upgrade -y;
+sudo apt-get -y install --no-install-recommends \
+  python3-venv \
+  python3.8 \
+  python3.8-dev \
+  python3-pip \
+  imagemagick \
+  ghostscript \
+  git \
+  nginx \
+  certbot \
+  authbind \
+  s3fs \
+  awscli;
+
+# Clone asset server repo
+git clone https://github.com/specify/web-asset-server.git;
+cd ~/web-asset-server;
+git checkout arm-build;
+
+# python 3.6 install with apt
+sudo apt install -y software-properties-common;
+sudo add-apt-repository ppa:deadsnakes/ppa;
+sudo apt update;
+sudo apt install -y python3.6;
+sudo apt-get install -y python3.6-distutils;
+pip install --no-cache-dir -r requirements.txt;
+
+
+
+
+

Docker Build

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/Add New User Insance to Specify Cloud.html b/aws/Add New User Insance to Specify Cloud.html new file mode 100644 index 0000000..901ccd8 --- /dev/null +++ b/aws/Add New User Insance to Specify Cloud.html @@ -0,0 +1,190 @@ + + + + + + + <no title> — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Example for UNSM_VP

+
    +
  1. Create Database

    +
      +
    1. look through the sql file for issues and do test upload to local database

    2. +
    3. mysql -umaster -p’master’ -e “create database unsm_vp;”

    4. +
    5. mysql -umaster -p’master’ unsm_vp < unsm_vp.sql

    6. +
    7. may need to run grant all privileges on eurl.* to 'master'@'%'; if master doesn’t have access flush privileges;

    8. +
    +
  2. +
  3. DNS Registtration:

    +
      +
    1. Login to Dreamhost, select Websites -> Manage Websites

    2. +
    3. For specifycloud.org, select DNS

    4. +
    5. Add CNAME record in the style of the other users. +4. unsm-vp points to na-specify7-1.specifycloud.org.

    6. +
    7. Wait at least 10 minutes for domain to circulate.

    8. +
    9. For the ku servers, request the dns CNAME record to bitech@ku.edu

    10. +
    +
  4. +
  5. Config

    +
      +
    1. Add to spcloudserver.json

    2. +
    3. Make sure to add https: false

    4. +
    5. su specify -c make

    6. +
    7. docker-compose up -d

    8. +
    9. docker-compose restart nginx (actually just reload is fine here: docker exec -it specifycloud_nginx_1 nginx -s reload)

    10. +
    11. check url

    12. +
    +
  6. +
  7. Add SSL

    +
      +
    1. mkdir /var/www/unsm-vp

    2. +
    3. certbot –webroot -w /var/www/unsm-vp -d unsm-vp.specifycloud.org certonly

    4. +
    5. certbot certificates

    6. +
    7. Remove https: false from spcloudserver.json

    8. +
    9. su specify -c make

    10. +
    11. docker-compose up -d

    12. +
    13. docker-compose restart (maybe just reload instead)

    14. +
    15. check url

    16. +
    17. note: after an ssl certificate renewal -> docker exec -it specifycloud_nginx_1 nginx -s reload

      +
        +
      1. For automatic nginx reloading on certificate renewal create /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh #!/bin/bash docker exec -it specifycloud_nginx_1 nginx -s reload

      2. +
      3. crontab -e; and then add the line “0 3 * * 0,2,4,6 docker exec specifycloud_nginx_1 nginx -s reload”

      4. +
      5. crontab -l to list cronjobs

      6. +
      +
    18. +
    +
  8. +
  9. Database Backup

    +
      +
    1. ssh into biprdsp6ap.cc.ku.edu

    2. +
    3. sudo su - spcloudbackup

    4. +
    5. Add unsm_vp into the file /home/spcloudbackup/backup_specify_cloud.py

    6. +
    +
  10. +
  11. Asset Server

    +
      +
    1. ssh into asset

    2. +
    3. Add unsm_vp directory in attachments directory ‘su specify -c “mkdir attachments/unsm_vp”’

    4. +
    5. Add unsm_vp to /home/specify/new-asset-server/settings.py

    6. +
    7. systemctl restart web-asset-server.service

    8. +
    +
  12. +
  13. Updown

    +
      +
    1. Add url: unsm-vp.specifycloud.org/context/system_info.json

    2. +
    3. Add alias: unsm-vp

    4. +
    +
  14. +
+

Restarting the Database droplet, handle mariadb failing to restart:

+
    +
  • mysqld –tc-heuristic-recover=ROLLBACK

  • +
  • systemctl start mariadb.service

  • +
+

Fixing an instance by restarting it:

+
sudo docker exec -it specifycloud_nginx_1 nginx -s reload;
+sudo docker stop client client-worker;
+sudo docker compose up -d;
+sudo docker exec -it specifycloud_nginx_1 nginx -s reload;
+
+
+

Add ssh key:

+
vim .ssh/authorized_keys
+sudo systemctl reload sshd
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/Asset Server Config.html b/aws/Asset Server Config.html new file mode 100644 index 0000000..95a38a9 --- /dev/null +++ b/aws/Asset Server Config.html @@ -0,0 +1,291 @@ + + + + + + + <no title> — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Here is an asset server configuration example with the latest image specifyconsortium/specify-asset-service:connection_fix. +I spun up a server with the following config and got it working assets-docker.specifycloud.org/web_asset_store.xml +Make sure to configure your dns record to the IP address of your server.

+

docker-compose.yml ->

+
version: '3.7'
+services:
+  nginx:
+    image: nginx:alpine
+    ports:
+      - "80:80"
+
+    volumes:
+      - "./nginx.conf:/etc/nginx/conf.d/default.conf:ro"
+
+  asset-server:
+    restart: unless-stopped
+    image: specifyconsortium/specify-asset-service:connection_fix
+    init: true
+    volumes:
+      # Store all attachments outside the container, in a separate volume
+      # - "attachments:/home/specify/attachments"
+      - "/home/ubuntu/attachments:/home/specify/attachments"
+    environment:
+      # Replace this with the URL at which asset server would be publicly available
+      SERVER_NAME: assets-docker.specifycloud.org
+      SERVER_PORT: 8080
+      # SERVER: paste
+      ATTACHMENT_KEY: qwertyasdfghzxcvbnlmnop
+      DEBUG_MODE: false
+      COLLECTION_DIRS: >
+        {
+          'sp7demofish':'sp7demofish',
+          'KUFishvoucher':'KUFishvoucher',
+          'KUFishtissue':'KUFishtissue'
+        }
+      BASE_DIR: /home/ubuntu/attachments
+
+volumes:
+  attachments: # the asset-servers attachment files
+
+
+

nginx.conf ->

+
server {
+    listen 80 default_server;
+    server_name assets-docker.specifycloud.org;
+    client_max_body_size 0;
+
+    location /.well-known/ {
+        root /var/www/assets-docker/;
+    }
+
+    location = /web_asset_store.xml {
+        proxy_pass http://asset-server:8080/web_asset_store.xml;
+        sub_filter 'http://assets-docker.specifycloud.org:8080' 'http://assets-docker.specifycloud.org';
+        sub_filter_once off;
+        sub_filter_types text/xml;
+    }
+
+    location / {
+        proxy_pass http://asset-server:8080/;
+    }
+}
+
+
+

Then to get the asset server working with https and connected with Specify7, I created certificates with certbot, and then used the follow config

+

certbot bash commands ->

+
# Make sure the nginx server is running
+sudo mkdir /var/www;
+sudo mkdir /var/www/assets-docker;
+sudo certbot --webroot -w /var/www/assets-docker -d assets-docker.specifycloud.org certonly;
+
+
+

docker-compose.yml ->

+
version: '3.7'
+services:
+  nginx:
+    image: nginx:alpine
+    ports:
+      - "80:80"
+      - "443:443"
+
+    volumes:
+      - "./nginx.conf:/etc/nginx/conf.d/default.conf:ro"
+
+  asset-server:
+    restart: unless-stopped
+    image: specifyconsortium/specify-asset-service:connection_fix
+    init: true
+    volumes:
+      # Store all attachments outside the container, in a separate volume
+      # - "attachments:/home/specify/attachments"
+      - "/home/ubuntu/attachments:/home/specify/attachments"
+    environment:
+      # Replace this with the URL at which asset server would be publicly available
+      SERVER_NAME: assets-docker.specifycloud.org
+      SERVER_PORT: 8080
+      # SERVER: paste
+      ATTACHMENT_KEY: qwertyasdfghzxcvbnlmnop
+      DEBUG_MODE: false
+      HTTPS: true
+      COLLECTION_DIRS: >
+        {
+          'sp7demofish':'sp7demofish',
+          'KUFishvoucher':'KUFishvoucher',
+          'KUFishtissue':'KUFishtissue'
+        }
+      BASE_DIR: /home/ubuntu/attachments
+
+volumes:
+  attachments: # the asset-servers attachment files
+
+
+

nginx.conf ->

+
server {
+    listen 80 default_server;
+    server_name assets-docker.specifycloud.org;
+    client_max_body_size 0;
+
+    location /.well-known/ {
+        root /var/www/assets-docker/;
+    }
+
+    location = /web_asset_store.xml {
+        proxy_pass http://asset-server:8080/web_asset_store.xml;
+        sub_filter 'http://assets-docker.specifycloud.org:8080' 'http://assets-docker.specifycloud.org';
+        sub_filter_once off;
+        sub_filter_types text/xml;
+    }
+
+    location / {
+        proxy_pass http://asset-server:8080/;
+    }
+}
+
+server {
+       # This stanza defines the HTTPS end point.
+       listen 443 ssl default_server;
+       server_name assets-docker.specifycloud.org;
+       client_max_body_size 0;
+
+       ssl_certificate /etc/letsencrypt/live/assets-docker.specifycloud.org/fullchain.pem;
+       ssl_certificate_key /etc/letsencrypt/live/assets-docker.specifycloud.org/privkey.pem;
+
+       # from https://cipherli.st/
+       # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+
+       ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+       ssl_prefer_server_ciphers on;
+       ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+       ssl_ecdh_curve secp384r1;
+       ssl_session_cache shared:SSL:10m;
+       ssl_session_tickets off;
+       ssl_stapling on;
+       ssl_stapling_verify on;
+       resolver 8.8.8.8 8.8.4.4 valid=300s;
+       resolver_timeout 5s;
+       # Disable preloading HSTS for now.  You can use the commented out header line that includes
+       # the "preload" directive if you understand the implications.
+       #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
+       add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
+       add_header X-Frame-Options DENY;
+       add_header X-Content-Type-Options nosniff;
+
+       ssl_dhparam /etc/ssl/certs/dhparam.pem;
+
+       # The LetsEncrypt pass-though. I'm not sure if this is needed
+       # on HTTPS side, but I'm including it just in case.
+       location /.well-known/ {
+                root /var/www/assets-docker/;
+       }
+
+       # This is the same as the above, except the links get rewritten
+       # to use HTTPS in addition to changing the port.
+       location = /web_asset_store.xml {
+                proxy_pass http://asset-server:8080/web_asset_store.xml;
+                sub_filter 'http://assets-docker.specifycloud.org:8080' 'https://assets-docker.specifycloud.org';
+                sub_filter_once off;
+                sub_filter_types text/xml;
+       }
+
+       # Everything else is just passed through.
+       location / {
+                proxy_pass http://asset-server:8080/;
+       }
+}
+
+
+

Make sure to set - ASSET_SERVER_URL=https://assets-docker.specifycloud.org/web_asset_store.xml in your specify7 and specify7-worker docker containers

+

Let me know if the new image and config works for you!

+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/Asset Server Setup.html b/aws/Asset Server Setup.html new file mode 100644 index 0000000..d791fe9 --- /dev/null +++ b/aws/Asset Server Setup.html @@ -0,0 +1,429 @@ + + + + + + + EC2 Non-Dockerized Build — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

EC2 Non-Dockerized Build

+
#!/bin/bash
+
+sudo apt update;
+sudo apt upgrade -y;
+sudo apt-get -y install --no-install-recommends \
+  python3-venv \
+  python3.8 \
+  python3.8-dev \
+  python3-pip \
+  imagemagick \
+  ghostscript \
+  git \
+  nginx \
+  certbot \
+  authbind \
+  s3fs \
+  awscli;
+
+# python 3.6 install with apt
+sudo apt install software-properties-common;
+sudo add-apt-repository ppa:deadsnakes/ppa;
+sudo apt update;
+sudo apt install python3.6;
+sudo apt-get install python3.6-distutils;
+
+# install pip3.6
+#wget https://bootstrap.pypa.io/pip/3.6/get-pip.py;
+python3.6 -m venv --without-pip ve;
+source ve/bin/activate;
+wget https://bootstrap.pypa.io/get-pip.py;
+#wget https://bootstrap.pypa.io/pip/3.5/get-pip.py
+
+# activate python3.6 venv
+sudo apt install python3-virtualenv;
+python3.6 -m venv myenv;
+source myenv/bin/activate;
+pip install --no-cache-dir -r requirements.txt;
+
+# TLS dependencies
+sudo apt-get -y install --no-install-recommends \
+	certbot \
+	python3-certbot-nginx \
+	software-properties-common;
+
+# Import attachment files
+#mkdir attachments;
+#aws s3 cp s3://specify-cloud/assets-server/attachments/ ~/attachments --recursive;
+
+# S3 Mounting
+mkdir attachments;
+s3fs specify-cloud /assets-server/attachments/;
+
+# Clone asset server repo
+git clone https://github.com/specify/web-asset-server.git;
+cd ~/web-asset-server;
+git checkout arm-build;
+
+# Build python web asset server
+python3.8 -m venv ve;
+sudo ve/bin/pip install --no-cache-dir -r requirements.txt
+#sudo pip install -r requirements.txt;
+
+# Port config if needed
+# not needed when running with nginx
+#sudo apt-get install authbind;
+#touch 80;
+#chmod u+x 80;
+#sudo mv 80 /etc/authbind/byport;
+
+# Create SystemD service
+sudo cat > /etc/systemd/system/web-asset-server.service << EOF
+[Unit]
+Description=Specify Web Asset Server
+Wants=network.target
+
+[Service]
+User=ubuntu
+WorkingDirectory=/home/ubuntu/web-asset-server
+ExecStart=/home/ubuntu/web-asset-server/ve/bin/python /home/ubuntu/web-asset-server/server.py
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
+
+EOF
+
+sudo systemctl daemon-reload;
+sudo systemctl enable web-asset-server.service;
+sudo systemctl start web-asset-server.service;
+sudo systemctl status web-asset-server.service;
+
+# nginx
+# sudo vim etc/nginx/sites-enabled/assets.conf
+sudo rm -f /etc/nginx/sites-enabled/default;
+sudo nginx -t;
+sudo /etc/init.d/nginx reload;
+
+# S3 Mounting
+mount -o discard,defaults,noatime /dev/disk/by-id/scsi-0DO_Volume_volume-nyc1-01 /mnt/volume-nyc1-01
+
+# Certbot TLS config
+sudo mkdir /var/www/.well-known;
+sudo certbot --nginx -d assets-test.specifycloud.org -d assets-test.specifycloud.org;
+sudo ls -la /etc/letsencrypts/live/assets-test.specifycloud.org;
+sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096; #2048 or 1024
+sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 1024;
+# add https server config to nginx assets.conf
+
+
+

/etc/systemd/system/web-asset-server.service ->

+
[Unit]
+Description=Specify Web Asset Server
+Wants=network.target
+
+[Service]
+User=ubuntu
+WorkingDirectory=/home/ubuntu/web-asset-server
+ExecStart=/usr/bin/ubuntu/web-asset-server/ve/usr/bin/python /home/ubuntu/web-asset-server/server.py
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
+
+
+

settings.py ->

+
# Sample Specify web asset server settings.
+
+# Turns on bottle.py debugging, module reloading and printing some
+# information to console.
+DEBUG = True
+
+# This secret key is used to generate authentication tokens for requests.
+# The same key must be set in the Web Store Attachment Preferences in Specify.
+# A good source for key value is: https://www.grc.com/passwords.htm
+# Set KEY to None to disable security. This is NOT recommended since doing so
+# will allow anyone on the internet to use the attachment server to store
+# arbitrary files.
+KEY = 'test_attachment_key'
+
+# Auth token timestamp must be within this many seconds of server time
+# in order to be considered valid. This prevents replay attacks.
+# Set to None to disable time validation.
+TIME_TOLERANCE = 600
+
+# Set this to True to require authentication for downloads in addition
+# to uploads and deletes.  Static file access, if enabled, is not
+# affected by this setting.
+REQUIRE_KEY_FOR_GET = False
+
+# This is required for use with the Web Portal.
+# Enables the 'getfileref' and '/static/...' URLs.
+ALLOW_STATIC_FILE_ACCESS = True
+
+# These values are interpolated into the web_asset_store.xml resource
+# so the client knows how to talk to the server.
+#HOST = 'localhost'
+HOST = 'assets-test.specifycloud.org'
+PORT = 8080
+#PORT = 80
+
+SERVER_NAME = HOST
+SERVER_PORT = PORT
+
+# Port the development test server should listen on.
+DEVELOPMENT_PORT = PORT
+
+# Map collection names to directories.  Set to None to store
+# everything in the same originals and thumbnail directories.  This is
+# recommended unless some provision is made to allow attachments for
+# items scoped above collections to be found.
+
+# COLLECTION_DIRS = {
+#     # 'COLLECTION_NAME': 'DIRECTORY_NAME',
+#     'KUFishvoucher': 'Ichthyology',
+#     'KUFishtissue': 'Ichthyology',
+# }
+
+COLLECTION_DIRS = {
+    'herb_rbge': 'herb_rbge',
+    'KUFishvoucher': 'sp7demofish',
+    'KUFishtissue': 'sp7demofish',
+}
+
+# Base directory for all attachments.
+#BASE_DIR = '/home/specify/attachments/'
+BASE_DIR = '/home/ubuntu/attachments/'
+
+# Originals and thumbnails are stored in separate directories.
+THUMB_DIR = 'thumbnails'
+ORIG_DIR = 'originals'
+
+# Set of mime types that the server will try to thumbnail.
+CAN_THUMBNAIL = {'image/jpeg', 'image/gif', 'image/png', 'image/tiff', 'application/pdf'}
+
+# What HTTP server to use for stand-alone operation.
+# SERVER = 'paste' # Requires python-paste package. Fast, and seems to work good.
+SERVER = 'wsgiref'  # For testing. Requires no extra packages.
+
+
+

/etc/nginx/sites-enabled/assets.conf from the aasets1.specifycloud.org- ->

+
# Nginx configuration for supplying an HTTPS end point for the web
+# asset server. The asset server is running on the same system
+# (demo-assets.specifycloud.org) on port 8080 meaning it can run
+# without root privileges and without using authbind. Nginx proxies
+# HTTP requests on port 80 and HTTPS requests on port 443 to the
+# underlying asset server. It also rewrites the web_asset_store.xml
+# response to cause subsequent request to go through the proxy.
+
+server {
+       # HTTP access is needed for Specify 6. It will not work with HTTPS.
+       listen 80 default_server;
+       server_name assets1.specifycloud.org;
+       client_max_body_size 0;
+
+       # The LetsEncrypt certificate mechanism places a nonce
+       # challenge at this location to prove we have control of the
+       # domain. Mapping it to a location in the filesystem allows us
+       # to easily use their auto renew system.
+       location /.well-known/ {
+                root /var/www/;
+       }
+
+       # The web_asset_store.xml resource must be proxied to the
+       # actual server so that it gets the correct timestamp headers.
+       # We do a string substitution on the response to make the links
+       # it defines point to this proxy.
+       location = /web_asset_store.xml {
+                proxy_pass http://localhost:8080/web_asset_store.xml;
+                sub_filter 'http://assets1.specifycloud.org:8080' 'http://assets1.specifycloud.org';
+                sub_filter_once off;
+                sub_filter_types text/xml;
+       }
+
+       # All other requests are passed to the actual asset server
+       # unchanged.
+       location / {
+                proxy_pass http://localhost:8080/;
+       }
+}
+
+server {
+       # This stanza defines the HTTPS end point.
+       listen 443 ssl default_server;
+       server_name assets1.specifycloud.org;
+       client_max_body_size 0;
+
+       ssl_certificate /etc/letsencrypt/live/assets1.specifycloud.org/fullchain.pem;
+       ssl_certificate_key /etc/letsencrypt/live/assets1.specifycloud.org/privkey.pem;
+
+       # from https://cipherli.st/
+       # and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+
+       ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+       ssl_prefer_server_ciphers on;
+       ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+       ssl_ecdh_curve secp384r1;
+       ssl_session_cache shared:SSL:10m;
+       ssl_session_tickets off;
+       ssl_stapling on;
+       ssl_stapling_verify on;
+       resolver 8.8.8.8 8.8.4.4 valid=300s;
+       resolver_timeout 5s;
+       # Disable preloading HSTS for now.  You can use the commented out header line that includes
+       # the "preload" directive if you understand the implications.
+       #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
+       add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
+       add_header X-Frame-Options DENY;
+       add_header X-Content-Type-Options nosniff;
+
+       ssl_dhparam /etc/ssl/certs/dhparam.pem;
+
+       # The LetsEncrypt pass-though. I'm not sure if this is needed
+       # on HTTPS side, but I'm including it just in case.
+       location /.well-known/ {
+                root /var/www/;
+       }
+
+       # This is the same as the above, except the links get rewritten
+       # to use HTTPS in addition to changing the port.
+       location = /web_asset_store.xml {
+                proxy_pass http://localhost:8080/web_asset_store.xml;
+                sub_filter 'http://assets1.specifycloud.org:8080' 'https://assets1.specifycloud.org';
+                sub_filter_once off;
+                sub_filter_types text/xml;
+       }
+
+       # Everything else is just passed through.
+       location / {
+                proxy_pass http://localhost:8080/;
+       }
+}
+
+
+

/etc/letsencrypt/renewal/assets1.specifycloud.org.conf ->

+
# renew_before_expiry = 30 days
+cert = /etc/letsencrypt/live/assets1.specifycloud.org/cert.pem
+privkey = /etc/letsencrypt/live/assets1.specifycloud.org/privkey.pem
+chain = /etc/letsencrypt/live/assets1.specifycloud.org/chain.pem
+fullchain = /etc/letsencrypt/live/assets1.specifycloud.org/fullchain.pem
+version = 1.9.0
+archive_dir = /etc/letsencrypt/archive/assets1.specifycloud.org
+
+# Options and defaults used in the renewal process
+[renewalparams]
+authenticator = webroot
+account = a563615cc912ed3d7a3edfede09d6760
+post_hook = systemctl reload nginx
+server = https://acme-v02.api.letsencrypt.org/directory
+[[webroot_map]]
+assets1.specifycloud.org = /var/www
+
+
+

/etc/ssl/certs/dhparam.pem from assets1.specofycloud.org->

+
-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEAlcFKsIuFylwX47jxqbNT0wSVD6ifznsMcti8f7T+zaQQNr84IYIM
+pNTT9E6SrVkkJg2u1nGScNqj5lArXvrda6zL66T8WmkFFrGfNW7RYCQ3vpg6BpGs
+dJ3+HtWYDNoMbeCrDyMz1DDfX/3OWblTTZRbjpvn/tEgTAn3DexP/QkE9E2c1AUX
+Mf/07vWpZ7giemaNgaME3fHDKyReNhTpfg1eDKypUUhEmr+PJmWQ9LQBc12LyXOP
+DaFwAJUrqwEqrQP5fEQdOMdh522RwuD2/fPeXTukQHI8gUuMjk652aeLOcn1Ufhy
+/KbbV6TJi7wS5F3HVaNXGOLMsHq+CywOCwIBAg==
+-----END DH PARAMETERS-----
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/Client Migration Notes.html b/aws/Client Migration Notes.html new file mode 100644 index 0000000..c9226d9 --- /dev/null +++ b/aws/Client Migration Notes.html @@ -0,0 +1,114 @@ + + + + + + + <no title> — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

The migration from Digital Ocean to Amazon Web Services has our hosting provider will increase Specify’s reliability and security.

+

Since MySQL 5.7 is now deprecated, we are now using MariaDB v10.11. In the future we hope to upgrade to PostgreSQL

+

The database backups will be further improved by storing daily backups for a month

+

For connecting Specify6 to the database via ssh, two things have changed, there will be no root login to the server, and the IP address for the database. The Linux user name will be the same as in your institution’s url, but with underscore _ replacing dashes -

+

Here are the new database IPs (they have been updated in the wiki as well https://github.com/specify/specify7/wiki/Specify-6-Remote-Access): +NA: 172.31.96.36 +EU: 172.31.16.73 +CA: 172.31.35.249

+

Here is an example +On Linux/Macssh -N -L3307:172.31.96.36:3306 institution_id@na-db-1.specifycloud.org +On Windows PuTTY target C:\Program Files\PuTTY\putty.exe" -ssh -i C:\users\your_user\private_key_.ppk institution_id@eu-db-1.specifycloud.org -L 3307:172.31.16.73:3306 -N

+

For now, you will log into the database as master with the same previous passwords, but we will soon be creating database user for each institution.

+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/EC2 & RDS Specify7 Setup.html b/aws/EC2 & RDS Specify7 Setup.html new file mode 100644 index 0000000..ff3f083 --- /dev/null +++ b/aws/EC2 & RDS Specify7 Setup.html @@ -0,0 +1,341 @@ + + + + + + + Spin Up EC2 Instance — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

These are the detailed instructions to get Specify7 up and running on an EC2 instance. This is a temporary solution for deployment, with an ECS solution coming in the future. These instruction require access to the specify/docker-compositions private repo. For this kind of AWS deployment, it is easiest to use the specifycloud deployment, even with just one client instance. I used the specifycloud deployment for my AWS EC2 instance, but if you want to include things like the asset server, you can create a separate instance of the asset server deployment (instructions here https://github.com/specify/web-asset-server). You could also try to use the all-in-one deployment instead.

+

Specify7 was not originally designed to be cloud native, but since I have joined the team and picked up the project, I have been working on developing a modern cloud native solution. I hope to soon have a set of CDK scripts that will make deployment to ECS, S3, and RDS simple and scalable.

+
+

Spin Up EC2 Instance

+
    +
  • For the Amazon Machine Image (AMI), choose the default Ubuntu Server (Ubuntu Server 22.04 LTS) with the 64-bit x86 architecture.

  • +
  • For the instance type, start with the t3.small or t3.medium. Upgrade to a better instance if needed.

  • +
  • In Network Settings, make sure to allow HTTP and HTTPS traffic.

  • +
  • Setup your Key Pair for logging in.

  • +
  • Launch Instance.

  • +
+
+
+

S3 Setup

+

Create an S3 bucket for storing some static files.

+

You can either upload the GitHub repo to s3, or you can clone the repo directly in the EC2 instance. If you are cloning inside the EC2 instance, note that you will need a GitHub access token to clone the private repo, instructions here https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token. Make sure when configuring the token that you allow it to read from private repos, otherwise cloning will not work.

+

Upload your SQL file to the S3 bucket for the initial database upload.

+

There are two config files that you need to create for specifycloud:

+

spcloudservers.json ->

+
{
+	"servers": {
+		"archireef": {
+			"sp7": "edge",
+			"sp6": "specify6803",
+			"https": false,
+			"database": "archireef"
+		}
+	},
+	"decommissioned": [],
+	"sp6versions": {
+		"specify6803": "6.8.03"
+	}
+}
+
+
+

Your need to edit the version of Specify6 for your database’s schema version (latest is 6.8.03). For sp7, input the GitHub tag that you want to use in the repo (latest is always edge). For your first run, keep https set to false. Change it to true only after you have setup SSL/TLS. The database value is the name of your database.

+

defaults.env ->

+
DATABASE_HOST=<YOUR_DATABASE_HOST>
+DATABASE_PORT=3306
+MASTER_NAME=master
+MASTER_PASSWORD=<YOUR_MASTER_PASSWORD>
+SECRET_KEY=temp
+ASSET_SERVER_URL=https://assets1.specifycloud.org/web_asset_store.xml
+ASSET_SERVER_KEY=<YOUR_ASSET_SERVER_KEY>
+REPORT_RUNNER_HOST=10.132.218.32
+REPORT_RUNNER_PORT=8080
+CELERY_BROKER_URL=redis://redis/0
+CELERY_RESULT_BACKEND=redis://redis/1
+LOG_LEVEL=WARNING
+SP7_DEBUG=false
+
+
+

You will only need to edit the DATABASE_HOST, MASTER_PASSWORD, ASSET_SERVER_URL, and ASSET_SERVER_KEY

+
+
+

Configure Specify7

+

Once the EC2 instance is up and running, ssh into the EC2 instance.

+
    +
  • Download your AWS access key

  • +
  • chmod 600 YOUR-AWS-ACCESS-KEY.pem

  • +
  • On the EC2 Instance webpage, click connect, choose ssh, and copy the ssh command. Make sure you are in the directory with the access key.

  • +
  • You can try using AWS CloudShell if you don’t want to ssh from your local machine.

  • +
+

Here are the commands to run to setup Specify7. Note that for the AWS access key, you can use the AWS Secrets manager if you prefer. Also note that after some of these commands, SystemD services will restart. When that happens, just press enter once or twice to you get back to the shell.

+
# Avoid services restarting during apt updates
+sudo sed -i "s/#\$nrconf{kernelhints} = -1;/\$nrconf{kernelhints} = -1;/g" /etc/needrestart/needrestart.conf;
+
+# Run apt installs
+sudo apt update;
+sudo apt upgrade -y;
+sudo apt install -y apt-transport-https ca-certificates git gh curl software-properties-common wget python3-pip awscli mysql-client j2cli;
+
+# Configure AWS
+aws configure set aws_access_key_id "YOUR_AWS_ACCESS_KEY_ID";
+aws configure set aws_secret_access_key "YOUR_AWS_ACCESS_KEY_SECRET";
+aws configure set default.region us-east-1;
+aws configure set default.output json;
+
+# Install Docker
+curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg;
+echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null;
+sudo apt update;
+apt-cache policy docker-ce;
+sudo apt install -y docker-ce;
+docker --version; # Check to make sure it installed correctly
+
+# Install docker compose
+mkdir .docker;
+mkdir .docker/cli-plugins;
+curl -L "https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-linux-$(uname -m)" -o ~/.docker/cli-plugins/docker-compose;
+chmod +x ~/.docker/cli-plugins/docker-compose;
+docker compose version; # Check to make sure it installed correctly
+
+# Copy files from S3, only need to copy the file that you need
+aws s3 cp s3://specify-cloud/repo-snapshots/docker-compositions/ ./ --recursive;
+aws s3 cp s3://specify-cloud/config/github-access-key.txt ./;
+aws s3 cp s3://specify-cloud/config/spcloudservers.json ./specifycloud/;
+aws s3 cp s3://specify-cloud/config/defaults.env ./specifycloud/;
+
+# Optional, need to clone if you did not copy repo from S3
+# Clone private repo, need to use github cli
+gh auth login --with-token < github-access-key.txt;
+gh auth setup-git;
+gh repo clone https://github.com/specify/docker-compositions.git;
+
+
+
+
+

Spin Up RDS Instance

+
    +
  • For choosing the RDS engine type, I have been experimenting with using Aurora MySQL 5.7 to handle dynamic scaling, but the default option for Specify7 on AWS RDS is MariaDB 10.6.10.

  • +
  • For Templates, I would suggest starting with Dev/Test instead of production.

  • +
  • Setup Master password.

  • +
  • For testing, I would suggest using either the db.t3.medium or db.t3.large instances. You might feel the need to upgrade to the db.m5.large instance or something similar.

  • +
  • Decide on storage size to accommodate your data size. The general purpose SSD storage option should suffice, but you can try the provisioned IOPS SSD option if needed.

  • +
  • Important, make sure in the connectivity options, select “Connect to an EC2 compute resource” and add your EC2 instance.

  • +
  • Create database.

  • +
+
+
+

Upload Data

+

Get the database host from the AWS RDS webpage. In the connectivity tab of your RDS instance, copy the test marked ‘Endpoint’. Then run the following commands to upload your data to the database.

+
# Database setup
+cd specifycloud;
+mkdir seed-databases;
+aws s3 cp s3://specify-cloud/seed-database/archireef.sql ./seed-databases/;
+mysql --host specify-cloud-swiss-demo-database-1.c9qlnkmf2lfl.eu-central-2.rds.amazonaws.com --port 3306 -u master -p -e "create database archireef;";
+mysql --host specify-cloud-swiss-demo-database-1.c9qlnkmf2lfl.eu-central-2.rds.amazonaws.com --port 3306 -u master -p specify < ./seed-databases/archireef.sql;
+rm -f ./seed-databases/archireef.sql;
+
+
+
+
+

Deploy Specify7

+

Here are the remaining commands on the EC2 instance to setup the database connection and start running Specify7. Replace the S3 and RDS urls with your own.

+
# Run Specify Network
+cd specifycloud;
+make;
+sudo docker compose up -d;
+
+
+

Go to the AWS console webpage and navigate to the EC2 instance page. For your instance, select the networking tab, then copy the the public IPv4 DNS address and open if in you r browser. Note to change the url from https to http if you haven’t setup SSL/TLS yet (https notes in the Concluding notes section).

+
+
+

Docker Container Dependencies

+

Containers:

+
    +
  • specify7

    +
      +
    • django application of specify7

    • +
    • depends on connections to the webpack, specify7-worker, redis, asset-server, nginx, and report-runner containers

    • +
    • depends on files being created by the specify6 container

    • +
    +
  • +
  • webpack

    +
      +
    • holds static files for the front-end

    • +
    • the nginx server depends on this container

    • +
    • independent container

    • +
    +
  • +
  • specify7-worker

    +
      +
    • python celery worker that runs long running tasks for specify7

    • +
    • depends on connection to specify7 container

    • +
    +
  • +
  • redis

    +
      +
    • acts as the job queue broker between the specify7 and specify7-worker containers

    • +
    • could possible be replaced by AWS SQS in the future

    • +
    • depends on connections to both the specify7 and specify7-worker containers

    • +
    +
  • +
  • asset-server

    +
      +
    • serves the assets to the specify7 container

    • +
    • independent container

    • +
    +
  • +
  • specify6

    +
      +
    • generates files and database schema version for specify7

    • +
    • the conainter stops after the files are created

    • +
    • independent container

    • +
    • this container can be removed if the static files are moved to S3 and then copied into the specify7 container on startup

    • +
    +
  • +
  • nginx

    +
      +
    • webserver for specify7 django application

    • +
    • depends on connection to specify7 and webpack containers

    • +
    • could possibly be replaced with AWS CloudFront

    • +
    +
  • +
  • report-runner

    +
      +
    • java application the generates reports for specify7

    • +
    • independent container

    • +
    +
  • +
+
+
+

Concluding Notes

+

You can use ‘AWS Certificate Manager’ or the tool ‘CertBot’ for setting up HTTPS SSL/TLS certificates. Who will need to setup a domain name through AWS Route 53. You will probably want to setup an elastic ip address for the EC2 instance through AWS as well. +Here are the instructions for using certbot:

+
sudo apt install -y certbot python3-certbot-apache;
+sudo mkdir /var/www/archireef;
+sudo certbot --webroot -w /var/www/archireef -d <YOUR_EC2_DOMAIN_NAME> certonly;
+certbot certificates;
+vim spcloudservers.json # change line to `https: true`
+make;
+sudo docker compose up -d;
+sudo docker exec -it specifycloud_nginx_1 nginx -s reload; # Maybe needed
+
+
+

You might want to extend the time of your ssh connection: +SSH Client: vim ~/.ssh/config

+
Host *
+    ServerAliveInterval 20
+    #TCPKeepAlive no
+
+
+

SSH Server: sudo vim /etc/ssh/sshd_config

+
ClientAliveInterval 1200
+ClientAliveCountMax 3
+
+
+

Then run sudo systemctl reload sshd

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/Hybrid Asset Server Setup Instructions.html b/aws/Hybrid Asset Server Setup Instructions.html new file mode 100644 index 0000000..ab102b3 --- /dev/null +++ b/aws/Hybrid Asset Server Setup Instructions.html @@ -0,0 +1,183 @@ + + + + + + + <no title> — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Instructions to setup an asset server with

+
#!/bin/bash
+
+sudo apt update;
+sudo apt upgrade -y;
+sudo apt-get -y install --no-install-recommends \
+  python3-venv \
+  python3.8 \
+  python3.8-dev \
+  python3-pip \
+  imagemagick \
+  ghostscript \
+  git \
+  nginx \
+  certbot \
+  authbind \
+  s3fs \
+  awscli;
+
+# python 3.6 install with apt
+sudo apt install software-properties-common;
+sudo add-apt-repository ppa:deadsnakes/ppa;
+sudo apt update;
+sudo apt install python3.6;
+sudo apt-get install python3.6-distutils;
+
+# install pip3.6
+#wget https://bootstrap.pypa.io/pip/3.6/get-pip.py;
+python3.6 -m venv --without-pip ve;
+source ve/bin/activate;
+wget https://bootstrap.pypa.io/get-pip.py;
+#wget https://bootstrap.pypa.io/pip/3.5/get-pip.py
+
+# activate python3.6 venv
+sudo apt install python3-virtualenv;
+python3.6 -m venv myenv;
+source myenv/bin/activate;
+pip install --no-cache-dir -r requirements.txt;
+
+# TLS dependencies
+sudo apt-get -y install --no-install-recommends \
+	certbot \
+	python3-certbot-nginx \
+	software-properties-common;
+
+# Import attachment files
+#mkdir attachments;
+#aws s3 cp s3://specify-cloud/assets-server/attachments/ ~/attachments --recursive;
+
+# S3 Mounting
+mkdir attachments;
+s3fs specify-cloud /assets-server/attachments/;
+
+# Clone asset server repo
+git clone https://github.com/specify/web-asset-server.git;
+cd ~/web-asset-server;
+git checkout arm-build;
+
+# Build python web asset server
+python3.8 -m venv ve;
+sudo ve/bin/pip install --no-cache-dir -r requirements.txt
+#sudo pip install -r requirements.txt;
+
+# Port config if needed
+# not needed when running with nginx
+#sudo apt-get install authbind;
+#touch 80;
+#chmod u+x 80;
+#sudo mv 80 /etc/authbind/byport;
+
+
+
+# Certbot TLS config
+sudo mkdir /var/www/.well-known;
+sudo certbot --nginx -d assets-test.specifycloud.org -d assets-test.specifycloud.org;
+sudo ls -la /etc/letsencrypts/live/assets-test.specifycloud.org;
+sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096; #2048 or 1024
+sudo openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 1024;
+# add https server config to nginx assets.conf
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/KU IT Notes.html b/aws/KU IT Notes.html new file mode 100644 index 0000000..8433c22 --- /dev/null +++ b/aws/KU IT Notes.html @@ -0,0 +1,194 @@ + + + + + + + New certificate on biprdsp7wbdb.cc.ku.edu server — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

New certificate on biprdsp7wbdb.cc.ku.edu server

+

Form to request new certificate: https://kuit.service-now.com/nav_to.do?uri=%2Fcom.glideapp.servicecatalog_cat_item_view.do%3Fv%3D1%26sysparm_id%3D78fee42fdb2a8850162673e1ba96195b%26sysparm_link_parent%3D322911f41bec6490cf2d337e034bcb23%26sysparm_catalog%3De0d08b13c3330100c8b837659bba8fb4%26sysparm_catalog_view%3Dcatalog_default%26sysparm_view%3Dcatalog_default

+

generate CSR string:

+
openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr
+
+
+

with configuration:

+
Country Name (2 letter code) [XX]:US
+State or Province Name (full name) []:Kansas
+Locality Name (eg, city) [Default City]:Lawrence
+Organization Name (eg, company) [Default Company Ltd]:University of Kansas
+Organizational Unit Name (eg, section) []:Specify
+Common Name (eg, your name or your server's hostname) []:biimages.biodiversity.ku.edu
+Email Address []:alec.white@ku.edu
+A challenge password []:
+An optional company name []:
+
+
+

verify configuration with openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr with output

+
C = US, ST = Kansas, L = Lawrence, O = University of Kansas, OU = Specify, CN = biimages.biodiversity.ku.edu, emailAddress = alec.white@ku.edu
+
+
+

after receiving new certificate files

+
biimages_biodiversity_ku_edu.cer
+biimages_biodiversity_ku_edu_cert.cer
+biimages.biodiversity.ku.edu.conf
+biimages_biodiversity_ku_edu.crt
+biimages_biodiversity_ku_edu_interm.cer
+biimages_biodiversity_ku_edu.p7b
+biimages_biodiversity_ku_edu.pem
+
+
+

generate ‘fullchain.pem’ file with concatenation

+
cat biimages_biodiversity_ku_edu.pem biimages_biodiversity_ku_edu_interm.cer > fullchain.pem
+
+
+

then run commands to copy files into proper locations (make sure the number is incremented ex. 40)

+
sudo cp server.key /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/privkey40.pem
+sudo cp biimages_biodiversity_ku_edu.pem /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/cert40.pem;
+sudo cp biimages_biodiversity_ku_edu_interm.cer /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/chain40.pem;
+sudo cp fullchain.pem /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/fullchain40.pem;
+
+
+

then create symbolic links to where the nginx file looks for SSL files

+
sudo ln -sf /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/fullchain40.pem /etc/letsencrypt/live/biimages.biodiversity.ku.edu/fullchain.pem;
+sudo ln -sf /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/privkey40.pem /etc/letsencrypt/live/biimages.biodiversity.ku.edu/privkey.pem;
+sudo ln -sf /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/chain40.pem /etc/letsencrypt/live/biimages.biodiversity.ku.edu/chain.pem;
+sudo ln -sf /etc/letsencrypt/archive/biimages.biodiversity.ku.edu/cert40.pem /etc/letsencrypt/live/biimages.biodiversity.ku.edu/cert.pem;
+
+
+

here are the line in the ‘/etc/nginx/conf.d/web-asset-server.conf’ nginx file

+
server_name biimages.biodiversity.ku.edu;
+ssl_certificate /etc/letsencrypt/live/biimages.biodiversity.ku.edu/fullchain.pem;
+ssl_certificate_key /etc/letsencrypt/live/biimages.biodiversity.ku.edu/privkey.pem;
+
+
+

verify the key and cert are correct by making sure their hashes are the same

+
sudo openssl x509 -noout -modulus -in /etc/letsencrypt/live/biimages.biodiversity.ku.edu/cert.pem | openssl md5
+sudo openssl rsa -noout -modulus -in /etc/letsencrypt/live/biimages.biodiversity.ku.edu/privkey.pem | openssl md5
+
+
+

restart nginx

+
sudo systemctl restart web-asset-server.service
+sudo systemctl status web-asset-server.service
+
+
+
+
+

web-portal certificate

+

here are the lines in the /etc/nginx/conf.d/webportal-nginx.conf nginx file

+
server_name collections.biodiversity.ku.edu;
+ssl_certificate /home/specify/keystore/collections_biodiversity_ku_edu_cert.cer;
+ssl_certificate_key /home/specify/keystore/collections_biodiversity_ku_edu.key;
+
+
+
cat collections_biodiversity_ku_edu.pem collections_biodiversity_ku_edu_interm.cer > fullchain.pem
+
+
+
sudo cp collections_biodiversity_ku_edu_cert.cer /home/specify/keystore/cert.pem
+sudo cp ~/webportal-keys/webportal_server.key /home/specify/keystore/privkey.pem
+sudo cp ~/webportal-keys/fullchain.pem /home/specify/keystore/fullchain.pem
+
+
+
sudo chown specify:bi-sp7access cert.pem;
+sudo chown specify:bi-sp7access privkey.pem;
+sudo chown specify:bi-sp7access fullchain.pem;
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/Setup AWS Specify Cloud.html b/aws/Setup AWS Specify Cloud.html new file mode 100644 index 0000000..bc605bb --- /dev/null +++ b/aws/Setup AWS Specify Cloud.html @@ -0,0 +1,555 @@ + + + + + + + Setup Aurora MySQL Database — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Setup Aurora MySQL Database

+
+
+

Setup EC2 Server

+

EC2 Parameters:

+
    +
  • ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-arm64-server-20220131
    +ami-0770bf1d6ae61c858 +Initial Commands:

  • +
+
#!/bin/bash
+
+sudo apt-get update;
+sudo apt upgrade;
+sudo apt install -y make python3-pip
+
+#sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose mysql-client;
+#sudo apt install docker.io
+
+# Install Docker
+sudo apt install -y apt-transport-https ca-certificates curl software-properties-common;
+curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg;
+echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null;
+sudo apt update;
+sudo apt -y install docker-ce;
+docker --version;
+sudo systemctl status docker;
+
+#sudo systemctl start docker.service
+#sudo systemctl enable docker.service
+#sudo systemctl status docker.service
+
+# Install docker compose
+mkdir .docker
+mkdir .docker/cli-plugins
+curl -L "https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-linux-$(uname -m)" -o ~/.docker/cli-plugins/docker-compose;
+chmod +x ~/.docker/cli-plugins/docker-compose;
+docker compose version;
+
+# Install awscli
+sudo apt -y install awscli;
+aws configure; # See aws credentials at bottom of notes
+
+# Setup mysql datbase connection
+sudo apt install -y mysql-client;
+
+# Copy files from S3
+aws s3 cp s3://specify-cloud/repo-snapshots/docker-compositions/ ./ --recursive
+
+# python setup
+sudo apt install -y python3-pip;
+python3 -m pip install j2cli;
+
+# Create config files for specifycloud
+cd specifycloud;
+vim spcloudservers.json
+vim defaults.env
+sudo apt install j2cli
+make
+
+# ssh client-server alive settings
+sudo echo -e "ClientAliveInterval 1200\nClientAliveCountMax 3" >> /etc/ssh/sshd_config
+sudo systemctl reload sshd;
+
+#docker pull specifyconsortium/specify7-service:edge
+
+# su specify -c make
+docker-compose up -d
+
+
+

SSH Client: vim ~/.ssh/config

+
Host *
+    ServerAliveInterval 20
+    #TCPKeepAlive no
+
+
+

SSH Server: sudo vim /etc/ssh/sshd_config

+
ClientAliveInterval 1200
+ClientAliveCountMax 3
+
+
+

Then run sudo systemctl reload sshd

+

spcloudservers.json ->

+
{
+	"servers": {
+		"freshfish": {
+			"sp7": "edge",
+			"sp6": "specify6803",
+			"https": false,
+			"env": {
+                "ASSET_SERVER_URL": "https://demo-assets.specifycloud.org/web_asset_store.xml",
+                "ANONYMOUS_USER": "sp7demofish"
+            }
+		}
+	},
+	"decommissioned": [],
+	"sp6versions": {
+		"specify6800": "6.8.00",
+		"specify6801": "6.8.01",
+		"specify6802": "6.8.02",
+		"specify6803": "6.8.03"
+	}
+}
+
+
+

defaults.env ->

+
DATABASE_HOST=specifycloud-dev-database-1-instance-1.cqvncffkwz9t.us-east-1.rds.amazonaws.com
+DATABASE_PORT=3306
+MASTER_NAME=master
+MASTER_PASSWORD=mastermaster
+SECRET_KEY=bogus
+ASSET_SERVER_URL=https://assets1.specifycloud.org/web_asset_store.xml
+ASSET_SERVER_KEY=tnhercbrhtktanehul.dukb
+REPORT_RUNNER_HOST=10.132.218.32
+REPORT_RUNNER_PORT=8080
+CELERY_BROKER_URL=redis://redis/0
+CELERY_RESULT_BACKEND=redis://redis/1
+LOG_LEVEL=WARNING
+SP7_DEBUG=false
+
+
+
+
+

Info Misc.

+

aws credentials:

+
    +
  • username: specify.user

  • +
  • password: Specify-Cloud-aws-user

  • +
  • access key: ACCESS_KEY

  • +
  • secret access key: ACCESS_KEY_SECRET

  • +
  • default region: us-east-1

  • +
  • default output format: json

  • +
+

AWS EC2 User data:

+
# Avoid services restarting during apt upgrade
+sudo sed -i "s/#\$nrconf{kernelhints} = -1;/\$nrconf{kernelhints} = -1;/g" /etc/needrestart/needrestart.conf;
+sudo sed -i "s/#\$nrconf{restart} = 'i';/\$nrconf{restart} = 'a';/g" /etc/needrestart/needrestart.conf;
+
+# Run apt installs
+sudo apt update;
+sudo apt upgrade -y;
+sudo apt install -y apt-transport-https ca-certificates git gh curl software-properties-common wget python3-pip awscli mysql-client j2cli;
+
+# Configure AWS
+aws configure set aws_access_key_id "ACCESS_KEY";
+aws configure set aws_secret_access_key "ACCESS_KEY_SECRET";
+aws configure set default.region us-east-1;
+aws configure set default.output json;
+
+# Install Docker
+curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg;
+echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null;
+sudo apt update;
+apt-cache policy docker-ce;
+sudo apt install -y docker-ce;
+docker --version;
+
+# Install docker compose
+mkdir .docker;
+mkdir .docker/cli-plugins;
+curl -L "https://github.com/docker/compose/releases/download/v2.17.2/docker-compose-linux-$(uname -m)" -o ~/.docker/cli-plugins/docker-compose;
+chmod +x ~/.docker/cli-plugins/docker-compose;
+docker compose version;
+
+# Python setup
+# python3 -m pip install j2cli;
+# export PATH=$PATH:/home/ubuntu/.local/bin;
+# sudo apt install j2cli;
+
+# Copy files from S3
+aws s3 cp s3://specify-cloud/repo-snapshots/docker-compositions/ ./ --recursive;
+aws s3 cp s3://specify-cloud/repo-snapshots/spcloudservers.json ./specifycloud/;
+aws s3 cp s3://specify-cloud/repo-snapshots/defaults.env ./specifycloud/;
+
+# Database setup
+mkdir seed-databases;
+aws s3 cp s3://specify-cloud/seed-database/specify.sql ./seed-databases/;
+mysql --host specify-cloud-swiss-demo-database-1.c9qlnkmf2lfl.eu-central-2.rds.amazonaws.com --port 3306 -u master -p'mastermaster' -e "create database specify;";
+mysql --host specify-cloud-swiss-demo-database-1.c9qlnkmf2lfl.eu-central-2.rds.amazonaws.com --port 3306 -u master -p'mastermaster' specify < ./seed-databases/specify.sql;
+rm -f ./seed-databases/specify.sql;
+
+# Configure Specify Network
+cd specifycloud;
+touch spcloudservers.json;
+touch defaults.env;
+
+# Run Specify Network
+make;
+sudo docker compose up -d;
+
+# Certbot setup
+sudo apt install certbot python3-certbot-apache;
+sudo mkdir /var/www/sp7demofish;
+
+# Github clone private repo
+ssh-keygen -t ed25519 -C "acwhite211@gmail.com";
+#ssh-keygen -t rsa -b 4096 -C "acwhite211@gmail.com";
+
+# git clone repos
+git clone https://github.com/specify/specify7.git;
+git clone https://github.com/specify/specify6.git;
+git clone https://github.com/specify/report-runner-service.git;
+#git clone https://github.com/specify/web-asset-server.git;
+
+# Install nginx
+sudo apt install -y nginx openjdk-8-jdk maven ant;
+sudo ufw allow 'Nginx HTTP';
+sudo ufw status;
+sudo update-alternatives --set java /usr/lib/jvm/java-8-openjdk-arm64/jre/bin/java;
+
+# Build without docker
+cd specify6;
+ant compile-nonmac;
+
+
+

Database Prices:

+
    +
  • db.r5.large - 2 vCPUs - 16 gb ram - $0.24 per hour = $173.00 per month

  • +
  • db.m5.large - 4vCPUs - 16 gb ram - $0.171 per hour = $123.10 per month

  • +
  • db.t3.medium - 2vCPUs - 4 gb ram - $0.068 per hour = $49.00 per month

  • +
  • db.t3.large - 2vCPUs - 8 gb ram - $0.136 per hour = $97.92 per month

  • +
  • db.t3.xlarge - 4vCPUs - 16 gb ram - $0.272 per hour = $195.80 per month

  • +
  • +db.t4g.medium - 2vCPUs - 4 gb ram - $0.065 per hour = $46.80 per month

  • +
  • db.t4g.large - 2vCPUs - 8 gb ram - $0.129 per hour = $92.88 per month +Aurora v2 Prices:

  • +
  • 1 ACU - 2 vCPUs - 2 gb ram - $0.12 per ACU hour = $86.40 per ACU month +Aurora v1 Prices:

  • +
  • 1 ACU - 2 vCPUs - 2 gb ram - $0.06 per ACU hour = $43.29 per ACU month +EC2 Prices:

  • +
  • t4g.nano - 2vCPUs - 0.5 gb ram - $0.0042 per hour = $3.02 per month

  • +
  • t4g.micro - 2vCPUs - 1 gb ram - $0.0084 per hour = $6.05 per month

  • +
  • t4g.small - 2vCPUs - 2 gb ram - $0.0168 per hour = $12.10 per month

  • +
  • +t4g.medium - 2vCPUs - 4 gb ram - $0.0336 per hour = $24.19 per month

  • +
  • t4g.large - 2vCPUs - 8 gb ram - $0.0672 per hour = $48.38 per month

  • +
  • t4g.xlarge - 4vCPUs - 16 gb ram - $0.1344 per hour = $96.77 per month

  • +
  • m7g.medium - 1vCPUs - 4 gb ram - $0.0408 per hour = $29.38 per month

  • +
  • m7g.large - 2vCPUs - 8 gb ram - $0.0816 per hour = $58.75 per month

  • +
  • m7g.xlarge - 4vCPUs - 16 gb ram - $0.2232 per hour = $160.70 per month +Fargate Prices (Linux/ARM):

  • +
  • On Demand - $0.03238 per vCPU per hour and $0.00356 per GB per hour

  • +
  • Spot - $0.01279585 per vCPU per hour and $0.00140508 per GB per hour

  • +
  • Ephemeral Storage - $0.000111 per storage GB per hour

  • +
  • 1 On-Demand vCPU = $23.31 per month

  • +
  • 1 On-Demand GB ram = $2.56 per month

  • +
  • 0.25 On-Demand vCPU & 0.5 GB ram On-Demand = 5.82 + 1.28 = $7.10 per month

  • +
  • 1 Spot vCPU = $9.21 per month

  • +
  • 1 Spot GB ram = $1.01 per month

  • +
  • 1 On-Demand with Savings Plan vCPU = $12.59 per month

  • +
  • 1 On-Demand with Savings Plan GB ram = $1.38 per month

  • +
  • ex. 1 cpu and 1 gb = $10.22 per month

  • +
  • ex. 2 cpus and 8 gb = $26.52 per month

  • +
  • ex. 8 cpus and 16 gb = $89.89 per month

  • +
  • ex. 16 cpus and 32 gb = $179.78 per month +Notes:

  • +
  • m7g is general purpose using graviton 3

  • +
  • t4g is general purpose using graviton 2

  • +
  • for Fargate, memory and storage are cheep, it’s the vCPUs that get expensive

  • +
+

NA Server:

+
    +
  • 45 clients * 2 = 90 django containers

  • +
  • digital ocean 4vCPUs 8 GB memory

    +
      +
    • cpu usage nominal at 25% with spikes to 40%

    • +
    • memory usage nominal at 90%

    • +
    +
  • +
  • 45 / 0.25 vCPU = 11.25

  • +
  • 45 * 0.5 GB = 22.5

  • +
  • 10 containers per task definition

  • +
  • So 9 task definitions needed for django

  • +
  • vimsfish might need more than 0.5 GB +CA Server:

  • +
  • 8 clients

  • +
  • digital ocean 1vCPUs 2 GB memory

    +
      +
    • cpu usage nominal at 8% with spikes to 80%

    • +
    • memory usage nominal at 85%

    • +
    +
  • +
  • beaty might need more than 0.5 GB +EU Server:

  • +
  • 9 clients

  • +
  • digital ocean 1vCPUs 2 GB memory

    +
      +
    • cpu usage nominal at 6% with spikes to 72%

    • +
    • memory usage nominal at 80%

    • +
    +
  • +
  • herb_rbge might need more than 0.5 GB

  • +
+

So maybe 1vCPU and 0.5 GB of memory will be enough to handle each django container. Most are fine with 0.5 GB, only a few will go over with the django and worker containers combined.

+

Price Option Comparison +t4g.medium

+
    +
  • on-demand

  • +
  • spot

  • +
  • 12 month reserved instance

  • +
  • 36 month reserved instance

  • +
+
+
+

Specify Network Extract

+

Specify Network EC2 instance:

+
sudo apt update;
+sudo apt upgrade -y;
+sudo apt install -y wget awscli unzip;
+aws configure set aws_access_key_id "ACCESS_KEY";
+aws configure set aws_secret_access_key "ACCESS_KEY_SECRET";
+aws configure set default.region us-east-1;
+aws configure set default.output json;
+mkdir gbif;
+mkdir gbif/download;
+mkdir gbif/extract;
+cd gbif/download;
+aws s3 cp s3://specify-network/gbif/0146304-230224095556074.zip ./;
+wget $GBIF_URL;
+unzip 0146304-230224095556074.zip -d ../extract/;
+cd ../extract/;
+mv ./0146304-230224095556074.csv ./gbif.csv
+aws s3 cp gbif.csv s3://specify-network-dev/gbif_test/gbif_extract/;
+
+
+

specify aws github ssh key: +id_ed25519.pub ->

+
sh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKHq3lVhZ4U8j0Derpm37wgUPLGLgQtim77M68m+XNWL acwhite211@gmail.com
+
+
+

id_ed25519 ->

+
-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCh6t5VYWeFPI9A3q6Zt+8IFDyxi4ELYpu+zOvJvlzViwAAAJj4E1iO+BNY
+jgAAAAtzc2gtZWQyNTUxOQAAACCh6t5VYWeFPI9A3q6Zt+8IFDyxi4ELYpu+zOvJvlzViw
+AAAEDi0KTenAzeyomMyaqOBd8APyQjcL3YU7tXMMMrit8bjaHq3lVhZ4U8j0Derpm37wgU
+PLGLgQtim77M68m+XNWLAAAAFGFjd2hpdGUyMTFAZ21haWwuY29tAQ==
+-----END OPENSSH PRIVATE KEY-----
+
+
+

MariaDB version: 10.3.38-MariaDB-0ubuntu0.20.04.1-log +AWS DB password: dancing-taco-magic-rainbow-vibes +AWS DB password: dance-taco-magic-rainbow-vibe

+

Install Ubuntu EC2 instance all in one with no docker

+
#!/bin/bash
+
+# Avoid services restarting during apt upgrade
+sudo sed -i "s/#\$nrconf{kernelhints} = -1;/\$nrconf{kernelhints} = -1;/g" /etc/needrestart/needrestart.conf;
+sudo sed -i "s/#\$nrconf{restart} = 'i';/\$nrconf{restart} = 'a';/g" /etc/needrestart/needrestart.conf;
+
+# Run apt installs
+sudo apt update;
+sudo apt upgrade -y;
+sudo apt install -y --no-install-recommends \ 
+	apt-transport-https ca-certificates git curl software-properties-common wget \
+	python3-pip awscli mysql-client j2cli nginx openjdk-8-jdk maven ant gcc make \
+	openldap-devel \
+	#nodejs npm \
+	python3-venv \
+	#python3.8 python3.8-dev \
+	redis unzip \
+	apache2 \
+	libapache2-mod-wsgi-py3;
+
+# Install nodejs 18
+cd ~;
+#curl -sL https://deb.nodesource.com/setup_18.x -o nodesource_setup.sh;
+#sudo bash nodesource_setup.sh;
+#sudo apt install -y nodejs;
+curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash;
+source ~/.bashrc;
+nvm install v18;
+nvm use 18;
+nvm alias default 18;
+node -v;
+
+# Install python 3.8
+sudo add-apt-repository -y ppa:deadsnakes/ppa;
+sudo apt update;
+sudo apt install -y python3.8 python3.8-dev;
+
+# Git clone repos
+git clone https://github.com/specify/specify7.git;
+git clone https://github.com/specify/specify6.git;
+git clone https://github.com/specify/report-runner-service.git;
+
+# Setup database
+aws s3 cp s3://specify-cloud/seed-database/sp7demofish.sql ./specify7/seed-database/;
+
+# Setup specify6
+#cd ~/specify6;
+#sudo update-alternatives --set java /usr/lib/jvm/java-8-openjdk-arm64/jre/bin/java;
+#wget https://update.specifysoftware.org/6803/Specify_unix_64.sh;
+#sh Specify_unix_64.sh -q -dir ./Specify6.8.03;
+#sudo ln -s $(pwd)/Specify6.8.03 /opt/Specify;
+sudo ln -s $(pwd)/specify6 /opt/Specify;
+
+# Setup specify7
+cd ~/specify7;
+#git checkout tags/v7.8.10
+python3.8 -m venv specify7/ve;
+specify7/ve/bin/pip install wheel;
+specify7/ve/bin/pip install --upgrade -r specify7/requirements.txt;
+
+# Run specify dev
+cd ~/specify7;
+source ve/bin/activate;
+make runserver;
+
+# Setup specify-worker
+cd ~/specify7;
+#ve/bin/celery -A specifyweb worker -l INFO --concurrency=1 -Q specify;
+celery -A specifyweb worker -l INFO --concurrency=1;
+
+# Setup apache
+# sudo apt install -y apache2 libapache2-mod-wsgi-py3
+
+# Setup nginx
+#sudo apt install -y nginx openjdk-8-jdk maven ant;
+sudo ufw allow 'Nginx HTTP';
+sudo ufw status;
+
+
+

Using the Amazon arm54 centos image:

+
#!/bin/bash
+
+sudo yum upgrade;
+sudo yum install -y \
+	git gcc \
+	openldap-devel \
+	#mariadb-devel \
+	mariadb105-devel.aarch64 \
+	nodjs npm \
+	#java-11-openjdk-headless \
+	#java-11-amazon-corretto-headless.aarch64 \
+	java-1.8.0-amazon-corretto.aarch64 java-1.8.0-amazon-corretto-devel.aarch64 \
+	#python38-virtualenv \
+	#python38 python38u-devel \
+	redis6 unzip
+sudo dnf install mariadb105;
+sudo dnf install openldap-servers;
+
+# Specify 7
+python3 -m venv specify7/ve;
+specify7/ve/bin/python3 -m pip install --upgrade pip;
+specify7/ve/bin/pip install wheel;
+specify7/ve/bin/pip install --upgrade -r specify7/requirements.txt
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/Specify Cloud Graviton Setup.html b/aws/Specify Cloud Graviton Setup.html new file mode 100644 index 0000000..3548f4c --- /dev/null +++ b/aws/Specify Cloud Graviton Setup.html @@ -0,0 +1,502 @@ + + + + + + + <no title> — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Commands for running on ubuntu 20.04 arm64 +See ~/git/specify-aws-specify7-mosti-in-one-lite/docker-entrypoint.sh for latest

+
#!/bin/bash
+
+sudo apt update;
+sudo apt upgrade -y;
+sudo add-apt-repository ppa:openjdk-r/ppa; # repo for jdk-8
+curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -;
+sudo apt -y install --no-install-recommends \
+  build-essential \
+  git \
+  libldap2-dev \
+  libmariadbclient-dev \
+  libsasl2-dev \
+  nodejs \
+  npm \
+  python3-venv \
+  python3.8 \
+  python3.8-dev \
+  redis \
+  unzip \
+  openjdk-8-jdk \
+  maven \
+  ant \
+  awscli \
+  mysql-client \
+  nginx \
+  certbot \
+  python3-certbot-nginx;
+node -v;
+#sudo apt install -y j2cli;
+sudo apt install -y nginx;
+#sudo apt install -y apache2 libapache2-mod-wsgi-py3;
+sudo apt install -y mysql-client-core-8.0;
+sudo apt clean;
+
+# Configure AWS
+aws configure set aws_access_key_id "ACCESS_KEY";
+aws configure set aws_secret_access_key "ACCESS_KEY_SECRET";
+aws configure set default.region us-east-1;
+aws configure set default.output json;
+
+# Specify6
+wget https://update.specifysoftware.org/6803/Specify_unix_64.sh;
+sh Specify_unix_64.sh -q -dir ./Specify6.8.03;
+sudo ln -s $(pwd)/Specify6.8.03 /opt/Specify;
+
+# Specify7
+git clone https://github.com/specify/specify7.git;
+mkdir ~/wb_upload_logs;
+mkdir ~/specify_depository;
+#cd specify7
+#git checkout tags/v7.8.6
+
+# Specify settings config
+cd ~/specify7;
+echo 'export DOMAIN_NAME=ec2-3-87-116-210.compute-1.amazonaws.com' >> ~/.bashrc;
+echo 'export DATABASE_HOST=specify-cloud-aurora-v2-test-database-1-instance-1.cqvncffkwz9t.us-east-1.rds.amazonaws.com' >> ~/.bashrc;
+echo 'export DATABASE_PORT=3306' >> ~/.bashrc;
+echo 'export DATABASE_NAME=sp7demofish' >> ~/.bashrc;
+echo 'export MASTER_NAME=master' >> ~/.bashrc;
+echo 'export MASTER_PASSWORD=dance-taco-magic-rainbow-vibe' >> ~/.bashrc;
+echo 'export WEB_ATTACHMENT_URL=https://assets1.specifycloud.org/web_asset_store.xml' >> ~/.bashrc;
+echo 'export WEB_ATTACHMENT_KEY=tnhercbrhtktanehul.dukb' >> ~/.bashrc;
+echo 'export WEB_ATTACHMENT_COLLECTION=sp7demofish' >> ~/.bashrc;
+echo 'export REPORT_RUNNER_HOST=10.133.58.98' >> ~/.bashrc;
+echo 'export REPORT_RUNNER_PORT=8080' >> ~/.bashrc;
+source ~/.bashrc;
+sed -i "s/DATABASE_HOST = 'SpecifyDB'/DATABASE_HOST = '${DATABASE_HOST}'/g" specifyweb/settings/specify_settings.py;
+sed -i "s/DATABASE_PORT = ''/DATABASE_PORT = '${DATABASE_PORT}'/g" specifyweb/settings/specify_settings.py;
+sed -i "s/DATABASE_NAME = 'SpecifyDB'/DATABASE_NAME = '$DATABASE_NAME'/g" specifyweb/settings/specify_settings.py;
+sed -i "s/MASTER_NAME = 'MasterUser'/MASTER_NAME = '$MASTER_NAME'/g" specifyweb/settings/specify_settings.py;
+sed -i "s/MASTER_PASSWORD = 'MasterPassword'/MASTER_PASSWORD = '$MASTER_PASSWORD'/g" specifyweb/settings/specify_settings.py;
+sed -i "s|WEB_ATTACHMENT_URL = None|WEB_ATTACHMENT_URL = '$WEB_ATTACHMENT_URL'|g" specifyweb/settings/specify_settings.py;
+sed -i "s/WEB_ATTACHMENT_KEY = None/WEB_ATTACHMENT_KEY = '$WEB_ATTACHMENT_KEY'/g" specifyweb/settings/specify_settings.py;
+sed -i "s/WEB_ATTACHMENT_COLLECTION = None/WEB_ATTACHMENT_COLLECTION = '$WEB_ATTACHMENT_COLLECTION'/g" specifyweb/settings/specify_settings.py;
+sed -i "s/REPORT_RUNNER_HOST = ''/REPORT_RUNNER_HOST = '$REPORT_RUNNER_HOST'/g" specifyweb/settings/specify_settings.py;
+sed -i "s/REPORT_RUNNER_PORT = ''/REPORT_RUNNER_PORT = '$REPORT_RUNNER_PORT'/g" specifyweb/settings/specify_settings.py;
+sed -i "s/home\/specify/home\/ubuntu/g" specifyweb/settings/specify_settings.py;
+
+# Setup Specify7 python environment
+cd ~/specify7;
+python3.8 -m venv ./ve;
+./ve/bin/pip install wheel;
+./ve/bin/pip install --upgrade -r ./requirements.txt;
+ve/bin/pip install --no-cache-dir gunicorn;
+
+# Database setup
+aws s3 cp s3://specify-cloud/seed-database/sp7demofish.sql ~/specify7/seed-database/;
+mysql --host $DATABASE_HOST -u $MASTER_NAME -p"${MASTER_PASSWORD}" -e "create database ${DATABASE_NAME};";
+mysql --host $DATABASE_HOST -u $MASTER_NAME -p"${MASTER_PASSWORD}" $DATABASE_NAME < ~/specify7/seed-database/sp7demofish.sql;
+
+# Build Specify7
+cd specify7;
+source ve/bin/activate;
+make;
+#make runserver;
+#ve/bin/pip install gunicorn
+#ve/bin/gunicorn -w 3 -b 0.0.0.0.8000 -t 300 specifyweb_wsgi;
+sudo ln -s $(pwd)/specify7 /opt/specify7;
+
+# Specify7 worker
+cd ~/specify7;
+celery -A specifyweb worker -l INFO --concurrency=1 &;
+
+# Webserver setup
+mkdir ~/media;
+sed -i "s/MEDIA_ROOT = ''/MEDIA_ROOT = '\/home\/ubuntu\/media'/g" ~/specify7/specifyweb/settings/__init__.py;
+sed -i "s/MEDIA_URL = ''/MEDIA_URL = 'http:\/\/${DOMAIN_NAME}\/media'/g" ~/specify7/specifyweb/settings/__init__.py;
+
+# Nginx webserver
+#sudo ufw allow 'Nginx HTTP';
+#sudo ufw status;
+sed -i "s/server_name localhost/server_name sp7demofish/g" ~/specify7/nginx.conf;
+sudo cp ~/specify7/nginx.conf /etc/nginx/sites-available/specify7;
+sudo ln -s /etc/nginx/sites-available/specify7 /etc/nginx/sites-enabled/;
+sudo nginx -c ~/specify7/nginx.conf;
+
+# Apache webserver
+sed "s/\$servername/$DOMAIN_NAME/g" ~/specify7/specifyweb_apache.conf;
+sudo rm /etc/apache2/sites-enabled/000-default.conf;
+sudo ln -s $(pwd)/specify7/specifyweb_apache.conf /etc/apache2/sites-enabled/;
+sudo systemctl restart apache2.service;
+#sudo invoke-rc.d apache2 restart;
+
+# TLS/SSL
+sudo certbot --nginx -d your_domain;
+sudo ufw allow 'Nginx Full';
+sudo ufw delete allow 'Nginx HTTP';
+
+
+

bash script for setting env varibales and specify7 setting configs:

+
#!/bin/bash
+
+sed -i "s/DATABASE_NAME = 'SpecifyDB'/DATABASE_NAME = ''/g" specifyweb/settings/specify_settings.py;
+
+update_setting() {
+    local setting_key="$1"
+    local setting_value="$2"
+    local file_path="specifyweb/settings/specify_settings.py"
+
+    sed -i "s/${setting_key} = ''/${setting_key} = '${setting_value}'/g" "$file_path"
+}
+
+cat <<EOT >> ~/.bashrc
+export DATABASE_HOST=specify-cloud-aurora-test-database-1-instance-1.cqvncffkwz9t.us-east-1.rds.amazonaws.com
+export DATABASE_PORT=3306
+export DATABASE_NAME=sp7demofish
+export MASTER_NAME=master
+export MASTER_PASSWORD=mastermaster
+export WEB_ATTACHMENT_URL=https://assets1.specifycloud.org/web_asset_store.xml
+export WEB_ATTACHMENT_KEY=tnhercbrhtktanehul.dukb
+export WEB_ATTACHMENT_COLLECTION=sp7demofish
+export REPORT_RUNNER_HOST=10.133.58.98
+export REPORT_RUNNER_PORT=8080
+EOT
+
+source ~/.bashrc;
+
+update_setting "DATABASE_HOST" "$DATABASE_HOST"
+update_setting "DATABASE_PORT" "$DATABASE_PORT"
+update_setting "DATABASE_NAME" "$DATABASE_NAME"
+update_setting "MASTER_NAME" "$MASTER_NAME"
+update_setting "MASTER_PASSWORD" "$MASTER_PASSWORD"
+update_setting "WEB_ATTACHMENT_URL" "$WEB_ATTACHMENT_URL"
+update_setting "WEB_ATTACHMENT_KEY" "$WEB_ATTACHMENT_KEY"
+update_setting "WEB_ATTACHMENT_COLLECTION" "$WEB_ATTACHMENT_COLLECTION"
+update_setting "REPORT_RUNNER_HOST" "$REPORT_RUNNER_HOST"
+update_setting "REPORT_RUNNER_PORT" "$REPORT_RUNNER_PORT"
+
+
+

nginx.conf ->

+
server {
+    listen 80;
+    server_name ec2-54-162-114-41.compute-1.amazonaws.com;
+    root /usr/share/nginx;
+    client_max_body_size 128M;
+
+    # serve static files directly
+    location /static/ {
+        #client_max_body_size 0;
+        root /volumes;
+        #rewrite ^/static/config/(.*)$ /specify6/config/$1 break;
+        #rewrite ^/static/depository/(.*)$ /static-files/depository/$1 break;
+        #rewrite ^/static/js/(.*)$ /webpack-output/$1 break;
+        #rewrite ^/static/(.*)$ /static-files/frontend-static/$1 break;
+        rewrite ^/static/config/(.*)$ /home/ubuntu/specify6.8.03/config/$1 break;
+        rewrite ^/static/depository/(.*)$ /home/ubuntu/static-files/depository/$1 break;
+        #rewrite ^/static/js/(.*)$ /webpack-output/$1 break;
+        rewrite ^/static/(.*)$ /static-files/frontend-static/$1 break;
+    }
+
+    # proxy these urls to the asset server
+    location ~ ^/(fileget|fileupload|filedelete|getmetadata|testkey|web_asset_store.xml) {
+        client_max_body_size 0;
+        resolver 127.0.0.11 valid=30s;
+        set $backend "http://asset-server:8080";
+        proxy_pass $backend;
+        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 everything else to specify 7
+    location / {
+        client_max_body_size 400M;
+        client_body_buffer_size 400M;
+        client_body_timeout 120;
+        resolver 127.0.0.11 valid=30s;
+        set $backend "http://specify7:8000";
+        proxy_pass $backend;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    }
+}
+
+
+

simple nginx.conf ->

+
server {
+    listen 80;
+    server_name ec2-54-162-114-41.compute-1.amazonaws.com;
+    location / {
+        # django running in uWSGI
+        uwsgi_pass unix:///run/uwsgi/app/django/socket;
+        include uwsgi_params;
+        uwsgi_read_timeout 300s;
+        client_max_body_size 32m;
+    }
+    location /static/ {
+       # static files
+       alias /home/ubuntu/static/; # ending slash is required
+    }
+    location /media/ {
+        # media files, uploaded by users
+        alias /home/ubuntu/media/; # ending slash is required
+    }
+}
+
+
+
+

specifyweb_apache.conf ->

+
<VirtualHost *:80>
+        # Grant access to the Specify directories.
+        <Directory /home/ubuntu/specify_depository>
+	        Options +FollowSymLinks -Indexes -MultiViews
+            Require all granted
+        </Directory>
+
+        <Directory /home/ubuntu/web_upload_logs>
+	        Options +FollowSymLinks -Indexes -MultiViews
+            Require all granted
+        </Directory>
+
+        <Directory /opt/Specify/config>
+	        Options +FollowSymLinks -Indexes -MultiViews
+            Require all granted
+        </Directory>
+
+        <Directory /opt/specify7>
+	        Options +FollowSymLinks -Indexes -MultiViews
+            Require all granted
+        </Directory>
+
+        # Alias the following to the location set in specifyweb/settings/local_specify_settings.py
+        Alias /static/depository /home/ubuntu/specify_depository
+
+        # Alias the following to the Specify6 installation + /config
+        Alias /static/config    /opt/Specify/config
+
+        # Alias the following to the Specify7 installation + /specifyweb/frontend/static
+        Alias /static           /opt/specify7/specifyweb/frontend/static
+
+        # Set the user and group you want the Specify 7 python process to run as.
+        # The python-home points to the location of the python libraries in the
+        # virtualenv you established. If not using a virtualenv, leave off the
+        # python-home parameter.
+        WSGIDaemonProcess ec2-3-87-116-210.compute-1.amazonaws.com user=ubuntu group=ubuntu python-home=/opt/specify7/ve
+        WSGIProcessGroup ec2-3-87-116-210.compute-1.amazonaws.com
+
+        # Alias the following to the Specify7 installation + /specifyweb.wsgi
+        WSGIScriptAlias / /opt/specify7/specifyweb.wsgi
+
+        ErrorLog /var/log/apache2/error.log
+        # # Possible values include: debug, info, notice, warn, error, crit,
+        # # alert, emerg.
+        # LogLevel warn
+
+        CustomLog /var/log/apache2/access.log combined
+</VirtualHost>
+
+
+

default apache

+
<VirtualHost *:80>
+        # The ServerName directive sets the request scheme, hostname and port that
+        # the server uses to identify itself. This is used when creating
+        # redirection URLs. In the context of virtual hosts, the ServerName
+        # specifies what hostname must appear in the request's Host: header to
+        # match this virtual host. For the default virtual host (this file) this
+        # value is not decisive as it is used as a last resort host regardless.
+        # However, you must set it for any further virtual host explicitly.
+        #ServerName www.example.com
+
+        ServerAdmin webmaster@localhost
+        DocumentRoot /var/www/html
+
+        # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
+        # error, crit, alert, emerg.
+        # It is also possible to configure the loglevel for particular
+        # modules, e.g.
+        #LogLevel info ssl:warn
+
+        ErrorLog ${APACHE_LOG_DIR}/error.log
+        CustomLog ${APACHE_LOG_DIR}/access.log combined
+
+        # For most configuration files from conf-available/, which are
+        # enabled or disabled at a global level, it is possible to
+        # include a line for only one particular virtual host. For example the
+        # following line enables the CGI configuration for this host only
+        # after it has been globally disabled with "a2disconf".
+        #Include conf-available/serve-cgi-bin.conf
+</VirtualHost>
+
+
+

Webpack notes:

+
webpack 5.73.0 compiled with 2 warnings in 104175 ms
+make[1]: Leaving directory '/home/ubuntu/specify7/specifyweb/frontend/js_src'
+
+
+

spcloud nginx config notes: +rewrite ^/static/depository/(.*)$ /static-files-sp7demofish-eu/depository/$1 break; +check out /static-files-sp7demofish-eu/depository/ +example from spcloud nginx.conf using http ->

+
server {
+    listen 80;
+    server_name cryoarks-test.*;
+
+    # The LetsEncrypt pass-though.
+    location /.well-known/ {
+             root /var/www/cryoarks-test/;
+    }
+
+
+
+    root /usr/share/nginx;
+
+    location /static/ {
+        root /volumes;
+        rewrite ^/static/config/(.*)$ /specify6801/config/$1 break;
+        rewrite ^/static/depository/(.*)$ /static-files-cryoarks-test/depository/$1 break;
+        rewrite ^/static/(.*)$ /static-files-cryoarks-test/frontend-static/$1 break;
+    }
+
+    location / {
+        resolver 127.0.0.11 valid=30s;
+        set $backend "http://cryoarks-test:8000";
+        proxy_pass $backend;
+        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_read_timeout 600s;
+        client_max_body_size 0;
+    }
+}
+
+
+

then edited for aws ec2 web server ->

+
server {
+    listen 80;
+    server_name ec2-54-162-114-41.compute-1.amazonaws.com;
+
+    # The LetsEncrypt pass-though.
+    #location /.well-known/ {
+    #         root /var/www/cryoarks-test/;
+    #}
+
+    root /usr/share/nginx;
+
+    location /static/ {
+        root /volumes;
+        rewrite ^/static/config/(.*)$ /specify6803/config/$1 break;
+        rewrite ^/static/depository/(.*)$ /static-files-cryoarks-test/depository/$1 break;
+        rewrite ^/static/(.*)$ /static-files-cryoarks-test/frontend-static/$1 break;
+    }
+
+    location / {
+        resolver 127.0.0.11 valid=30s;
+        set $backend "http://cryoarks-test:8000";
+        proxy_pass $backend;
+        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_read_timeout 600s;
+        client_max_body_size 0;
+    }
+}
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/Specify7 ECS most-in-one.html b/aws/Specify7 ECS most-in-one.html new file mode 100644 index 0000000..b70029c --- /dev/null +++ b/aws/Specify7 ECS most-in-one.html @@ -0,0 +1,313 @@ + + + + + + + Image — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Image

+

create a one client pod with specify7, specify7-worker, webpack, nginx, and maybe specify6 +excludes mariadb, redis, asset-server

+
FROM arm64v8/ubuntu:20.04
+
+# Set environment variables
+ENV DATABASE_HOST=localhost
+ENV DATABASE_PORT=3306
+ENV MASTER_NAME=master
+ENV MASTER_PASSWORD=master
+ENV SECRET_KEY=bogus
+ENV ASSET_SERVER_URL=https://assets-test.specifycloud.org/web_asset_store.xml
+ENV ASSET_SERVER_KEY=tnhercbrhtktanehul.dukb
+ENV REPORT_RUNNER_HOST=report-runner
+ENV REPORT_RUNNER_PORT=8080
+ENV CELERY_BROKER_URL=redis://redis/0
+ENV CELERY_RESULT_BACKEND=redis://redis/1
+ENV LOG_LEVEL=WARNING
+ENV SP7_DEBUG=true
+ENV SP6_VERSION=6.8.03
+ENV SP6_VERSION_STR=6803
+# ENV SP6_VERSION_STR="${SP6_VERSION//.}"
+# ENV SP6_VERSION_STR=$(echo "$SP6_VERSION" | tr -d '.')
+
+#####################################################################
+
+RUN apt-get update && \
+ apt upgrade -y
+RUN apt-get -y install --no-install-recommends \
+ git \
+ build-essential \
+ libldap2-dev \
+ libmariadbclient-dev \
+ libsasl2-dev \
+ nodejs \
+ npm \
+ python3-venv \
+ python3.8 \
+ python3.8-dev \
+ redis \
+ unzip \
+ openjdk-8-jdk \
+ maven \
+ ant \
+ awscli \
+ mysql-client \
+ nginx \
+ certbot \
+ python3-certbot-nginx;
+RUN apt clean
+
+#####################################################################
+
+# Download repos
+RUN wget https://update.specifysoftware.org/${SP6_VERSION_STR}/Specify_unix_64.sh
+RUN git clone https://github.com/specify/specify7.git
+RUN git clone https://github.com/specify/report-runner-service.git
+
+#####################################################################
+
+# Webpack
+RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash;
+RUN export NVM_DIR="$HOME/.nvm";
+RUN [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh";  # This loads nvm
+RUN nvm install node;
+RUN cd ~/specify7/specifyweb/frontend/js_src;
+RUN npm ci;
+RUN mkdir dist;
+RUN npx webpack --mode production;
+
+#####################################################################
+
+# Specify7
+RUN cd ~/specify7;
+RUN make;
+
+COPY specify7-django.service /etc/systemd/system/
+COPY nginx.conf /home/ubuntu/specify7/nginx.conf
+
+#####################################################################
+
+# Report-runner-service
+COPY report-fonts.jar /home/ubuntu/report-runner-service
+
+RUN mkdir -p /tmp/build
+
+COPY pom.xml /tmp/build
+
+RUN cd ~/report-runner-service
+RUN mvn compile && mvn war:exploded
+
+COPY src /tmp/build/src
+RUN mvn compile && mvn war:exploded
+
+#####################################################################
+
+# nginx webserver
+RUN sed -i "s/server_name localhost/server_name sp7demofish/g" ~/specify7/nginx.conf;
+RUN rm -f /etc/nginx/sites-available/default;
+RUN sudo cp ~/specify7/nginx.conf /etc/nginx/sites-available/specify7;
+RUN sudo ln -s /etc/nginx/sites-available/specify7 /etc/nginx/sites-enabled/;
+
+#####################################################################
+
+# Networking ports
+EXPOSE 80 # nginx port
+EXPOSE 8000 # django gunicorn port
+EXPOSE 8080 # report runner port
+# EXPOSE 3000 # Django Debug
+# EXPOSE 8888 # Debugging service (ptvsd)
+
+#####################################################################
+
+COPY ./docker-entrypoint.sh ~/
+CMD ["/bin/bash", "~/docker-entrypoint.sh"]
+
+
+

docker-entrypoint.sh

+
#!/bin/bash
+
+# Set environment variables
+export DOMAIN_NAME=$(hostname)
+if ! [[ -v CLIENT_NAME ]]; then export CLIENT_NAME=sp7demofish; fi
+if ! [[ -v DATABASE_NAME ]]; then export DATABASE_NAME=sp7demofish; fi
+if ! [[ -v DATABASE_HOST ]]; then export DATABASE_HOST=localhost; fi
+if ! [[ -v DATABASE_PORT ]]; then export DATABASE_PORT=3306; fi
+if ! [[ -v MASTER_NAME ]]; then export MASTER_NAME=master; fi
+if ! [[ -v MASTER_PASSWORD ]]; then export MASTER_PASSWORD=master; fi
+if ! [[ -v SECRET_KEY ]]; then export SECRET_KEY=bogus; fi
+if ! [[ -v ASSET_SERVER_URL ]]; then export ASSET_SERVER_URL=https://assets-test.specifycloud.org/web_asset_store.xml; fi
+if ! [[ -v ASSET_SERVER_KEY ]]; then export ASSET_SERVER_KEY=tnhercbrhtktanehul.dukb; fi
+if ! [[ -v REPORT_RUNNER_HOST ]]; then export REPORT_RUNNER_HOST=localhost; fi
+if ! [[ -v REPORT_RUNNER_PORT ]]; then export REPORT_RUNNER_PORT=8080; fi
+if ! [[ -v CELERY_BROKER_URL ]]; then export CELERY_BROKER_URL=redis://redis/0; fi
+if ! [[ -v CELERY_RESULT_BACKEND ]]; then export CELERY_RESULT_BACKEND=redis://redis/1; fi
+if ! [[ -v LOG_LEVEL ]]; then export LOG_LEVEL=WARNING; fi
+if ! [[ -v SP7_DEBUG ]]; then export SP7_DEBUG=true; fi
+if ! [[ -v SP6_VERSION ]]; then export SP6_VERSION=6.8.03; fi
+if ! [[ -v SP6_VERSION_STR ]]; then export SP6_VERSION_STR="${SP6_VERSION//.}"; fi
+if ! [[ -v WORKER_COUNT ]]; then export WORKER_COUNT=4; fi
+
+echo 'export DOMAIN_NAME=$DOMAIN_NAME' >> ~/.bashrc;
+echo 'export CLIENT_NAME=$CLIENT_NAME' >> ~/.bashrc;
+echo 'export DATABASE_NAME=$DATABASE_NAME' >> ~/.bashrc;
+echo 'export DATABASE_HOST=$DATABASE_HOST' >> ~/.bashrc;
+echo 'export DATABASE_PORT=$DATABASE_PORT' >> ~/.bashrc;
+echo 'export MASTER_NAME=$MASTER_NAME' >> ~/.bashrc;
+echo 'export MASTER_PASSWORD=$MASTER_PASSWORD' >> ~/.bashrc;
+echo 'export SECRET_KEY=$SECRET_KEY' >> ~/.bashrc;
+echo 'export ASSET_SERVER_URL=$ASSET_SERVER_URL' >> ~/.bashrc;
+echo 'export ASSET_SERVER_KEY=$ASSET_SERVER_KEY' >> ~/.bashrc;
+echo 'export REPORT_RUNNER_HOST=$REPORT_RUNNER_HOST' >> ~/.bashrc;
+echo 'export REPORT_RUNNER_PORT=$REPORT_RUNNER_PORT' >> ~/.bashrc;
+echo 'export CELERY_BROKER_URL=$CELERY_BROKER_URL' >> ~/.bashrc;
+echo 'export CELERY_RESULT_BACKEND=$CELERY_RESULT_BACKEND' >> ~/.bashrc;
+echo 'export LOG_LEVEL=$LOG_LEVEL' >> ~/.bashrc;
+echo 'export SP7_DEBUG=$SP7_DEBUG' >> ~/.bashrc;
+echo 'export SP6_VERSION=$SP6_VERSION' >> ~/.bashrc;
+echo 'export SP6_VERSION_STR=$SP6_VERSION_STR' >> ~/.bashrc;
+echo 'export WORKER_COUNT=$WORKER_COUNT' >> ~/.bashrc;
+
+# Specify7 Django
+cp specifyweb.wsgi specifyweb_wsgi.py;
+sudo systemctl enable specify7-django.service;
+sudo systemctl start specify7-django.service;
+
+# Specify7 worker
+cd ~/specify7;
+celery -A specifyweb worker -l INFO --concurrency=1 &;
+
+# Report runner
+
+
+# nginx webserver
+sudo systemctl daemon-reload;
+sudo systemctl restart nginx;
+
+
+

specify7-django.service

+
[Unit]
+Description=Specify 7 Django Server
+Wants=network.target
+
+[Service]
+User=ubuntu
+WorkingDirectory=/home/ubuntu/specify7
+ExecStart=/home/ubuntu/specify7/ve/bin/gunicorn -w 3 -b 0.0.0.0:8000 -t 300 specifyweb_wsgi
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
+
+
+

report-runner.service

+
[Unit]
+Description=Specify Report Runner Service
+Wants=network.target
+ConditionPathExists=/home/ubuntu/report-runner-service
+
+[Service]
+User=ubuntu
+WorkingDirectory=/home/ubuntu/report-runner-service
+ExecStart=/usr/bin/mvn jetty:run
+
+[Install]
+Alias=ireportrunner.service
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/Useful Bash Commands.html b/aws/Useful Bash Commands.html new file mode 100644 index 0000000..e4a46c2 --- /dev/null +++ b/aws/Useful Bash Commands.html @@ -0,0 +1,220 @@ + + + + + + + <no title> — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

sftp

+
sftp -i alec_specify_ssh_key ubuntu@ec2-52-206-2-67.compute-1.amazonaws.com;
+pwd;
+put /Users/alecwhite/git/specify-aws/report-fonts.jar ./;
+ls;
+exit;
+
+
+

background & foreground tasks

+

+
+
+

check storage

+
df -H
+du -sh *
+
+
+

rsync

+
rsync -avz -e "ssh -i ~/specify/keys/specify-aws-ssh.pem" ~/git/specify-aws/specify7-cluster/ ubuntu@ec2-52-206-2-67.compute-1.amazonaws.com:/home/ubuntu/specify7-cluster/
+
+
+

docker images view architecture and OS

+
for img in $(docker image ls -q); do echo $img; docker image inspect $img | jq '.[0] | {image: .RepoTags[0], os: .Os, arch: .Architecture}'; done
+
+
+

run a django unit test through docker

+
sudo docker exec -it specify7-specify7-1 bash -c "ve/bin/python3 manage.py test specifyweb.notifications.tests.NotificationsTests"
+
+
+

git stash specify files

+
git stash push -m "stash-name" file1.txt file2.txt
+git stash list
+git stash apply stash^(0)
+
+
+

add user

+
adduser --disabled-password --gecos "" specify;
+su - specify;
+
+
+

docker build and push for multiple architectures

+
export DOCKER_CLI_EXPERIMENTAL=enabled
+docker buildx create --name mybuilder --use # only do once
+docker buildx inspect mybuilder --bootstrap
+
+docker buildx build --platform linux/amd64,linux/arm64 -t specifyconsortium/specify-asset-service:connection_fix . --push
+
+docker buildx use default # don't think needs to be done
+
+
+

create linux user for ssh login and database access

+
#!/bin/bash
+
+# Ask for the new username
+read -p "Enter the new username: " username
+
+# Create the new user without a password
+sudo adduser --disabled-password --gecos "" $username
+
+# Restrict the user with rbash
+sudo usermod -s /bin/rbash $username
+
+# Create a bin directory for the user for restricted commands
+mkdir /home/$username/bin
+
+# Symlink the MySQL client to the user's bin so they can use it
+ln -s /usr/bin/mysql /home/$username/bin/mysql
+
+# Restrict access to the user's home directory
+sudo chown $username:$username /home/$username
+sudo chmod 700 /home/$username
+
+# Set up .ssh directory for SSH key-based authentication
+mkdir /home/$username/.ssh
+chmod 700 /home/$username/.ssh
+
+# Ask for the user's public SSH key and add it to the authorized_keys file
+read -p "Enter the new user's public SSH key: " ssh_key
+echo "$ssh_key" > /home/$username/.ssh/authorized_keys
+chmod 600 /home/$username/.ssh/authorized_keys
+chown $username:$username /home/$username/.ssh/authorized_keys
+
+mysql -u root -p
+CREATE USER 'dbuser'@'localhost' IDENTIFIED BY 'DBUSER_PASSWORD';
+GRANT SELECT, INSERT, UPDATE, DELETE ON your_database_name.* TO 'dbuser'@'localhost';
+FLUSH PRIVILEGES;
+EXIT;
+
+
+

view live formatted nginx logs example

+
docker logs specifycloud-nginx-1 --tail 1000 --since 10m --follow | \
+grep -v updown | grep -v notification | grep specifycloud.org | \
+awk '{ split($4,time,"["); print time[2], "-", $6, $7, $8, $9, $10, $11; }'
+
+
+

Add swap memory

+
sudo fallocate -l 4G /swapfile;
+# sudo dd if=/dev/zero of=/swapfile bs=1024 count=4096k; # if fallocate is not available
+sudo chmod 600 /swapfile;
+sudo mkswap /swapfile; # make swap file
+sudo swapon /swapfile; # enable swap file
+echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab; # make change permanent
+
+# check swap
+sudo swapon --show;
+free -h;
+
+# swappiness defauls to 10, 100 is very argessive use, 0 is use only will absolutley necessary
+cat /proc/sys/vm/swappiness;
+sudo sysctl vm.swappiness=10;
+sudo echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf; sudo sysctl -p; # make chane permanent
+
+# increase swap size
+sudo swapoff -v /swapfile # turn off
+sudo fallocate -l 8G /swapfile; # resize
+sudo mkswap /swapfile; sudo swapon /swapfile; # turn on
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/aws/VS Code Django Unit Test Debugging Notes.html b/aws/VS Code Django Unit Test Debugging Notes.html new file mode 100644 index 0000000..fee0349 --- /dev/null +++ b/aws/VS Code Django Unit Test Debugging Notes.html @@ -0,0 +1,209 @@ + + + + + + + <no title> — Specify Developer Documentation documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

VS Code Django Unit Test Debugging Notes:

+

Might need to add permissions to the test db for the master user: +mysql --host 127.0.0.1 -u root -p'root' -e "GRANT ALL PRIVILEGES ON test_cuic.* TO 'master'@'%';" +GRANT ALL PRIVILEGES ON test_.* TO 'master'@'%';

+

https://devpress.csdn.net/python/62fe07607e66823466192fa3.html

+
ve/bin/pip install pytest pytest-django
+
+
+
ve/bin/pip install hypothesis==6.4.0 hypothesis-jsonschema==0.19.0
+
+
+

specifyweb/pytest.ini ->

+
[pytest]
+DJANGO_SETTINGS_MODULE=specifyweb.settings
+python_files=*test*.py testparsing.py
+addopts = --ignore=specifyweb/specify/selenium_tests.py
+
+
+

.vscode/settings.json ->

+
{
+    "python.pythonPath": "ve/bin/python",
+    "python.testing.pytestArgs": [
+        "specifyweb",
+        "-s",
+        "-vv"
+    ],
+    "python.testing.pytestEnabled": true,
+    "python.testing.nosetestsEnabled": false,
+    "python.testing.unittestEnabled": false
+}
+
+
+

.env -> not needed

+
PYTHONPATH=specifyweb/
+
+
+

manage.py -> +paste between “os.environ.setdefault” … “try: from django.core.management”

+
from django.conf import settings
+
+    if settings.DEBUG:
+        if os.environ.get('RUN_MAIN') or os.environ.get('WERKZEUG_RUN_MAIN'):
+            import debugpy
+            debugpy.listen(("0.0.0.0", 3000))
+            print('Attached!')
+
+
+

requirement-testing.txt ->

+
debugpy==1.6.5
+pytest==7.2.1
+pytest-django==4.5.2
+
+
+

docker-compose.yml ->

+
ports:
+  - 3000:3000 # Django Debug
+  - 8888:8888 # debugging service (ptvsd)
+
+SECRET_KEY=bogus
+
+
+

Dockerfile ->

+
COPY --chown=specify:specify requirements-testing.txt /home/specify/
+
+RUN python3.8 -m venv ve \
+ && ve/bin/pip install --no-cache-dir -r /home/specify/requirements-testing.txt \
+ && ve/bin/pip install --no-cache-dir -r /home/specify/requirements.txt
+
+
+

For the non-container instance of vscode, the debugger is used at runtime with the front-end:

+

.vscode/launch.json ->

+
{
+    "version": "0.2.0",
+    "configurations": [
+      {
+        "name": "Run Django",
+        "type": "python",
+        "request": "attach",
+        "pathMappings": [
+          {
+            "localRoot": "${workspaceFolder}/specifyweb",
+            "remoteRoot": "/opt/specify7/specifyweb"
+          }
+        ],
+        "port": 3000,
+        "host": "127.0.0.1",
+      }
+    ]
+  }
+
+
+

.vscode/settings.json ->

+
{
+    "python.testing.unittestArgs": [
+        "-v",
+        "-s",
+        "./specifyweb",
+        "-p",
+        "*test*.py"
+    ],
+    "python.testing.pytestEnabled": false,
+    "python.testing.unittestEnabled": true,
+    "python.linting.mypyEnabled": true,
+    "python.linting.enabled": true
+}
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html index 3bf5c72..04bb7cc 100644 --- a/genindex.html +++ b/genindex.html @@ -1,34 +1,71 @@ - - - - - - Index — Specify Developer Documentation documentation - - - - - - - - - - + + + + + Index — Specify Developer Documentation documentation + + + + + + + + + + + + - - + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+

Index

@@ -37,63 +74,31 @@

Index

+
- -
-
- -
- - - - - - - + + + + + \ No newline at end of file diff --git a/index.html b/index.html index def9f9f..c915a36 100644 --- a/index.html +++ b/index.html @@ -1,42 +1,84 @@ + + + - - - - - - Welcome to Specify Developer documentation! — Specify Developer Documentation documentation - - - - - - - - - - + + Welcome to Specify Developer documentation! — Specify Developer Documentation documentation + + + + + + + + + + + + - - + +
+ + +
+ +
+
+
+ +
+
+
+
+
-

Welcome to Specify Developer documentation!

+

Welcome to Specify Developer documentation!

-

Indices and tables

+

Indices and tables

-
-
- - - - - - +
+
+ + + \ No newline at end of file diff --git a/objects.inv b/objects.inv index 60ef01d..8540f9f 100644 Binary files a/objects.inv and b/objects.inv differ diff --git a/search.html b/search.html index f9ccd97..160b856 100644 --- a/search.html +++ b/search.html @@ -1,118 +1,119 @@ + + + + + Search — Specify Developer Documentation documentation + + - - - - - Search — Specify Developer Documentation documentation - - + - - - + + + + + + + + - - - - - - + + - - + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+
- -
-
- -
-
- +
+
+ + + + + - - - + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js index bbe4ac0..86aac54 100644 --- a/searchindex.js +++ b/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["index"], "filenames": ["index.rst"], "titles": ["Welcome to Specify Developer documentation!"], "terms": {"index": 0, "modul": 0, "search": 0, "page": 0}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"welcom": 0, "specifi": 0, "develop": 0, "document": 0, "indic": 0, "tabl": 0}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"Welcome to Specify Developer documentation!": [[0, "welcome-to-specify-developer-documentation"]], "Indices and tables": [[0, "indices-and-tables"]]}, "indexentries": {}}) \ No newline at end of file +Search.setIndex({"docnames": ["aws/AWS Config Files", "aws/AWS Infrastructure Notes", "aws/AWS Specify Asset Server Setup", "aws/Add New User Insance to Specify Cloud", "aws/Asset Server Config", "aws/Asset Server Setup", "aws/Client Migration Notes", "aws/EC2 & RDS Specify7 Setup", "aws/Hybrid Asset Server Setup Instructions", "aws/KU IT Notes", "aws/Setup AWS Specify Cloud", "aws/Specify Cloud Graviton Setup", "aws/Specify7 ECS most-in-one", "aws/Useful Bash Commands", "aws/VS Code Django Unit Test Debugging Notes", "index"], "filenames": ["aws/AWS Config Files.md", "aws/AWS Infrastructure Notes.md", "aws/AWS Specify Asset Server Setup.md", "aws/Add New User Insance to Specify Cloud.md", "aws/Asset Server Config.md", "aws/Asset Server Setup.md", "aws/Client Migration Notes.md", "aws/EC2 & RDS Specify7 Setup.md", "aws/Hybrid Asset Server Setup Instructions.md", "aws/KU IT Notes.md", "aws/Setup AWS Specify Cloud.md", "aws/Specify Cloud Graviton Setup.md", "aws/Specify7 ECS most-in-one.md", "aws/Useful Bash Commands.md", "aws/VS Code Django Unit Test Debugging Notes.md", "index.rst"], "titles": ["Specify 7 Docker Config Example", "<no title>", "EC2 Non-Dockerized Build", "<no title>", "<no title>", "EC2 Non-Dockerized Build", "<no title>", "Spin Up EC2 Instance", "<no title>", "New certificate on biprdsp7wbdb.cc.ku.edu server", "Setup Aurora MySQL Database", "<no title>", "Image", "<no title>", "<no title>", "Welcome to Specify Developer documentation!"], "terms": {"database_host": [0, 7, 10, 11, 12], "10": [0, 3, 7, 10, 11, 13], "133": [0, 11], "58": [0, 10, 11], "98": [0, 11], "database_port": [0, 7, 10, 11, 12], "3306": [0, 6, 7, 10, 11, 12], "master_nam": [0, 7, 10, 11, 12], "master": [0, 3, 6, 7, 10, 11, 12, 14], "master_password": [0, 7, 10, 11, 12], "secret_kei": [0, 7, 10, 12, 14], "asset_server_url": [0, 4, 7, 10, 12], "http": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14], "assets1": [0, 2, 5, 7, 10, 11], "specifycloud": [0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13], "org": [0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13], "web_asset_stor": [0, 2, 4, 5, 7, 10, 11, 12], "xml": [0, 2, 4, 5, 7, 10, 11, 12], "asset_server_kei": [0, 7, 10, 12], "report_runner_host": [0, 7, 10, 11, 12], "report_runner_port": [0, 7, 10, 11, 12], "8080": [0, 2, 4, 5, 7, 10, 11, 12], "celery_broker_url": [0, 7, 10, 12], "redi": [0, 7, 10, 11, 12], "0": [0, 2, 3, 4, 5, 7, 10, 11, 12, 13, 14], "celery_result_backend": [0, 7, 10, 12], "1": [0, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14], "log_level": [0, 7, 10, 12], "warn": [0, 7, 10, 11, 12], "sp7_debug": [0, 7, 10, 12], "fals": [0, 2, 3, 4, 5, 7, 10, 14], "version": [0, 2, 4, 5, 7, 10, 14], "3": [0, 2, 3, 4, 5, 7, 8, 10, 11, 12], "servic": [0, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14], "sp7demofish": [0, 2, 4, 5, 10, 11, 12], "eu": [0, 6, 7, 10, 11], "imag": [0, 2, 4, 5, 7, 10, 13], "specifyconsortium": [0, 4, 10, 13], "specify7": [0, 3, 4, 6, 10, 11, 12, 13, 14], "v7": [0, 10, 11], "command": [0, 4, 7, 9, 10, 11, 13], "ve": [0, 2, 5, 8, 10, 11, 12, 13, 14], "bin": [0, 2, 3, 5, 8, 10, 11, 12, 13, 14], "gunicorn": [0, 11, 12], "w": [0, 3, 4, 7, 11, 12], "thread": 0, "5": [0, 2, 4, 5, 6, 7, 8, 10, 11, 14], "b": [0, 10, 11, 12, 13], "8000": [0, 11, 12], "t": [0, 2, 3, 5, 7, 10, 11, 12, 13], "300": [0, 2, 4, 5, 11, 12], "specifyweb_wsgi": [0, 11, 12], "init": [0, 2, 4, 5], "true": [0, 2, 4, 5, 7, 12, 14], "restart": [0, 2, 3, 4, 5, 7, 9, 10, 11, 12], "unless": [0, 2, 4, 5], "stop": [0, 3, 4, 7], "volum": [0, 2, 4, 5, 11], "specify6801": [0, 10, 11], "opt": [0, 10, 11, 14], "ro": [0, 4], "static": [0, 2, 5, 7, 11], "env_fil": 0, "environ": [0, 4, 11, 12, 14], "database_nam": [0, 11, 12], "sp7demofish_eu": 0, "asset_server_collect": 0, "demo": [0, 2, 5, 7, 10], "worker": [0, 3, 4, 7, 10, 11, 12], "celeri": [0, 7, 10, 11, 12], "A": [0, 2, 5, 9, 10, 11, 12], "specifyweb": [0, 10, 11, 12, 13, 14], "l": [0, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13], "info": [0, 11, 12], "concurr": [0, 10, 11, 12], "q": [0, 10, 11, 13], "rjb": 0, "madrid": 0, "specify6803": [0, 7, 10, 11], "rjb_madrid": 0, "fanerogamia": 0, "mcnb": 0, "herb": 0, "rbge": 0, "local_specify_set": [0, 11], "herb_rbg": [0, 2, 5, 10], "sandbox": 0, "issue_388": 0, "sandbox_rbg": 0, "cryoark": [0, 11], "test": [0, 2, 3, 5, 7, 8, 11, 12, 13, 14], "cryoarks_test": 0, "eurl": [0, 3], "specify6800": [0, 10], "specify6": [0, 6, 7, 10, 11, 12], "6": [0, 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14], "8": [0, 2, 4, 5, 7, 8, 10, 11, 12, 13, 14], "00": [0, 10], "01": [0, 2, 5, 10], "03": [0, 7, 10, 11, 12], "port": [0, 2, 4, 5, 7, 8, 10, 11, 12, 14], "80": [0, 2, 4, 5, 8, 10, 11, 12], "443": [0, 2, 4, 5], "etc": [0, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13], "d": [0, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12], "letsencrypt": [0, 2, 3, 4, 5, 8, 9, 11], "ssl": [0, 2, 3, 4, 5, 7, 8, 9, 11], "cert": [0, 2, 4, 5, 8, 9], "dhparam": [0, 2, 4, 5, 8], "pem": [0, 2, 4, 5, 7, 8, 9, 13], "var": [0, 2, 3, 4, 5, 7, 8, 10, 11], "www": [0, 2, 3, 4, 5, 7, 8, 10, 11], "listen": [0, 2, 4, 5, 11, 14], "server_nam": [0, 2, 4, 5, 9, 11, 12], "The": [0, 2, 4, 5, 6, 7, 11], "pass": [0, 2, 4, 5, 11], "though": [0, 2, 4, 5, 11], "locat": [0, 2, 4, 5, 9, 11], "well": [0, 2, 4, 5, 6, 7, 8, 11], "known": [0, 2, 4, 5, 8, 11], "root": [0, 2, 4, 5, 6, 11, 13, 14], "return": 0, "301": 0, "host": [0, 2, 5, 6, 7, 10, 11, 14], "request_uri": 0, "thi": [0, 2, 4, 5, 7, 11, 12], "stanza": [0, 2, 4, 5], "defin": [0, 2, 4, 5], "end": [0, 2, 4, 5, 7, 10, 11, 14], "point": [0, 2, 3, 4, 5, 11], "ssl_certif": [0, 2, 4, 5, 9], "live": [0, 2, 4, 5, 8, 9, 13], "fullchain": [0, 2, 4, 5, 9], "ssl_certificate_kei": [0, 2, 4, 5, 9], "privkei": [0, 2, 4, 5, 9], "from": [0, 2, 3, 4, 5, 6, 7, 10, 11, 12, 14], "cipherli": [0, 2, 4, 5], "st": [0, 2, 4, 5, 9], "raymii": [0, 2, 4, 5], "": [0, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14], "tutori": [0, 2, 4, 5], "strong_ssl_security_on_nginx": [0, 2, 4, 5], "html": [0, 2, 4, 5, 11, 14], "ssl_protocol": [0, 2, 4, 5], "tlsv1": [0, 2, 4, 5], "2": [0, 2, 3, 4, 5, 7, 9, 10, 11, 13, 14], "ssl_prefer_server_ciph": [0, 2, 4, 5], "ssl_cipher": [0, 2, 4, 5], "eecdh": [0, 2, 4, 5], "aesgcm": [0, 2, 4, 5], "edh": [0, 2, 4, 5], "aes256": [0, 2, 4, 5], "ssl_ecdh_curv": [0, 2, 4, 5], "secp384r1": [0, 2, 4, 5], "ssl_session_cach": [0, 2, 4, 5], "share": [0, 2, 4, 5, 7, 10, 11], "10m": [0, 2, 4, 5, 13], "ssl_session_ticket": [0, 2, 4, 5], "off": [0, 2, 4, 5, 11, 13], "ssl_stapl": [0, 2, 4, 5], "ssl_stapling_verifi": [0, 2, 4, 5], "resolv": [0, 2, 4, 5, 11], "4": [0, 2, 3, 4, 5, 10, 12, 13, 14], "valid": [0, 2, 4, 5, 11], "resolver_timeout": [0, 2, 4, 5], "disabl": [0, 2, 4, 5, 11, 13], "preload": [0, 2, 4, 5], "hst": [0, 2, 4, 5], "now": [0, 2, 4, 5, 6, 9], "you": [0, 2, 4, 5, 6, 7, 11], "can": [0, 2, 4, 5, 7, 13], "us": [0, 2, 4, 5, 6, 7, 10, 11, 13, 14], "comment": [0, 2, 4, 5], "out": [0, 2, 4, 5, 8, 9, 11], "header": [0, 2, 4, 5, 11], "line": [0, 2, 3, 4, 5, 7, 9, 11], "includ": [0, 2, 4, 5, 7, 11], "direct": [0, 2, 4, 5, 11], "understand": [0, 2, 4, 5], "implic": [0, 2, 4, 5], "add_head": [0, 2, 4, 5], "strict": [0, 2, 4, 5], "transport": [0, 2, 4, 5, 7, 10], "secur": [0, 2, 4, 5, 6, 7], "max": [0, 2, 4, 5], "ag": [0, 2, 4, 5], "63072000": [0, 2, 4, 5], "includesubdomain": [0, 2, 4, 5], "x": [0, 2, 4, 5, 7, 8, 10, 11], "frame": [0, 2, 4, 5], "option": [0, 2, 4, 5, 7, 9, 10, 11], "deni": [0, 2, 4, 5], "content": [0, 2, 4, 5], "type": [0, 2, 4, 5, 7, 14], "nosniff": [0, 2, 4, 5], "ssl_dhparam": [0, 2, 4, 5], "i": [0, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14], "m": [0, 2, 4, 5, 7, 8, 10, 11, 13, 14], "sure": [0, 2, 3, 4, 5, 7, 9], "need": [0, 2, 3, 4, 5, 7, 8, 10, 13, 14], "side": [0, 2, 4, 5], "just": [0, 2, 3, 4, 5, 7], "case": [0, 2, 4, 5], "usr": [0, 2, 5, 7, 10, 11, 12, 13], "rewrit": [0, 2, 5, 11], "break": [0, 11], "depositori": [0, 11], "frontend": [0, 11, 12], "127": [0, 11, 14], "11": [0, 6, 10, 11, 13], "30": [0, 2, 5, 11], "backend": [0, 11], "proxy_pass": [0, 2, 4, 5, 11], "proxy_set_head": [0, 11], "real": [0, 11], "ip": [0, 4, 6, 7, 11], "remote_addr": [0, 11], "forward": [0, 11], "For": [0, 2, 3, 5, 6, 7, 11, 14], "proxy_add_x_forwarded_for": [0, 11], "proto": [0, 11], "scheme": [0, 11], "proxy_read_timeout": [0, 11], "600": [0, 2, 5, 7, 11, 13], "client_max_body_s": [0, 2, 4, 5, 11], "github": [0, 2, 5, 6, 7, 8, 10, 11, 12], "repo": [0, 2, 5, 7, 8, 10, 11, 12], "com": [0, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13], "sampl": [0, 2, 5], "turn": [0, 2, 5, 13], "bottl": [0, 2, 5], "debug": [0, 2, 5, 11, 12, 14], "modul": [0, 2, 5, 11, 15], "reload": [0, 2, 3, 5, 7, 10, 12], "print": [0, 2, 5, 7, 10, 13, 14], "some": [0, 2, 5, 7], "inform": [0, 2, 5], "consol": [0, 2, 5, 7], "secret": [0, 2, 5, 7, 10], "kei": [0, 2, 3, 5, 7, 9, 10, 13], "gener": [0, 2, 5, 7, 9, 10], "authent": [0, 2, 5, 7, 13], "token": [0, 2, 5, 7], "request": [0, 2, 3, 5, 9, 11, 14], "same": [0, 2, 4, 5, 6, 9], "must": [0, 2, 5, 11], "store": [0, 2, 4, 5, 6, 7], "attach": [0, 2, 3, 4, 5, 8, 14], "prefer": [0, 2, 5, 7], "good": [0, 2, 5], "sourc": [0, 2, 5, 7, 8, 10, 11], "valu": [0, 2, 5, 7, 11], "grc": [0, 2, 5], "password": [0, 2, 5, 6, 7, 9, 10, 13], "htm": [0, 2, 5], "none": [0, 2, 5, 11, 13], "NOT": [0, 2, 5], "recommend": [0, 2, 5, 8, 10, 11, 12], "sinc": [0, 2, 5, 6, 7, 13], "do": [0, 2, 3, 5, 9, 13], "so": [0, 2, 5, 10, 13], "allow": [0, 2, 5, 7, 10, 11], "anyon": [0, 2, 5], "internet": [0, 2, 5], "arbitrari": [0, 2, 5], "tnhercbrhtktanehul": [0, 2, 10, 11, 12], "dukb": [0, 2, 10, 11, 12], "auth": [0, 2, 5, 7], "timestamp": [0, 2, 5], "within": [0, 2, 5], "mani": [0, 2, 5], "second": [0, 2, 5], "time": [0, 2, 5, 7, 13], "order": [0, 2, 5], "consid": [0, 2, 5], "prevent": [0, 2, 5], "replai": [0, 2, 5], "attack": [0, 2, 5], "time_toler": [0, 2, 5], "requir": [0, 2, 5, 7, 8, 10, 11, 14], "download": [0, 2, 5, 7, 10, 12], "addit": [0, 2, 4, 5], "upload": [0, 2, 3, 5, 11], "delet": [0, 2, 5, 11, 13], "access": [0, 2, 3, 5, 6, 7, 10, 11, 13], "enabl": [0, 2, 5, 10, 11, 12, 13, 14], "affect": [0, 2, 5], "require_key_for_get": [0, 2, 5], "portal": [0, 2, 5], "getfileref": [0, 2, 5], "url": [0, 2, 3, 4, 5, 6, 7, 11], "allow_static_file_access": [0, 2, 5], "These": [0, 2, 5, 7], "ar": [0, 2, 5, 6, 7, 9, 10, 11], "interpol": [0, 2, 5], "resourc": [0, 2, 5, 7], "client": [0, 2, 3, 5, 7, 10, 11, 12, 13], "know": [0, 2, 4, 5], "how": [0, 2, 5], "talk": [0, 2, 5], "server_port": [0, 2, 4, 5], "develop": [0, 2, 5, 7], "should": [0, 2, 5, 7], "development_port": [0, 2, 5], "map": [0, 2, 5], "collect": [0, 2, 5, 9], "name": [0, 2, 5, 6, 7, 9, 13, 14], "directori": [0, 2, 3, 5, 7, 11, 13], "everyth": [0, 2, 4, 5, 11], "origin": [0, 2, 5, 7], "thumbnail": [0, 2, 5], "provis": [0, 2, 5, 7], "made": [0, 2, 5], "item": [0, 2, 5], "scope": [0, 2, 5], "abov": [0, 2, 4, 5], "found": [0, 2, 5], "collection_dir": [0, 2, 4, 5], "collection_nam": [0, 2, 5], "directory_nam": [0, 2, 5], "beati": [0, 10], "ncslgfungi": 0, "ocpc": 0, "stlawu": 0, "asnhcmamm": 0, "dwuspid": 0, "nha": 0, "cornellmamm": 0, "cornellherp": 0, "cornellfish": 0, "uprm_invcol": 0, "nybg_vanuatu": 0, "demobird": 0, "emoryherbarium": 0, "uw_geo_collect": 0, "keherbariumvascular": 0, "cornellbird": 0, "critter": 0, "williherb": 0, "sbccprofantmuseum": 0, "josc": 0, "ccnhm": 0, "lsumz_mamm": 0, "pripaleo": 0, "invertebr": 0, "paleontologi": 0, "usgs_nmnhpaleontologycollect": 0, "vimsfish": [0, 10], "vimsworkshop": 0, "ilstu": 0, "wespalcol": 0, "royfungi": 0, "isua": 0, "chagasecohealth": 0, "plu": 0, "uconnvert": 0, "kelichen": 0, "lakeforestbiologi": 0, "mariamitchel": 0, "gree": 0, "pennstatefishmuseum": 0, "dukebryo": 0, "bishopmuseum": 0, "mndragonfli": 0, "cudiatom": 0, "fwri": 0, "sdinvert": 0, "shellmuseum": 0, "ansppaleo": 0, "osuorton": 0, "ciscollect": 0, "cuic": 0, "lsumz_fish": 0, "calvertmarinemuseum": 0, "non": [0, 14], "arthropod": 0, "lsumz_herp": 0, "uwfc": 0, "oregon": 0, "state": [0, 9], "ichthyologi": [0, 2, 5], "o": [0, 2, 5, 7, 9, 10, 12, 13, 14], "unitec": 0, "herbarium": 0, "nbm_mnb": 0, "amphibian": 0, "reptil": 0, "sight": 0, "purdueherbaria": 0, "montrealinsectarium": 0, "workshop": 0, "ptrm": 0, "herb_umass": 0, "unsm_vp": [0, 3], "morpalo": 0, "aafc_aac": 0, "spnhc": 0, "base": [0, 2, 5, 13], "all": [0, 2, 3, 4, 5, 7, 10, 11, 14], "base_dir": [0, 2, 4, 5], "home": [0, 2, 3, 4, 5, 9, 10, 11, 12, 13, 14], "separ": [0, 2, 4, 5, 7], "thumb_dir": [0, 2, 5], "orig_dir": [0, 2, 5], "mime": [0, 2, 5], "try": [0, 2, 5, 7, 14], "can_thumbnail": [0, 2, 5], "jpeg": [0, 2, 5], "gif": [0, 2, 5], "png": [0, 2, 5], "tiff": [0, 2, 5], "applic": [0, 2, 5, 7], "pdf": [0, 2, 5], "what": [0, 2, 5, 11], "stand": [0, 2, 5], "alon": [0, 2, 5], "oper": [0, 2, 5], "past": [0, 2, 4, 5, 14], "python": [0, 2, 5, 7, 8, 10, 11, 14], "packag": [0, 2, 5], "fast": [0, 2, 5], "seem": [0, 2, 5], "work": [0, 2, 4, 5, 7], "wsgiref": [0, 2, 5], "extra": [0, 2, 5], "arm64v8": [0, 12], "ubuntu": [0, 2, 4, 5, 7, 10, 11, 12, 13], "18": [0, 10], "04": [0, 7, 10, 11, 12], "AS": 0, "run": [0, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14], "apt": [0, 2, 5, 7, 8, 10, 11, 12], "get": [0, 2, 4, 5, 7, 8, 10, 12, 14], "updat": [0, 2, 5, 6, 7, 8, 10, 11, 12, 13], "y": [0, 2, 5, 7, 8, 10, 11, 12], "instal": [0, 2, 5, 7, 8, 10, 11, 12, 14], "ghostscript": [0, 2, 5, 8], "imagemagick": [0, 2, 5, 8], "python3": [0, 2, 5, 7, 8, 10, 11, 12, 13, 14], "dev": [0, 2, 5, 7, 8, 10, 11, 12, 13], "pip": [0, 2, 5, 7, 8, 10, 11, 14], "venv": [0, 2, 5, 8, 10, 11, 12, 14], "git": [0, 2, 5, 7, 8, 10, 11, 12, 13], "certbot": [0, 2, 3, 4, 5, 7, 8, 10, 11, 12], "awscli": [0, 2, 5, 7, 8, 10, 11, 12], "s3f": [0, 2, 5, 8], "clean": [0, 11, 12], "rm": [0, 2, 5, 7, 10, 11, 12], "rf": 0, "lib": [0, 10], "list": [0, 3, 7, 10, 13], "groupadd": 0, "g": [0, 7, 10, 11, 12], "999": 0, "useradd": 0, "r": [0, 2, 5, 7, 8, 10, 11, 14], "u": [0, 2, 5, 7, 8, 9, 10, 11, 13, 14], "mkdir": [0, 2, 3, 4, 5, 7, 8, 10, 11, 12, 13], "p": [0, 3, 7, 10, 11, 12, 13, 14], "chown": [0, 9, 13, 14], "user": [0, 2, 3, 5, 6, 10, 11, 12, 13, 14], "workdir": 0, "copi": [0, 7, 9, 10, 12, 14], "txt": [0, 2, 5, 7, 8, 10, 11, 13, 14], "cach": [0, 2, 5, 7, 8, 10, 11, 14], "dir": [0, 2, 5, 8, 10, 11, 14], "view": [0, 13], "echo": [0, 7, 10, 11, 12, 13], "import": [0, 2, 5, 7, 8, 14], "nserver": 0, "nserver_nam": 0, "nserver_port": 0, "int": 0, "getenv": 0, "nkei": 0, "attachment_kei": [0, 4], "ndebug": 0, "debug_mod": [0, 4], "lower": 0, "configur": [0, 2, 4, 5, 9, 10, 11, 14], "aw": [0, 2, 5, 7, 8, 10, 11, 13], "aws_access_key_id": [0, 2, 7, 10, 11], "aws_secret_access_kei": [0, 2, 7, 10, 11], "region": [0, 2, 7, 10, 11], "east": [0, 2, 7, 10, 11], "output": [0, 2, 7, 9, 10, 11], "json": [0, 2, 3, 7, 10, 11, 14], "s3": [0, 2, 5, 8, 10, 11], "mount": [0, 2, 5, 8], "cloud": [0, 2, 5, 7, 8, 10, 11], "expos": [0, 12], "cmd": [0, 12], "It": [0, 2, 5, 11], "default_serv": [0, 2, 4, 5], "certif": [0, 2, 3, 4, 5, 7, 10], "mechan": [0, 2, 5], "place": [0, 2, 5], "nonc": [0, 2, 5], "challeng": [0, 2, 5, 9], "prove": [0, 2, 5], "we": [0, 2, 5, 6], "have": [0, 2, 3, 5, 6, 7], "control": [0, 2, 5], "domain": [0, 2, 3, 5, 7], "filesystem": [0, 2, 5], "easili": [0, 2, 5], "auto": [0, 2, 5], "renew": [0, 2, 3, 5], "system": [0, 2, 5, 12], "proxi": [0, 2, 5, 11], "actual": [0, 2, 3, 5], "correct": [0, 2, 5, 9], "string": [0, 2, 5, 9], "substitut": [0, 2, 5], "respons": [0, 2, 5], "make": [0, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13], "link": [0, 2, 4, 5, 9], "localhost": [0, 2, 5, 11, 12, 13], "sub_filt": [0, 2, 4, 5], "sub_filter_onc": [0, 2, 4, 5], "sub_filter_typ": [0, 2, 4, 5], "text": [0, 2, 4, 5], "other": [0, 2, 3, 5], "unchang": [0, 2, 5], "except": [0, 2, 4, 5], "rewritten": [0, 2, 4, 5], "chang": [0, 2, 4, 5, 6, 7, 13], "els": [0, 2, 4, 5, 11], "through": [0, 2, 3, 4, 5, 7, 13], "bash": [2, 3, 4, 5, 8, 10, 11, 12, 13], "sudo": [2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13], "upgrad": [2, 5, 6, 7, 8, 10, 11, 12], "nginx": [2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13], "authbind": [2, 5, 8], "essenti": [2, 11, 12], "checkinstal": 2, "zlib1g": 2, "libncurses5": 2, "libgdbm": 2, "libnss3": 2, "libssl": 2, "libreadlin": 2, "libffi": 2, "libsqlite3": 2, "wget": [2, 5, 7, 8, 10, 11, 12], "libbz2": 2, "ftp": 2, "15": 2, "tgz": 2, "tar": 2, "xf": 2, "cd": [2, 5, 7, 8, 10, 11, 12], "optim": 2, "j": [2, 11], "nproc": 2, "altinstal": 2, "softwar": [2, 5, 7, 8, 10], "properti": [2, 5, 7, 8, 10], "common": [2, 5, 7, 8, 9, 10], "add": [2, 3, 5, 7, 8, 10, 11, 13, 14], "repositori": [2, 5, 8, 10, 11], "ppa": [2, 5, 8, 10, 11], "deadsnak": [2, 5, 8, 10], "distutil": [2, 5, 8], "pip3": [2, 5, 8], "bootstrap": [2, 5, 8, 13], "pypa": [2, 5, 8], "io": [2, 5, 8, 10], "py": [2, 3, 5, 8, 11, 12, 13, 14], "without": [2, 5, 8, 10, 13], "activ": [2, 5, 8, 10, 11], "deactiv": 2, "virtualenv": [2, 5, 8, 10, 11], "myenv": [2, 5, 8], "tl": [2, 5, 7, 8, 11], "depend": [2, 5, 8], "set": [2, 3, 4, 5, 7, 10, 11, 12, 13, 14], "access_kei": [2, 10, 11], "access_key_secret": [2, 10, 11], "default": [2, 4, 5, 7, 9, 10, 11, 12, 13], "file": [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13], "cp": [2, 5, 7, 8, 9, 10, 11, 12], "specifi": [2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14], "asset": [2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13], "server": [2, 3, 4, 5, 6, 7, 8, 11, 12], "recurs": [2, 5, 7, 8, 10], "clone": [2, 5, 7, 8, 10, 11, 12], "web": [2, 3, 5, 6, 7, 8, 10, 11], "checkout": [2, 5, 8, 10, 11], "arm": [2, 5, 8, 10], "config": [2, 3, 4, 5, 7, 8, 10, 11], "when": [2, 5, 7, 8, 11], "touch": [2, 5, 8, 10], "chmod": [2, 5, 7, 8, 10, 13], "mv": [2, 5, 8, 10], "byport": [2, 5, 8], "creat": [2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13], "systemd": [2, 5, 7, 12], "cat": [2, 5, 9, 11, 13], "eof": [2, 5], "unit": [2, 5, 9, 12, 13, 14], "descript": [2, 5, 12], "want": [2, 5, 7, 11, 12], "network": [2, 5, 7, 12], "target": [2, 5, 6, 12], "workingdirectori": [2, 5, 12], "execstart": [2, 5, 12], "alwai": [2, 5, 7, 12], "wantedbi": [2, 5, 12], "multi": [2, 5, 12], "systemctl": [2, 3, 5, 7, 9, 10, 11, 12], "daemon": [2, 5, 12], "start": [2, 3, 5, 7, 10, 12], "statu": [2, 5, 9, 10, 11], "vim": [2, 3, 5, 7, 10], "site": [2, 5, 11, 12], "conf": [2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14], "f": [2, 5, 7, 10, 12], "discard": [2, 5], "noatim": [2, 5], "disk": [2, 5], "id": [2, 5], "scsi": [2, 5], "0do_volume_volum": [2, 5], "nyc1": [2, 5], "mnt": [2, 5], "todo": 2, "ef": 2, "la": [2, 5, 8], "openssl": [2, 5, 8, 9], "4096": [2, 5, 8, 10], "2048": [2, 5, 8, 9], "1024": [2, 5, 8, 13], "dsaparam": [2, 5, 8], "edit": [2, 7, 11], "test_attachment_kei": [2, 5], "kufishvouch": [2, 4, 5], "kufishtissu": [2, 4, 5], "aasets1": [2, 5], "suppli": [2, 5], "an": [2, 3, 4, 5, 6, 7, 8, 9], "mean": [2, 5], "privileg": [2, 3, 5, 13, 14], "underli": [2, 5], "also": [2, 5, 7, 11], "caus": [2, 5], "subsequ": [2, 5], "go": [2, 5, 7, 10], "renew_before_expiri": [2, 5], "dai": [2, 5], "chain": [2, 5, 9], "9": [2, 5, 10, 13], "archive_dir": [2, 5], "archiv": [2, 5, 7, 9, 10], "process": [2, 5, 11], "renewalparam": [2, 5], "webroot": [2, 3, 4, 5, 7], "account": [2, 5, 7], "a563615cc912ed3d7a3edfede09d6760": [2, 5], "post_hook": [2, 5], "acm": [2, 5], "v02": [2, 5], "api": [2, 5], "webroot_map": [2, 5], "specofycloud": [2, 5], "begin": [2, 5, 10], "dh": [2, 5], "paramet": [2, 5, 10, 11], "miibcakcaqealcfksiufylwx47jxqbnt0wsvd6ifznsmcti8f7t": [2, 5], "zaqqnr84iyim": [2, 5], "pntt9e6srvkkjg2u1ngscnqj5larxvrda6zl66t8wmkffrgfnw7rycq3vpg6bpg": [2, 5], "dj3": [2, 5], "htwydnombecrdymz1ddfx": [2, 5], "3owblttzrbjpvn": [2, 5], "tegtan3dexp": [2, 5], "qke9e2c1aux": [2, 5], "mf": [2, 5], "07vwpz7giemangame3fhdkyrenhtpfg1edkypuuhemr": [2, 5], "pjmwq9lqbc12lyxop": [2, 5], "dafwajurqweqrqp5feqdomdh522rwud2": [2, 5], "fpextukqhi8guumjk652aelocn1ufhi": [2, 5], "kbbv6tji7ws5f3hvanxgolmshq": [2, 5], "cywocwibag": [2, 5], "swiss": [2, 7, 10], "xd5dakesktkxceb": 2, "exampl": [3, 4, 6, 11, 13], "databas": [3, 6, 7, 11, 13], "look": [3, 9], "sql": [3, 7, 10, 11], "issu": 3, "local": [3, 7, 9, 10, 11], "mysql": [3, 6, 7, 11, 12, 13, 14], "umast": 3, "e": [3, 7, 10, 11, 13, 14], "mai": 3, "grant": [3, 11, 13, 14], "doesn": 3, "flush": [3, 13], "dn": [3, 4, 7], "registtr": 3, "login": [3, 6, 7, 13], "dreamhost": 3, "select": [3, 7, 13], "websit": 3, "manag": [3, 7, 13, 14], "cname": 3, "record": [3, 4], "style": 3, "unsm": 3, "vp": 3, "na": [3, 6, 10], "wait": 3, "least": 3, "minut": 3, "circul": 3, "ku": 3, "bitech": 3, "edu": 3, "spcloudserv": [3, 7, 10], "su": [3, 10, 13], "c": [3, 6, 7, 9, 10, 11, 13], "docker": [3, 4, 10, 11, 12, 13, 14], "compos": [3, 4, 7, 10, 14], "up": [3, 4, 10, 13], "fine": [3, 10], "here": [3, 4, 6, 7, 9], "exec": [3, 7, 13], "specifycloud_nginx_1": [3, 7], "check": [3, 7, 11, 13], "certonli": [3, 4, 7], "remov": [3, 7], "mayb": [3, 7, 10, 12], "instead": [3, 7], "note": [3, 10, 11, 14], "after": [3, 7, 9, 11], "automat": 3, "hook": 3, "post": 3, "sh": [3, 10, 11, 12, 13], "crontab": 3, "cronjob": 3, "backup": [3, 6], "ssh": [3, 6, 7, 10, 13], "biprdsp6ap": 3, "cc": 3, "spcloudbackup": 3, "backup_specify_cloud": 3, "new": [3, 4, 6, 13], "updown": [3, 13], "context": [3, 11], "system_info": 3, "alia": [3, 10, 11, 12], "droplet": 3, "handl": [3, 7, 10], "mariadb": [3, 6, 7, 10, 12], "fail": 3, "mysqld": 3, "tc": 3, "heurist": 3, "recov": 3, "rollback": 3, "fix": 3, "instanc": [3, 10, 11, 14], "authorized_kei": [3, 13], "sshd": [3, 7, 10], "latest": [4, 7, 11], "connection_fix": [4, 13], "spun": 4, "follow": [4, 7, 11, 13], "got": 4, "your": [4, 6, 7, 9], "address": [4, 6, 7, 9], "yml": [4, 14], "7": [4, 6, 7, 10, 11, 12, 13, 14], "alpin": 4, "outsid": 4, "contain": [4, 10, 14], "replac": [4, 6, 7], "which": [4, 11], "would": [4, 7], "publicli": 4, "avail": [4, 11, 12, 13], "qwertyasdfghzxcvbnlmnop": 4, "Then": [4, 7, 10], "connect": [4, 6, 7, 10], "let": 4, "me": 4, "migrat": 6, "digit": [6, 10], "ocean": [6, 10], "amazon": [6, 7, 10], "ha": [6, 11], "our": 6, "provid": 6, "increas": [6, 13], "reliabl": 6, "deprec": 6, "v10": 6, "In": [6, 7, 11], "futur": [6, 7], "hope": [6, 7], "postgresql": 6, "further": [6, 11], "improv": 6, "daili": 6, "month": [6, 10], "via": 6, "two": [6, 7], "thing": [6, 7], "linux": [6, 7, 10, 13], "institut": 6, "underscor": 6, "_": 6, "dash": 6, "thei": [6, 13], "been": [6, 7, 11], "wiki": 6, "remot": 6, "172": 6, "31": [6, 10], "96": [6, 10], "36": [6, 10], "16": [6, 10], "73": [6, 11], "ca": [6, 7, 10], "35": 6, "249": 6, "On": [6, 7, 10], "mac": 6, "n": 6, "l3307": 6, "institution_id": 6, "db": [6, 7, 10, 14], "window": 6, "putti": 6, "program": 6, "ex": [6, 9, 10], "your_us": 6, "private_key_": 6, "ppk": 6, "3307": 6, "log": [6, 7, 10, 11, 13], "previou": 6, "soon": [6, 7], "each": [6, 10], "detail": 7, "instruct": [7, 8], "temporari": 7, "solut": 7, "deploy": 7, "ec": 7, "come": 7, "composit": [7, 10], "privat": [7, 10], "kind": 7, "easiest": 7, "even": 7, "one": [7, 10, 11, 12], "my": 7, "like": 7, "could": 7, "wa": 7, "design": 7, "nativ": 7, "join": 7, "team": 7, "pick": 7, "project": 7, "modern": 7, "cdk": 7, "script": [7, 11], "simpl": [7, 11], "scalabl": 7, "machin": 7, "ami": [7, 10], "choos": 7, "22": [7, 10], "lt": 7, "64": 7, "bit": 7, "x86": 7, "architectur": [7, 10, 13], "t3": [7, 10], "small": [7, 10], "medium": [7, 10], "better": 7, "traffic": 7, "pair": 7, "launch": [7, 14], "bucket": 7, "either": 7, "directli": [7, 11], "If": [7, 11], "insid": 7, "doc": 7, "en": 7, "keep": 7, "person": 7, "read": [7, 13], "otherwis": 7, "initi": [7, 10], "There": 7, "archireef": 7, "sp7": [7, 10], "edg": [7, 10], "sp6": [7, 10], "decommiss": [7, 10], "sp6version": [7, 10], "schema": 7, "input": 7, "tag": [7, 10, 11], "first": 7, "onli": [7, 10, 11, 13], "env": [7, 10, 11, 12, 14], "your_database_host": 7, "your_master_password": 7, "temp": 7, "your_asset_server_kei": 7, "132": [7, 10], "218": [7, 10], "32": [7, 10], "onc": [7, 13], "webpag": 7, "click": 7, "cloudshel": 7, "don": [7, 13], "happen": 7, "press": 7, "enter": [7, 13], "twice": 7, "back": 7, "shell": 7, "avoid": [7, 10], "dure": [7, 10], "sed": [7, 10, 11, 12], "nrconf": [7, 10], "kernelhint": [7, 10], "needrestart": [7, 10], "gh": [7, 10], "curl": [7, 10, 11, 12], "j2cli": [7, 10, 11], "your_aws_access_key_id": 7, "your_aws_access_key_secret": 7, "fssl": [7, 10, 11], "gpg": [7, 10], "dearmor": [7, 10], "keyr": [7, 10], "deb": [7, 10, 11], "arch": [7, 10, 13], "dpkg": [7, 10], "sign": [7, 10], "lsb_releas": [7, 10], "stabl": [7, 10], "tee": [7, 10, 13], "null": [7, 10], "polici": [7, 10], "ce": [7, 10], "correctli": 7, "cli": [7, 10], "plugin": [7, 10], "releas": [7, 10], "v2": [7, 10, 11], "17": [7, 10], "unam": [7, 10], "snapshot": [7, 10], "did": 7, "engin": 7, "experi": 7, "aurora": [7, 11], "dynam": 7, "scale": 7, "templat": 7, "suggest": 7, "product": [7, 12], "larg": [7, 10], "might": [7, 10, 14], "feel": 7, "m5": [7, 10], "someth": 7, "similar": 7, "decid": 7, "storag": [7, 10, 13], "size": [7, 13], "accommod": 7, "purpos": [7, 10], "ssd": [7, 10], "suffic": 7, "iop": 7, "comput": [7, 11, 13], "tab": 7, "mark": 7, "endpoint": 7, "seed": [7, 10, 11], "c9qlnkmf2lfl": [7, 10], "central": [7, 10], "amazonaw": [7, 10, 11, 13], "remain": 7, "own": 7, "navig": 7, "page": [7, 15], "public": [7, 13], "ipv4": 7, "open": 7, "browser": 7, "haven": 7, "yet": 7, "section": [7, 9], "django": [7, 10, 11, 12, 13, 14], "webpack": [7, 11, 12], "report": [7, 10, 12, 13], "runner": [7, 10, 12], "being": 7, "hold": 7, "front": [7, 14], "independ": 7, "long": 7, "task": [7, 10, 13], "act": 7, "job": 7, "queue": 7, "broker": 7, "between": [7, 14], "possibl": [7, 11], "sq": 7, "both": 7, "serv": [7, 11], "conaint": 7, "move": 7, "startup": 7, "webserv": [7, 11, 12], "possibli": 7, "cloudfront": 7, "java": [7, 10], "tool": 7, "who": 7, "rout": 7, "53": 7, "probabl": 7, "elast": 7, "apach": [7, 10, 11], "your_ec2_domain_nam": 7, "extend": 7, "serveraliveinterv": [7, 10], "20": [7, 10, 11, 12], "tcpkeepal": [7, 10], "sshd_config": [7, 10], "clientaliveinterv": [7, 10], "1200": [7, 10], "clientalivecountmax": [7, 10], "setup": [8, 11], "build": [8, 10, 11, 12, 13], "form": 9, "kuit": 9, "nav_to": 9, "uri": 9, "2fcom": 9, "glideapp": 9, "servicecatalog_cat_item_view": 9, "3fv": 9, "3d1": 9, "26sysparm_id": 9, "3d78fee42fdb2a8850162673e1ba96195b": 9, "26sysparm_link_par": 9, "3d322911f41bec6490cf2d337e034bcb23": 9, "26sysparm_catalog": 9, "3de0d08b13c3330100c8b837659bba8fb4": 9, "26sysparm_catalog_view": 9, "3dcatalog_default": 9, "26sysparm_view": 9, "csr": 9, "req": 9, "newkei": 9, "rsa": [9, 10], "node": [9, 10, 11, 12], "keyout": 9, "countri": 9, "letter": 9, "code": [9, 14], "xx": 9, "provinc": 9, "full": [9, 11], "kansa": 9, "eg": 9, "citi": 9, "lawrenc": 9, "organ": 9, "compani": 9, "ltd": 9, "univers": 9, "organiz": 9, "hostnam": [9, 11, 12], "biimag": 9, "biodivers": 9, "email": 9, "alec": 9, "white": 9, "verifi": 9, "ou": 9, "cn": 9, "emailaddress": 9, "receiv": 9, "biimages_biodiversity_ku_edu": 9, "cer": 9, "biimages_biodiversity_ku_edu_cert": 9, "crt": 9, "biimages_biodiversity_ku_edu_interm": 9, "p7b": 9, "concaten": 9, "proper": 9, "number": 9, "increment": 9, "40": [9, 10], "privkey40": 9, "cert40": 9, "chain40": 9, "fullchain40": 9, "symbol": 9, "where": 9, "ln": [9, 10, 11, 12, 13], "sf": 9, "hash": 9, "x509": 9, "noout": 9, "modulu": 9, "md5": 9, "webport": 9, "keystor": 9, "collections_biodiversity_ku_edu_cert": 9, "collections_biodiversity_ku_edu": 9, "collections_biodiversity_ku_edu_interm": 9, "webportal_serv": 9, "bi": 9, "sp7access": 9, "hvm": 10, "bionic": 10, "arm64": [10, 11, 13], "20220131": 10, "0770bf1d6ae61c858": 10, "containerd": 10, "see": [10, 11], "credenti": 10, "bottom": 10, "datbas": 10, "aliv": 10, "nclientalivecountmax": 10, "pull": 10, "freshfish": 10, "anonymous_us": 10, "specify6802": 10, "02": 10, "cqvncffkwz9t": [10, 11], "rd": [10, 11], "mastermast": [10, 11], "bogu": [10, 12, 14], "usernam": [10, 13], "format": [10, 13], "data": 10, "export": [10, 11, 12, 13], "path": 10, "keygen": 10, "ed25519": 10, "acwhite211": 10, "gmail": 10, "openjdk": [10, 11, 12], "jdk": [10, 11, 12], "maven": [10, 11, 12], "ant": [10, 11, 12], "ufw": [10, 11], "altern": 10, "jvm": 10, "jre": 10, "compil": [10, 11, 12], "nonmac": 10, "price": 10, "r5": 10, "vcpu": 10, "gb": 10, "ram": 10, "24": 10, "per": 10, "hour": 10, "173": 10, "4vcpu": 10, "171": 10, "123": 10, "2vcpu": 10, "068": 10, "49": 10, "136": 10, "97": 10, "92": 10, "xlarg": 10, "272": 10, "195": 10, "t4g": 10, "065": 10, "46": 10, "129": 10, "88": 10, "acu": 10, "12": 10, "86": 10, "v1": 10, "06": 10, "43": 10, "29": 10, "nano": 10, "0042": 10, "micro": 10, "0084": 10, "05": 10, "0168": 10, "0336": 10, "19": [10, 14], "0672": 10, "48": 10, "38": [10, 12], "1344": 10, "77": 10, "m7g": 10, "1vcpu": 10, "0408": 10, "0816": 10, "75": 10, "2232": 10, "160": 10, "70": 10, "fargat": 10, "demand": 10, "03238": 10, "00356": 10, "spot": 10, "01279585": 10, "00140508": 10, "ephemer": 10, "000111": 10, "23": 10, "56": 10, "25": 10, "82": 10, "28": 10, "21": 10, "save": 10, "plan": 10, "59": 10, "cpu": 10, "26": 10, "52": [10, 13], "89": 10, "179": 10, "78": 10, "graviton": 10, "memori": [10, 13], "cheep": 10, "expens": 10, "45": 10, "90": 10, "usag": 10, "nomin": 10, "spike": 10, "definit": 10, "more": 10, "than": 10, "85": 10, "72": 10, "enough": 10, "most": [10, 11], "few": 10, "over": 10, "combin": [10, 11], "comparison": 10, "reserv": 10, "unzip": [10, 11, 12], "gbif": 10, "0146304": 10, "230224095556074": 10, "zip": 10, "gbif_url": 10, "csv": 10, "gbif_test": 10, "gbif_extract": 10, "id_ed25519": 10, "pub": 10, "aaaac3nzac1lzdi1nte5aaaaikhq3lvhz4u8j0derpm37wguplglgqtim77m68m": 10, "xnwl": 10, "openssh": 10, "b3blbnnzac1rzxktdjeaaaaabg5vbmuaaaaebm9uzqaaaaaaaaabaaaamwaaaatzc2gtzw": 10, "qyntuxoqaaacch6t5vywefpi9a3q6zt": 10, "8ifdyxi4elypu": 10, "zovjvlzviwaaajj4e1io": 10, "bny": 10, "jgaaaatzc2gtzwqyntuxoqaaacch6t5vywefpi9a3q6zt": 10, "zovjvlzviw": 10, "aaaedi0ktenazeyommyaqobd8apyqjcl3yu7txmmmrit8bjahq3lvhz4u8j0derpm37wgu": 10, "plglgqtim77m68m": 10, "xnwlaaaafgfjd2hpdguymtfaz21hawwuy29taq": 10, "0ubuntu0": 10, "danc": [10, 11], "taco": [10, 11], "magic": [10, 11], "rainbow": [10, 11], "vibe": [10, 11], "gcc": 10, "openldap": 10, "devel": 10, "nodej": [10, 11, 12], "npm": [10, 11, 12], "apache2": [10, 11], "libapache2": [10, 11], "mod": [10, 11], "wsgi": [10, 11, 12], "py3": [10, 11], "sl": 10, "nodesourc": [10, 11], "setup_18": [10, 11], "nodesource_setup": 10, "raw": [10, 12], "githubusercont": [10, 12], "nvm": [10, 12], "v0": [10, 12], "39": 10, "bashrc": [10, 11, 12], "v18": 10, "v": [10, 11, 12, 13, 14], "specifysoftwar": [10, 11, 12], "6803": [10, 11, 12], "specify_unix_64": [10, 11, 12], "pwd": [10, 11, 13], "wheel": [10, 11], "runserv": [10, 11], "arm54": 10, "cento": 10, "yum": 10, "mariadb105": 10, "aarch64": 10, "nodj": 10, "headless": 10, "corretto": 10, "python38": 10, "python38u": 10, "redis6": 10, "dnf": 10, "mosti": 11, "lite": 11, "entrypoint": [11, 12], "libldap2": [11, 12], "libmariadbcli": [11, 12], "libsasl2": [11, 12], "core": [11, 14], "wb_upload_log": 11, "specify_depositori": 11, "domain_nam": [11, 12], "ec2": [11, 13], "87": 11, "116": 11, "210": 11, "web_attachment_url": 11, "web_attachment_kei": 11, "web_attachment_collect": 11, "specifydb": 11, "specify_set": 11, "masterus": 11, "masterpassword": 11, "media": 11, "media_root": 11, "__init__": 11, "media_url": 11, "servernam": 11, "specifyweb_apach": 11, "000": 11, "invok": 11, "rc": 11, "your_domain": 11, "varibal": 11, "update_set": 11, "setting_kei": 11, "setting_valu": 11, "file_path": 11, "eot": 11, "54": 11, "162": 11, "114": 11, "41": 11, "128m": 11, "fileget": 11, "fileupload": 11, "filedelet": 11, "getmetadata": 11, "testkei": 11, "400m": 11, "client_body_buffer_s": 11, "client_body_timeout": 11, "120": 11, "uwsgi": 11, "uwsgi_pass": 11, "unix": 11, "app": 11, "socket": 11, "uwsgi_param": 11, "uwsgi_read_timeout": 11, "32m": 11, "slash": 11, "virtualhost": 11, "followsymlink": 11, "index": [11, 15], "multiview": 11, "web_upload_log": 11, "group": 11, "librari": 11, "establish": 11, "leav": 11, "wsgidaemonprocess": 11, "wsgiprocessgroup": 11, "wsgiscriptalia": 11, "errorlog": 11, "error": 11, "notic": 11, "crit": 11, "alert": 11, "emerg": 11, "loglevel": 11, "customlog": 11, "identifi": [11, 13], "itself": 11, "redirect": 11, "virtual": 11, "appear": 11, "match": 11, "decis": 11, "last": 11, "resort": 11, "regardless": 11, "howev": 11, "ani": 11, "explicitli": 11, "serveradmin": 11, "webmast": 11, "documentroot": 11, "trace8": 11, "trace1": 11, "particular": 11, "apache_log_dir": 11, "global": 11, "level": 11, "cgi": 11, "a2disconf": 11, "104175": 11, "js_src": [11, 12], "spcloud": 11, "pod": 12, "exclud": 12, "variabl": 12, "sp6_version": 12, "sp6_version_str": 12, "tr": 12, "nvm_dir": 12, "load": 12, "ci": 12, "dist": 12, "npx": 12, "mode": 12, "font": [12, 13], "jar": [12, 13], "tmp": 12, "pom": 12, "mvn": 12, "war": 12, "explod": 12, "src": 12, "3000": [12, 14], "8888": [12, 14], "ptvsd": [12, 14], "client_nam": 12, "fi": 12, "worker_count": 12, "conditionpathexist": 12, "jetti": 12, "ireportrunn": 12, "sftp": 13, "alec_specify_ssh_kei": 13, "206": 13, "67": 13, "put": 13, "alecwhit": 13, "exit": 13, "background": 13, "foreground": 13, "df": 13, "h": 13, "du": 13, "rsync": 13, "avz": 13, "cluster": 13, "img": 13, "inspect": 13, "jq": 13, "repotag": 13, "done": 13, "notif": 13, "notificationstest": 13, "stash": 13, "push": 13, "file1": 13, "file2": 13, "appli": 13, "addus": 13, "geco": 13, "multipl": 13, "docker_cli_experiment": 13, "buildx": 13, "mybuild": 13, "platform": 13, "amd64": 13, "think": 13, "ask": 13, "restrict": 13, "rbash": 13, "usermod": 13, "symlink": 13, "700": 13, "ssh_kei": 13, "dbuser": 13, "BY": 13, "dbuser_password": 13, "insert": 13, "ON": [13, 14], "your_database_nam": 13, "TO": [13, 14], "tail": 13, "1000": 13, "grep": 13, "awk": 13, "split": 13, "swap": 13, "falloc": 13, "4g": 13, "swapfil": 13, "dd": 13, "zero": 13, "count": 13, "4096k": 13, "mkswap": 13, "swapon": 13, "sw": 13, "fstab": 13, "perman": 13, "show": 13, "free": 13, "swappi": 13, "defaul": 13, "100": 13, "veri": 13, "argess": 13, "absolutlei": 13, "necessari": 13, "proc": 13, "sy": 13, "vm": 13, "sysctl": 13, "chane": 13, "swapoff": 13, "8g": 13, "resiz": 13, "permiss": 14, "test_cuic": 14, "test_": 14, "devpress": 14, "csdn": 14, "net": 14, "62fe07607e66823466192fa3": 14, "pytest": 14, "hypothesi": 14, "jsonschema": 14, "ini": 14, "django_settings_modul": 14, "python_fil": 14, "testpars": 14, "addopt": 14, "ignor": 14, "selenium_test": 14, "vscode": 14, "pythonpath": 14, "pytestarg": 14, "vv": 14, "pytesten": 14, "nosetestsen": 14, "unittesten": 14, "setdefault": 14, "run_main": 14, "werkzeug_run_main": 14, "debugpi": 14, "dockerfil": 14, "debugg": 14, "runtim": 14, "pathmap": 14, "localroot": 14, "workspacefold": 14, "remoteroot": 14, "unittestarg": 14, "lint": 14, "mypyen": 14, "search": 15}, "objects": {}, "objtypes": {}, "objnames": {}, "titleterms": {"specifi": [0, 10, 15], "7": 0, "docker": [0, 2, 5, 7], "config": 0, "exampl": 0, "default": 0, "env": 0, "compos": 0, "yml": 0, "nginx": 0, "conf": 0, "asset": 0, "server": [0, 9, 10], "set": 0, "py": 0, "file": 0, "possibl": 0, "dockerfil": 0, "ec": 0, "build": [0, 2, 5], "unfinish": 0, "web": [0, 9], "ec2": [2, 5, 7, 10], "non": [2, 5], "shell": 2, "script": 2, "spin": 7, "up": 7, "instanc": 7, "s3": 7, "setup": [7, 10], "configur": 7, "specify7": 7, "rd": 7, "upload": 7, "data": 7, "deploi": 7, "contain": 7, "depend": 7, "conclud": 7, "note": 7, "new": 9, "certif": 9, "biprdsp7wbdb": 9, "cc": 9, "ku": 9, "edu": 9, "portal": 9, "aurora": 10, "mysql": 10, "databas": 10, "info": 10, "misc": 10, "network": 10, "extract": 10, "imag": 12, "welcom": 15, "develop": 15, "document": 15, "indic": 15, "tabl": 15}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1, "sphinx": 60}, "alltitles": {"Specify 7 Docker Config Example": [[0, "specify-7-docker-config-example"]], "Example defaults.env": [[0, "example-defaults-env"]], "Example docker-compose.yml": [[0, "example-docker-compose-yml"]], "Example nginx.conf": [[0, "example-nginx-conf"]], "Asset Server": [[0, "asset-server"]], "Example settings.py file": [[0, "example-settings-py-file"]], "Possible Dockerfile for ECS Build (Unfinished)": [[0, "possible-dockerfile-for-ecs-build-unfinished"]], "Asset nginx web-server config": [[0, "asset-nginx-web-server-config"]], "EC2 Non-Dockerized Build": [[2, "ec2-non-dockerized-build"], [5, "ec2-non-dockerized-build"]], "EC2 Non-docker build shell script": [[2, "ec2-non-docker-build-shell-script"]], "Docker Build": [[2, "docker-build"]], "Spin Up EC2 Instance": [[7, "spin-up-ec2-instance"]], "S3 Setup": [[7, "s3-setup"]], "Configure Specify7": [[7, "configure-specify7"]], "Spin Up RDS Instance": [[7, "spin-up-rds-instance"]], "Upload Data": [[7, "upload-data"]], "Deploy Specify7": [[7, "deploy-specify7"]], "Docker Container Dependencies": [[7, "docker-container-dependencies"]], "Concluding Notes": [[7, "concluding-notes"]], "New certificate on biprdsp7wbdb.cc.ku.edu server": [[9, "new-certificate-on-biprdsp7wbdb-cc-ku-edu-server"]], "web-portal certificate": [[9, "web-portal-certificate"]], "Setup Aurora MySQL Database": [[10, "setup-aurora-mysql-database"]], "Setup EC2 Server": [[10, "setup-ec2-server"]], "Info Misc.": [[10, "info-misc"]], "Specify Network Extract": [[10, "specify-network-extract"]], "Image": [[12, "image"]], "Welcome to Specify Developer documentation!": [[15, "welcome-to-specify-developer-documentation"]], "Indices and tables": [[15, "indices-and-tables"]]}, "indexentries": {}}) \ No newline at end of file