From 00f29513339fb7d5095d476567f60267727cd71e Mon Sep 17 00:00:00 2001 From: psuedomagi Date: Wed, 19 Jun 2024 14:35:06 -0400 Subject: [PATCH] more housekeeping --- .devenv/docker-compose.override.yml | 77 ++++++++++++++++ .devenv/docker-compose.traefik.yml | 77 ++++++++++++++++ .devenv/docker-compose.yml | 131 ++++++++++++++++++++++++++++ .env | 4 +- CODE_OF_CONDUCT.md | 4 +- README.md | 30 +++++-- 6 files changed, 310 insertions(+), 13 deletions(-) create mode 100644 .devenv/docker-compose.override.yml create mode 100644 .devenv/docker-compose.traefik.yml create mode 100644 .devenv/docker-compose.yml diff --git a/.devenv/docker-compose.override.yml b/.devenv/docker-compose.override.yml new file mode 100644 index 0000000000..9fdf8f369d --- /dev/null +++ b/.devenv/docker-compose.override.yml @@ -0,0 +1,77 @@ +services: + + proxy: + image: traefik:v2.3 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + ports: + - "80:80" + - "8090:8080" + # Duplicate the command from docker-compose.yml to add --api.insecure=true + command: + # Enable Docker in Traefik, so that it reads labels from Docker services + - --providers.docker + # Add a constraint to only use services with the label for this stack + - --providers.docker.constraints=Label(`traefik.constraint-label`, `traefik-public`) + # Do not expose all Docker services, only the ones explicitly exposed + - --providers.docker.exposedbydefault=false + # Create an entrypoint "http" listening on port 80 + - --entrypoints.http.address=:80 + # Create an entrypoint "https" listening on port 443 + - --entrypoints.https.address=:443 + # Enable the access log, with HTTP requests + - --accesslog + # Enable the Traefik log, for configurations and errors + - --log + # Enable debug logging for local development + - --log.level=DEBUG + # Enable the Dashboard and API + - --api + # Enable the Dashboard and API in insecure mode for local development + - --api.insecure=true + labels: + # Enable Traefik for this service, to make it available in the public network + - traefik.enable=true + - traefik.constraint-label=traefik-public + # Dummy https-redirect middleware that doesn't really redirect, only to + # allow running it locally + - traefik.http.middlewares.https-redirect.contenttype.autodetect=false + networks: + - traefik-public + - default + + db: + restart: "no" + ports: + - "5432:5432" + + adminer: + restart: "no" + ports: + - "8080:8080" + + backend: + restart: "no" + ports: + - "8888:8888" + volumes: + - ./backend/:/app + build: + context: ./backend + args: + INSTALL_DEV: ${INSTALL_DEV-true} + # command: sleep infinity # Infinite loop to keep container alive doing nothing + command: /start-reload.sh + + frontend: + restart: "no" + build: + context: ./frontend + args: + - VITE_API_URL=http://${DOMAIN?Variable not set} + - NODE_ENV=development + +networks: + traefik-public: + # For local dev, don't expect an external Traefik network + external: false diff --git a/.devenv/docker-compose.traefik.yml b/.devenv/docker-compose.traefik.yml new file mode 100644 index 0000000000..3c918968c2 --- /dev/null +++ b/.devenv/docker-compose.traefik.yml @@ -0,0 +1,77 @@ +services: + traefik: + image: traefik:v2.3 + ports: + # Listen on port 80, default for HTTP, necessary to redirect to HTTPS + - 80:80 + # Listen on port 443, default for HTTPS + - 443:443 + restart: always + labels: + # Enable Traefik for this service, to make it available in the public network + - traefik.enable=true + # Use the traefik-public network (declared below) + - traefik.docker.network=traefik-public + # Define the port inside of the Docker service to use + - traefik.http.services.traefik-dashboard.loadbalancer.server.port=8080 + # Make Traefik use this domain (from an environment variable) in HTTP + - traefik.http.routers.traefik-dashboard-http.entrypoints=http + - traefik.http.routers.traefik-dashboard-http.rule=Host(`traefik.${DOMAIN?Variable not set}`) + # traefik-https the actual router using HTTPS + - traefik.http.routers.traefik-dashboard-https.entrypoints=https + - traefik.http.routers.traefik-dashboard-https.rule=Host(`traefik.${DOMAIN?Variable not set}`) + - traefik.http.routers.traefik-dashboard-https.tls=true + # Use the "le" (Let's Encrypt) resolver created below + - traefik.http.routers.traefik-dashboard-https.tls.certresolver=le + # Use the special Traefik service api@internal with the web UI/Dashboard + - traefik.http.routers.traefik-dashboard-https.service=api@internal + # https-redirect middleware to redirect HTTP to HTTPS + - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https + - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true + # traefik-http set up only to use the middleware to redirect to https + - traefik.http.routers.traefik-dashboard-http.middlewares=https-redirect + # admin-auth middleware with HTTP Basic auth + # Using the environment variables USERNAME and HASHED_PASSWORD + - traefik.http.middlewares.admin-auth.basicauth.users=${USERNAME?Variable not set}:${HASHED_PASSWORD?Variable not set} + # Enable HTTP Basic auth, using the middleware created above + - traefik.http.routers.traefik-dashboard-https.middlewares=admin-auth + volumes: + # Add Docker as a mounted volume, so that Traefik can read the labels of other services + - /var/run/docker.sock:/var/run/docker.sock:ro + # Mount the volume to store the certificates + - traefik-public-certificates:/certificates + command: + # Enable Docker in Traefik, so that it reads labels from Docker services + - --providers.docker + # Do not expose all Docker services, only the ones explicitly exposed + - --providers.docker.exposedbydefault=false + # Create an entrypoint "http" listening on port 80 + - --entrypoints.http.address=:80 + # Create an entrypoint "https" listening on port 443 + - --entrypoints.https.address=:443 + # Create the certificate resolver "le" for Let's Encrypt, uses the environment variable EMAIL + - --certificatesresolvers.le.acme.email=${EMAIL?Variable not set} + # Store the Let's Encrypt certificates in the mounted volume + - --certificatesresolvers.le.acme.storage=/certificates/acme.json + # Use the TLS Challenge for Let's Encrypt + - --certificatesresolvers.le.acme.tlschallenge=true + # Enable the access log, with HTTP requests + - --accesslog + # Enable the Traefik log, for configurations and errors + - --log + # Enable the Dashboard and API + - --api + networks: + # Use the public network created to be shared between Traefik and + # any other service that needs to be publicly available with HTTPS + - traefik-public + +volumes: + # Create a volume to store the certificates, even if the container is recreated + traefik-public-certificates: + +networks: + # Use the previously created public network "traefik-public", shared with other + # services that need to be publicly available via this Traefik + traefik-public: + external: true diff --git a/.devenv/docker-compose.yml b/.devenv/docker-compose.yml new file mode 100644 index 0000000000..3f8c3cc903 --- /dev/null +++ b/.devenv/docker-compose.yml @@ -0,0 +1,131 @@ +services: + db: + image: postgres:12 + restart: always + volumes: + - app-db-data:/var/lib/postgresql/data/pgdata + env_file: + - .env + environment: + - PGDATA=/var/lib/postgresql/data/pgdata + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} + - POSTGRES_USER=${POSTGRES_USER?Variable not set} + - POSTGRES_DB=${POSTGRES_DB?Variable not set} + + adminer: + image: adminer + restart: always + networks: + - traefik-public + - default + depends_on: + - db + environment: + - ADMINER_DESIGN=pepa-linha-dark + labels: + - traefik.enable=true + - traefik.docker.network=traefik-public + - traefik.constraint-label=traefik-public + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-http.rule=Host(`adminer.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-http.entrypoints=http + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-http.middlewares=https-redirect + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.rule=Host(`adminer.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.entrypoints=https + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.tls=true + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.tls.certresolver=le + - traefik.http.services.${STACK_NAME?Variable not set}-adminer.loadbalancer.server.port=8080 + + backend: + image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' + restart: always + networks: + - traefik-public + - default + depends_on: + - db + env_file: + - .env + environment: + - DOMAIN=${DOMAIN} + - ENVIRONMENT=${ENVIRONMENT} + - BACKEND_CORS_ORIGINS=${BACKEND_CORS_ORIGINS} + - SECRET_KEY=${SECRET_KEY?Variable not set} + - FIRST_SUPERUSER=${FIRST_SUPERUSER?Variable not set} + - FIRST_SUPERUSER_PASSWORD=${FIRST_SUPERUSER_PASSWORD?Variable not set} + - USERS_OPEN_REGISTRATION=${USERS_OPEN_REGISTRATION} + - SMTP_HOST=${SMTP_HOST} + - SMTP_USER=${SMTP_USER} + - SMTP_PASSWORD=${SMTP_PASSWORD} + - EMAILS_FROM_EMAIL=${EMAILS_FROM_EMAIL} + - POSTGRES_SERVER=db + - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_DB=${POSTGRES_DB} + - POSTGRES_USER=${POSTGRES_USER?Variable not set} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} + - SENTRY_DSN=${SENTRY_DSN} + + build: + context: ./backend + args: + INSTALL_DEV: ${INSTALL_DEV-false} + platform: linux/amd64 # Patch for M1 Mac + labels: + - traefik.enable=true + - traefik.docker.network=traefik-public + - traefik.constraint-label=traefik-public + + - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=80 + + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`) && PathPrefix(`/api`, `/docs`, `/redoc`) + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.entrypoints=http + + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`) && PathPrefix(`/api`, `/docs`, `/redoc`) + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.entrypoints=https + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls=true + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls.certresolver=le + + # Define Traefik Middleware to handle domain with and without "www" to redirect to only one + - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.regex=^http(s)?://www.(${DOMAIN?Variable not set})/(.*) + # Redirect a domain with www to non-www + - traefik.http.middlewares.${STACK_NAME?Variable not set}-www-redirect.redirectregex.replacement=http$${1}://${DOMAIN?Variable not set}/$${3} + + # Enable www redirection for HTTP and HTTPS + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.middlewares=https-redirect,${STACK_NAME?Variable not set}-www-redirect + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.middlewares=${STACK_NAME?Variable not set}-www-redirect + + frontend: + image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}' + restart: always + networks: + - traefik-public + - default + build: + context: ./frontend + args: + - VITE_API_URL=https://${DOMAIN?Variable not set} + - NODE_ENV=production + labels: + - traefik.enable=true + - traefik.docker.network=traefik-public + - traefik.constraint-label=traefik-public + + - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 + + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.entrypoints=http + + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`${DOMAIN?Variable not set}`, `www.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.entrypoints=https + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls=true + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls.certresolver=le + + # Enable www redirection for HTTP and HTTPS + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.middlewares=${STACK_NAME?Variable not set}-www-redirect + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.middlewares=https-redirect,${STACK_NAME?Variable not set}-www-redirect +volumes: + app-db-data: + +networks: + traefik-public: + # Allow setting it to false for testing + external: true diff --git a/.env b/.env index cd90c54b90..c9c1d47084 100644 --- a/.env +++ b/.env @@ -5,8 +5,8 @@ DOMAIN=localhost # Environment: local, staging, production ENVIRONMENT=local -PROJECT_NAME="Full Stack FastAPI Project" -STACK_NAME=full-stack-fastapi-project +PROJECT_NAME="ChatHoard" +STACK_NAME=chat-horde-stack # Backend BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.tiangolo.com" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 2590ad215e..218bf141bc 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ -# Contributor Code of Conduct +# ChatHoard Contributor Code of Conduct ## Contributions welcome! @@ -30,7 +30,7 @@ You have the right and responsibility to remove, edit, or reject comments, commi ## Scope -Always applies. Any behavior that doesn't align with these standards in *any* setting and will not be tolerated. If you can't be accepting, civil, and respectful, then you are not welcome to contribute to this project. +Always applies. I will not tolerate any behavior that doesn't align with these standards in *any* setting. If you can't be accepting, civil, and respectful, then you aren't welcome here. ## Enforcement diff --git a/README.md b/README.md index 0f55151446..92bef5e941 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,41 @@ -# ChatHoard - Take Control of Your Chats +# ChatHoard - Take Control of Your Chat History -ChatHoard is a ready-to-deploy full-stake web app that allows your to upload and store your ChatGPT chats (other chat formats planned). It offers robust search and filtering capabilities, and is lightning fast - based on Sebastián Ramírez's Full-StackFastAPI-Template with FastAPI, Pydantic validation/serialization, SQLModel for ORM, and a front-end with React using Chakra UI. It can be deployed locally with Docker Compose in a minimalist deployment, or publicly with authentication and authorization using OAuth2 with JWT tokens and multi-user support. I created ChatHoard to help me manage my ChatGPT chats, but it's designed to scale robustly and will easily extend to other chat formats with added Pydantic models. +ChatHoard is a ready-to-deploy full-stack web app for making the most of your ChatGPT chat history (other chat formats planned). ChatHorde offers robust search and filtering capabilities, and is lightning fast - based on Full-Stack-FastAPI-Template with FastAPI, Pydantic validation/serialization, SQLModel for ORM, and a front-end with React using Chakra UI. You can be deploy ChatHorde locally with Docker Compose in a minimalist deployment, or publicly with authentication and authorization using OAuth2 with JWT tokens and multi-user support. I created ChatHoard to help me manage my ChatGPT chats, but it's designed to scale robustly and can extend to other chat formats with added Pydantic models. I was continually frustrated by how hard it is to search my ChatGPT chats and the lack of organization capabilities. The Pydantic models are robust and offer a lot of definition for the data; most of this isn't used in the current version, but it would put anyone wanting to run ML models on the data in a good position. -## Currently in active, early development. More to follow. Stay tuned +## Currently in active, early development. More to follow. Stay tuned. NOT YET FUNCTIONAL -## Planned Features +## Planned Initial Features + +User Interface: - [ ] Modern full-featured UI with React and Chakra UI - [ ] Superb search and filtering capabilities (powered by meilisearch) -- [ ] Group and organize chats -- [ ] Tag chats -- [ ] Add notes to chats +- [ ] Easy graphical import/export of chats + +Backend: - [ ] Minimalist local and full/public deployment options with Docker Compose - [ ] OAuth2 with JWT tokens -- [ ] Passkey authentication - [ ] Multi-user support - [ ] Admin/owner UI panel for managing users, permissions, and settings ### Later Planned Features -- [ ] Support for other chat formats (Gemini and Claude); if you want to help add support for your favorite chat format, please reach out. I'd love to collaborate. +- [ ] Group and organize chats +- [ ] Tag chats +- [ ] Add notes to chats +- [ ] Automated download requests for new chats from ChatGPT (these are emailed to you); and deletion of old chats from OpenAI +- [ ] Additional encryption options (i.e. client-side encryption) +- [ ] Support for other chat formats (Gemini and Claude); I welcome your help adding support for your favorite chat format, please reach out. I'd love to collaborate. - [ ] ML-powered search assistant - [ ] Chat sharing and in-line commenting +- [ ] Passkey/webauthn authentication +- [ ] NLP-powered groupings and tagging + +### Maybe Later Features + +- [ ] Fully private subscription-based cloud deployment/hosting or SaaS??? (I'd do it for free if I could, but, you know, money)