Compare commits

...

10 commits

Author SHA1 Message Date
Dave Lane
20c80852b3 updates to make this run on the new RightPlant server 2025-07-21 10:54:26 +12:00
Matthew Northcott
d09e6f3914 Actually fix the bugs this time 2023-03-30 15:30:35 +13:00
Matthew Northcott
00afd05abb Further production updates
- add collectstatic job to docker-compose.yaml
- remove old dev script
- add a recipe for building the frontend distributable
- fix nginx location for react-router endpoints
- fix bug in tasks.py
2023-03-30 14:27:56 +13:00
Matthew Northcott
7e505ad493 Add recipe for creating certificates 2023-03-30 10:35:08 +13:00
Matthew Northcott
cd93fe8708 Run backend as non-root user 2023-03-29 16:41:46 +13:00
Matthew Northcott
625291b4bf Update docker-compose
- add healthcheck to celery
- update redis
- update healthcheck for postgres
2023-03-29 16:40:45 +13:00
Matthew Northcott
c980479d6c Small fixes and updates 2023-03-29 16:37:39 +13:00
Matthew Northcott
b30fd62f48 Fix JS console errors 2023-03-28 13:00:29 +13:00
Matthew Northcott
c8851d552f Update deployment config 2023-03-28 13:00:15 +13:00
Matthew Northcott
5990144005 Allow Stripe configuration via environment variables 2023-03-28 12:05:03 +13:00
21 changed files with 151 additions and 304 deletions

View file

@ -10,8 +10,8 @@ export GID
frontend/node_modules:
docker run --rm -v ${PWD}/frontend:/app -w /app -u ${UID}:${GID} node:16-bullseye npm i
backend/right_tree/staticfiles:
docker run --rm -v ${PWD}/backend:/app -w /app -u ${UID}:${GID} right-tree python manage.py collectstatic --noinput
frontend/build: frontend/node_modules
docker run --rm -v ${PWD}/frontend:/app -w /app -u ${UID}:${GID} node:16-bullsye npm build
ingest:
docker-compose up -d backend postgres
@ -63,6 +63,20 @@ logs:
stop:
docker-compose down
cert:
docker run --rm \
--name certbot \
-p 443:443 \
-p 80:80 \
-v /etc/letsencrypt:/etc/letsencrypt \
certbot/certbot \
certonly \
--standalone \
--non-interactive \
--preferred-challenges http \
--logs-dir /etc/letsencrypt/logs \
-d rightplant.nz
clean: stop
git clean -dxf

View file

@ -1,3 +1,4 @@
__pycache__/
staticfiles/
media/
*.pyc

View file

@ -7,10 +7,12 @@ RUN apt update \
&& rm -rf /var/lib/apt/lists/* \
&& apt clean
COPY ./requirements.txt /app/requirements.txt
RUN pip install -U --no-cache-dir -r requirements.txt
COPY . /app
RUN pip install -U --no-cache-dir -r requirements.txt && \
useradd -Mu 1000 righttree && \
chown -R righttree:righttree /app
ENV DJANGO_SETTINGS_MODULE="right_tree.settings"
USER righttree

View file

@ -8,6 +8,6 @@ gunicorn==20.1.0
pandas==1.5.3
pdfkit==1.0.0
PyPDF2==1.28.6
redis==4.5.1
redis==4.5.3
celery[redis]==5.2.7
stripe==5.2.0

View file

@ -0,0 +1,3 @@
from .celery import app as celery_app
__all__ = ("celery_app",)

View file

@ -1,5 +1,10 @@
import os
from celery import Celery
app = Celery('righttree')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'right_tree.settings')
app = Celery('right_tree')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()

View file

@ -0,0 +1,19 @@
# Generated by Django 3.2.17 on 2023-03-29 03:36
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('api', '0015_auto_20230306_1620'),
]
operations = [
migrations.AlterField(
model_name='activationkey',
name='key_set',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='api.activationkeyset'),
),
]

View file

@ -222,7 +222,7 @@ class ActivationKey(models.Model):
)
key = models.CharField(max_length=20, unique=True, default=key_default)
key_set = models.ForeignKey(ActivationKeySet, on_delete=models.CASCADE, null=True)
key_set = models.ForeignKey(ActivationKeySet, on_delete=models.PROTECT, null=True)
remaining_activations = models.SmallIntegerField(default=1)
creation_date = models.DateTimeField(auto_now_add=True)

View file

@ -1,3 +1,4 @@
from pathlib import Path
from shutil import rmtree
from django.db.models.signals import post_save, post_delete
@ -10,7 +11,10 @@ from .resource_generation_utils import storage
@receiver(post_delete, sender=Export)
def delete_export(sender, instance, *args, **kwargs):
"""Clean up created files on the filesystem when an export is deleted"""
rmtree(storage.path(f"export_{instance.pk}"))
path = storage.path(f"export_{instance.pk}")
if Path(path).exists():
rmtree(path)
@receiver(post_save, sender=ActivationKeySet)

View file

@ -14,9 +14,10 @@ from .resource_generation_utils import create_planting_guide_pdf, get_filter_val
@shared_task
def generate_pdf(questionnaire_id, export_id):
q = Questionnaire.objects.get(pk=questionnaire_id)
e = Export.objects.get(pk=export_id)
z = q.zone
filename = f"export_{e.pk}/{q.slug}.pdf"
export = Export.objects.get(pk=export_id)
filename = f"export_{export.pk}/{q.slug}.pdf"
try:
create_planting_guide_pdf(
@ -34,9 +35,9 @@ def generate_pdf(questionnaire_id, export_id):
else:
if not storage.exists(filename):
raise FileNotFoundError(f"There was an error creating file: {filename}")
finally:
if e.completion >= 1:
generate_zip.delay(export_id)
if export.completion >= 1:
generate_zip.delay(export_id)
@shared_task

View file

@ -3,6 +3,7 @@ import stripe
from datetime import timedelta
from django.conf import settings
from django.http import HttpResponse, HttpResponseNotAllowed, HttpResponseNotFound, HttpResponseBadRequest, FileResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
@ -222,12 +223,15 @@ def activate_key(request):
def purchase_key(request):
"""Generate a prospective key and redirect to the Stripe payment portal"""
stripe.api_key = settings.STRIPE_API_KEY
key = ActivationKey.key_default()
redirect_url = request.build_absolute_uri(reverse(activate_key)) + f"?key={key}"
stripe_session = stripe.checkout.Session.create(
line_items=[
{
"price": "price_1Mh1I6GLlkkooLVio8W3TGkR",
"price": settings.STRIPE_PRICE_ID,
"quantity": 1,
},
],
@ -235,7 +239,7 @@ def purchase_key(request):
invoice_creation={
'enabled': True,
'invoice_data': {
'description': f'Your product code is {key}',
'description': f'Your activation key is {key}',
'rendering_options': {'amount_tax_display': 'include_inclusive_tax'},
'footer': 'BioSphere Capital Limited',
},

View file

@ -11,7 +11,6 @@ https://docs.djangoproject.com/en/3.2/ref/settings/
"""
import os
import stripe
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
@ -164,4 +163,5 @@ CELERY_TIMEZONE = TIME_ZONE
CELERY_BROKER_URL = REDIS_CELERY_URL
# Stripe payment processing
stripe.api_key = os.environ['STRIPE_API_KEY']
STRIPE_API_KEY = os.environ['STRIPE_API_KEY']
STRIPE_PRICE_ID = os.environ['STRIPE_PRICE_ID']

View file

@ -1,9 +1,14 @@
LINZ_API_KEY=myapikey
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
DATABASE_NAME=righttree
DATABASE_USER=righttree
DATABASE_PASSWORD=righttree
DATABASE_HOST=postgres
CELERY_BROKER_URL=redis://redis:6379/0
BASE_URL=localhost:8000
REDIS_HOST=redis
REDIS_PASSWORD=redis
DJANGO_SECRET_KEY=changeme
DJANGO_DEBUG_MODE=True
STRIPE_API_KEY=sk_test_key
STRIPE_PRICE_ID=price_priceid

138
dev
View file

@ -1,138 +0,0 @@
#!/bin/bash
# Load .env file if it exists
if [ -f .env ]
then
export $(cat .env | sed 's/#.*//g' | xargs)
fi
cmd_create_database() {
echo "Creating right_tree database..."
docker-compose down --remove-orphans --volumes
docker-compose -f docker-compose.yaml up postgres | sed '/PostgreSQL init process complete; ready for start up./q'
docker-compose down
}
cmd_makemigrations() {
echo "Creating database migrations..."
docker-compose exec backend python manage.py makemigrations --no-input
}
cmd_migrate() {
echo "Running database migrations..."
docker-compose exec backend python manage.py migrate
}
cmd_createsuperuser() {
echo "Creating django superuser..."
docker-compose run backend python manage.py createsuperuser
}
cmd_load_fixtures() {
echo "Loading fixtures..."
docker-compose exec backend bash -c "python manage.py loaddata right_tree/api/data/fixtures/*.json"
}
cmd_load_shapefiles() {
echo "Loading shapefiles into the database..."
docker-compose exec backend python manage.py loadshapefiles
}
cmd_create_plant_fixtures() {
echo "Creates fixtures for plants using spreadsheet."
docker-compose exec backend python manage.py createplantfixtures
}
cmd_reset_plants() {
echo "Resetting plants..."
docker-compose exec backend python manage.py resetplants
}
cmd_load_plant_fixtures() {
echo "Loading plants..."
docker-compose exec backend python manage.py loaddata right_tree/api/data/fixtures/plants.json
}
cmd_load_plants() {
cmd_create_plant_fixtures
cmd_reset_plants
cmd_load_plant_fixtures
}
cmd_load_sites_from_spreadsheet() {
echo "Loading habitats and zones..."
docker-compose exec backend python manage.py loadsitedata
}
cmd_populate_database() {
echo "Populating the database..."
docker-compose up -d backend postgres
cmd_makemigrations
cmd_migrate
cmd_load_fixtures
cmd_load_shapefiles
cmd_load_plants
docker-compose down
}
cmd_init_database() {
cmd_create_database
cmd_populate_database
}
cmd_reset_database() {
cmd_init_database
}
cmd_build() {
docker-compose build
}
cmd_start() {
docker-compose up --remove-orphans
}
cmd_collectstatic() {
docker-compose -f docker-compose.production.yaml build
docker-compose -f docker-compose.production.yaml run backend python manage.py collectstatic --no-input
}
cmd_build_frontend() {
docker run -v $PWD/frontend:/app -w /app node:16-alpine3.11 npm install
docker run -v $PWD/frontend:/app -w /app node:16-alpine3.11 mkdir -p node_modules/.cache
docker run -v $PWD/frontend:/app -w /app node:16-alpine3.11 chmod -R 777 node_modules/.cache
docker run -v $PWD/frontend:/app -w /app node:16-alpine3.11 npm run build
}
cmd_create_staticfiles() {
cmd_collectstatic
cmd_build_frontend
}
cmd_build_production() {
docker-compose -f docker-compose.production.yaml build
}
cmd_start_production() {
docker-compose -f docker-compose.production.yaml up -d --remove-orphans
}
cmd_stop_production() {
docker-compose -f docker-compose.production.yaml stop --remove-orphans
}
cmd_renew_certifcate() {
cmd_stop_production
sudo docker run -i --rm --name certbot -p 443:443 -p 80:80 -v /etc/letsencrypt:/etc/letsencrypt/ certbot/certbot renew --dry-run -d $BASE_URL --logs-dir /etc/letsencrypt/logs
cmd_start_production
}
cmd_process_svg_files() {
docker run -v $PWD/frontend/src/assets/:/app/assets -v $PWD/process_svg.py:/app/process_svg.py -w /app python:3.8-slim-bullseye python process_svg.py
}
# Run the command
cmd="$1"
"cmd_$cmd" "$@"

View file

@ -1,89 +0,0 @@
version: "3.8"
volumes:
righttree-postgres-data:
name: righttree-postgres-data
x-django: &django
image: right-tree
depends_on:
postgres:
condition: service_healthy
env_file: .env
user: "$UID:$GID"
restart: always
services:
backend:
<<: *django
container_name: backend
expose:
- "8000"
command:
- gunicorn
- --bind=0.0.0.0:8000
- right_tree.wsgi
nginx:
container_name: nginx
restart: always
image: nginx
depends_on:
- backend
volumes:
- ./nginx.production.conf:/etc/nginx/nginx.conf
- ./backend/right_tree/staticfiles:/etc/nginx/html/staticfiles
- ./frontend/build:/etc/nginx/html/build
- /etc/letsencrypt:/etc/letsencrypt
ports:
- "80:80"
- "443:443"
postgres:
image: postgis/postgis:13-3.1
restart: always
container_name: postgres
volumes:
- righttree-postgres-data:/var/lib/postgresql/data
- ./create_database.sql:/docker-entrypoint-initdb.d/create_database.sql
ports:
- "5432:5432"
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD", "pg_isready", "--dbname", "righttree", "--username", "righttree"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7.0.8
restart: always
container_name: redis
expose:
- "6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
celery:
<<: *django
container_name: celery
command:
- celery
- -A
- right_tree.api
- worker
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
deploy:
resources:
limits:
cpus: '1'

View file

@ -1,21 +1,35 @@
version: "3.8"
#version: "3.8"
volumes:
righttree-static:
name: righttree-static
external: true
righttree-media:
name: righttree-media
external: true
righttree-postgres-data:
name: righttree-postgres-data
external: true
x-django: &django
image: right-tree
depends_on:
postgres:
condition: service_healthy
volumes:
- ./backend:/app
env_file: .env
user: "$UID:$GID"
restart: unless-stopped
restart: always
volumes:
- righttree-static:/app/right_tree/staticfiles
- ./backend/right_tree/media:/app/right_tree/media
services:
collectstatic:
<<: *django
container_name: collectstatic
command:
- python
- manage.py
- collectstatic
- --noinput
restart: on-failure
backend:
<<: *django
container_name: backend
@ -23,58 +37,54 @@ services:
- "8000"
command:
- gunicorn
- --reload
- --bind=0.0.0.0:8000
- --timeout=300
- right_tree.wsgi
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
celery:
condition: service_healthy
collectstatic:
condition: service_completed_successfully
frontend:
image: node:16-bullseye
restart: unless-stopped
container_name: frontend
nginx:
container_name: nginx
restart: always
image: nginx
depends_on:
- backend
volumes:
- ./frontend:/app
working_dir: /app
user: "$UID:$GID"
- ./nginx.production.conf:/etc/nginx/nginx.conf:ro
- ./backend/right_tree/staticfiles:/etc/nginx/html/staticfiles:ro
- ./frontend/build:/etc/nginx/html/build:ro
- /etc/letsencrypt:/etc/letsencrypt:ro
ports:
- 3000:3000
command:
- npm
- start
- "80:80"
- "443:443"
postgres:
image: postgis/postgis:13-3.1
restart: unless-stopped
restart: always
container_name: postgres
volumes:
- righttree-postgres-data:/var/lib/postgresql/data
- ./create_database.sql:/docker-entrypoint-initdb.d/create_database.sql
ports:
- 5432:5432
expose:
- "5432"
environment:
POSTGRES_PASSWORD: postgres
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD", "pg_isready", "--dbname", "righttree", "--username", "righttree"]
test: ["CMD", "pg_isready", "--dbname", "$DATABASE_NAME", "--username", "$DATABASE_USER"]
interval: 10s
timeout: 5s
retries: 5
nginx:
image: nginx
restart: unless-stopped
container_name: nginx
depends_on:
- backend
- frontend
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./backend/right_tree/staticfiles:/etc/nginx/html/staticfiles:ro
ports:
- 80:80
redis:
image: redis:7.0.8
restart: unless-stopped
image: redis:7.0.10
restart: always
container_name: redis
volumes:
- ./redis.conf:/usr/local/etc/redis/redis.conf:ro
@ -98,8 +108,15 @@ services:
- right_tree.api
- worker
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "celery", "-A", "right_tree.api", "inspect", "ping"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:

View file

@ -29,7 +29,7 @@ const StepperWizard = ({children}) => {
<Box sx={{ width: '100%', height: '100%', display: "flex", flexDirection: "column", overflow: "hidden" }}>
<Stepper activeStep={step} sx={{ paddingRight: '3vw', paddingLeft: '3vw', marginBottom: '2vw' }}>
{children.map(child => (
<Tooltip title={child.props.tooltip}>
<Tooltip title={child.props.tooltip} key={child.props.label}>
<Step key={child.props.label}>
<StepLabel>{child.props.label}</StepLabel>
</Step>

View file

@ -28,9 +28,5 @@ http {
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location ~* \.(eot|otf|ttf|woff|woff2)$ {
add_header Access-Control-Allow-Origin *;
}
}
}

View file

@ -4,22 +4,25 @@ http {
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
index index.html;
include /etc/nginx/mime.types;
proxy_set_header Host $http_host;
include /etc/nginx/mime.types;
proxy_set_header Host $http_host;
ssl_certificate /etc/letsencrypt/live/rightplant.biospherecapital.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/rightplant.biospherecapital.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/rightplant.biospherecapital.com/chain.pem;
ssl_certificate /etc/letsencrypt/live/rightplant.nz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/rightplant.nz/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/rightplant.nz/chain.pem;
location / {
root /etc/nginx/html/build;
root /etc/nginx/html/build;
index index.html;
try_files $uri /index.html;
}
location /staticfiles {
root /etc/nginx/html/;
root /etc/nginx/html/;
}
location ~* ^/(api|admin) {

View file

@ -1 +1 @@
requirepass "redis"
requirepass "9zWMuCgbPaLNZhu8"