diff --git a/app/core/views/__init__.py b/app/core/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/core/views/__pycache__/__init__.cpython-312.pyc b/app/core/views/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 000000000..8b2e8ec9b Binary files /dev/null and b/app/core/views/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/core/views/__pycache__/main.cpython-312.pyc b/app/core/views/__pycache__/main.cpython-312.pyc new file mode 100644 index 000000000..e77928e7e Binary files /dev/null and b/app/core/views/__pycache__/main.cpython-312.pyc differ diff --git a/app/core/views/main.py b/app/core/views/main.py new file mode 100644 index 000000000..0feb9530b --- /dev/null +++ b/app/core/views/main.py @@ -0,0 +1,4 @@ +from django.http import HttpResponse + +def home(request): + return HttpResponse('this is Materia') diff --git a/app/logfile.log b/app/logfile.log new file mode 100644 index 000000000..b48482ba3 --- /dev/null +++ b/app/logfile.log @@ -0,0 +1,7 @@ +Not Found: / +Not Found: /home +Not Found: / +Not Found: / +Not Found: /home/ +Not Found: / +Not Found: /home diff --git a/app/manage.py b/app/manage.py new file mode 100755 index 000000000..d63ab9af8 --- /dev/null +++ b/app/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'materia.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/app/materia.wsgi b/app/materia.wsgi new file mode 100644 index 000000000..81d13eafe --- /dev/null +++ b/app/materia.wsgi @@ -0,0 +1,14 @@ +import os,sys + +sys.stdout = sys.stderr + +sys.path.append('/usr/local/lib/python3.12') +sys.path.append('/usr/local/lib/python3.12/site-packages') + +os.environ['DJANGO_SETTINGS_MODULE'] = 'materia.settings' + +root_path = os.path.abspath(os.path.split(__file__)[0]) +sys.path.insert(0, root_path) + +from django.core.wsgi import get_wsgi_application +application = get_wsgi_application() diff --git a/app/materia/__init__.py b/app/materia/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/materia/__pycache__/__init__.cpython-312.pyc b/app/materia/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 000000000..3bd6cdc90 Binary files /dev/null and b/app/materia/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/materia/__pycache__/settings.cpython-312.pyc b/app/materia/__pycache__/settings.cpython-312.pyc new file mode 100644 index 000000000..f742820d3 Binary files /dev/null and b/app/materia/__pycache__/settings.cpython-312.pyc differ diff --git a/app/materia/__pycache__/urls.cpython-312.pyc b/app/materia/__pycache__/urls.cpython-312.pyc new file mode 100644 index 000000000..fc2801dcc Binary files /dev/null and b/app/materia/__pycache__/urls.cpython-312.pyc differ diff --git a/app/materia/asgi.py b/app/materia/asgi.py new file mode 100644 index 000000000..49f9af93a --- /dev/null +++ b/app/materia/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for materia project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'materia.settings') + +application = get_asgi_application() diff --git a/app/materia/settings.py b/app/materia/settings.py new file mode 100644 index 000000000..2afb68361 --- /dev/null +++ b/app/materia/settings.py @@ -0,0 +1,150 @@ +""" +Django settings for materia project. + +Generated by 'django-admin startproject' using Django 5.0.1. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" + +import os +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'materia-local-dev-secret-key' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + # apps + 'core', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'materia.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'materia.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': os.environ.get('MYSQL_DATABASE'), + 'USER': os.environ.get('MYSQL_USER'), + 'PASSWORD': os.environ.get('MYSQL_PASSWORD'), + 'HOST': os.environ.get('MYSQL_HOST'), + 'PORT': os.environ.get('MYSQL_PORT') + }, +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'US/Eastern' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'file': { + 'level': 'INFO', + 'class': 'logging.FileHandler', + 'filename': './logfile.log' + } + }, + 'loggers': { + 'django': { + 'handlers': ['file'], + 'level': 'INFO', + 'propagate': True + } + } +} diff --git a/app/materia/urls.py b/app/materia/urls.py new file mode 100644 index 000000000..fd82ba534 --- /dev/null +++ b/app/materia/urls.py @@ -0,0 +1,24 @@ +""" +URL configuration for materia project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" + +from django.urls import include, path + +from core.views import main as core_views + +urlpatterns = [ + path('', core_views.home, name='placeholder home page') +] diff --git a/app/materia/wsgi.py b/app/materia/wsgi.py new file mode 100644 index 000000000..26e5fc41a --- /dev/null +++ b/app/materia/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for materia project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'materia.settings') + +application = get_wsgi_application() diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 000000000..c8763e96f --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,7 @@ +asgiref==3.7.2 +Django==5.0.1 +mysqlclient==2.2.4 +setuptools==69.0.3 +sqlparse==0.4.4 +uWSGI==2.0.23 +wheel==0.42.0 diff --git a/docker/.env b/docker/.env deleted file mode 100644 index 2392fe5c5..000000000 --- a/docker/.env +++ /dev/null @@ -1,12 +0,0 @@ -# Database settings -MYSQL_ROOT_PASSWORD=drRoots -MYSQL_USER=materia -MYSQL_PASSWORD=odin -MYSQL_DATABASE=materia - -# passwords/hashes/eys -DEV_ONLY_USER_PASSWORD=kogneato -# see readme for how to create these -DEV_ONLY_AUTH_SALT=111b776e5f862058e2e075b640b3de5fb601d0ac57639c733a2d10edffd2a3d5 -DEV_ONLY_AUTH_SIMPLEAUTH_SALT=33e0d379060e3877d634632853c10a70dff9710b751e5af00a0f637884df417e -DEV_ONLY_SECRET_CIPHER_KEY=e0beaea1704555ae3c75650703bb106fac24b8967c77a667124fbe745c3346ed diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index f9263a42d..000000000 --- a/docker/README.md +++ /dev/null @@ -1,202 +0,0 @@ -# Materia Docker Container - -We publish production ready docker containers for each release in the [Materia GitHub Docker Repository](https://github.com/orgs/ucfopen/packages/container/package/materia). These images are built and published automatically using GitHub Actions on every tagged release. - -``` -docker pull ghcr.io/ucfopen/materia:webserver-v8.0.0 -docker pull ghcr.io/ucfopen/materia:app-v8.0.0 -``` - -## Container Architecture - - 1. [webserver (Nginx)](https://www.nginx.com/) as a web server (proxies to phpfpm for app and serves static files directly) - 3. [app (PHP-FPM)](https://php-fpm.org/) manages the PHP processes for the application - 4. [mysql](https://www.mysql.com/) for storing relational application data - 5. [memcached](https://memcached.org/) for caching data and sessions - 6. [fakeS3](https://github.com/jubos/fake-s3) mocks AWS S3 behavior for asset uploading - -## Setup - -Clone Materia, cd into `/docker` and execute `./run_first.sh`. - -Please take note of the user accounts that are created for you in the install process. The user names and a random password will be echoed to the terminal after Composer installs the required PHP libraries. If you're on linux, setup should create a user with your current host machine's user name automatically. - -### Common Dev Commands - -* Run the containers after ./run_first.sh has finished - ``` - docker-compose up - ``` -* Run the servers in background - ``` - docker-compose up -d - ``` -* Tail logs from background process - ``` - docker-compose logs -f app - ``` -* Run commands on the app container (like php, composer, or fuelphp oil commands) - ``` - ./run.sh php -i - ./run.sh php oil r admin:help - ./run.sh composer run --list - ``` -* Stop containers (db data is retained) - ``` - docker-compose stop - ``` -* Stop and destroy the containers (deletes database data!, first_run.sh required after) - ``` - docker-compose down - ``` -* Compile the javascript and sass - ``` - ./run_build_assets.sh - ``` -* Install composer libraries - ``` - ./run.sh composer install - ``` -* Install all Widgets in fuel/app/tmp/widget_packages/*.wigt - ``` - ./run_widgets_install.sh '*.wigt' - ``` -* Run Tests for development - ``` - ./run_tests.sh - ``` -* Run Tests for as like the CI server - ``` - ./run_tests_ci.sh - ``` -* Run Tests with code coverage - ``` - ./run_tests_coverage.sh - ``` -* Create a user based on your docker host machine's current user - ``` - $ iturgeon@ucf: ./run_create_me.sh - User Created: iturgeon password: kogneato - iturgeon now in role: super_user - iturgeon now in role: basic_author - ``` -* Create the [default users outlined in the config](https://github.com/ucfopen/Materia/blob/master/fuel/app/config/materia.php#L56-L78) - ``` - ./run_create_default_users.sh - ``` -* Build a deployable materia package (zip w/ compiled assets, and dependencies; see [assets on our releases](https://github.com/ucfopen/Materia/releases)) - ``` - ./run_build_github_release_package.sh - ``` -* Installing widgets: Copy the widget file you want to install into **app/fuel/app/tmp/widget\_packages/** and then run **install_widget.sh** passing the name of the widget file to install. Example: - - ``` - cp my_widget.wigt ~/my_projects/materia_docker/app/fuel/app/tmp - cd ~/my_projects/materia_docker - ./run_widgets_install.sh my_widget.wigt - ``` -* Installing test widgets? - ``` - traverse to app/fuel/packages/materia/test/widget_source/ - Update test widgets as desired. - traverse into the widget folder. - read build instructions in that widget's README.md - Note: these widget are necessary when running run_tests.sh - ``` -### Default User Accounts - -If you wish to log into Materia, there are [3 default accounts created for you based on the config](https://github.com/ucfopen/Materia/blob/master/fuel/app/config/materia.php#L56-L78). If you're on OSX or Linux, you'll also get a user based on the username you use on the host machine. - -### Updating a container - -If you're wanting to update a php or mysql version, this can be done locally for testing before updating the global image. - -1. finish your edits. -2. Execute `docker-compose build` to rebuild any images. -4. Removing any existing running container using that image: `docker-compose stop app` and `docker-compose rm app` -5. Start the desired container: `docker-compose up app` - -## Production Ready Docker Compose - -If you plan on deploying a production server using these docker images, we suggest using docker-compose. You will probably want to have an external database service (like AWS's RDS), and you'll need a place to keep backups of any uploaded files. - -### Dynamic Files to Backup - -* MySQL Database Contents -* Uploaded Media -* Installed Widget Engine Files - -### Sample Docker Compose - -```yaml -version: '3.5' - -services: - webserver: - image: ghcr.io/ucfopen/materia:webserver-v8.0.0 - ports: - # 443 would be terminated at the load balancer - # Some customization required to terminate 443 here (see dev nginx config) - - "80:80" - networks: - - frontend - volumes: - # mount css/js assets from the app image - - compiled_assets:/var/www/html/public - # mount installed widget engines on the host - - ./widget/:/var/www/html/public/widget - depends_on: - - app - - app: - image: ghcr.io/ucfopen/materia:app-v8.0.0 - env_file: - # View Materia Readme for ENV vars - - .env - networks: - - frontend - - backend - volumes: - # share css/js assets - - compiled_assets:/var/www/html/public - # mount installed widget engines on the host - - ./widget/:/var/www/html/public/widget/ - # # mount uploaded media on the host - - ./media/:/var/www/html/fuel/app/media/ - depends_on: - - memcached - # - mysql - - memcached: - image: memcached:1.6.6-alpine - networks: - - backend - -# Mysql in production should probably be an external server -# mysql: -# image: mysql:5.7.18 -# environment: -# - MYSQL_ROOT_PASSWORD -# - MYSQL_USER -# - MYSQL_PASSWORD -# - MYSQL_DATABASE -# networks: -# - backend - -networks: - frontend: - name: materia_frontend - backend: - name: materia_backend - -volumes: - compiled_assets: {} # used to share pre-compiled assets with nginx container - -``` - -### Troubleshooting - -#### Table Not Found - -When running fuelphp's install, it uses fuel/app/config/development/migrations.php file to know the current state of your database. Fuel assumes this file is truth, and won't create tables even on an empty database. You probably need to delete the file and run the setup scripts again. run_first.sh does this for you if needed. - diff --git a/docker/compose_local.sh b/docker/compose_local.sh new file mode 100755 index 000000000..e96601394 --- /dev/null +++ b/docker/compose_local.sh @@ -0,0 +1 @@ +docker-compose -f docker-compose.local.yml ${1:-up} \ No newline at end of file diff --git a/docker/config/mysql/01_create_test.sql b/docker/config/mysql/01_create_test.sql deleted file mode 100644 index a57ba5452..000000000 --- a/docker/config/mysql/01_create_test.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE DATABASE IF NOT EXISTS `test`; -GRANT ALL ON `test`.* TO 'materia'@'%'; -FLUSH PRIVILEGES; diff --git a/docker/config/mysql/override.cnf b/docker/config/mysql/override.cnf new file mode 100644 index 000000000..0cd471eb8 --- /dev/null +++ b/docker/config/mysql/override.cnf @@ -0,0 +1,4 @@ +[mysqld] +innodb_buffer_pool_size = 20M +sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION" +default_authentication_plugin=mysql_native_password \ No newline at end of file diff --git a/docker/config/nginx/nginx-dev.conf b/docker/config/nginx/nginx-dev.conf deleted file mode 100644 index bd0d3fc8a..000000000 --- a/docker/config/nginx/nginx-dev.conf +++ /dev/null @@ -1,174 +0,0 @@ -worker_processes auto; -error_log /var/log/nginx/error.log; -pid /tmp/nginx.pid; - -events { - worker_connections 1024; -} - -http { - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - log_format slim '[$time_local] "$request_method $request_uri" $status - $remote_addr'; - - access_log /var/log/nginx/access.log main; - - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - gzip on; - gzip_disable "msei6"; - gzip_http_version 1.1; - gzip_comp_level 5; - gzip_min_length 256; - gzip_proxied any; - gzip_vary on; - gzip_types - application/javascript - application/json - application/vnd.ms-fontobject - application/x-font-ttf - application/x-web-app-manifest+json - application/xhtml+xml - application/xml - font/opentype - image/svg+xml - image/x-icon - text/css - text/javascript - text/xml - text/plain; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - index index.html index.htm; - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_prefer_server_ciphers on; - ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; - ssl_certificate /etc/nginx/conf.d/cert.pem; - ssl_certificate_key /etc/nginx/conf.d/key.pem; - - # redirect to https - server { - listen 80; - listen [::]:80; - server_name default_server; - - location / { - return 302 https://$host$request_uri; - } -} - - # Main Application Server - server { - listen *:443 ssl; - listen [::]:443 ssl; - - error_page 404 = @handler; - error_page 405 = @handler; - - root /var/www/html/public; # mounted from app/public - index index.php index.html index.htm; - - charset utf-8; - - access_log /var/log/nginx/access.log slim; - error_log /var/log/nginx/error.log; - - client_max_body_size 50M; - - # block .files - location ~ /\. { - deny all; - log_not_found off; - } - - # block .composer files - location ~ composer\..* { - deny all; - log_not_found off; - } - - # block node files - location ~ node_modules { - deny all; - log_not_found off; - } - - location /elb-status { - access_log off; - return 200 'OH YEAAA'; - add_header Content-Type text/plain; - break; - } - - # try static files first, then directory, then fall back to @handler - location / { - try_files $uri $uri/ @handler; - expires 30d; - } - - # redirect requests into index.php?some/directory/thing - location @handler { - rewrite ^ /index.php?/$request_uri; - } - - - # handle any request starting with index.php - location ~ ^/index.php$ { - try_files $uri =404; - - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass app:9000; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - include fastcgi_params; - } - - # Directives to send expires headers and turn off 404 error logging. - location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|js|css|json|obj)$ { - access_log off; - log_not_found off; - # Normally, we'd use max in production - # expires max; - # But for development, it can cause some headaches - so lets not cache js - expires -1; - } - - # deny direct access to any php files - location ~ \.php$ { - deny all; - } - } - - # CDN/Sandboxing for media & widgets - server{ - listen *:8008 ssl; - listen [::]:8008 ssl; - - # In the dev environment, js and css assets are emitted to public/dist instead of public/ - # However, server pages will expect them to be in public/js or public/css instead - # Redirect requests for these assets to public/dist - location ~* ^\/(?:js|css)\/.+\.(?:js|css)$ { - proxy_pass https://$server_addr/dist$uri; - } - - # @TODO: match only /js/* /css/* and /widget/* - location ~* ^.+\.(html|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|js|css|json|obj)$ { - # pass all requests back to origin server transparently - proxy_pass https://$server_addr$request_uri; - } - - location / { - deny all; - } - - } - -} diff --git a/docker/config/nginx/nginx-production.conf b/docker/config/nginx/nginx-production.conf deleted file mode 100644 index 1e9d8245c..000000000 --- a/docker/config/nginx/nginx-production.conf +++ /dev/null @@ -1,141 +0,0 @@ -worker_processes auto; -error_log /var/log/nginx/error.log; -pid /tmp/nginx.pid; - -events { - worker_connections 1024; -} - -http { - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - log_format slim '[$time_local] "$request_method $request_uri" $status - $remote_addr'; - - access_log /var/log/nginx/access.log main; - - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - gzip on; - gzip_disable "msei6"; - gzip_http_version 1.1; - gzip_comp_level 5; - gzip_min_length 256; - gzip_proxied any; - gzip_vary on; - gzip_types - application/javascript - application/json - application/vnd.ms-fontobject - application/x-font-ttf - application/x-web-app-manifest+json - application/xhtml+xml - application/xml - font/opentype - image/svg+xml - image/x-icon - text/css - text/javascript - text/xml - text/plain; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - index index.html index.htm; - - # Main Application Server - server { - listen 80; - listen [::]:80; - server_name default_server; - - # Forward ip addresses from the load balancer - set_real_ip_from 10.0.0.0/8; - real_ip_header X-Forwarded-For; - real_ip_recursive on; - - # redirect to https behind an aws load balancer - if ($http_x_forwarded_proto = "http") { - rewrite ^/(.*)$ https://$host/$1 permanent; - } - - error_page 404 = @handler; - error_page 405 = @handler; - - root /var/www/html/public; # mounted from app/public - index index.php index.html index.htm; - - charset utf-8; - - access_log /var/log/nginx/access.log slim; - error_log /var/log/nginx/error.log; - - client_max_body_size 50M; - - # block .files - location ~ /\. { - deny all; - log_not_found off; - } - - # block .composer files - location ~ composer\..* { - deny all; - log_not_found off; - } - - # block node files - location ~ node_modules { - deny all; - log_not_found off; - } - - location /webserver-status { - access_log off; - return 200 'OH YEAAA'; - add_header Content-Type text/plain; - break; - } - - # try static files first, then directory, then fall back to @handler - location / { - try_files $uri $uri/ @handler; - expires 30d; - } - - # redirect requests into index.php?some/directory/thing - location @handler { - rewrite ^ /index.php?/$request_uri; - } - - # handle any request starting with index.php - location ~ ^/index.php$ { - try_files $uri =404; - - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass app:9000; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param X-Forwarded-Proto $scheme; - fastcgi_param HTTPS on; - include fastcgi_params; - } - - # Directives to send expires headers and turn off 404 error logging. - location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf|js|css|json|obj)$ { - access_log off; - log_not_found off; - expires max; - } - - # deny direct access to any php files - location ~ \.php$ { - deny all; - } - } -} diff --git a/docker/config/nginx/nginx.conf b/docker/config/nginx/nginx.conf new file mode 100644 index 000000000..9fd562ac8 --- /dev/null +++ b/docker/config/nginx/nginx.conf @@ -0,0 +1,31 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + #sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/docker/config/nginx/sites-enabled/site.conf b/docker/config/nginx/sites-enabled/site.conf new file mode 100644 index 000000000..306db274a --- /dev/null +++ b/docker/config/nginx/sites-enabled/site.conf @@ -0,0 +1,45 @@ +server { + listen *:80; + server_name default; + + root /var/www/html; + index index.html; + + location / { + uwsgi_pass python:8001; + + uwsgi_param QUERY_STRING $query_string; + uwsgi_param REQUEST_METHOD $request_method; + uwsgi_param CONTENT_TYPE $content_type; + uwsgi_param CONTENT_LENGTH $content_length; + + uwsgi_param REQUEST_URI $request_uri; + uwsgi_param PATH_INFO $document_uri; + uwsgi_param DOCUMENT_ROOT $document_root; + uwsgi_param SERVER_PROTOCOL $server_protocol; + uwsgi_param HTTPS $https if_not_empty; + + uwsgi_param REMOTE_ADDR $remote_addr; + uwsgi_param REMOTE_PORT $remote_port; + uwsgi_param SERVER_PORT $server_port; + uwsgi_param SERVER_NAME $server_name; + uwsgi_param UWSGI_SCHEME http; + + uwsgi_modifier1 30; + + uwsgi_read_timeout 300; + uwsgi_send_timeout 300; + uwsgi_connect_timeout 300; + + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /static { + alias /var/www/html/static; + autoindex on; + expires 1d; + } +} \ No newline at end of file diff --git a/docker/config/php/materia.php.ini b/docker/config/php/materia.php.ini deleted file mode 100644 index 9fdebe247..000000000 --- a/docker/config/php/materia.php.ini +++ /dev/null @@ -1,25 +0,0 @@ -[PHP] -short_open_tag = Off -expose_php = off -max_execution_time = 100 -memory_limit = 250M -track_errors = Off -html_errors = On -variables_order = "EGPCS" -post_max_size = 50M -fastcgi.logging = 0 -upload_max_filesize = 50M - -[Date] -date.timezone = America/New_York - -[Pdo] -pdo_mysql.cache_size = 2000 - -[mail function] -mail.add_x_header = On - -[opcache] -opcache.enable=1 - -xdebug.mode=off diff --git a/docker/config/php/materia.test.php.ini b/docker/config/php/materia.test.php.ini deleted file mode 100644 index 94ff65a33..000000000 --- a/docker/config/php/materia.test.php.ini +++ /dev/null @@ -1,2 +0,0 @@ - -xdebug.mode=coverage diff --git a/docker/config/uwsgi/uwsgi.ini b/docker/config/uwsgi/uwsgi.ini new file mode 100644 index 000000000..9a9bda4eb --- /dev/null +++ b/docker/config/uwsgi/uwsgi.ini @@ -0,0 +1,45 @@ +[uwsgi] +# uwsgi process runs as user:group +uid = www-data +gid = www-data + +vhost = true + +# Process name for easy identification in top +procname = materia + +# Number of worker processes +processes = 4 +threads = 2 + +# django >= 1.4 project +chdir = /var/www/html +chown = www-data:www-data + +# module = wsgi +wsgi-file = /var/www/html/materia.wsgi + +# http socket +socket = :8001 + +vacuum = true + +# Use Master Process +master = True + +# Use harakiri to kill requests cosing more than 150 seconds +harakiri = 150 +harakiri-verbose = True + +#Reload the uwsgi process if the wsgi file is touched +touch-reload = /var/www/html/materia.wsgi + +env = DJANGO_SETTINGS_MODULE=settings +pythonpath = /var/www + +# Required for New Relic reporting +enable-threads = True +single-interpreter = True + +block-size=8192 +buffer-size=8192 diff --git a/docker/docker-compose.local.yml b/docker/docker-compose.local.yml new file mode 100644 index 000000000..ffc8047d0 --- /dev/null +++ b/docker/docker-compose.local.yml @@ -0,0 +1,58 @@ +version: "3.4" + +networks: + backend: + +services: + nginx: + image: nginx + ports: + - "80:80" + volumes: + - ../app:/var/www/html:ro + - ./config/nginx/sites-enabled:/etc/nginx/conf.d:ro + - ./config/nginx/nginx.conf:/etc/nginx/nginx.conf + networks: + - backend + depends_on: + - python + + python: + build: + context: ../ + dockerfile: materia-app.python.Dockerfile + networks: + - backend + ports: + - "8001:8001" + depends_on: + - mysql + environment: + - DJANGO_ENV=dev + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=materia_dev + - MYSQL_USER=materia_user + - MYSQL_PASSWORD=kogneato + - MYSQL_HOST=mysql + - MYSQL_PORT=3306 + - EMAIL_HOST=host.docker.internal + - EMAIL_PORT=1025 + volumes: + - ../app:/var/www/html + - ./config/uwsgi/:/etc/uwsgi + # command: uwsgi --ini=/etc/uwsgi/uwsgi.ini --py-autoreload=2 + command: /wait_for_it.sh mysql:3306 -t 10 -- uwsgi --ini=/etc/uwsgi/uwsgi.ini --py-autoreload=2 + + mysql: + image: mariadb:10.6 + networks: + - backend + ports: + - "3306:3306" + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=materia_dev + - MYSQL_USER=materia_user + - MYSQL_PASSWORD=kogneato + volumes: + - ./config/mysql/override.cnf:/etc/mysql/conf.d/override.cnf diff --git a/docker/docker-compose.override.test.yml b/docker/docker-compose.override.test.yml deleted file mode 100644 index 2115b1a11..000000000 --- a/docker/docker-compose.override.test.yml +++ /dev/null @@ -1,46 +0,0 @@ -version: '3.5' -# use with dockercompose -f docker-compose.yml -f docker-compose.overide.test.yml -services: - webserver: - volumes: - - ../public:/var/www/html/public:ro - - static_widget_files_test:/var/www/html/public/widget/:ro - - ./config/nginx/key.pem:/etc/nginx/conf.d/key.pem:ro - - ./config/nginx/cert.pem:/etc/nginx/conf.d/cert.pem:ro - - ./config/nginx/nginx-dev.conf:/etc/nginx/nginx.conf:ro - - app: - environment: - # View Materia README for env settings - - DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql/test - - FUEL_ENV=test - - FUEL_LOG_THRESHOLD=300 - - LOG_HANDLER=DEFAULT - volumes: - - ..:/var/www/html/ - # isolate test widget files just for test - - static_widget_files_test:/var/www/html/public/widget - # isolate test media just for test - - uploaded_media_test:/var/www/html/fuel/packages/materia/media - - ./config/php/materia.test.php.ini:/usr/local/etc/php/conf.d/test.ini - - ./dockerfiles/wait-for-it.sh:/wait-for-it.sh - - mysql: - environment: - - MYSQL_ROOT_PASSWORD - - MYSQL_USER - - MYSQL_PASSWORD - - MYSQL_DATABASE - # this makes the unit tests much faster but it's a little weird jumping - # back and forth between running the server and testing - # tmpfs: - # - /var/lib/mysql - - fakes3: - volumes: - - uploaded_media_test:/s3mnt/fakes3_root/fakes3_uploads/media/ - -volumes: - # static_files: {} # compiled js/css and uploaded widgets - static_widget_files_test: {} # contain widgets installed in tests - uploaded_media_test: {} # contain files uploaded in tests diff --git a/docker/docker-compose.override.yml b/docker/docker-compose.override.yml deleted file mode 100644 index a705ea366..000000000 --- a/docker/docker-compose.override.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: '3.5' -# loaded by default unless you use docker-compose -f flag -# this is needed because we want test to mount throw-away file storage that doesn't affect your dev environment -# docker-compose won't let an override file define the same mount point. -# since this file is loaded by default except when using -f to load a different override, that avoids -# the problem of multiple mounts at the same point - -services: - webserver: - volumes: - - ../public:/var/www/html/public:ro - - uploaded_widgets:/var/www/html/public/widget/:ro - - ./config/nginx/key.pem:/etc/nginx/conf.d/key.pem:ro - - ./config/nginx/cert.pem:/etc/nginx/conf.d/cert.pem:ro - - ./config/nginx/nginx-dev.conf:/etc/nginx/nginx.conf:ro - - app: - volumes: - - ..:/var/www/html/ - - uploaded_widgets:/var/www/html/public/widget/ - - ./dockerfiles/wait-for-it.sh:/wait-for-it.sh - - mysql: - environment: - - MYSQL_ROOT_PASSWORD - - MYSQL_USER - - MYSQL_PASSWORD - - MYSQL_DATABASE - - fakes3: - volumes: - - uploaded_media:/s3mnt/fakes3_root/fakes3_uploads/media/ - -volumes: - # static_files: {} # compiled js/css and uploaded widgets - uploaded_media: {} # uploaded media files - uploaded_widgets: {} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 25c26347b..000000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,93 +0,0 @@ -version: '3.5' - -services: - webserver: - image: ucfopen/materia:webserver-dev - build: - context: ../ - dockerfile: materia-webserver.Dockerfile - ports: - - "80:80" # main materia - - "443:443" # main materia - - "8008:8008" # static files (simulates a different domain sandbox & cdn) - networks: - - frontend - depends_on: - - app - - app: - image: ucfopen/materia:app-dev - build: - context: ../ - dockerfile: materia-app.Dockerfile - environment: - # View Materia README for env settings - - ASSET_STORAGE_DRIVER=file - - ASSET_STORAGE_S3_BUCKET=fake_bucket - - ASSET_STORAGE_S3_ENDPOINT=http://fakes3:10001 - - ASSET_STORAGE_S3_KEY=KEY - - ASSET_STORAGE_S3_SECRET=SECRET - - AUTH_DRIVERS=Materiaauth - - AUTH_SALT=${DEV_ONLY_AUTH_SALT} - - AUTH_SIMPLEAUTH_SALT=${DEV_ONLY_AUTH_SIMPLEAUTH_SALT} - - BOOL_LTI_LOG_FOR_DEBUGGING=true - - CACHE_DRIVER=memcached - - CIPHER_KEY=${DEV_ONLY_SECRET_CIPHER_KEY} - - CRYPTO_HMAC=${DEV_ONLY_SECRET_CIPHER_KEY} - - CRYPTO_IV=${DEV_ONLY_SECRET_CIPHER_KEY} - - CRYPTO_KEY=${DEV_ONLY_SECRET_CIPHER_KEY} - - DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql/${MYSQL_DATABASE} - - FUEL_ENV=development - - FUEL_LOG_THRESHOLD=100 - - LOG_HANDLER=STDOUT - - LTI_KEY=materia-lti-key - - LTI_SECRET=materia-lti-secret - - MEMCACHED_HOST=memcached - - SESSION_DRIVER=memcached - - SYSTEM_EMAIL=noReply@materia.edu - - THEME_PACKAGE=materia-theme-ucf - - URLS_ENGINES=https://localhost:8008/widget/ - - URLS_STATIC=https://localhost:8008/ - - USER_INSTRUCTOR_PASSWORD=${DEV_ONLY_USER_PASSWORD} - - USER_STUDENT_PASSWORD=${DEV_ONLY_USER_PASSWORD} - - USER_SYSTEM_PASSWORD=${DEV_ONLY_USER_PASSWORD} - networks: - - frontend - - backend - depends_on: - - mysql - - memcached - - fakes3 - - mysql: - image: mysql:5.7.34 - platform: linux/amd64 - ports: - - "3306:3306" # allow mysql access from the host - use /etc/hosts to set mysql to your docker-machine ip - networks: - - backend - volumes: - - "./config/mysql/01_create_test.sql:/docker-entrypoint-initdb.d/01_create_test.sql" - - memcached: - image: memcached:1.6.6-alpine - networks: - - backend - - fakes3: - image: ucfopen/materia:fake-s3-dev - build: - context: ../ - dockerfile: materia-fake-s3.Dockerfile - ports: - - "10001:10001" - networks: - - frontend - - backend - -networks: - frontend: - name: materia_frontend - backend: - name: materia_backend - diff --git a/docker/dockerfiles/wait-for-it.sh b/docker/dockerfiles/wait-for-it.sh deleted file mode 100755 index d990e0d36..000000000 --- a/docker/dockerfiles/wait-for-it.sh +++ /dev/null @@ -1,182 +0,0 @@ -#!/usr/bin/env bash -# Use this script to test if a given TCP host/port are available - -WAITFORIT_cmdname=${0##*/} - -echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } - -usage() -{ - cat << USAGE >&2 -Usage: - $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] - -h HOST | --host=HOST Host or IP under test - -p PORT | --port=PORT TCP port under test - Alternatively, you specify the host and port as host:port - -s | --strict Only execute subcommand if the test succeeds - -q | --quiet Don't output any status messages - -t TIMEOUT | --timeout=TIMEOUT - Timeout in seconds, zero for no timeout - -- COMMAND ARGS Execute command with args after the test finishes -USAGE - exit 1 -} - -wait_for() -{ - if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then - echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" - else - echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" - fi - WAITFORIT_start_ts=$(date +%s) - while : - do - if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then - nc -z $WAITFORIT_HOST $WAITFORIT_PORT - WAITFORIT_result=$? - else - (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 - WAITFORIT_result=$? - fi - if [[ $WAITFORIT_result -eq 0 ]]; then - WAITFORIT_end_ts=$(date +%s) - echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" - break - fi - sleep 1 - done - return $WAITFORIT_result -} - -wait_for_wrapper() -{ - # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 - if [[ $WAITFORIT_QUIET -eq 1 ]]; then - timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & - else - timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & - fi - WAITFORIT_PID=$! - trap "kill -INT -$WAITFORIT_PID" INT - wait $WAITFORIT_PID - WAITFORIT_RESULT=$? - if [[ $WAITFORIT_RESULT -ne 0 ]]; then - echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" - fi - return $WAITFORIT_RESULT -} - -# process arguments -while [[ $# -gt 0 ]] -do - case "$1" in - *:* ) - WAITFORIT_hostport=(${1//:/ }) - WAITFORIT_HOST=${WAITFORIT_hostport[0]} - WAITFORIT_PORT=${WAITFORIT_hostport[1]} - shift 1 - ;; - --child) - WAITFORIT_CHILD=1 - shift 1 - ;; - -q | --quiet) - WAITFORIT_QUIET=1 - shift 1 - ;; - -s | --strict) - WAITFORIT_STRICT=1 - shift 1 - ;; - -h) - WAITFORIT_HOST="$2" - if [[ $WAITFORIT_HOST == "" ]]; then break; fi - shift 2 - ;; - --host=*) - WAITFORIT_HOST="${1#*=}" - shift 1 - ;; - -p) - WAITFORIT_PORT="$2" - if [[ $WAITFORIT_PORT == "" ]]; then break; fi - shift 2 - ;; - --port=*) - WAITFORIT_PORT="${1#*=}" - shift 1 - ;; - -t) - WAITFORIT_TIMEOUT="$2" - if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi - shift 2 - ;; - --timeout=*) - WAITFORIT_TIMEOUT="${1#*=}" - shift 1 - ;; - --) - shift - WAITFORIT_CLI=("$@") - break - ;; - --help) - usage - ;; - *) - echoerr "Unknown argument: $1" - usage - ;; - esac -done - -if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then - echoerr "Error: you need to provide a host and port to test." - usage -fi - -WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} -WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} -WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} -WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} - -# Check to see if timeout is from busybox? -WAITFORIT_TIMEOUT_PATH=$(type -p timeout) -WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) - -WAITFORIT_BUSYTIMEFLAG="" -if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then - WAITFORIT_ISBUSY=1 - # Check if busybox timeout uses -t flag - # (recent Alpine versions don't support -t anymore) - if timeout &>/dev/stdout | grep -q -e '-t '; then - WAITFORIT_BUSYTIMEFLAG="-t" - fi -else - WAITFORIT_ISBUSY=0 -fi - -if [[ $WAITFORIT_CHILD -gt 0 ]]; then - wait_for - WAITFORIT_RESULT=$? - exit $WAITFORIT_RESULT -else - if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then - wait_for_wrapper - WAITFORIT_RESULT=$? - else - wait_for - WAITFORIT_RESULT=$? - fi -fi - -if [[ $WAITFORIT_CLI != "" ]]; then - if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then - echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" - exit $WAITFORIT_RESULT - fi - exec "${WAITFORIT_CLI[@]}" -else - exit $WAITFORIT_RESULT -fi diff --git a/docker/dockerfiles/wait_for_it.sh b/docker/dockerfiles/wait_for_it.sh new file mode 100644 index 000000000..dd7039598 --- /dev/null +++ b/docker/dockerfiles/wait_for_it.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available +# FROM git@github.com:vishnubob/wait-for-it.git + +cmdname=$(basename $0) + +echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $TIMEOUT -gt 0 ]]; then + echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" + else + echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" + fi + start_ts=$(date +%s) + while : + do + (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 + result=$? + if [[ $result -eq 0 ]]; then + end_ts=$(date +%s) + echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" + break + fi + sleep 1 + done + return $result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $QUIET -eq 1 ]]; then + timeout $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & + else + timeout $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & + fi + PID=$! + trap "kill -INT -$PID" INT + wait $PID + RESULT=$? + if [[ $RESULT -ne 0 ]]; then + echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" + fi + return $RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + hostport=(${1//:/ }) + HOST=${hostport[0]} + PORT=${hostport[1]} + shift 1 + ;; + --child) + CHILD=1 + shift 1 + ;; + -q | --quiet) + QUIET=1 + shift 1 + ;; + -s | --strict) + STRICT=1 + shift 1 + ;; + -h) + HOST="$2" + if [[ $HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + HOST="${1#*=}" + shift 1 + ;; + -p) + PORT="$2" + if [[ $PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + PORT="${1#*=}" + shift 1 + ;; + -t) + TIMEOUT="$2" + if [[ $TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + CLI="$@" + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$HOST" == "" || "$PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +TIMEOUT=${TIMEOUT:-15} +STRICT=${STRICT:-0} +CHILD=${CHILD:-0} +QUIET=${QUIET:-0} + +if [[ $CHILD -gt 0 ]]; then + RESULT=$(wait_for) + exit $RESULT +else + if [[ $TIMEOUT -gt 0 ]]; then + wait_for_wrapper + RESULT=$? + else + RESULT=$(wait_for) + fi +fi + +if [[ $CLI != "" ]]; then + if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then + echoerr "$cmdname: strict mode, refusing to execute subprocess" + exit $RESULT + fi + exec $CLI +else + exit $RESULT +fi \ No newline at end of file diff --git a/docker/kick_start.sh b/docker/kick_start.sh new file mode 100755 index 000000000..a4de3e837 --- /dev/null +++ b/docker/kick_start.sh @@ -0,0 +1 @@ +docker exec "${PWD##*/}_python_1" touch /var/www/html/learn.wsgi diff --git a/docker/run.sh b/docker/run.sh index 0483ece12..c896c5217 100755 --- a/docker/run.sh +++ b/docker/run.sh @@ -1,16 +1,3 @@ -#!/bin/bash -####################################################### -# ABOUT THIS SCRIPT -# -# Run ad hoc commands on the app container (non-test env) -# -# Arguments are executed string -# EX: ./run.sh echo "hello" -# EX: ./run.sh composer update -# EX: ./run.sh composer test --group=Lti -# EX; DC='docker-compose -f docker-compose.yml -f docker-compose.alpine.yml' ./run.sh commposer update -####################################################### - -set -e - -docker-compose run --rm app /wait-for-it.sh mysql:3306 -t 20 -- "$@" +FULLPATH=${PWD##*/} +CONTAINER="${FULLPATH}_python_1" +docker exec -it $CONTAINER "$@" \ No newline at end of file diff --git a/docker/run_build_assets.sh b/docker/run_build_assets.sh deleted file mode 100755 index f74a1a3ab..000000000 --- a/docker/run_build_assets.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -####################################################### -# ABOUT THIS SCRIPT -# -# Builds assets using the included node container -####################################################### -set -e - -# build the js/css assets into the public directory using node in a docker container - -# create a volume for 2 reaons: -# 1. cache node_modules -# 2. don't interact with the host's node_modules files -docker volume create materia-asset-build-vol - -# bind the root of materia into /build, install git, and run yarn -# yarn install will install all deps & execute build -# which will populate files in the host's public directory -docker run \ - --rm \ - --name materia-asset-build \ - --mount type=bind,source="$(pwd)"/../,target=/build \ - --mount source=materia-asset-build-vol,target=/build/node_modules \ - node:18.13.0-alpine \ - /bin/ash -c "apk add --no-cache git && cd build && yarn install --frozen-lockfile --non-interactive --silent --pure-lockfile --force && npm run-script build" diff --git a/docker/run_build_github_release_package.sh b/docker/run_build_github_release_package.sh deleted file mode 100755 index f101cc313..000000000 --- a/docker/run_build_github_release_package.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/bin/bash -####################################################### -# ABOUT THIS SCRIPT -# -# Install and build a base release package -# This should try to include as many constructed -# assets as possible to reduce the work needed -# to deploy Materia. This build will not -# disrupt the current files on disk - -# ex: no need to install node # or npm packages -# to build js - just include the js -# -# EX: ./run_build_github_release_package.sh.sh ghcr.io/ucfopen/private-materia:app-v8.0.0 -####################################################### -set -e - - -die () { - echo >&2 "$@" - exit 1 -} - -# exit without args -if [ $# -lt 1 ]; then - die "1 required argument: docker-image-name" -fi - -DOCKER_IMAGE=$1 - -# declare files that should have been created -declare -a FILES_THAT_SHOULD_EXIST=( - "public/js/materia.enginecore.js" - "public/css/player-page.css" -) - -# declare files to omit from zip -declare -a FILES_TO_EXCLUDE=( - "*.git*" - "*.gitignore" - "app.json" - "nginx_app.conf" - "Procfile" - "node_modules*" - "githooks" - "phpcs.xml" - "src*" - "fuel/app/config/development*" - "fuel/app/config/heroku*" - "fuel/app/config/test*" - "fuel/app/config/production*" - "public/widget*" - "githooks*" - "coverage.xml" - "coverage*" -) - -# combine the files to exclude -EXCLUDE='' -for i in "${FILES_TO_EXCLUDE[@]}" -do - EXCLUDE="$EXCLUDE -x \"$i\"" -done - -set -o xtrace - -# get rid of any left over package files -rm -rf clean_build_clone || true -rm -rf ../materia-pkg* || true -git clone ../ ./clean_build_clone - -# gather build info -DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") -GITUSER=$(git config user.name) -GITEMAIL=$(git config user.email) -GITCOMMIT=$(cd clean_build_clone && git rev-parse HEAD) -GITREMOTE=$(git remote get-url origin) - -# remove .git dir for slightly faster copy -rm -rf ./clean_build_clone/.git -rm -rf ./clean_build_clone/public - -# copy the clean build clone into the container -docker cp $(docker create --rm $DOCKER_IMAGE):/var/www/html/public ./clean_build_clone/public/ - -# compile js & css assets into the public/dist directory -docker volume create materia-asset-build-vol -docker run \ - --rm \ - --name materia-asset-build \ - --mount type=bind,source="$(pwd)"/clean_build_clone/,target=/build \ - --mount source=materia-asset-build-vol,target=/build/node_modules \ - node:18.13.0-alpine \ - /bin/ash -c "apk add --no-cache git && cd build && yarn install --frozen-lockfile --non-interactive --pure-lockfile --force && npm run-script build-for-image" - -# verify all files we expect to be created exist -for i in "${FILES_THAT_SHOULD_EXIST[@]}" -do - stat ./clean_build_clone/$i -done - -# zip, excluding some files -cd ./clean_build_clone -eval "zip -r ../../materia-pkg.zip ./ $EXCLUDE" - -# calulate hashes -MD5=$(md5sum ../../materia-pkg.zip | awk '{ print $1 }') -SHA1=$(sha1sum ../../materia-pkg.zip | awk '{ print $1 }') -SHA256=$(sha256sum ../../materia-pkg.zip | awk '{ print $1 }') - -# write build info file -echo "build_date: $DATE" > ../../materia-pkg-build-info.yml -echo "git: $GITREMOTE" >> ../../materia-pkg-build-info.yml -echo "git_version: $GITCOMMIT" >> ../../materia-pkg-build-info.yml -echo "git_user: $GITUSER" >> ../../materia-pkg-build-info.yml -echo "git_user_email: $GITEMAIL" >> ../../materia-pkg-build-info.yml -echo "sha1: $SHA1" >> ../../materia-pkg-build-info.yml -echo "sha256: $SHA256" >> ../../materia-pkg-build-info.yml -echo "md5: $MD5" >> ../../materia-pkg-build-info.yml - -cd .. && rm -rf ./clean_build_clone diff --git a/docker/run_create_default_users.sh b/docker/run_create_default_users.sh deleted file mode 100755 index 5b6d887df..000000000 --- a/docker/run_create_default_users.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -####################################################### -# ABOUT THIS SCRIPT -# -# create/update the default users. -# use this if you forget or lost the defaul -# user passwords -# -# EX: ./run_create_default_users.sh -####################################################### - -# create/update the default users -docker-compose run --rm app bash -c "php oil r admin:create_default_users" diff --git a/docker/run_create_me.sh b/docker/run_create_me.sh deleted file mode 100755 index 3fffc3d05..000000000 --- a/docker/run_create_me.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -####################################################### -# ABOUT THIS SCRIPT -# -# Create an admin user based on the -# current user of this machine -# -# EX: ./run_create_me.sh -####################################################### - -# create an admin for the current host user -# customize your password by setting MATERIA_DEV_PASS -PASS=${MATERIA_DEV_PASS:-kogneato} - -# create or update the user and pw -docker-compose run --rm app bash -c "php oil r admin:new_user $USER $USER M Lastname $USER@mail.com $PASS || php oil r admin:reset_password $USER $PASS" - -# give them super_user and basic_author -docker-compose run --rm app bash -c "php oil r admin:give_user_role $USER super_user || true && php oil r admin:give_user_role $USER basic_author" diff --git a/docker/run_first.sh b/docker/run_first.sh deleted file mode 100755 index ed52d00c5..000000000 --- a/docker/run_first.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -####################################################### -# ABOUT THIS SCRIPT -# -# Initializes a new local Dev Materia environment in Docker -# -# If you find you really need to burn everything down -# Run "docker-compose down" to get rid of all containers -# -####################################################### -set -e -# set -o xtrace - -DOCKER_IP="localhost" - -# clean migration files in every environment so they can run again -rm -f ../fuel/app/config/**/migrations.php - -# clear out any existing certs -rm -rf ./config/nginx/key.pem -rm -rf ./config/nginx/cert.pem - -# generate a self-signed ssl cert -openssl req -subj '/CN=localhost' -x509 -newkey rsa:4096 -nodes -keyout ./config/nginx/key.pem -out ./config/nginx/cert.pem -days 365 - -# quietly pull any docker images we can -docker-compose pull --ignore-pull-failures - -# install php composer deps -docker-compose run --rm --no-deps app composer install --ignore-platform-reqs - -# run migrations and seed any db data needed for a new install -docker-compose run --rm app /wait-for-it.sh mysql:3306 --timeout=120 --strict -- composer oil-install-quiet - -# install all the configured widgets -docker-compose run --rm app bash -c 'php oil r widget:install_from_config' - -# Install any widgets in the tmp dir -source run_widgets_install.sh '*.wigt' - -# build all the js/css assets -source run_build_assets.sh - -# create a dev user based on your current shell user (password will be 'kogneato') MATERIA_DEV_PASS=whatever can be used to set a custom pw -source run_create_me.sh - -echo -e "Materia will be hosted on \033[32m$DOCKER_IP\033[0m" -echo -e "\033[1mRun an oil comand:\033[0m ./run.sh php oil r widget:show_engines" -echo -e "\033[1mRun the web app:\033[0m docker-compose up" diff --git a/docker/run_tests.sh b/docker/run_tests.sh deleted file mode 100755 index 3e6660317..000000000 --- a/docker/run_tests.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -####################################################### -# ABOUT THIS SCRIPT -# -# Script to run the unit tests without coverage -####################################################### - -echo "remember you can limit your test groups with './run_tests.sh --group=Lti'" - -# If you have an issue with a broken widget package breaking this script, run the following to clear the widgets -# docker-compose -f docker-compose.yml -f docker-compose.admin.yml run --rm app bash -c -e 'rm /var/www/html/fuel/packages/materia/vendor/widget/test/*' - -DCTEST="docker-compose -f docker-compose.yml -f docker-compose.override.test.yml" - -set -e -set -o xtrace - -$DCTEST run -T --rm app /wait-for-it.sh mysql:3306 -t 20 -- composer run testci -- "$@" diff --git a/docker/run_tests_ci.sh b/docker/run_tests_ci.sh deleted file mode 100755 index f65fa05d9..000000000 --- a/docker/run_tests_ci.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -####################################################### -# ABOUT THIS SCRIPT -# -# CI script for reliably running clean tests with current boxes -# -# Continuously builds assets using the included node container -# Doesn't seem to stop properly with ctrl-c -# use "docker stop " to kill it -####################################################### -set -e -set -o xtrace - -DCTEST="docker-compose -f docker-compose.yml -f docker-compose.override.test.yml" - -$DCTEST pull --ignore-pull-failures app fakes3 - -# annoying workaround to get host mounted file ownership mapped to the user inside the container -docker run --rm -v $(pwd)/../:/source alpine:latest chown -R 1000 /source - -# install php deps -$DCTEST run -T --rm --no-deps app composer install --no-progress - -# run linter -source run_tests_lint.sh - -# install widgets and run tests -source run_tests_coverage.sh - -# turn off failure stop on error -set +e - -# stop and remove docker containers -$DCTEST rm --force --stop diff --git a/docker/run_tests_coverage.sh b/docker/run_tests_coverage.sh deleted file mode 100755 index 5b3d9c511..000000000 --- a/docker/run_tests_coverage.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -####################################################### -# ABOUT THIS SCRIPT -# -# RUNS TESTS WITH COVERAGE -# -# place .wigt files in app/fuel/app/tmp/widget_packages/ -# Supports globs, but you have to quote them so they aren't -# expanded in your host's shell instead of the container's -# -# EX: ./run_tests_coverage.sh -# EX: ./run_tests_coverage.sh --group=Lti -####################################################### -set -e - -DCTEST="docker-compose -f docker-compose.yml -f docker-compose.override.test.yml" - -echo "remember you can limit your test groups with './run_tests_coverage.sh --group=Lti'" -echo "If you have an issue with a broken widget, clear the widgets with:" -echo "$DCTEST run --rm app bash -c -e 'rm /var/www/html/fuel/packages/materia/vendor/widget/test/*'" - -# store the docker compose command to shorten the following commands -$DCTEST run -T --rm app /wait-for-it.sh mysql:3306 -t 20 -- composer run coverageci -- "$@" diff --git a/docker/run_tests_lint.sh b/docker/run_tests_lint.sh deleted file mode 100755 index 945ec6bb0..000000000 --- a/docker/run_tests_lint.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -####################################################### -# ABOUT THIS SCRIPT -# -# Script to run the linter in docker -####################################################### - -DCTEST="docker-compose -f docker-compose.yml -f docker-compose.override.test.yml" - -set -e -set -o xtrace - -$DCTEST run -T --rm --no-deps app composer sniff-ci diff --git a/docker/run_widgets_install.sh b/docker/run_widgets_install.sh deleted file mode 100755 index 2b45bbbf8..000000000 --- a/docker/run_widgets_install.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -####################################################### -# ABOUT THIS SCRIPT -# -# INSTALLS WIDGETS FROM EXISTING WIGT FILE -# -# place .wigt files in app/fuel/app/tmp/widget_packages/ -# Supports globs, but you have to quote them so they aren't -# expanded in your host's shell instead of the container's -# -# EX: ./install_widget.sh adventure.wigt -# EX: ./install_widget.sh '*.wigt' -####################################################### -set -e - -docker-compose run --rm app bash -c 'php oil r widget:install fuel/app/tmp/widget_packages/'$1 diff --git a/materia-app.python.Dockerfile b/materia-app.python.Dockerfile new file mode 100644 index 000000000..ab67c9341 --- /dev/null +++ b/materia-app.python.Dockerfile @@ -0,0 +1,34 @@ +FROM python:3.12.1 + +# Run updates +RUN apt-get update +RUN pip install --upgrade pip + +# Install some dependencies necessary for supporting the SAML library +RUN apt-get install -y --no-install-recommends libxmlsec1-dev pkg-config + +# Install uwsgi now because it takes a little while +RUN pip install uwsgi +# RUN useradd --system --no-create-home --shell /bin/false uwsgi + +RUN mkdir /var/www/ +RUN mkdir /var/www/html +RUN mkdir /var/www/site +RUN mkdir /var/www/site/learn +RUN ln -s /var/www/html /var/www/site/learn + +# Add the wait for it script. +COPY docker/dockerfiles/wait_for_it.sh /wait_for_it.sh +RUN chmod +x /wait_for_it.sh + +RUN chown -R www-data:www-data /var/www + +# either the site-packages directory needs to be writable for www-data for pip3 to work +# RUN chown -R www-data:www-data usr/local/lib/python3.9/site-packages +# or all dependencies need to be installed by root before switching users +COPY app/requirements.txt /var/www/html/requirements.txt +RUN pip install -r /var/www/html/requirements.txt + +USER www-data + +WORKDIR /var/www/html