Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions .github/workflows/deploy-login-hanko.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: Deploy login-hanko to testlogin.fair.hotosm.org

on:
push:
branches:
- login_hanko
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_PREFIX: hotosm/fair

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push backend
run: |
docker build -t ghcr.io/${{ env.IMAGE_PREFIX }}/backend:login-hanko \
-f backend/Dockerfile.API \
./backend
docker push ghcr.io/${{ env.IMAGE_PREFIX }}/backend:login-hanko

- name: Build and push frontend
run: |
docker build -t ghcr.io/${{ env.IMAGE_PREFIX }}/frontend:login-hanko \
--target prod \
--build-arg VITE_AUTH_PROVIDER=${{ vars.AUTH_PROVIDER || 'hanko' }} \
--build-arg VITE_HANKO_URL=https://dev.login.hotosm.org \
--build-arg VITE_BASE_API_URL=https://testlogin.fair.hotosm.org/api/v1/ \
-f frontend/Dockerfile.prod \
./frontend
docker push ghcr.io/${{ env.IMAGE_PREFIX }}/frontend:login-hanko

deploy:
needs: build
runs-on: ubuntu-latest
environment: testlogin
steps:
- uses: actions/checkout@v4
- uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.EC2_SSH_KEY }}

- name: Add host to known_hosts
run: ssh-keyscan -H ${{ secrets.EC2_HOST }} >> ~/.ssh/known_hosts

- name: Deploy
env:
EC2_HOST: ${{ secrets.EC2_HOST }}
EC2_USER: ${{ secrets.EC2_USER }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_ACTOR: ${{ github.actor }}
COOKIE_SECRET: ${{ secrets.COOKIE_SECRET }}
AUTH_PROVIDER: ${{ vars.AUTH_PROVIDER || 'hanko' }}
run: |
ssh $EC2_USER@$EC2_HOST << ENDSSH
set -e

# Ensure Traefik is running
if ! docker ps | grep -q traefik; then
echo "ERROR: Traefik not running. Run setup-test-server.sh first."
exit 1
fi

APP_DIR="/opt/fair-test"

# Setup inicial si no existe
if [ ! -d "\$APP_DIR" ]; then
sudo mkdir -p \$APP_DIR
sudo chown \$USER:\$USER \$APP_DIR
git clone -b login_hanko https://github.com/hotosm/fAIr.git \$APP_DIR
echo "Cloned repository"
fi

cd \$APP_DIR

# Pull latest changes
git fetch origin login_hanko
git reset --hard origin/login_hanko
echo "Updated to latest login_hanko"

# Create .env with secrets
cat > .env << EOF
POSTGRES_USER=fair
POSTGRES_PASSWORD=fair
POSTGRES_DB=fair
SECRET_KEY=test-secret-key-for-testing-only-min-32-chars
COOKIE_SECRET=${COOKIE_SECRET}
AUTH_PROVIDER=${AUTH_PROVIDER}
EOF
echo "Created .env"

# Login to GHCR
echo "${GH_TOKEN}" | docker login ghcr.io -u ${GH_ACTOR} --password-stdin

# Pull and deploy
docker compose -f compose.test.yaml pull
docker compose -f compose.test.yaml up -d --force-recreate

# Cleanup
docker image prune -af

echo "Deployment complete"
ENDSSH
1 change: 1 addition & 0 deletions backend/Dockerfile.API
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ ENV UV_LINK_MODE=copy
ENV UV_PYTHON_DOWNLOADS=0

RUN apt-get update && apt-get install -y --no-install-recommends \
git \
gdal-bin \
libgdal-dev \
gcc \
Expand Down
117 changes: 117 additions & 0 deletions compose.test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# compose.test.yaml - Test environment for login-hanko branch
# Deploy to: testlogin.fair.hotosm.org
# Requires: Traefik running in /opt/traefik with hotosm-test network

services:
# Frontend syncs built files to a shared volume
frontend:
image: ghcr.io/hotosm/fair/frontend:login-hanko
restart: unless-stopped
volumes:
- frontend-html:/frontend_html
networks:
- internal

# Nginx serves the static files
nginx:
image: nginx:alpine
restart: unless-stopped
depends_on:
- frontend
volumes:
- frontend-html:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- hotosm-test
labels:
- "traefik.enable=true"
- "traefik.http.routers.fair-frontend.rule=Host(`testlogin.fair.hotosm.org`) && !PathPrefix(`/api`)"
- "traefik.http.routers.fair-frontend.entrypoints=websecure"
- "traefik.http.routers.fair-frontend.tls.certresolver=letsencrypt"
- "traefik.http.services.fair-frontend.loadbalancer.server.port=80"

backend:
image: ghcr.io/hotosm/fair/backend:login-hanko
restart: unless-stopped
depends_on:
- db
- redis
environment:
- DATABASE_URL=postgis://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
- AUTH_PROVIDER=${AUTH_PROVIDER:-hanko}
- HANKO_API_URL=https://dev.login.hotosm.org
- JWT_ISSUER=https://dev.login.hotosm.org
- COOKIE_SECRET=${COOKIE_SECRET}
- COOKIE_DOMAIN=.hotosm.org
- SECRET_KEY=${SECRET_KEY}
- REDIS_HOST=redis
- REDIS_PORT=6379
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/0
- DEBUG=True
- ALLOWED_HOSTS=testlogin.fair.hotosm.org
- ALLOWED_ORIGINS=https://testlogin.fair.hotosm.org,https://dev.login.hotosm.org
- FRONTEND_URL=https://testlogin.fair.hotosm.org
- LOGIN_URL=https://dev.login.hotosm.org
- ADMIN_EMAILS=hernangigena@gmail.com,justina@animus.com.ar,andreatchirillano@hotmail.com,emilio.mariscal@hotosm.org
command: gunicorn --bind 0.0.0.0:8000 --workers 2 fairproject.wsgi:application
networks:
- hotosm-test
- internal
labels:
- "traefik.enable=true"
- "traefik.http.routers.fair-backend.rule=Host(`testlogin.fair.hotosm.org`) && PathPrefix(`/api`)"
- "traefik.http.routers.fair-backend.entrypoints=websecure"
- "traefik.http.routers.fair-backend.tls.certresolver=letsencrypt"
- "traefik.http.services.fair-backend.loadbalancer.server.port=8000"

db:
image: postgis/postgis:16-3.4
restart: unless-stopped
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- fair-db:/var/lib/postgresql/data
networks:
- internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5

redis:
image: redis:7-alpine
restart: unless-stopped
networks:
- internal
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5

migrations:
image: ghcr.io/hotosm/fair/backend:login-hanko
command: python manage.py migrate --noinput
depends_on:
db:
condition: service_healthy
environment:
- DATABASE_URL=postgis://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
- SECRET_KEY=${SECRET_KEY}
- DEBUG=True
- ALLOWED_HOSTS=testlogin.fair.hotosm.org
networks:
- internal

networks:
hotosm-test:
external: true
internal:

volumes:
fair-db:
frontend-html:
6 changes: 5 additions & 1 deletion frontend/Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ RUN echo "Installing dependencies..." && \

COPY . .

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 3000

CMD ["pnpm", "run", "dev", "--host", "0.0.0.0"]
ENTRYPOINT ["/entrypoint.sh"]
CMD ["pnpm", "run", "dev", "--port", "3000", "--host", "0.0.0.0"]
34 changes: 25 additions & 9 deletions frontend/Dockerfile.prod
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
## docker build -t fair-frontend:latest -f Dockerfile.prod . && container_id=$(docker create fair-frontend:latest) && docker cp $container_id:/app/dist ./dist && docker rm $container_id
# Build stage
FROM node:22-slim AS build

ARG VITE_AUTH_PROVIDER
ARG VITE_HANKO_URL
ARG VITE_BASE_API_URL

ENV VITE_AUTH_PROVIDER=${VITE_AUTH_PROVIDER} \
VITE_HANKO_URL=${VITE_HANKO_URL} \
VITE_BASE_API_URL=${VITE_BASE_API_URL}

# Build stage
FROM node:22 AS builder
WORKDIR /app

COPY package.json pnpm-lock.yaml ./
# COPY .env ./.env
RUN npm install -g pnpm
RUN pnpm install --force
RUN corepack enable && corepack prepare pnpm@9 --activate
RUN pnpm install --frozen-lockfile

COPY . .
RUN pnpm run build

# Export stage
FROM alpine:latest AS exporter

# Production stage - copies dist to shared volume
FROM docker.io/rclone/rclone:1 AS prod

VOLUME /frontend_html

COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh

WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=build /app/dist /app

ENTRYPOINT ["/docker-entrypoint.sh"]
13 changes: 13 additions & 0 deletions frontend/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh

set -e

# Copy frontend to attached volume
echo "Syncing files from /app --> /frontend_html"
rclone sync /app /frontend_html
echo "Updating directory permissions 101:101 (nginx)."
chown -R 101:101 /frontend_html
echo "Sync done."

# Successful exit (stop container)
exit 0
23 changes: 23 additions & 0 deletions frontend/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh
set -e

CACHE_FILE="/app/.package-checksum"

# Calculate checksum of package.json and pnpm-lock.yaml
current_checksum=$(cat /app/package.json /app/pnpm-lock.yaml 2>/dev/null | md5sum | cut -d' ' -f1)

# Check if we need to reinstall
if [ -f "$CACHE_FILE" ]; then
cached_checksum=$(cat "$CACHE_FILE")
if [ "$current_checksum" != "$cached_checksum" ]; then
echo "📦 package.json or pnpm-lock.yaml changed, reinstalling dependencies..."
pnpm install --force
echo "$current_checksum" > "$CACHE_FILE"
echo "✅ Dependencies updated"
fi
else
# First run, save checksum
echo "$current_checksum" > "$CACHE_FILE"
fi

exec "$@"
17 changes: 17 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;

# SPA routing - serve index.html for all routes
location / {
try_files $uri $uri/ /index.html;
}

# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Loading