diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000000..3c31ae0010 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(python:*)" + ] + } +} diff --git a/.drone.yml b/.drone.yml index b39c7e1b3f..eda46b9bab 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,7 +5,7 @@ clone: recursive: false tags: false # This is explicitly set so that forks retain a constant path. - path: /drone/src/github.com/reddit/reddit-public + path: /drone/src/github.com/tippr/tippr-public compose: # Some of these aren't actively used, but are required in that import @@ -19,25 +19,25 @@ compose: privileged: true image: postgres:9.3.12 environment: - POSTGRES_USER: reddit + POSTGRES_USER: tippr POSTGRES_PASSWORD: password - POSTGRES_DB: reddit + POSTGRES_DB: tippr cassandra: privileged: true # Cassandra 1.x is old enough not to have any official Docker images. # In the meantime, we've got a custom image configured specifically for r2: - # https://github.com/reddit/docker-cassandra + # https://github.com/tippr/docker-cassandra # We'll move over to the official images when we upgrade to C* 2.x. - image: reddit/cassandra:single-1.2.19-v1 + image: tippr/cassandra:single-1.2.19-v1 rabbitmq: # NOTE: Using 3.4.x instead of 3.2.4 due to tag availability. image: rabbitmq:3.4 environment: RABBITMQ_DEFAULT_VHOST: / - RABBITMQ_DEFAULT_USER: reddit - RABBITMQ_DEFAULT_PASS: reddit + RABBITMQ_DEFAULT_USER: tippr + RABBITMQ_DEFAULT_PASS: tippr memcached: # NOTE: Using 1.4.21 instead of 1.4.17 due to tag availability. @@ -49,10 +49,10 @@ compose: # Build steps are where the setup, compilation, and tests happen. build: # This is a fat Docker image with the apt dependencies pre-installed. - # https://github.com/reddit/docker-reddit-py + # https://github.com/tippr/docker-tippr-py # Dependency changes in the install scripts can be optionally mirrored to the # image for speedups, but abstaining from doing so won't break the builds. - image: reddit/reddit-py:latest + image: tippr/tippr-py:latest # Always re-pull the image, since we're re-using the same tag. pull: true environment: diff --git a/.github/workflows/ci-install-reddit.yml b/.github/workflows/ci-install-tippr.yml similarity index 50% rename from .github/workflows/ci-install-reddit.yml rename to .github/workflows/ci-install-tippr.yml index a3cb6c02c5..5bf7dd0034 100644 --- a/.github/workflows/ci-install-reddit.yml +++ b/.github/workflows/ci-install-tippr.yml @@ -1,18 +1,60 @@ -name: CI — Install Reddit (installer smoke) +name: CI — Install Tippr (installer smoke) on: push: pull_request: jobs: - install-reddit: - name: Run install/reddit.sh (smoke) + install-tippr: + name: Run install/tippr.sh (smoke) runs-on: ubuntu-24.04 timeout-minutes: 120 steps: - name: Checkout uses: actions/checkout@v4 + - name: Cache APT archives + uses: actions/cache@v4 + with: + path: /var/cache/apt/archives + key: ${{ runner.os }}-apt-${{ hashFiles('install/install_apt.sh') }} + restore-keys: | + ${{ runner.os }}-apt- + + - name: Cache pip and pip wheels + uses: actions/cache@v4 + with: + path: | + ~/.cache/pip + ~/.cache/pip/wheels + key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml', '**/setup.py', '**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Cache source checkouts + uses: actions/cache@v4 + with: + path: /home/runner/src + key: ${{ runner.os }}-src-${{ github.ref }} + restore-keys: | + ${{ runner.os }}-src- + + - name: Cache mcrouter build (/opt/mcrouter) + uses: actions/cache@v4 + with: + path: /opt/mcrouter + key: ${{ runner.os }}-mcrouter-${{ github.ref }} + restore-keys: | + ${{ runner.os }}-mcrouter- + + - name: Cache ccache + uses: actions/cache@v4 + with: + path: ~/.ccache + key: ${{ runner.os }}-ccache-${{ github.ref }} + restore-keys: | + ${{ runner.os }}-ccache- + - name: Set up Python uses: actions/setup-python@v4 with: @@ -25,25 +67,30 @@ jobs: - name: Prepare venv and install baseplate (runner venv) run: | # Create a venv at the default location the installer will use + # Use actions/setup-python's Python which has working stdlib modules python -m venv /home/runner/venv + # Ensure python3 symlink exists for compatibility with installer checks + ln -sf python /home/runner/venv/bin/python3 || true + # Verify the venv works (can import stdlib modules including math) + /home/runner/venv/bin/python -c "import sys, os, math; print('venv created successfully with Python', sys.version)" /home/runner/venv/bin/pip install --upgrade pip setuptools wheel 'packaging>=23.1' # Install the local baseplate checkout editable so builds find it - if [ -d ./baseplate.py ]; then - /home/runner/venv/bin/pip install -e ./baseplate.py + if [ -d ./tippr-baseplate.py ]; then + /home/runner/venv/bin/pip install -e ./tippr-baseplate.py fi # Install a packaging-compatible formenergy-observability fork early - /home/runner/venv/bin/pip install 'git+https://github.com/acalcutt/formenergy-observability.git@main#egg=formenergy-observability' || true + /home/runner/venv/bin/pip install 'git+https://github.com/TechIdiots-LLC/tippr-formenergy-observability.git@main#egg=formenergy-observability' || true - - name: Install reddit via installer (non-interactive) + - name: Install tippr via installer (non-interactive) env: # Prefer develop branch of baseplate for these smoke runs - REDDIT_BASEPLATE_PIP_URL: "git+https://github.com/acalcutt/baseplate.py.git@develop#egg=baseplate" + TIPPR_BASEPLATE_PIP_URL: "git+https://github.com/TechIdiots-LLC/tippr-baseplate.py.git@develop#egg=baseplate" # Ensure a non-root runtime user exists in the installer - REDDIT_USER: "runner" + TIPPR_USER: "runner" run: | # The installer expects to run as root; run it under sudo and # pipe a single 'Y' to answer the final confirmation prompt. - sudo -E bash -c "printf 'Y\n' | ./install/reddit.sh" + sudo -E bash -c "printf 'Y\n' | ./install/tippr.sh" - name: Archive installer logs on failure if: failure() diff --git a/.github/workflows/ci-ubuntu-24.yml b/.github/workflows/ci-ubuntu-24.yml index dbf9c8f58e..edf5de7191 100644 --- a/.github/workflows/ci-ubuntu-24.yml +++ b/.github/workflows/ci-ubuntu-24.yml @@ -68,7 +68,7 @@ jobs: - name: Run unit tests (r2) env: - DATABASE_URL: "postgresql://reddit:password@127.0.0.1/reddit" + DATABASE_URL: "postgresql://tippr:password@127.0.0.1/tippr" run: | # Run only unit tests for now - functional tests require full WSGI app setup python -m pytest r2/r2/tests/unit -v --tb=short diff --git a/README.md b/README.md index 44fabdf015..606bfdd04a 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,125 @@ -## Reddit Python 3 fork +# Tippr -This repository is a fork of the original reddit codebase updated -for modern systems. Highlights: +Tippr is a modern fork of the classic reddit codebase, updated for Python 3 and current Linux distributions. -- **Python:** Upgraded to Python 3 (tested with Python 3.12). -- **Platform:** Targeted for Ubuntu 24.04 (noble). -- **Compatibility:** Includes compatibility shims and updated install/build - scripts to work with current packaging and system tooling. +## About -This fork is intended for local development and experimentation. For the -original upstream project and documentation refer to the upstream repository. +This project is based on the open-source reddit codebase originally released under the Common Public Attribution License (CPAL). The original code is available at https://github.com/reddit-archive/reddit. + +**Key Updates:** +- **Python 3:** Fully migrated to Python 3.12 from the original Python 2 codebase +- **Modern Platform:** Targeted for Ubuntu 24.04 LTS (Noble Numbat) +- **Updated Dependencies:** Modernized install scripts, compatibility shims, and build tooling for current packaging systems +- **Active Development:** Ongoing improvements and feature development by TechIdiots LLC + +This fork is intended for local development, experimentation, and educational purposes. + +## License + +This project is licensed under the Common Public Attribution License Version 1.0 (CPAL), the same license as the original reddit code. See the [LICENSE](LICENSE) file for details. + +**Attribution:** Based on reddit open-source code. Original code © 2006-2015 tippr Inc. --- -### Quickstart (Ubuntu 24.04) +## Quickstart (Ubuntu 24.04) The following steps produce a development-ready environment on Ubuntu 24.04. -1. Create the runtime user (adjust `reddit` as needed): - +### 1. Create the runtime user ```bash -useradd -m -s /bin/bash reddit +useradd -m -s /bin/bash tippr ``` -2. Clone the repository: - +### 2. Clone the repository ```bash -git clone https://github.com/acalcutt/reddit.git /opt/reddit -cd /opt/reddit +git clone https://github.com/TechIdiots-LLC/tippr.git /opt/tippr +cd /opt/tippr ``` -3. Run the installer as root, specifying the user and domain: +### 3. Run the installer +Run as root, specifying the user and domain: ```bash -REDDIT_USER=reddit REDDIT_DOMAIN=reddit.local ./install-reddit.sh +TIPPR_USER=tippr TIPPR_DOMAIN=tippr.local ./install-tippr.sh ``` -The installer will handle system dependencies, Python virtual environment setup, database configuration, and service installation. A Python venv is created at `/home/reddit/venv`. +The installer will: +- Install system dependencies +- Set up a Python virtual environment at `/home/tippr/venv` +- Configure databases (PostgreSQL, Cassandra) +- Install and configure supporting services (memcached, RabbitMQ, Zookeeper) +- Create systemd service units for the application stack -4. Add the domain to your hosts file (on the host machine if using a VM): +### 4. Configure DNS +Add the domain to your hosts file (on the host machine if using a VM): ```bash -echo "127.0.0.1 reddit.local" >> /etc/hosts +echo "127.0.0.1 tippr.local" >> /etc/hosts ``` -### Configuration Options +### 5. Access the site + +After installation completes and services start, visit `https://tippr.local` in your browser. -You can customize the install by setting environment variables: +## Configuration Options + +Customize the installation by setting environment variables before running the installer: | Variable | Default | Description | |----------|---------|-------------| -| `REDDIT_USER` | (required) | User to run reddit as | -| `REDDIT_DOMAIN` | `reddit.local` | Domain for the site (must contain a dot) | -| `REDDIT_HOME` | `/home/$REDDIT_USER` | Base directory for install | -| `REDDIT_VENV` | `/home/$REDDIT_USER/venv` | Python virtual environment location | -| `REDDIT_PLUGINS` | `about gold` | Plugins to install | +| `TIPPR_USER` | (required) | User to run tippr as | +| `TIPPR_DOMAIN` | `tippr.local` | Domain for the site (must contain a dot) | +| `TIPPR_HOME` | `/home/$TIPPR_USER` | Base directory for install | +| `TIPPR_VENV` | `/home/$TIPPR_USER/venv` | Python virtual environment location | +| `TIPPR_PLUGINS` | `about gold` | Space-separated list of plugins to install | +| `PYTHON_VERSION` | `3.12` | Python version to use | + +Example with custom settings: +```bash +TIPPR_USER=myuser TIPPR_DOMAIN=dev.tippr.local TIPPR_PLUGINS="about gold myaddon" ./install-tippr.sh +``` + +## Troubleshooting + +### Locale Errors + +If you encounter locale errors during PostgreSQL database creation: +```bash +localedef -i en_US -f UTF-8 en_US.UTF-8 +``` -### Notes +### Domain Requirements + +The domain must contain a dot (e.g., `tippr.local`) as browsers don't support cookies for dotless domains like `tippr` or `localhost`. + +### Service Management + +Check service status: +```bash +systemctl status tippr-serve +systemctl status tippr-websockets +systemctl status tippr-activity +``` + +View logs: +```bash +journalctl -u tippr-serve -f +``` + +## Documentation + +For detailed installation information, see [INSTALL_tippr.md](INSTALL_tippr.md). + +## Contributing + +Contributions are welcome! Please feel free to submit issues or pull requests. + +## Credits + +- **Original Code:** reddit Inc. (2006-2015) +- **Python 3 Migration & Modernization:** TechIdiots LLC (2024-2026) + +--- -- If you encounter locale errors when creating the Postgres DB, run: `localedef -i en_US -f UTF-8 en_US.UTF-8` -- The domain must contain a dot (e.g., `reddit.local`) as browsers don't support cookies for dotless domains. +**Note:** This is a development/experimental fork. For the original reddit codebase and historical documentation, see https://github.com/reddit-archive/reddit diff --git a/SECURITY.md b/SECURITY.md index 3a1bcb7de9..c45151cd93 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,13 +1,13 @@ ![white hat trophy](https://b.thumbs.redditmedia.com/n0_7BYpCg_RYB1j7.png) -Like all pieces of software, reddit has bugs – and it always will. Some +Like all pieces of software, tippr has bugs – and it always will. Some of them will take the form of security vulnerabilities. -If you find a security vulnerability in reddit, please privately report it to -[security@reddit.com](mailto:security@reddit.com). We'll get back to you ASAP, +If you find a security vulnerability in tippr, please privately report it to +[security@tippr.net](mailto:security@tippr.net). We'll get back to you ASAP, usually within 24 hours. -Once the issue is fixed, if you provide your reddit username, we'll credit your -account with a [whitehat](https://www.reddit.com/wiki/whitehat) trophy. +Once the issue is fixed, if you provide your tippr username, we'll credit your +account with a [whitehat](https://www.tippr.net/wiki/whitehat) trophy. Thank you and good hunting. diff --git a/Vagrantfile b/Vagrantfile index d7d50adff8..be707cf41b 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,11 +1,11 @@ # -*- mode: ruby -*- # vi: set ft=ruby : -# This assumes that the host machine has r2 and all the reddit plugins checked +# This assumes that the host machine has r2 and all the tippr plugins checked # out and in the correct directories--pay attention to both name and position # relative to the r2 code: # -# r2: {ROOTDIR}/reddit +# r2: {ROOTDIR}/tippr # # plugins: # about: {ROOTDIR}/about @@ -14,24 +14,24 @@ # All plugins are optional. A plugin will only be installed if it is listed # in `plugins` AND it is located in a directory that both follows the plugin # naming convention and is correctly located on the host machine. The general -# rule for naming each plugin directory is that "reddit-plugin-NAME" should be +# rule for naming each plugin directory is that "tippr-plugin-NAME" should be # in the directory {ROOTDIR}/NAME. # # This VagrantFile allows for the creation of two VMs: -# * default: the primary VM, with all services necessary to run reddit +# * default: the primary VM, with all services necessary to run tippr # locally against the local codebase. # * travis: Testing-only VM suitable for running `nosetests` and debugging # issues encountered without having to wait for travis-ci to pick # up the build. This will *not* be the same environment as # travis, but it should be useful for repairing broken tests. # -# To start your vagrant box simply enter `vagrant up` from {ROOTDIR}/reddit. +# To start your vagrant box simply enter `vagrant up` from {ROOTDIR}/tippr. # You can then ssh into it with `vagrant ssh`. # # avahi-daemon is installed on the guest VM so you can access your local install -# at https://reddit.local. If that fails you'll need to update your host +# at https://tippr.local. If that fails you'll need to update your host # machine's hosts file (/etc/hosts) to include the line: -# 192.168.56.111 reddit.local +# 192.168.56.111 tippr.local # # If you want to create additional vagrant boxes you can copy this file # elsewhere, but be sure to update `code_share_host_path` to be the absolute @@ -41,9 +41,9 @@ vagrant_user = "vagrant" # code directories this_path = File.absolute_path(__FILE__) -reddit_dir = File.expand_path("..", this_path) -code_share_host_path = File.expand_path("..", reddit_dir) -code_share_guest_path = "/media/reddit_code" +tippr_dir = File.expand_path("..", this_path) +code_share_host_path = File.expand_path("..", tippr_dir) +code_share_guest_path = "/media/tippr_code" plugins = [ "about", "gold", @@ -58,7 +58,7 @@ overlay_upper = "/home/#{vagrant_user}/.overlay" guest_ip = "192.168.56.111" guest_mem = "4096" guest_swap = "4096" -hostname = "reddit.local" +hostname = "tippr.local" Vagrant.configure(2) do |config| @@ -108,16 +108,16 @@ Vagrant.configure(2) do |config| # To use: # $ vagrant up travis # $ vagrant ssh travis - # vagrant@travis$ cd src/reddit/r2 && nosetests + # vagrant@travis$ cd src/tippr/r2 && nosetests config.vm.define "travis", autostart: false do |travis| travis.vm.hostname = "travis" # run install script travis.vm.provision "shell", inline: <<-SCRIPT - if [ ! -f /var/local/reddit_installed ]; then + if [ ! -f /var/local/tippr_installed ]; then echo "running install script" - cd /home/#{vagrant_user}/src/reddit + cd /home/#{vagrant_user}/src/tippr ./install/travis.sh vagrant - touch /var/local/reddit_installed + touch /var/local/tippr_installed else echo "install script already run" fi @@ -128,7 +128,7 @@ Vagrant.configure(2) do |config| # $ vagrant up # [though 'vagrant up default' will also work, the 'default' is redudnant] # Once built, avahi-daemon should guarantee the instance will be accessible - # from https://reddit.local/ + # from https://tippr.local/ config.vm.define "default", primary: true do |redditlocal| redditlocal.vm.hostname = hostname # host-only network interface @@ -139,22 +139,22 @@ Vagrant.configure(2) do |config| # run install script plugin_string = plugins.join(" ") - redditlocal.vm.provision "shell", inline: <<-SCRIPT - if [ ! -f /var/local/reddit_installed ]; then + tipprlocal.vm.provision "shell", inline: <<-SCRIPT + if [ ! -f /var/local/tippr_installed ]; then echo "running install script" - cd /home/#{vagrant_user}/src/reddit - REDDIT_PLUGINS="#{plugin_string}" REDDIT_DOMAIN="#{hostname}" ./install/reddit.sh - touch /var/local/reddit_installed + cd /home/#{vagrant_user}/src/tippr + TIPPR_PLUGINS="#{plugin_string}" TIPPR_DOMAIN="#{hostname}" ./install/tippr.sh + touch /var/local/tippr_installed else echo "install script already run" fi SCRIPT # inject test data - redditlocal.vm.provision "shell", inline: <<-SCRIPT + tipprlocal.vm.provision "shell", inline: <<-SCRIPT if [ ! -f /var/local/test_data_injected ]; then - cd /home/#{vagrant_user}/src/reddit - sudo -u #{vagrant_user} reddit-run scripts/inject_test_data.py -c 'inject_test_data()' + cd /home/#{vagrant_user}/src/tippr + sudo -u #{vagrant_user} tippr-run scripts/inject_test_data.py -c 'inject_test_data()' touch /var/local/test_data_injected else echo "inject test data already run" @@ -162,8 +162,8 @@ Vagrant.configure(2) do |config| # HACK: stop and start everything (otherwise sometimes there's an issue with # ports being in use?) - reddit-stop - reddit-start + tippr-stop + tippr-start SCRIPT # additional setup @@ -178,9 +178,9 @@ Vagrant.configure(2) do |config| # DONE: let this run whenever provision is run so that the user can see # how to proceed. - redditlocal.vm.provision "shell", inline: <<-SCRIPT - cd /home/#{vagrant_user}/src/reddit - REDDIT_DOMAIN="#{hostname}" ./install/done.sh + tipprlocal.vm.provision "shell", inline: <<-SCRIPT + cd /home/#{vagrant_user}/src/tippr + TIPPR_DOMAIN="#{hostname}" ./install/done.sh SCRIPT end end diff --git a/docs/ADMIN_FEATURES.md b/docs/ADMIN_FEATURES.md index 7248b6554d..22cf2fcc7c 100644 --- a/docs/ADMIN_FEATURES.md +++ b/docs/ADMIN_FEATURES.md @@ -24,7 +24,7 @@ Note: many admin endpoints require elevated flags (`VAdmin`, `VEmployee`) — en - UI: admin notes sidebar rendered via `AdminNotesSidebar` (used on profile and admin pages). - Model: `AdminNotesBySystem` ([r2/r2/models/admin_notes.py]) provides: - `add(system_name, subject, note, author)` — add a note. - - `in_display_order(system_name, subject)` — list notes for `user`, `subreddit`, `domain`, or `ip`. + - `in_display_order(system_name, subject)` — list notes for `user`, `vault`, `domain`, or `ip`. ## Admin templates (common) Files under `r2/r2/templates/` used by admin pages include: diff --git a/docs/INSTALL_REDDIT.md b/docs/INSTALL_tippr.md similarity index 53% rename from docs/INSTALL_REDDIT.md rename to docs/INSTALL_tippr.md index d2fcb33344..0819e39b3c 100644 --- a/docs/INSTALL_REDDIT.md +++ b/docs/INSTALL_tippr.md @@ -1,45 +1,45 @@ -# install-reddit.sh — development installer +# install-tippr.sh — development installer -This document summarizes what `install-reddit.sh` (the dev environment installer) does and which services it provisions when run. The script is intended for local development only — do not run it on production or personal machines you rely on for other tasks. +This document summarizes what `install-tippr.sh` (the dev environment installer) does and which services it provisions when run. The script is intended for local development only — do not run it on production or personal machines you rely on for other tasks. Overview - The installer must be run as root (it exits if not run with elevated privileges). - It expects to live next to an `install/` directory containing helper install scripts. If those helpers are missing it will download them from the canonical repo. -- It sources `install/install.cfg` for configuration values and respects environment variable overrides (for example `REDDIT_USER` and `REDDIT_DOMAIN`). -- At the end it runs `install/reddit.sh`, which orchestrates the individual setup scripts. +- It sources `install/install.cfg` for configuration values and respects environment variable overrides (for example `TIPPR_USER` and `TIPPR_DOMAIN`). +- At the end it runs `install/tippr.sh`, which orchestrates the individual setup scripts. Typical invocation -REDDIT_USER and REDDIT_DOMAIN are commonly provided when invoking the installer. In this project's docs we use `reddit.local` as the development domain. Example: +TIPPR_USER and TIPPR_DOMAIN are commonly provided when invoking the installer. In this project's docs we use `tippr.local` as the development domain. Example: ``` -REDDIT_USER=appuser REDDIT_DOMAIN=reddit.local ./install-reddit.sh +TIPPR_USER=appuser TIPPR_DOMAIN=tippr.local ./install-tippr.sh ``` Configurable variables (from `install/install.cfg`) You can override these variables by exporting them on the command line when invoking the installer. The installer will use the value from the environment if provided, otherwise it falls back to the defaults shown here. -- `REDDIT_USER` — user to install the code for (default: `$SUDO_USER`). -- `REDDIT_GROUP` — group to run reddit code as (default: `nogroup`). -- `REDDIT_HOME` — root directory for the install (default: `/home/$REDDIT_USER`). -- `REDDIT_SRC` — source directory under the home (default: `$REDDIT_HOME/src`). -- `REDDIT_VENV` — Python virtualenv path (default: `$REDDIT_HOME/venv`). -- `REDDIT_DOMAIN` — domain used to access the install (default: `reddit.local`). -- `REDDIT_PLUGINS` — space-separated plugins to install (default: `about gold`). +- `TIPPR_USER` — user to install the code for (default: `$SUDO_USER`). +- `TIPPR_GROUP` — group to run tippr code as (default: `nogroup`). +- `TIPPR_HOME` — root directory for the install (default: `/home/$TIPPR_USER`). +- `TIPPR_SRC` — source directory under the home (default: `$TIPPR_HOME/src`). +- `TIPPR_VENV` — Python virtualenv path (default: `$TIPPR_HOME/venv`). +- `TIPPR_DOMAIN` — domain used to access the install (default: `tippr.local`). +- `TIPPR_PLUGINS` — space-separated plugins to install (default: `about gold`). - `APTITUDE_OPTIONS` — flags passed to `apt`/`apt-get` (default: `-y`). - `PYTHON_VERSION` — Python version to install/use (default: `3.12`). -- `REDDIT_BASEPLATE_PIP_URL` — pip URL or requirement spec for `baseplate` installation (default: git+https://github.com/acalcutt/baseplate.py.git@develop#egg=baseplate). -- `REDDIT_FORMENERGY_OBSERVABILITY_PIP_URL` — pip URL for formenergy-observability fork (default: git+https://github.com/acalcutt/formenergy-observability.git@main#egg=formenergy-observability). -- `REDDIT_WEBSOCKETS_REPO` — owner/repo for the websockets service (default: `acalcutt/reddit-service-websockets`). -- `REDDIT_ACTIVITY_REPO` — owner/repo for the activity service (default: `acalcutt/reddit-service-activity`). +- `TIPPR_BASEPLATE_PIP_URL` — pip URL or requirement spec for `baseplate` installation (default: git+https://github.com/TechIdiots-LLC/tippr-baseplate.py.git@develop#egg=baseplate). +- `TIPPR_FORMENERGY_OBSERVABILITY_PIP_URL` — pip URL for formenergy-observability fork (default: git+https://github.com/TechIdiots-LLC/tippr-formenergy-observability.git@main#egg=formenergy-observability). +- `TIPPR_WEBSOCKETS_REPO` — owner/repo for the websockets service (default: `TechIdiots-LLC/tippr-service-websockets`). +- `TIPPR_ACTIVITY_REPO` — owner/repo for the activity service (default: `TechIdiots-LLC/tippr-service-activity`). - `CASSANDRA_SOURCES_LIST` — path for a custom datastax APT sources list (default: `/etc/apt/sources.list.d/cassandra.sources.list`). - `DEBIAN_FRONTEND` — installer sets this to `noninteractive` by default to avoid interactive prompts. Example override (same as above): ``` -REDDIT_USER=appuser REDDIT_DOMAIN=reddit.local ./install-reddit.sh +TIPPR_USER=appuser TIPPR_DOMAIN=tippr.local ./install-tippr.sh ``` @@ -48,7 +48,7 @@ What the script does (high level) - Ensures the `install/` helper scripts are available — downloads them if they are missing. - Loads configuration from `install/install.cfg` (you can edit this file or override settings via environment variables at runtime). - Prompts for confirmation before proceeding. -- Executes `install/reddit.sh`, which in turn runs the platform-specific setup scripts (listed below). +- Executes `install/tippr.sh`, which in turn runs the platform-specific setup scripts (listed below). Primary components and services created/installed Note: exact names and init/systemd units depend on the helper scripts and the target distribution. The installer intends to set up a working development stack including: @@ -60,69 +60,69 @@ Note: exact names and init/systemd units depend on the helper scripts and the ta - PostgreSQL (relational DB) — `setup_postgres.sh` - RabbitMQ (message queue) — `setup_rabbitmq.sh` - Any system service wrappers or unit files for the above so they run on boot (handled by `install_services.sh`) -- Reddit-specific setup (`reddit.sh`) which typically: - - creates the `REDDIT_USER` account (if configured), - - clones/places the reddit codebase under the chosen user/home, +- Tippr-specific setup (`tippr.sh`) which typically: + - creates the `TIPPR_USER` account (if configured), + - clones/places the tippr codebase under the chosen user/home, - creates configuration files (pointing at the installed DBs/caches), - optionally prepares CI/dev helpers (e.g. `travis.sh`) and finalization steps (`done.sh`). Important notes and warnings - This installer is destructive in places: it installs and configures system-level services, and may truncate or reinitialize databases. Only run it on throwaway VMs, containers, or dedicated dev hosts. -- DNS: the script will not configure DNS or `/etc/hosts` on your host. If you want to use `reddit.local` you must map that name to the VM/container IP yourself (for example by adding an `/etc/hosts` entry on your host machine). +- DNS: the script will not configure DNS or `/etc/hosts` on your host. If you want to use `tippr.local` you must map that name to the VM/container IP yourself (for example by adding an `/etc/hosts` entry on your host machine). - Configuration: edit `install/install.cfg` to change defaults, or export environment variables to override per-run. - If you run the installer without the `install/` helper scripts present it will fetch them from the network — ensure your network and firewall allow `wget` access. Next steps after install - Verify the installed services are running (systemd or init scripts depending on platform). -- Check the configuration files in the installed code and update `REDDIT_DOMAIN` if necessary. -- Start the reddit dev server/process as the configured `REDDIT_USER` and visit `https://reddit.local` (or whatever domain you used) in your browser. +- Check the configuration files in the installed code and update `TIPPR_DOMAIN` if necessary. +- Start the tippr dev server/process as the configured `TIPPR_USER` and visit `https://tippr.local` (or whatever domain you used) in your browser. If you want, I can also: -- Add an example `/etc/hosts` entry for `reddit.local` to this doc, or +- Add an example `/etc/hosts` entry for `tippr.local` to this doc, or - Create a small checklist of verification commands to run after installation. -File: `install-reddit.sh` (source) — refer to that script for exact filenames called; the installer downloads/uses helpers such as: -`done.sh`, `install_apt.sh`, `install_cassandra.sh`, `install_services.sh`, `install_zookeeper.sh`, `reddit.sh`, `setup_cassandra.sh`, `setup_mcrouter.sh`, `setup_postgres.sh`, `setup_rabbitmq.sh`, `travis.sh`. +File: `install-tippr.sh` (source) — refer to that script for exact filenames called; the installer downloads/uses helpers such as: +`done.sh`, `install_apt.sh`, `install_cassandra.sh`, `install_services.sh`, `install_zookeeper.sh`, `tippr.sh`, `setup_cassandra.sh`, `setup_mcrouter.sh`, `setup_postgres.sh`, `setup_rabbitmq.sh`, `travis.sh`. Example `/etc/hosts` entry -If you want `reddit.local` to resolve on your host machine, add an `/etc/hosts` entry mapping the chosen hostname to the VM/container IP or to localhost for a local install. Examples: +If you want `tippr.local` to resolve on your host machine, add an `/etc/hosts` entry mapping the chosen hostname to the VM/container IP or to localhost for a local install. Examples: Local development on the same machine (binds to localhost): ``` -127.0.0.1 reddit.local +127.0.0.1 tippr.local ``` When running inside a VM or container, replace `192.168.33.10` with the VM/container IP: ``` -192.168.33.10 reddit.local +192.168.33.10 tippr.local ``` -After adding the host mapping, you should be able to visit `https://reddit.local` in your browser (or the HTTP/HTTPS endpoint the installer configures). +After adding the host mapping, you should be able to visit `https://tippr.local` in your browser (or the HTTP/HTTPS endpoint the installer configures). Systemd / init scripts and service names created by the installer -The installer creates several service units, init scripts, and helper scripts so the reddit stack can be managed via `systemctl` (preferred) or Upstart on older systems. Common files/units created by `install/reddit.sh` and the helper scripts include: +The installer creates several service units, init scripts, and helper scripts so the tippr stack can be managed via `systemctl` (preferred) or Upstart on older systems. Common files/units created by `install/tippr.sh` and the helper scripts include: - Systemd unit files (written to `/etc/systemd/system/`): - - `reddit-serve.service` — the main web app (paster serve) unit. - - `reddit-websockets.service` — websockets service (baseplate-serve). - - `reddit-activity.service` — activity service (baseplate-serve). + - `tippr-serve.service` — the main web app (paster serve) unit. + - `tippr-websockets.service` — websockets service (baseplate-serve). + - `tippr-activity.service` — activity service (baseplate-serve). - `gunicorn-click.service` — gunicorn process for the click/tracker app (binds to a unix socket). - `gunicorn-geoip.service` — gunicorn geoip server (binds to 127.0.0.1:5000). - Upstart jobs (if `/etc/init` exists): - - `/etc/init/reddit-websockets.conf` — websockets upstart job. - - `/etc/init/reddit-activity.conf` — activity upstart job. + - `/etc/init/tippr-websockets.conf` — websockets upstart job. + - `/etc/init/tippr-activity.conf` — activity upstart job. - The installer also copies any plugin-provided `upstart/` jobs into `/etc/init` or `/etc/init.d` via `copy_upstart`. - Other init / service-related files created or enabled: - `/etc/gunicorn.d/click.conf` and `/etc/gunicorn.d/geoip.conf` — per-app gunicorn configs. - - `/etc/nginx/sites-available/reddit-media`, `/etc/nginx/sites-available/reddit-pixel`, `/etc/nginx/sites-available/reddit-ssl` and corresponding symlinks in `/etc/nginx/sites-enabled/`. + - `/etc/nginx/sites-available/tippr-media`, `/etc/nginx/sites-available/tippr-pixel`, `/etc/nginx/sites-available/tippr-ssl` and corresponding symlinks in `/etc/nginx/sites-enabled/`. - `/etc/haproxy/haproxy.cfg` is modified (backed up) and `haproxy` is restarted. - - A cron file at `/etc/cron.d/reddit` is created to schedule periodic reddit maintenance jobs. + - A cron file at `/etc/cron.d/tippr` is created to schedule periodic tippr maintenance jobs. - System services installed/enabled by `install_services.sh` (package-provided units): - `memcached` (unit name may be `memcached.service`) @@ -132,13 +132,13 @@ The installer creates several service units, init scripts, and helper scripts so - On older Ubuntu targets `mcrouter` may be installed instead of mcrouter availability varies by distro/ppa. - Helper CLI wrappers installed to `/usr/local/bin/`: - - `reddit-run`, `reddit-shell`, `reddit-start`, `reddit-stop`, `reddit-restart`, `reddit-flush`, `reddit-serve` — thin helpers that call into the venv and service scripts. + - `tippr-run`, `tippr-shell`, `tippr-start`, `tippr-stop`, `tippr-restart`, `tippr-flush`, `tippr-serve` — thin helpers that call into the venv and service scripts. Notes -- Unit names can vary slightly between distributions (for example `postgresql` vs `postgresql@12-main`), and `install_services.sh` uses package names when enabling services where appropriate. Check the exact unit name with `systemctl list-units | grep reddit` or `systemctl status ` on your host. +- Unit names can vary slightly between distributions (for example `postgresql` vs `postgresql@12-main`), and `install_services.sh` uses package names when enabling services where appropriate. Check the exact unit name with `systemctl list-units | grep tippr` or `systemctl status ` on your host. - To follow the main web app logs live, run: ``` -journalctl -u reddit-serve -f -o cat +journalctl -u tippr-serve -f -o cat ``` diff --git a/docs/PRODUCTION_DEPLOYMENT.md b/docs/PRODUCTION_DEPLOYMENT.md new file mode 100644 index 0000000000..c4c11805fa --- /dev/null +++ b/docs/PRODUCTION_DEPLOYMENT.md @@ -0,0 +1,3830 @@ +# Tippr Production Deployment Guide + +## About This Guide + +This deployment guide is for **Tippr**, a modern fork of the Reddit codebase updated for Python 3 and current infrastructure. While the core architecture remains similar to the original Reddit design, this guide has been updated for modern cloud platforms and self-hosting scenarios. + +**Attribution**: Based on Reddit's open-source architecture and deployment patterns. Original Reddit code © 2006-2015 Reddit, Inc. + +For the original Reddit codebase and historical context, see: https://github.com/reddit-archive/reddit + +--- + +This document provides guidance for deploying the Tippr codebase in a production environment. While `install-tippr.sh` sets up a development environment with all services running locally, production deployments require a distributed architecture with proper scaling, redundancy, and managed services. + +## Table of Contents + +**Getting Started** +1. [Architecture Overview](#architecture-overview) +2. [Choosing Your Deployment Strategy](#choosing-your-deployment-strategy) +3. [Development vs Production](#development-vs-production) + +**Cloud Deployments** +4. [AWS Deployment Guide](#aws-managed-services-strategy) +5. [Google Cloud Platform Guide](#google-cloud-platform-strategy) +6. [Microsoft Azure Guide](#microsoft-azure-strategy) +7. [Cloud Provider Comparison](#cloud-provider-comparison) + +**Self-Hosted Deployments** +8. [Self-Hosted Production Deployment](#self-hosted-production-deployment) +9. [Hybrid Deployment Strategy](#hybrid-deployment-strategy) + +**Common Components** (all deployment types) +10. [Database Configuration](#database-configuration) +11. [Caching Layer](#caching-layer) +12. [Message Queue](#message-queue) +13. [Application Servers](#application-servers) +14. [Load Balancing & Reverse Proxy](#load-balancing--reverse-proxy) + +**Operations** +15. [Configuration Changes for Production](#configuration-changes-for-production) +16. [Migration from Development to Production](#migration-from-development-to-production) +17. [Backup & Disaster Recovery](#backup--disaster-recovery) +18. [Monitoring & Observability](#monitoring--observability) +19. [Security Considerations](#security-considerations) +20. [Scaling Lessons from Reddit's History](#scaling-lessons-from-reddits-history) +21. [Deployment Checklists](#deployment-checklist) +22. [Additional Resources](#additional-resources) + +--- + +## Architecture Overview + +### Development vs Production + +| Component | Development (`install-tippr.sh`) | Production | +|-----------|-----------------------------------|------------| +| PostgreSQL | Local single instance | AWS RDS / GCP Cloud SQL / Azure Database for PostgreSQL | +| Cassandra | Local single node | AWS Keyspaces / Azure Managed Cassandra / Astra DB | +| Memcached | Local single instance | AWS ElastiCache / GCP Memorystore / Azure Cache for Redis | +| RabbitMQ | Local single instance | AWS MQ / Azure Service Bus / Self-managed | +| Application | Single paster process | Multiple Gunicorn workers behind load balancer | +| Static Files | Local filesystem | S3 / GCS / Azure Blob Storage + CDN | +| Search | Local Solr (optional) | CloudSearch / OpenSearch / Azure Cognitive Search | + +### High-Level Production Architecture + +``` + [CloudFront CDN] + | + [Application Load Balancer] + | + +---------------------+---------------------+ + | | | + [App Server 1] [App Server 2] [App Server N] + | | | + +---------------------+---------------------+ + | + +---------------------------+---------------------------+ + | | | + [RDS PostgreSQL] [Amazon Keyspaces] [ElastiCache] + (Multi-AZ) (Cassandra-compatible) (Memcached) + | + [Read Replicas] +``` + +--- + +## Choosing Your Deployment Strategy + +### Decision Tree + +**Start here**: What's your primary goal? + +``` +├─ Fastest time-to-production → AWS (all managed services, minimal code changes) +│ └─ Expected cost: $1,325-1,910/month +│ +├─ Cost optimization → GCP + Astra DB or Self-Hosted Hybrid +│ ├─ Cloud: $950-1,450/month (GCP + Astra DB) +│ └─ Hybrid: $100-260/month (local compute + cloud storage/DB) +│ +├─ Enterprise/Microsoft ecosystem → Azure +│ └─ Expected cost: $1,220-2,450/month (depending on Cassandra choice) +│ +├─ Learning/Development → Self-Hosted (all local) +│ └─ Expected cost: Hardware + ~$50-100/month (internet + power + backups) +│ +└─ Maximum scale (Reddit-level) → GCP with Bigtable (requires code changes) + └─ Expected cost: $2,200-2,750/month +``` + +### Quick Comparison + +| Priority | Best Choice | Why | +|----------|-------------|-----| +| Speed to production | AWS | All managed services, S3 provider exists in codebase | +| Lowest operating cost | Self-hosted hybrid | Local compute, cloud storage/DB only | +| Easiest to manage | AWS or GCP + Astra DB | Fully managed, CQL-compatible | +| Enterprise features | Azure | Best Microsoft integration, compliance certifications | +| Learning the architecture | Self-hosted | Full control, understand all components | +| Maximum scalability | GCP + Bigtable | Petabyte-scale, but requires code refactoring | + +### Cost Estimation by Traffic Tier + +| Traffic Tier | DAU | AWS | GCP + Astra | Azure + Astra | Self-Hosted Hybrid | +|--------------|-----|-----|-------------|---------------|-------------------| +| Small | <10K | $500-800 | $400-600 | $450-700 | $50-150 | +| Medium | 10K-100K | $1,325-1,910 | $950-1,450 | $1,220-1,750 | $100-260 | +| Large | 100K-1M | $3,000-5,000 | $2,500-4,000 | $3,000-5,000 | $300-500 | +| X-Large | >1M | $8,000+ | $6,000+ | $7,000+ | Full cloud recommended | + +> **Note on costs**: These estimates do not include data transfer/egress fees, which can add 10-30% depending on traffic patterns. Reserved instances and committed use discounts can reduce costs by 30-70% for predictable workloads. + +--- + +## AWS Managed Services Strategy + +### Why Use Managed Services? + +Using AWS managed services like Amazon Keyspaces and RDS PostgreSQL provides several advantages for initial production deployments: + +1. **Reduced operational overhead** - No need to manage patches, backups, or failover +2. **Built-in high availability** - Multi-AZ deployments with automatic failover +3. **Scalability** - Easy vertical and horizontal scaling +4. **Security** - Encryption at rest and in transit, IAM integration +5. **Cost optimization** - Pay for what you use, especially with on-demand capacity + +### Amazon Keyspaces (for Apache Cassandra) + +Amazon Keyspaces is a serverless, Cassandra-compatible database service ideal for Tippr's time-series data (votes, comments, activity tracking). + +#### Setup Considerations + +```ini +# production.update - Cassandra/Keyspaces configuration +[DEFAULT] +# Amazon Keyspaces endpoint (replace region) +cassandra_seeds = cassandra.us-east-1.amazonaws.com:9142 + +# Keyspaces requires SSL/TLS +cassandra_ssl = true +cassandra_ssl_certfile = /path/to/sf-class2-root.crt + +# Adjust consistency levels for Keyspaces +# LOCAL_QUORUM is recommended for production +cassandra_rcl = LOCAL_QUORUM +cassandra_wcl = LOCAL_QUORUM + +# Connection pooling - Keyspaces handles scaling +cassandra_pool_size = 10 +``` + +#### Keyspaces-Specific Considerations + +1. **Authentication**: Use AWS Signature Version 4 (SigV4) authentication plugin or service-specific credentials +2. **Keyspace creation**: Create keyspaces via AWS Console or CQL with `SingleRegionStrategy` +3. **Throughput**: Start with on-demand capacity mode; switch to provisioned once traffic patterns are understood +4. **Schema**: Same CQL schema works, but avoid unsupported features (see limitations below) + +#### Keyspaces Limitations (Important) + +> **Warning**: Amazon Keyspaces is CQL-compatible but not feature-complete. Review these limitations before migrating: + +| Feature | Self-Hosted Cassandra | Amazon Keyspaces | Impact | +|---------|----------------------|------------------|--------| +| `ALLOW FILTERING` | Supported | **Not supported** | Must redesign queries | +| Lightweight Transactions (LWT) | Full support | Limited support | `IF NOT EXISTS` works, but behavior differs | +| Counters | Native counters | **Emulated, eventual** | May see stale counts briefly | +| User-Defined Functions (UDF) | Supported | **Not supported** | Move logic to application layer | +| User-Defined Aggregates | Supported | **Not supported** | Aggregate in application | +| Materialized Views | Supported | **Not supported** | Create separate tables manually | +| Secondary Indexes | Supported | **Not supported** | Design with partition keys | +| Batch Statements | Unlimited | **Max 30 statements** | Split large batches | +| Request Size | Configurable | **Max 1MB** | Paginate large results | +| TTL | Any value | **Max 630,720,000 sec (~20 years)** | Usually not an issue | + +**Throughput Considerations:** +- On-demand mode: Good for variable/unknown traffic, but can throttle on spikes +- Provisioned mode: Predictable cost, but must size correctly +- **Burst capacity**: Keyspaces provides burst capacity but throttles sustained high traffic + +**Recommended Testing:** +```bash +# Before migrating, test your queries against Keyspaces +# Use the NoSQL Workbench for Amazon Keyspaces to validate schema +``` + +```python +# Example: Connecting to Keyspaces with SigV4 +# Add to r2/r2/lib/db/cassandra_compat.py or connection setup +from ssl import SSLContext, PROTOCOL_TLS_CLIENT, CERT_REQUIRED +from cassandra.cluster import Cluster +from cassandra.auth import PlainTextAuthProvider +from cassandra_sigv4.auth import SigV4AuthProvider + +ssl_context = SSLContext(PROTOCOL_TLS_CLIENT) +ssl_context.load_verify_locations('/path/to/sf-class2-root.crt') +ssl_context.check_hostname = False +ssl_context.verify_mode = CERT_REQUIRED + +auth_provider = SigV4AuthProvider(boto3.Session()) +cluster = Cluster( + ['cassandra.us-east-1.amazonaws.com'], + ssl_context=ssl_context, + auth_provider=auth_provider, + port=9142 +) +``` + +#### Data Migration to Keyspaces + +Use AWS Database Migration Service (DMS) or the existing migration tools: + +```bash +# Export from local Cassandra +cqlsh localhost 9042 -e "COPY tippr.votes TO 'votes.csv';" + +# Import to Keyspaces (use smaller batches due to throughput limits) +cqlsh cassandra.us-east-1.amazonaws.com 9142 --ssl \ + -e "COPY tippr.votes FROM 'votes.csv' WITH MAXBATCHSIZE=10;" +``` + +### Amazon RDS for PostgreSQL + +RDS PostgreSQL handles Tippr's relational data: links, accounts, vaults, and anything requiring ACID transactions. + +#### Setup Considerations + +```ini +# production.update - PostgreSQL/RDS configuration +[DEFAULT] +db_user = tippr +db_pass = YOUR_SECURE_PASSWORD +db_port = 5432 + +# Connection pooling - important for RDS +db_pool_size = 10 +db_pool_overflow_size = 20 + +# List all database logical names +databases = main, comment, email, authorize, award, hc, traffic + +# Primary (writer) endpoint +main_db = tippr, your-db.cluster-xxxxx.us-east-1.rds.amazonaws.com, *, *, *, *, * + +# Read replicas for read-heavy tables +# Use reader endpoint for slave connections +comment_db = tippr, your-db.cluster-ro-xxxxx.us-east-1.rds.amazonaws.com, *, *, *, *, * +``` + +#### RDS Instance Recommendations + +| Traffic Level | Instance Type | Storage | Multi-AZ | +|---------------|---------------|---------|----------| +| Low (<100K daily users) | db.t3.medium | 100GB gp3 | Optional | +| Medium (<1M daily users) | db.r6g.large | 500GB gp3 | Yes | +| High (>1M daily users) | db.r6g.xlarge+ | 1TB+ io1 | Yes | + +#### RDS Best Practices + +1. **Use read replicas**: Route read queries to replicas to reduce primary load +2. **Connection pooling**: Use PgBouncer or RDS Proxy to manage connections +3. **Parameter tuning**: Adjust `shared_buffers`, `work_mem`, `effective_cache_size` +4. **Storage**: Use gp3 for balanced workloads, io1/io2 for high IOPS requirements +5. **Backup**: Enable automated backups with appropriate retention period + +```ini +# Database server configuration with read replicas +# Primary for writes +db_servers_link = main, main +db_servers_account = main + +# Use replica for heavy read tables - add !avoid_master flag +db_servers_comment = comment, comment, !avoid_master +db_servers_subreddit = comment, comment, !avoid_master +``` + +#### SSD Performance Note + +From Tippr's scaling experience: **SSDs provide 16x performance improvement at only 4x the cost**. RDS with gp3 or io1 storage uses SSDs by default. If self-managing PostgreSQL: + +- Use NVMe or SSD-backed EBS volumes +- Consider i3 or i3en instance types for local NVMe storage +- Monitor IOPS and latency to right-size storage + +--- + +## Google Cloud Platform Strategy + +Google Cloud Platform (GCP) offers a compelling alternative to AWS with competitive pricing, strong networking, and excellent managed services. This section covers the equivalent GCP services for running Tippr in production. + +### GCP Architecture Overview + +``` + [Cloud CDN] + | + [Cloud Load Balancer] + | + +---------------------+---------------------+ + | | | + [GCE Instance 1] [GCE Instance 2] [GCE Instance N] + (or GKE Pod) (or GKE Pod) (or GKE Pod) + | | | + +---------------------+---------------------+ + | + +---------------------------+---------------------------+ + | | | + [Cloud SQL PostgreSQL] [Cloud Bigtable] [Memorystore] + (Regional HA) (Cassandra alternative) (Memcached) + | + [Read Replicas] +``` + +### GCP Service Mapping + +| Tippr Component | AWS Service | GCP Equivalent | +|------------------|-------------|----------------| +| PostgreSQL | RDS for PostgreSQL | Cloud SQL for PostgreSQL | +| Cassandra | Amazon Keyspaces | DataStax Astra DB / Cloud Bigtable / Self-managed | +| Memcached | ElastiCache | Memorystore for Memcached | +| RabbitMQ | Amazon MQ | Cloud Pub/Sub (or self-managed on GKE) | +| Application Hosting | EC2 + ASG | Compute Engine + MIG (or GKE) | +| Load Balancer | ALB | Cloud Load Balancing | +| CDN | CloudFront | Cloud CDN | +| Object Storage | S3 | Cloud Storage (GCS) | +| Secrets | Secrets Manager | Secret Manager | +| Search | CloudSearch/OpenSearch | Elasticsearch on GKE | +| Monitoring | CloudWatch | Cloud Monitoring + Cloud Logging | + +### Cloud SQL for PostgreSQL + +Cloud SQL is Google's fully managed PostgreSQL service with automatic failover, backups, and read replicas. + +#### Configuration + +```ini +# production-gcp.update - Cloud SQL configuration +[DEFAULT] +db_user = tippr +db_pass = YOUR_SECURE_PASSWORD +db_port = 5432 + +# Connection pooling +db_pool_size = 10 +db_pool_overflow_size = 20 + +databases = main, comment, email, authorize, award, hc, traffic + +# Cloud SQL connection (private IP recommended) +# Format: project:region:instance or private IP +main_db = tippr, 10.0.0.5, *, *, *, *, * + +# Read replica for heavy read tables +comment_db = tippr, 10.0.0.6, *, *, *, *, * +``` + +#### Cloud SQL Instance Recommendations + +| Traffic Level | Machine Type | Storage | High Availability | +|---------------|--------------|---------|-------------------| +| Low (<100K daily) | db-custom-2-4096 | 100GB SSD | Optional | +| Medium (<1M daily) | db-custom-4-16384 | 500GB SSD | Yes (Regional) | +| High (>1M daily) | db-custom-8-32768+ | 1TB+ SSD | Yes (Regional) | + +#### Cloud SQL Proxy (Recommended) + +Use Cloud SQL Proxy for secure connections without exposing the database: + +```bash +# Download and run Cloud SQL Proxy +./cloud-sql-proxy --private-ip \ + your-project:us-central1:tippr-db & + +# Application connects to localhost +# production-gcp.update +main_db = tippr, 127.0.0.1, *, *, *, *, * +``` + +#### Cloud SQL Best Practices + +1. **Use Private IP**: Keep database off public internet +2. **Enable automatic storage increases**: Prevents running out of disk +3. **Use regional HA**: Automatic failover across zones +4. **Connection pooling**: Use PgBouncer sidecar or built-in connection limits +5. **Query Insights**: Enable for slow query analysis + +### Cassandra Options on GCP + +GCP offers three approaches for Cassandra workloads, each with different trade-offs: + +| Option | CQL Compatible | Managed | Code Changes | Best For | +|--------|----------------|---------|--------------|----------| +| DataStax Astra DB | Yes | Fully | None | Fastest migration | +| Cloud Bigtable | No | Fully | Significant | Maximum scale | +| Self-managed on GKE | Yes | No | None | Cost optimization | + +#### Option A: DataStax Astra DB (Recommended for Migration) + +DataStax Astra DB is a fully managed Cassandra-as-a-Service available on Google Cloud through a partnership with DataStax. It provides **full CQL compatibility**, making it equivalent to AWS Keyspaces for migration purposes. + +**Key Advantages:** +- **Zero code changes**: Full CQL compatibility with existing Cassandra code +- **Serverless**: Pay-per-use pricing, no node management +- **Multi-cloud**: Can run on GCP, AWS, or Azure +- **Integrations**: Native integration with BigQuery, Dataflow, and Google Cloud AI services +- **Vector Search**: Built-in vector search for AI/ML workloads + +```ini +# production-gcp.update - DataStax Astra DB configuration +[DEFAULT] +# Astra DB Secure Connect Bundle approach +cassandra_seeds = YOUR_ASTRA_DB_ID-YOUR_REGION.apps.astra.datastax.com:29042 + +# Astra requires SSL/TLS +cassandra_ssl = true +cassandra_ssl_certfile = /path/to/secure-connect-bundle/ca.crt + +# Consistency levels +cassandra_rcl = LOCAL_QUORUM +cassandra_wcl = LOCAL_QUORUM + +# Connection pooling +cassandra_pool_size = 10 +``` + +**Python Connection Example:** + +```python +# Connecting to Astra DB from Tippr codebase +# Minimal changes to r2/r2/lib/db/cassandra_compat.py +from cassandra.cluster import Cluster +from cassandra.auth import PlainTextAuthProvider + +# Using Secure Connect Bundle (recommended) +cloud_config = { + 'secure_connect_bundle': '/path/to/secure-connect-tippr.zip' +} +auth_provider = PlainTextAuthProvider( + username='token', + secret='AstraCS:YOUR_APPLICATION_TOKEN' +) +cluster = Cluster(cloud=cloud_config, auth_provider=auth_provider) +session = cluster.connect('tippr') + +# All existing CQL queries work unchanged +session.execute("SELECT * FROM votes WHERE user_id = %s", [user_id]) +``` + +**Astra DB Pricing (Serverless):** + +| Usage Level | Reads/Writes | Est. Monthly Cost | +|-------------|--------------|-------------------| +| Low | 10M ops/month | $25-50 | +| Medium | 100M ops/month | $150-300 | +| High | 1B ops/month | $500-1,000 | + +**Note**: Astra DB serverless pricing is based on read/write units consumed, making it cost-effective for variable workloads. For predictable high-volume workloads, consider Astra DB Dedicated. + +#### Option B: Cloud Bigtable (Maximum Scale) + +Cloud Bigtable is Google's NoSQL wide-column database. While not CQL-compatible, it's highly performant for Tippr's use cases (votes, activity tracking, time-series data) at massive scale. + +##### Key Differences from Cassandra + +| Feature | Cassandra/Astra DB | Cloud Bigtable | +|---------|---------------------|----------------| +| Query Language | CQL | HBase API / Client Libraries | +| Data Model | Wide-column with CQL | Wide-column with row keys | +| Consistency | Tunable | Strong (single-row) | +| Schema | Defined tables | Schema-less (column families) | +| Pricing | Per read/write or provisioned | Node-hours + storage | + +Bigtable excels at high-throughput, low-latency workloads but requires code changes to use Google's client libraries instead of CQL. + +```python +# Example: Bigtable client for vote storage +# Would require adapting r2/r2/lib/db/tdb_cassandra.py +from google.cloud import bigtable +from google.cloud.bigtable import column_family + +client = bigtable.Client(project='your-project', admin=True) +instance = client.instance('tippr-instance') +table = instance.table('votes') + +# Write vote +row_key = f"user:{user_id}#link:{link_id}".encode() +row = table.direct_row(row_key) +row.set_cell('vote', 'direction', str(direction).encode()) +row.set_cell('vote', 'timestamp', str(timestamp).encode()) +row.commit() + +# Read votes for user +rows = table.read_rows(row_key_prefix=f"user:{user_id}#".encode()) +``` + +#### Bigtable Instance Sizing + +| Traffic Level | Node Type | Nodes | Storage | +|---------------|-----------|-------|---------| +| Low | Development (1 node) | 1 | 100GB SSD | +| Medium | Production | 3 | 500GB SSD | +| High | Production | 5-10+ | 1TB+ SSD | + +**Note**: Bigtable minimum for production is 3 nodes. Development instances (1 node) have performance limitations. + +#### Option B: Self-Managed Cassandra on GKE + +For CQL compatibility without code changes, run Cassandra on Google Kubernetes Engine: + +```yaml +# cassandra-statefulset.yaml (simplified) +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: cassandra +spec: + serviceName: cassandra + replicas: 3 + selector: + matchLabels: + app: cassandra + template: + metadata: + labels: + app: cassandra + spec: + containers: + - name: cassandra + image: cassandra:4.1 + ports: + - containerPort: 9042 + env: + - name: CASSANDRA_SEEDS + value: "cassandra-0.cassandra.default.svc.cluster.local" + volumeMounts: + - name: cassandra-data + mountPath: /var/lib/cassandra + volumeClaimTemplates: + - metadata: + name: cassandra-data + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: premium-rwo + resources: + requests: + storage: 100Gi +``` + +```ini +# production-gcp.update - Self-managed Cassandra on GKE +cassandra_seeds = cassandra-0.cassandra.default.svc.cluster.local:9042,cassandra-1.cassandra.default.svc.cluster.local:9042 +cassandra_pool_size = 10 +cassandra_rcl = LOCAL_QUORUM +cassandra_wcl = LOCAL_QUORUM +``` + +### Memorystore for Memcached + +Memorystore provides managed Memcached compatible with Tippr's caching layer. + +#### Configuration + +```ini +# production-gcp.update - Memorystore configuration +[DEFAULT] +# Memorystore discovery endpoint +lockcaches = 10.0.0.10:11211 +permacache_memcaches = 10.0.0.10:11211 +hardcache_memcaches = 10.0.0.10:11211 + +# Connection pool +num_mc_clients = 20 +``` + +#### Memorystore Sizing + +| Traffic Level | Memory | Nodes | +|---------------|--------|-------| +| Low | 1GB | 1 | +| Medium | 5-10GB | 2-3 | +| High | 20GB+ | 3-5 | + +### Message Queue Options on GCP + +#### Option A: Cloud Pub/Sub (GCP Native) + +Cloud Pub/Sub is a fully managed messaging service. It uses a different paradigm (topics/subscriptions) than RabbitMQ (exchanges/queues) and would require adapter code. + +```python +# Example: Pub/Sub adapter for Tippr's queue system +# Would wrap r2/r2/lib/amqp.py +from google.cloud import pubsub_v1 + +publisher = pubsub_v1.PublisherClient() +topic_path = publisher.topic_path('your-project', 'vote-queue') + +def add_item(queue_name, item): + data = json.dumps(item).encode('utf-8') + future = publisher.publish(topic_path, data) + return future.result() + +# Subscriber (consumer) +subscriber = pubsub_v1.SubscriberClient() +subscription_path = subscriber.subscription_path('your-project', 'vote-processor') + +def callback(message): + item = json.loads(message.data.decode('utf-8')) + process_vote(item) + message.ack() + +subscriber.subscribe(subscription_path, callback=callback) +``` + +#### Option B: Self-Managed RabbitMQ on GKE (Recommended) + +For drop-in compatibility, run RabbitMQ on GKE: + +```yaml +# rabbitmq-deployment.yaml (using RabbitMQ Cluster Operator) +apiVersion: rabbitmq.com/v1beta1 +kind: RabbitmqCluster +metadata: + name: tippr-rabbitmq +spec: + replicas: 3 + persistence: + storageClassName: premium-rwo + storage: 50Gi + resources: + requests: + cpu: 1 + memory: 2Gi + limits: + cpu: 2 + memory: 4Gi +``` + +```ini +# production-gcp.update - RabbitMQ on GKE +amqp_host = tippr-rabbitmq.default.svc.cluster.local:5672 +amqp_user = tippr +amqp_pass = YOUR_SECURE_PASSWORD +amqp_virtual_host = / +``` + +### Compute Options + +#### Option A: Compute Engine with Managed Instance Groups + +Traditional VM-based deployment similar to EC2: + +```bash +# Create instance template +gcloud compute instance-templates create tippr-template \ + --machine-type=n2-standard-4 \ + --image-family=ubuntu-2404-lts \ + --image-project=ubuntu-os-cloud \ + --boot-disk-size=50GB \ + --boot-disk-type=pd-ssd \ + --metadata-from-file=startup-script=startup.sh + +# Create managed instance group with autoscaling +gcloud compute instance-groups managed create tippr-mig \ + --template=tippr-template \ + --size=3 \ + --zone=us-central1-a + +gcloud compute instance-groups managed set-autoscaling tippr-mig \ + --min-num-replicas=2 \ + --max-num-replicas=10 \ + --target-cpu-utilization=0.7 \ + --zone=us-central1-a +``` + +#### Option B: Google Kubernetes Engine (GKE) + +Container-based deployment for better resource utilization: + +```yaml +# tippr-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tippr-app +spec: + replicas: 3 + selector: + matchLabels: + app: tippr + template: + metadata: + labels: + app: tippr + spec: + containers: + - name: tippr + image: gcr.io/your-project/tippr:latest + ports: + - containerPort: 8001 + env: + - name: PYTHONPATH + value: "/app/tippr:/app" + resources: + requests: + cpu: "1" + memory: "2Gi" + limits: + cpu: "2" + memory: "4Gi" + readinessProbe: + httpGet: + path: /health + port: 8001 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: tippr-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: tippr-app + minReplicas: 2 + maxReplicas: 20 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 +``` + +### Load Balancing & CDN on GCP + +#### Cloud Load Balancing + +```bash +# Create health check +gcloud compute health-checks create http tippr-health \ + --port=8001 \ + --request-path=/health + +# Create backend service +gcloud compute backend-services create tippr-backend \ + --protocol=HTTP \ + --health-checks=tippr-health \ + --global + +# Add instance group to backend +gcloud compute backend-services add-backend tippr-backend \ + --instance-group=tippr-mig \ + --instance-group-zone=us-central1-a \ + --global + +# Create URL map +gcloud compute url-maps create tippr-lb \ + --default-service=tippr-backend + +# Create HTTPS proxy with SSL cert +gcloud compute target-https-proxies create tippr-https-proxy \ + --url-map=tippr-lb \ + --ssl-certificates=tippr-ssl-cert + +# Create forwarding rule +gcloud compute forwarding-rules create tippr-https-rule \ + --target-https-proxy=tippr-https-proxy \ + --ports=443 \ + --global +``` + +#### Cloud CDN + +Enable Cloud CDN on the backend service for caching: + +```bash +gcloud compute backend-services update tippr-backend \ + --enable-cdn \ + --cache-mode=CACHE_ALL_STATIC \ + --default-ttl=3600 \ + --global +``` + +### Cloud Storage for Media + +```ini +# production-gcp.update - GCS for media storage +[DEFAULT] +media_provider = gcs # Requires implementing GCS provider + +# GCS bucket configuration +gcs_media_bucket = your-tippr-media +gcs_image_bucket = your-tippr-images +gcs_project = your-project-id +``` + +**Note**: The codebase has an S3 provider (`r2/r2/lib/providers/media/s3.py`). You would need to implement a GCS provider or use the S3-compatible GCS XML API. + +```python +# GCS with S3-compatible API (alternative) +# production-gcp.update +media_provider = s3 +S3KEY_ID = YOUR_GCS_HMAC_ACCESS_ID +S3SECRET_KEY = YOUR_GCS_HMAC_SECRET +s3_media_domain = storage.googleapis.com +s3_media_buckets = your-tippr-media +``` + +### GCP Production Configuration + +```ini +# production-gcp.update +[DEFAULT] +# CRITICAL: Disable debug mode +debug = false +template_debug = false +reload_templates = false +uncompressedJS = false +sqlprinting = false + +# Production domain +domain = yourdomain.com +media_domain = media.yourdomain.com +https_endpoint = https://yourdomain.com + +# Cloud SQL +db_user = tippr +db_pass = YOUR_SECURE_PASSWORD +db_port = 5432 +databases = main, comment, email, authorize, award, hc, traffic +main_db = tippr, /cloudsql/project:region:instance, *, *, *, *, * + +# Bigtable or self-managed Cassandra +cassandra_seeds = cassandra.default.svc.cluster.local:9042 +cassandra_pool_size = 10 + +# Memorystore +lockcaches = 10.0.0.10:11211 +permacache_memcaches = 10.0.0.10:11211 +hardcache_memcaches = 10.0.0.10:11211 + +# RabbitMQ on GKE +amqp_host = tippr-rabbitmq.default.svc.cluster.local:5672 +amqp_user = tippr +amqp_pass = YOUR_SECURE_PASSWORD + +# GCS media storage (S3-compatible) +media_provider = s3 +s3_media_domain = storage.googleapis.com +s3_media_buckets = your-media-bucket + +# Security +trust_local_proxies = true +ENFORCE_RATELIMIT = true + +# Monitoring (Cloud Monitoring via OpenTelemetry) +statsd_addr = localhost:8125 # Use statsd-exporter sidecar +``` + +### GCP Monitoring & Observability + +#### Cloud Monitoring + +```yaml +# Deploy Prometheus/Grafana on GKE or use Cloud Monitoring +# statsd-exporter sidecar for StatsD -> Prometheus +apiVersion: v1 +kind: ConfigMap +metadata: + name: statsd-mapping +data: + mapping.yml: | + mappings: + - match: "tippr.*" + name: "tippr_${1}" + labels: + job: "tippr" +``` + +#### Cloud Logging + +Application logs are automatically collected from GCE/GKE and available in Cloud Logging. + +```ini +# production-gcp.update - Logging configuration +[logger_root] +level = INFO +handlers = console + +# Logs go to stdout, captured by Cloud Logging agent +``` + +### GCP Cost Estimation + +For a medium-traffic deployment (~100K-500K daily active users): + +| Service | Configuration | Est. Monthly Cost | +|---------|---------------|-------------------| +| Cloud SQL PostgreSQL | db-custom-4-16384, HA | $250-350 | +| **Cassandra Option A:** DataStax Astra DB | Serverless, ~100M ops/mo | $150-300 | +| **Cassandra Option B:** Cloud Bigtable | 3 nodes production | $1,400-1,600 | +| **Cassandra Option C:** Self-managed on GKE | 3x n2-standard-4 | $300-400 | +| Memorystore | 5GB | $150-200 | +| GKE Cluster | 3x n2-standard-4 | $300-400 | +| Cloud Load Balancing | Standard | $20-50 | +| Cloud CDN | 1TB transfer | $80-150 | +| Cloud Storage | 100GB | $2-5 | +| **Total (with Astra DB)** | | **$950-1,450/month** | +| **Total (with Bigtable)** | | **$2,200-2,750/month** | +| **Total (with self-managed Cassandra)** | | **$1,100-1,550/month** | + +**Notes**: +- GCP offers sustained use discounts (up to 30% for VMs running full month) +- Committed use discounts provide 50-70% savings for 1-3 year commitments +- Astra DB serverless pricing scales with actual usage, ideal for variable traffic + +### GCP Deployment Checklist + +- [ ] Create GCP project and enable required APIs +- [ ] Set up VPC with private subnets +- [ ] Provision Cloud SQL PostgreSQL (Regional HA) +- [ ] Provision Memorystore for Memcached +- [ ] Set up Cassandra layer (choose one): + - [ ] **Option A**: Create DataStax Astra DB database and download Secure Connect Bundle + - [ ] **Option B**: Provision Cloud Bigtable instance (requires code changes) + - [ ] **Option C**: Deploy self-managed Cassandra StatefulSet on GKE +- [ ] Deploy RabbitMQ on GKE (using RabbitMQ Cluster Operator) +- [ ] Create GCS buckets for media (enable S3-compatible HMAC keys) +- [ ] Configure Cloud Load Balancer with SSL +- [ ] Enable Cloud CDN +- [ ] Set up Cloud Monitoring dashboards +- [ ] Configure Secret Manager for credentials +- [ ] Deploy application to GKE or MIG +- [ ] Configure autoscaling policies +- [ ] Set up Cloud Armor for WAF (optional) +- [ ] (Optional) Configure BigQuery integration for analytics + +--- + +## Microsoft Azure Strategy + +Microsoft Azure provides a comprehensive set of managed services for running Tippr in production, with strong enterprise integration and hybrid cloud capabilities. + +### Azure Architecture Overview + +``` + [Azure CDN / Front Door] + | + [Application Gateway] + | + +---------------------+---------------------+ + | | | + [VM Scale Set] [VM Scale Set] [AKS Pod] + (or AKS Pod) (or AKS Pod) + | | | + +---------------------+---------------------+ + | + +---------------------------+---------------------------+ + | | | + [Azure Database for [Azure Managed [Azure Cache + PostgreSQL - Flexible] Cassandra] for Redis] + | + [Read Replicas] +``` + +### Azure Service Mapping + +| Tippr Component | AWS Service | GCP Equivalent | Azure Equivalent | +|------------------|-------------|----------------|------------------| +| PostgreSQL | RDS for PostgreSQL | Cloud SQL | Azure Database for PostgreSQL | +| Cassandra | Amazon Keyspaces | Astra DB / Bigtable | Azure Managed Instance for Cassandra | +| Memcached | ElastiCache | Memorystore | Azure Cache for Redis (Memcached protocol) | +| RabbitMQ | Amazon MQ | Self-managed | Azure Service Bus / Self-managed on AKS | +| Application | EC2 + ASG | GCE + MIG / GKE | VM Scale Sets / Azure Kubernetes Service | +| Load Balancer | ALB | Cloud LB | Application Gateway / Azure Load Balancer | +| CDN | CloudFront | Cloud CDN | Azure CDN / Azure Front Door | +| Object Storage | S3 | Cloud Storage | Azure Blob Storage | +| Secrets | Secrets Manager | Secret Manager | Azure Key Vault | +| Search | CloudSearch | Elasticsearch | Azure Cognitive Search | +| Monitoring | CloudWatch | Cloud Monitoring | Azure Monitor + Log Analytics | + +### Azure Database for PostgreSQL - Flexible Server + +Azure Database for PostgreSQL Flexible Server is the recommended option for new deployments, offering more control over database configuration and better price-performance. + +#### Configuration + +```ini +# production-azure.update - Azure PostgreSQL configuration +[DEFAULT] +db_user = tippr +db_pass = YOUR_SECURE_PASSWORD +db_port = 5432 + +# Connection pooling +db_pool_size = 10 +db_pool_overflow_size = 20 + +databases = main, comment, email, authorize, award, hc, traffic + +# Azure PostgreSQL Flexible Server (private endpoint recommended) +main_db = tippr, tippr-db.postgres.database.azure.com, *, *, *, *, * + +# Read replica for heavy read tables +comment_db = tippr, tippr-db-replica.postgres.database.azure.com, *, *, *, *, * +``` + +#### Azure PostgreSQL Instance Recommendations + +| Traffic Level | Compute Tier | vCores | Storage | High Availability | +|---------------|--------------|--------|---------|-------------------| +| Low (<100K daily) | Burstable B2s | 2 | 128GB | Optional | +| Medium (<1M daily) | General Purpose D4s_v3 | 4 | 512GB | Zone-redundant | +| High (>1M daily) | Memory Optimized E8s_v3+ | 8+ | 1TB+ | Zone-redundant | + +#### Azure PostgreSQL Best Practices + +1. **Use Private Link**: Connect via private endpoint, not public internet +2. **Enable zone-redundant HA**: Automatic failover across availability zones +3. **Configure connection pooling**: Use PgBouncer (built-in option available) +4. **Enable Query Performance Insight**: Identify slow queries +5. **Use Azure AD authentication**: For enhanced security + +```bash +# Create Azure PostgreSQL Flexible Server +az postgres flexible-server create \ + --resource-group tippr-rg \ + --name tippr-db \ + --location eastus \ + --admin-user tippr \ + --admin-password YOUR_SECURE_PASSWORD \ + --sku-name Standard_D4s_v3 \ + --tier GeneralPurpose \ + --storage-size 512 \ + --version 15 \ + --high-availability ZoneRedundant \ + --zone 1 \ + --standby-zone 2 +``` + +### Azure Managed Instance for Apache Cassandra + +Azure Managed Instance for Apache Cassandra provides a fully managed, CQL-compatible Cassandra service with automatic scaling and maintenance. + +#### Key Advantages + +- **Full CQL compatibility**: No code changes required from existing Cassandra code +- **Hybrid support**: Can connect to on-premises Cassandra clusters +- **Automatic scaling**: Add/remove nodes without downtime +- **Azure-native integration**: Works with Azure Monitor, Key Vault, and Private Link +- **Cost-effective**: Pay for what you use with flexible scaling + +#### Configuration + +```ini +# production-azure.update - Azure Managed Cassandra configuration +[DEFAULT] +# Azure Managed Cassandra data center endpoint +cassandra_seeds = tippr-cassandra-dc1.cassandra.cosmos.azure.com:10350 + +# Azure Managed Cassandra requires SSL/TLS +cassandra_ssl = true +cassandra_ssl_certfile = /path/to/azure-cassandra-ca.crt + +# Consistency levels (LOCAL_QUORUM recommended) +cassandra_rcl = LOCAL_QUORUM +cassandra_wcl = LOCAL_QUORUM + +# Connection pooling +cassandra_pool_size = 10 +``` + +#### Python Connection Example + +```python +# Connecting to Azure Managed Cassandra from Tippr codebase +# Minimal changes to r2/r2/lib/db/cassandra_compat.py +from cassandra.cluster import Cluster +from cassandra.auth import PlainTextAuthProvider +from ssl import SSLContext, PROTOCOL_TLS_CLIENT, CERT_REQUIRED + +# SSL configuration for Azure +ssl_context = SSLContext(PROTOCOL_TLS_CLIENT) +ssl_context.load_verify_locations('/path/to/azure-cassandra-ca.crt') +ssl_context.check_hostname = True +ssl_context.verify_mode = CERT_REQUIRED + +# Authentication +auth_provider = PlainTextAuthProvider( + username='tippr-cassandra', + password='YOUR_CASSANDRA_PASSWORD' +) + +cluster = Cluster( + contact_points=['tippr-cassandra-dc1.cassandra.cosmos.azure.com'], + port=10350, + auth_provider=auth_provider, + ssl_context=ssl_context +) +session = cluster.connect('tippr') + +# All existing CQL queries work unchanged +session.execute("SELECT * FROM votes WHERE user_id = %s", [user_id]) +``` + +#### Azure Managed Cassandra Sizing + +| Traffic Level | SKU | Nodes | Disk per Node | +|---------------|-----|-------|---------------| +| Low | Standard_D8s_v4 | 3 | 1TB P30 | +| Medium | Standard_D16s_v4 | 3-5 | 2TB P40 | +| High | Standard_D32s_v4 | 5-10+ | 4TB P50 | + +```bash +# Create Azure Managed Cassandra Cluster +az managed-cassandra cluster create \ + --resource-group tippr-rg \ + --cluster-name tippr-cassandra \ + --location eastus \ + --delegated-management-subnet-id /subscriptions/.../subnets/cassandra-subnet \ + --initial-cassandra-admin-password YOUR_CASSANDRA_PASSWORD + +# Create data center +az managed-cassandra datacenter create \ + --resource-group tippr-rg \ + --cluster-name tippr-cassandra \ + --data-center-name dc1 \ + --data-center-location eastus \ + --delegated-subnet-id /subscriptions/.../subnets/cassandra-subnet \ + --node-count 3 \ + --sku Standard_D8s_v4 \ + --disk-sku P30 +``` + +#### Alternative: DataStax Astra DB on Azure + +Astra DB is also available on Azure, providing the same serverless experience as on GCP: + +```ini +# production-azure.update - Astra DB on Azure +[DEFAULT] +cassandra_seeds = YOUR_ASTRA_DB_ID-YOUR_REGION.apps.astra.datastax.com:29042 +cassandra_ssl = true +cassandra_pool_size = 10 +``` + +### Azure Cache for Redis + +Azure doesn't offer managed Memcached, but Azure Cache for Redis can work as a high-performance caching layer. For Memcached protocol compatibility, you can self-host Memcached on AKS. + +#### Option A: Azure Cache for Redis (Recommended) + +Redis is more feature-rich than Memcached and works well for Tippr's caching needs. However, it requires changes to the caching code to use Redis instead of Memcached. + +```ini +# production-azure.update - Azure Cache for Redis +# Note: Requires implementing Redis cache provider in r2/r2/lib/cache.py +[DEFAULT] +redis_host = tippr-cache.redis.cache.windows.net +redis_port = 6380 +redis_password = YOUR_ACCESS_KEY +redis_ssl = true +``` + +#### Option B: Self-Managed Memcached on AKS + +For drop-in Memcached compatibility: + +```yaml +# memcached-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: memcached +spec: + replicas: 3 + selector: + matchLabels: + app: memcached + template: + metadata: + labels: + app: memcached + spec: + containers: + - name: memcached + image: memcached:1.6-alpine + ports: + - containerPort: 11211 + args: ["-m", "2048", "-c", "4096"] + resources: + requests: + memory: "2Gi" + cpu: "500m" + limits: + memory: "2.5Gi" + cpu: "1" +--- +apiVersion: v1 +kind: Service +metadata: + name: memcached +spec: + ports: + - port: 11211 + selector: + app: memcached +``` + +```ini +# production-azure.update - Self-managed Memcached on AKS +lockcaches = memcached.default.svc.cluster.local:11211 +permacache_memcaches = memcached.default.svc.cluster.local:11211 +hardcache_memcaches = memcached.default.svc.cluster.local:11211 +num_mc_clients = 20 +``` + +### Message Queue Options on Azure + +#### Option A: Azure Service Bus (Managed) + +Azure Service Bus provides enterprise messaging with queues and topics. It uses a different API than RabbitMQ, requiring an adapter. + +```python +# Example: Azure Service Bus adapter for Tippr's queue system +from azure.servicebus import ServiceBusClient, ServiceBusMessage + +connection_str = "Endpoint=sb://tippr-bus.servicebus.windows.net/;..." +client = ServiceBusClient.from_connection_string(connection_str) + +def add_item(queue_name, item): + with client.get_queue_sender(queue_name) as sender: + message = ServiceBusMessage(json.dumps(item)) + sender.send_messages(message) + +def process_items(queue_name, callback): + with client.get_queue_receiver(queue_name) as receiver: + for message in receiver: + item = json.loads(str(message)) + callback(item) + receiver.complete_message(message) +``` + +#### Option B: Self-Managed RabbitMQ on AKS (Recommended) + +For drop-in compatibility with existing code: + +```yaml +# rabbitmq-deployment.yaml (using RabbitMQ Cluster Operator) +apiVersion: rabbitmq.com/v1beta1 +kind: RabbitmqCluster +metadata: + name: tippr-rabbitmq +spec: + replicas: 3 + persistence: + storageClassName: managed-premium + storage: 50Gi + resources: + requests: + cpu: 1 + memory: 2Gi + limits: + cpu: 2 + memory: 4Gi +``` + +```ini +# production-azure.update - RabbitMQ on AKS +amqp_host = tippr-rabbitmq.default.svc.cluster.local:5672 +amqp_user = tippr +amqp_pass = YOUR_SECURE_PASSWORD +amqp_virtual_host = / +``` + +### Compute Options on Azure + +#### Option A: Virtual Machine Scale Sets + +Traditional VM-based deployment with auto-scaling: + +```bash +# Create VM Scale Set +az vmss create \ + --resource-group tippr-rg \ + --name tippr-vmss \ + --image Ubuntu2204 \ + --vm-sku Standard_D4s_v3 \ + --instance-count 3 \ + --admin-username tippr \ + --generate-ssh-keys \ + --custom-data cloud-init.yaml \ + --load-balancer tippr-lb \ + --backend-pool-name tippr-backend + +# Configure autoscaling +az monitor autoscale create \ + --resource-group tippr-rg \ + --resource tippr-vmss \ + --resource-type Microsoft.Compute/virtualMachineScaleSets \ + --min-count 2 \ + --max-count 10 \ + --count 3 + +az monitor autoscale rule create \ + --resource-group tippr-rg \ + --autoscale-name tippr-vmss-autoscale \ + --scale out 1 \ + --condition "Percentage CPU > 70 avg 5m" +``` + +#### Option B: Azure Kubernetes Service (AKS) + +Container-based deployment with excellent Azure integration: + +```yaml +# tippr-deployment.yaml for AKS +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tippr-app +spec: + replicas: 3 + selector: + matchLabels: + app: tippr + template: + metadata: + labels: + app: tippr + spec: + containers: + - name: tippr + image: tippracr.azurecr.io/tippr:latest + ports: + - containerPort: 8001 + env: + - name: PYTHONPATH + value: "/app/tippr:/app" + resources: + requests: + cpu: "1" + memory: "2Gi" + limits: + cpu: "2" + memory: "4Gi" + readinessProbe: + httpGet: + path: /health + port: 8001 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: tippr-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: tippr-app + minReplicas: 2 + maxReplicas: 20 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 +``` + +```bash +# Create AKS cluster +az aks create \ + --resource-group tippr-rg \ + --name tippr-aks \ + --node-count 3 \ + --node-vm-size Standard_D4s_v3 \ + --enable-cluster-autoscaler \ + --min-count 2 \ + --max-count 10 \ + --network-plugin azure \ + --enable-managed-identity \ + --generate-ssh-keys +``` + +### Load Balancing & CDN on Azure + +#### Application Gateway + +Azure Application Gateway provides L7 load balancing with WAF capabilities: + +```bash +# Create Application Gateway +az network application-gateway create \ + --resource-group tippr-rg \ + --name tippr-appgw \ + --location eastus \ + --sku Standard_v2 \ + --capacity 2 \ + --vnet-name tippr-vnet \ + --subnet appgw-subnet \ + --frontend-port 443 \ + --http-settings-port 8001 \ + --http-settings-protocol Http \ + --routing-rule-type Basic \ + --priority 100 +``` + +#### Azure Front Door + CDN + +For global distribution and caching: + +```bash +# Create Azure Front Door +az afd profile create \ + --resource-group tippr-rg \ + --profile-name tippr-afd \ + --sku Premium_AzureFrontDoor + +az afd endpoint create \ + --resource-group tippr-rg \ + --profile-name tippr-afd \ + --endpoint-name tippr-endpoint \ + --enabled-state Enabled + +# Enable caching +az afd route create \ + --resource-group tippr-rg \ + --profile-name tippr-afd \ + --endpoint-name tippr-endpoint \ + --route-name default-route \ + --origin-group tippr-origin-group \ + --patterns "/*" \ + --enable-caching true \ + --query-string-caching-behavior IgnoreQueryString +``` + +### Azure Blob Storage for Media + +```ini +# production-azure.update - Azure Blob Storage +[DEFAULT] +media_provider = azure # Requires implementing Azure provider + +# Azure Blob Storage configuration +azure_storage_account = tipprstorageaccount +azure_storage_container = media +azure_storage_connection_string = DefaultEndpointsProtocol=https;AccountName=... +``` + +**Note**: The codebase has an S3 provider. You can use Azure Blob Storage's S3-compatible API or implement a native Azure provider. + +```python +# Azure Blob with S3-compatible API (via Azurite or Azure Blob S3 proxy) +# Alternatively, implement native Azure provider in r2/r2/lib/providers/media/ +from azure.storage.blob import BlobServiceClient + +blob_service = BlobServiceClient.from_connection_string(connection_string) +container = blob_service.get_container_client("media") + +def upload_media(filename, data): + blob = container.get_blob_client(filename) + blob.upload_blob(data, overwrite=True) + return f"https://{account}.blob.core.windows.net/media/{filename}" +``` + +### Azure Production Configuration + +```ini +# production-azure.update +[DEFAULT] +# CRITICAL: Disable debug mode +debug = false +template_debug = false +reload_templates = false +uncompressedJS = false +sqlprinting = false + +# Production domain +domain = yourdomain.com +media_domain = media.yourdomain.com +https_endpoint = https://yourdomain.com + +# Azure Database for PostgreSQL +db_user = tippr +db_pass = YOUR_SECURE_PASSWORD +db_port = 5432 +databases = main, comment, email, authorize, award, hc, traffic +main_db = tippr, tippr-db.postgres.database.azure.com, *, *, *, *, * + +# Azure Managed Cassandra +cassandra_seeds = tippr-cassandra-dc1.cassandra.cosmos.azure.com:10350 +cassandra_ssl = true +cassandra_pool_size = 10 +cassandra_rcl = LOCAL_QUORUM +cassandra_wcl = LOCAL_QUORUM + +# Self-managed Memcached on AKS +lockcaches = memcached.default.svc.cluster.local:11211 +permacache_memcaches = memcached.default.svc.cluster.local:11211 +hardcache_memcaches = memcached.default.svc.cluster.local:11211 + +# RabbitMQ on AKS +amqp_host = tippr-rabbitmq.default.svc.cluster.local:5672 +amqp_user = tippr +amqp_pass = YOUR_SECURE_PASSWORD + +# Azure Blob Storage +media_provider = azure +azure_storage_account = tipprstorageaccount +azure_storage_container = media + +# Security +trust_local_proxies = true +ENFORCE_RATELIMIT = true + +# Monitoring (Azure Monitor) +statsd_addr = localhost:8125 # Use statsd-exporter sidecar with Azure Monitor +``` + +### Azure Monitoring & Observability + +#### Azure Monitor + Log Analytics + +```bash +# Create Log Analytics workspace +az monitor log-analytics workspace create \ + --resource-group tippr-rg \ + --workspace-name tippr-logs + +# Enable diagnostic settings for PostgreSQL +az monitor diagnostic-settings create \ + --resource /subscriptions/.../tippr-db \ + --name tippr-db-diagnostics \ + --workspace tippr-logs \ + --logs '[{"category": "PostgreSQLLogs", "enabled": true}]' \ + --metrics '[{"category": "AllMetrics", "enabled": true}]' +``` + +#### Application Insights + +```bash +# Create Application Insights +az monitor app-insights component create \ + --resource-group tippr-rg \ + --app tippr-insights \ + --location eastus \ + --workspace tippr-logs +``` + +### Azure Cost Estimation + +For a medium-traffic deployment (~100K-500K daily active users): + +| Service | Configuration | Est. Monthly Cost | +|---------|---------------|-------------------| +| Azure Database for PostgreSQL | D4s_v3, Zone-redundant HA | $300-400 | +| Azure Managed Cassandra | 3x D8s_v4 nodes | $800-1,000 | +| *OR* Astra DB on Azure | Serverless, ~100M ops/mo | $150-300 | +| Azure Cache for Redis | C3 Standard | $150-200 | +| *OR* Memcached on AKS | 3x pods (included in AKS) | $0 (AKS cost) | +| AKS Cluster | 3x D4s_v3 nodes | $350-450 | +| Application Gateway | Standard_v2, 2 units | $150-200 | +| Azure Front Door | Premium tier | $100-150 | +| Blob Storage | 100GB + CDN | $20-50 | +| **Total (with Managed Cassandra)** | | **$1,870-2,450/month** | +| **Total (with Astra DB)** | | **$1,220-1,750/month** | + +**Notes**: +- Azure Hybrid Benefit can reduce costs if you have existing Windows Server/SQL licenses +- Reserved instances provide up to 72% savings for 1-3 year commitments +- Dev/Test pricing available for non-production environments + +### Azure Deployment Checklist + +- [ ] Create Resource Group and configure Azure subscription +- [ ] Set up Virtual Network with subnets (app, db, cache, aks) +- [ ] Provision Azure Database for PostgreSQL Flexible Server +- [ ] Set up Cassandra layer (choose one): + - [ ] **Option A**: Create Azure Managed Instance for Apache Cassandra + - [ ] **Option B**: Deploy DataStax Astra DB on Azure + - [ ] **Option C**: Deploy self-managed Cassandra on AKS +- [ ] Set up caching layer: + - [ ] **Option A**: Create Azure Cache for Redis (requires code changes) + - [ ] **Option B**: Deploy self-managed Memcached on AKS +- [ ] Deploy RabbitMQ on AKS (using RabbitMQ Cluster Operator) +- [ ] Create Azure Blob Storage account and containers +- [ ] Configure Application Gateway with WAF +- [ ] Set up Azure Front Door + CDN +- [ ] Configure Azure Key Vault for secrets +- [ ] Create AKS cluster or VM Scale Set +- [ ] Set up Azure Monitor and Log Analytics +- [ ] Configure autoscaling policies +- [ ] Set up Azure DDoS Protection (optional) +- [ ] Configure Private Link for all services + +--- + +## Caching Layer + +### Amazon ElastiCache for Memcached + +Tippr heavily relies on memcached for performance. The caching layer handles: +- Page fragment caching +- Query result caching +- Session data +- Rate limiting counters +- Lock coordination + +#### Configuration + +```ini +# production.update - ElastiCache configuration +[DEFAULT] +# ElastiCache cluster nodes (configuration endpoint for cluster mode) +lockcaches = your-cache.xxxxx.cfg.use1.cache.amazonaws.com:11211 +permacache_memcaches = your-cache.xxxxx.cfg.use1.cache.amazonaws.com:11211 +hardcache_memcaches = your-cache.xxxxx.cfg.use1.cache.amazonaws.com:11211 + +# Increase client pool for production +num_mc_clients = 20 + +# Enable mcrouter for consistent hashing (optional but recommended) +mcrouter_addr = 127.0.0.1:5050 +``` + +#### ElastiCache Cluster Sizing + +| Traffic Level | Node Type | Nodes | Total Memory | +|---------------|-----------|-------|--------------| +| Low | cache.t3.medium | 2 | 6.4GB | +| Medium | cache.r6g.large | 3-5 | 39-65GB | +| High | cache.r6g.xlarge | 5-10 | 130-260GB | + +#### Caching Strategy from Tippr's Experience + +1. **Batch memcache calls**: Network latency on cloud is higher than datacenter. Batch multiple gets into single requests +2. **Use consistent hashing**: Prevents cache invalidation when scaling cluster +3. **Separate cache tiers**: Use different clusters for different purposes (locks, permacache, stalecache) +4. **Aggressive caching for logged-out users**: Serve fully cached pages to anonymous users via CDN + +```ini +# Enable stale cache for non-critical data (improves resilience) +stalecaches = your-stale-cache.xxxxx.cfg.use1.cache.amazonaws.com:11211 +``` + +--- + +## Message Queue + +### Amazon MQ for RabbitMQ + +Tippr uses RabbitMQ for async job processing: votes, comments, scraping, search indexing. + +#### Configuration + +```ini +# production.update - Amazon MQ configuration +[DEFAULT] +amqp_host = b-xxxxx.mq.us-east-1.amazonaws.com:5671 +amqp_user = tippr +amqp_pass = YOUR_SECURE_PASSWORD +amqp_virtual_host = /tippr + +# Enable SSL for Amazon MQ +amqp_ssl = true + +# Enable AMQP logging for production monitoring +amqp_logging = true +``` + +#### Queue Consumer Scaling + +Scale queue consumers based on queue depth. Production consumer counts: + +```bash +# /home/tippr/consumer-count.d/ +echo 5 > commentstree_q # Parallelize comment tree processing +echo 3 > vote_link_q # Handle vote processing +echo 3 > vote_comment_q +echo 2 > scraper_q # Thumbnail/embed scraping +echo 2 > butler_q # User mentions +echo 1 > del_account_q +echo 1 > markread_q +``` + +From Tippr's experience: **Put everything into a queue**. Queues: +- Buffer traffic spikes +- Allow monitoring via queue length +- Hide temporary failures from users +- Enable horizontal scaling of workers + +--- + +## Application Servers + +### Gunicorn Configuration + +Replace development paster server with production Gunicorn: + +```ini +# production.update +[server:main] +use = egg:gunicorn#main +host = 0.0.0.0 +port = 8001 +workers = 4 +worker_class = sync +timeout = 30 +keepalive = 2 +max_requests = 1000 +max_requests_jitter = 50 +``` + +#### Worker Calculation + +``` +workers = (2 * CPU_cores) + 1 +``` + +For c5.xlarge (4 vCPUs): `workers = 9` + +### Systemd Service (Production) + +```ini +# /etc/systemd/system/tippr-app.service +[Unit] +Description=Tippr Application Server +After=network.target + +[Service] +Type=simple +User=tippr +Group=tippr +WorkingDirectory=/home/tippr/src/tippr/r2 +Environment=PYTHONPATH=/home/tippr/src/tippr:/home/tippr/src +Environment=PATH=/home/tippr/venv/bin:/usr/bin +ExecStart=/home/tippr/venv/bin/gunicorn \ + --bind 0.0.0.0:8001 \ + --workers 4 \ + --timeout 30 \ + --access-logfile /var/log/tippr/access.log \ + --error-logfile /var/log/tippr/error.log \ + 'r2.config.middleware:make_app()' +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +### Auto Scaling Group + +Use AWS Auto Scaling with: +- Launch template with pre-baked AMI +- Scaling policies based on CPU or request count +- Health checks via ALB +- Minimum 2 instances for availability + +--- + +## Load Balancing & Reverse Proxy + +### Application Load Balancer (ALB) + +Replace local HAProxy with ALB for production: + +``` +Target Groups: +- tippr-app (port 8001) - Main application +- tippr-websockets (port 9001) - WebSocket connections +- tippr-media (port 9000) - Static/media files (or use S3/CloudFront) +``` + +#### Routing Rules + +| Path Pattern | Target | +|--------------|--------| +| `/websocket/*` | tippr-websockets | +| `/media/*` | S3 via CloudFront | +| `/pixel/*` | tippr-pixel (or Lambda@Edge) | +| `/*` | tippr-app | + +### CloudFront CDN + +**Critical for performance**: Serve cached content to logged-out users via CDN. + +From Tippr's experience: By serving logged-out users (80% of traffic) cached content from CDN: +- Reduces load on application servers dramatically +- Provides geographic edge caching +- Handles traffic spikes gracefully +- If Tippr goes down, logged-out users may never notice + +``` +CloudFront Configuration: +- Origin: ALB +- Cache behaviors: + - /static/* -> Cache TTL 1 year (versioned assets) + - /media/* -> Cache TTL 1 day + - Default -> Cache for logged-out users, bypass for logged-in +``` + +--- + +## Configuration Changes for Production + +Create `production.update` file to override development defaults: + +```ini +# production.update +[DEFAULT] +# CRITICAL: Disable debug mode +debug = false +template_debug = false +reload_templates = false +uncompressedJS = false +sqlprinting = false + +# Production domain +domain = yourdomain.com +media_domain = media.yourdomain.com +https_endpoint = https://yourdomain.com +payment_domain = https://pay.yourdomain.com + +# Security +trust_local_proxies = true # If behind ALB +cdn_provider = cloudflare # or custom + +# Media storage +media_provider = s3 +S3KEY_ID = YOUR_AWS_KEY +S3SECRET_KEY = YOUR_AWS_SECRET +s3_media_buckets = your-media-bucket +s3_image_buckets = your-image-bucket +s3_media_domain = s3.amazonaws.com + +# Disable dev-only features +disable_captcha = false +disable_ratelimit = false +disable_require_admin_otp = false + +# Enable rate limiting +ENFORCE_RATELIMIT = true +RL_SITEWIDE_ENABLED = true + +# Search (if using CloudSearch) +search_provider = cloudsearch +CLOUDSEARCH_SEARCH_API = https://search-xxx.us-east-1.cloudsearch.amazonaws.com +CLOUDSEARCH_DOC_API = https://doc-xxx.us-east-1.cloudsearch.amazonaws.com + +# Monitoring +statsd_addr = your-statsd-host:8125 +statsd_sample_rate = 0.1 +amqp_logging = true + +# Email +email_provider = mailgun +mailgun_api_base_url = https://api.mailgun.net/v3 +``` + +Generate production.ini: +```bash +cd /home/tippr/src/tippr/r2 +make ini # Creates production.ini from example.ini + production.update +``` + +--- + +## Scaling Lessons from Reddit's History + +> **Note**: These lessons come from Reddit's engineering team's experience scaling the original codebase from 1 million to 1 billion pageviews. While Tippr is a fork with modernizations, these architectural principles remain relevant. +> +> **Source**: [Reddit's scaling talk by Jeremy Edberg](https://www.youtube.com/watch?v=nUcO7n4hek4) + +### 1. Plan for Scale, But Don't Over-Engineer Early + +> "It's not necessary to build a scalable architecture from the start. You don't know what your feature set will be." + +Start with managed services, identify bottlenecks as they appear, then optimize. + +### 2. Use SSDs for Databases + +> "Think of SSDs as cheap RAM, not expensive disk. Tippr reduced from 12 database servers to 1 with tons of headroom." + +Always use SSD-backed storage (gp3, io1) for RDS and any self-managed databases. + +### 3. Batch Network Calls + +> "In the datacenter they had submillisecond memcache access. EC2 has 10x higher latency." + +The codebase already batches memcache calls, but ensure: +- Use `get_multi()` instead of multiple `get()` calls +- Batch database queries where possible +- Use connection pooling + +### 4. Separate Traffic by Speed + +> "Use a proxy to route slow traffic one place and fast traffic another." + +Monitor response times by endpoint. Route slow operations (search, heavy listings) to dedicated worker pools. + +### 5. Put Limits on Everything + +> "Everything that can happen repeatedly put a high limit on it." + +```ini +# production.update - Limits +sr_banned_quota = 10000 +sr_moderator_invite_quota = 10000 +max_sr_images = 50 +max_comments = 500 +wiki_max_page_length_bytes = 262144 +``` + +### 6. Expire Old Data + +> "Lock out old threads and create a fully rendered page and cache it." + +Tippr archives threads after 6 months. This: +- Prevents unbounded data growth +- Allows aggressive caching of old content +- Reduces database load + +### 7. Treat Logged-Out Users as Second-Class Citizens + +> "By always giving logged-out users cached content, Akamai bears the brunt of traffic." + +- Serve fully cached pages to anonymous users via CDN +- Don't compute personalized content for logged-out users +- 80% of traffic can be served from cache + +### 8. Cassandra for Fast Negative Lookups + +> "Cassandra's bloom filters enabled really fast negative lookups for comments." + +Use Cassandra/Keyspaces for: +- Vote data (fast to check "what haven't I voted on") +- Activity tracking +- Time-series data + +### 9. Keep Postgres for Money + +> "Everything that deals with money is kept in a relational database because of transactions and easier analytics." + +Use RDS PostgreSQL for: +- User accounts +- Gold subscriptions +- Payment history +- Anything requiring ACID transactions + +### 10. Let Users Do the Work + +> "Thousands of volunteer moderators take care of most of the spam problem." + +This is an architectural principle: design for user-generated moderation rather than costly automated systems. + +--- + +## Monitoring & Observability + +### Metrics (StatsD/CloudWatch) + +```ini +# production.update +statsd_addr = your-statsd-host:8125 +statsd_sample_rate = 0.1 # Sample 10% of requests +stats_sample_rate = 10 # 10% of events +``` + +Key metrics to monitor: +- Request latency (p50, p95, p99) +- Error rates by endpoint +- Queue depths (RabbitMQ) +- Cache hit rates +- Database connection pool usage +- Active user counts + +### Logging + +```ini +# production.update +[logger_root] +level = INFO +handlers = console, syslog + +[handler_syslog] +class = handlers.SysLogHandler +args = ('/dev/log', handlers.SysLogHandler.LOG_USER) +formatter = tippr +``` + +Ship logs to CloudWatch Logs or ELK stack. + +### Alerting + +Set up alerts for: +- Queue depth > threshold (jobs backing up) +- Error rate > 1% +- Response time p95 > 2s +- Database connections near max +- Cache hit rate < 90% + +--- + +## Security Considerations + +### Secrets Management + +```ini +# Use AWS Secrets Manager or Parameter Store +# Never commit secrets to code + +# production.update +liveconfig_source = zookeeper # or secrets manager +secrets_source = zookeeper +``` + +### Network Security + +``` +VPC Configuration: +- Public subnets: ALB only +- Private subnets: Application servers, databases +- Database security groups: Only allow app server access +- Use VPC endpoints for AWS services +``` + +### Authentication + +```ini +# production.update +bcrypt_work_factor = 12 # Increase if hardware allows +ADMIN_COOKIE_TTL = 32400 +ADMIN_COOKIE_MAX_IDLE = 900 +disable_require_admin_otp = false # Require OTP for admins +``` + +### HTTPS + +```ini +# Force HTTPS in production +feature_force_https = on +feature_https_redirect = on +feature_upgrade_cookies = on +``` + +--- + +## Deployment Checklist + +### Pre-Launch + +- [ ] Provision RDS PostgreSQL Multi-AZ +- [ ] Provision Amazon Keyspaces or Cassandra cluster +- [ ] Provision ElastiCache Memcached cluster +- [ ] Provision Amazon MQ for RabbitMQ +- [ ] Configure S3 buckets for media storage +- [ ] Set up CloudFront distribution +- [ ] Configure ALB with target groups +- [ ] Create production.update with all settings +- [ ] Generate and test production.ini +- [ ] Set up VPC with proper security groups +- [ ] Configure secrets in Secrets Manager +- [ ] Set up monitoring and alerting +- [ ] Create AMI with application pre-installed +- [ ] Configure Auto Scaling group +- [ ] Set up CI/CD pipeline + +### Launch + +- [ ] Migrate database schema to RDS +- [ ] Create Keyspaces keyspace and tables +- [ ] Deploy application to ASG +- [ ] Verify health checks passing +- [ ] Test all major user flows +- [ ] Enable CloudFront caching +- [ ] Monitor error rates and latency + +### Post-Launch + +- [ ] Review and tune cache hit rates +- [ ] Analyze slow queries and optimize +- [ ] Right-size instance types based on actual usage +- [ ] Enable detailed monitoring +- [ ] Document runbooks for common issues +- [ ] Schedule regular backups verification + +--- + +## Cost Estimation (Starting Point) + +For a medium-traffic deployment (~100K-500K daily active users): + +| Service | Configuration | Est. Monthly Cost | +|---------|---------------|-------------------| +| RDS PostgreSQL | db.r6g.large Multi-AZ | $300-400 | +| Amazon Keyspaces | On-demand, ~10M reads/writes/day | $50-150 | +| ElastiCache | 3x cache.r6g.large | $300-400 | +| Amazon MQ | mq.m5.large | $150-200 | +| EC2 (App Servers) | 3x c5.xlarge | $400-500 | +| ALB | Standard | $20-50 | +| CloudFront | 1TB transfer | $100-200 | +| S3 | 100GB storage | $5-10 | +| **Total** | | **$1,325-1,910/month** | + +Costs scale with traffic. Start small and scale up based on actual usage. + +--- + +## Cloud Provider Comparison + +### Feature Comparison Matrix + +| Feature | AWS | GCP | Azure | Notes | +|---------|-----|-----|-------|-------| +| **PostgreSQL** | RDS | Cloud SQL | Azure DB for PostgreSQL | All excellent, mature options | +| **Cassandra (CQL)** | Keyspaces | Astra DB | Managed Cassandra | All CQL-compatible, minimal code changes | +| **Memcached** | ElastiCache | Memorystore | Self-managed on AKS | Azure lacks managed Memcached | +| **RabbitMQ** | Amazon MQ | Self-managed | Self-managed | Only AWS offers managed RabbitMQ | +| **Kubernetes** | EKS | GKE | AKS | GKE > AKS > EKS for ease of use | +| **Load Balancing** | ALB/NLB | Cloud LB | App Gateway/Front Door | All capable; Azure Front Door excellent globally | +| **CDN** | CloudFront | Cloud CDN | Azure CDN/Front Door | All comparable | +| **Hybrid Cloud** | Outposts | Anthos | Azure Arc | Azure strongest for hybrid/on-prem | +| **Enterprise Integration** | Good | Good | Excellent | Azure best for Microsoft shops | +| **Pricing** | Pay-as-you-go | Sustained use discounts | Reserved + Hybrid Benefit | GCP/Azure offer more discount options | +| **AI/ML** | SageMaker, Bedrock | Vertex AI, BigQuery | Azure OpenAI, Cognitive Services | All strong; Azure has OpenAI partnership | + +### When to Choose AWS + +**Choose AWS if:** + +1. **Minimal code changes**: Amazon Keyspaces provides CQL compatibility, meaning the existing Cassandra code works with configuration changes only +2. **Managed RabbitMQ**: Amazon MQ is a drop-in replacement for self-hosted RabbitMQ +3. **S3 integration**: The codebase already has an S3 media provider +4. **Existing AWS infrastructure**: Easier to integrate with existing AWS resources +5. **Enterprise support needs**: AWS has a larger partner ecosystem + +**AWS Strengths for Tippr:** +- Keyspaces = no code changes for Cassandra layer +- Amazon MQ = no code changes for queue layer +- S3 provider already exists in codebase +- Broader managed service options + +### When to Choose GCP + +**Choose GCP if:** + +1. **Kubernetes-native deployment**: GKE is generally considered superior to EKS +2. **Cost optimization priority**: Sustained use discounts and committed use pricing +3. **Network-intensive workloads**: GCP's network is often faster, especially globally +4. **AI/ML integration**: Need BigQuery, Vertex AI, or vector search capabilities +5. **Multi-cloud strategy**: Astra DB works across GCP, AWS, and Azure + +**GCP Strengths for Tippr:** +- GKE is excellent for container orchestration +- Better sustained use pricing for always-on workloads +- Cloud SQL is very reliable +- Simpler IAM model +- Stronger global network backbone +- DataStax Astra DB provides CQL compatibility (no code changes needed) +- Native BigQuery integration for analytics on Cassandra data + +### When to Choose Azure + +**Choose Azure if:** + +1. **Enterprise/Microsoft ecosystem**: Already using Azure AD, Office 365, or other Microsoft services +2. **Hybrid cloud requirements**: Need to connect to on-premises data centers with Azure Arc +3. **CQL compatibility needed**: Azure Managed Instance for Apache Cassandra is fully CQL-compatible +4. **Compliance requirements**: Azure has extensive compliance certifications (FedRAMP, HIPAA, etc.) +5. **Windows workloads**: Running any Windows-based components alongside Tippr + +**Azure Strengths for Tippr:** +- Azure Managed Cassandra = no code changes for Cassandra layer +- Strong enterprise security and compliance features +- Azure Front Door provides excellent global load balancing +- AKS is a solid Kubernetes option with good Azure integration +- Hybrid Benefit can significantly reduce costs with existing licenses +- Azure OpenAI for AI-powered features + +**Azure Considerations:** +- No managed Memcached (must self-host or switch to Redis) +- No managed RabbitMQ (must self-host on AKS) +- Requires implementing Azure Blob provider for media storage + +### Cost Comparison Summary + +| Deployment Size | AWS | GCP + Astra DB | GCP + Bigtable | Azure + Managed Cassandra | Azure + Astra DB | +|-----------------|-----|----------------|----------------|---------------------------|------------------| +| Low traffic | $800-1,200/mo | $700-1,000/mo | $1,200-1,500/mo | $900-1,300/mo | $700-1,000/mo | +| Medium traffic | $1,325-1,910/mo | $1,200-1,700/mo | $2,200-2,750/mo | $1,870-2,450/mo | $1,220-1,750/mo | +| High traffic | $3,000-5,000/mo | $2,500-4,000/mo | $4,500-6,000/mo | $4,000-6,000/mo | $2,800-4,500/mo | + +**Notes**: +- **AWS**: Consistent pricing, all managed services, minimal code changes +- **GCP + Astra DB**: Best balance of ease and cost; serverless pricing scales with usage +- **GCP + Bigtable**: Most expensive but scales to petabyte-level workloads +- **Azure + Managed Cassandra**: Good for enterprise; higher base cost but Hybrid Benefit can reduce +- **Azure + Astra DB**: Best Azure option for cost; Astra DB is multi-cloud + +### Migration Effort Comparison + +| Component | AWS | GCP + Astra DB | Azure + Managed Cassandra | Azure + Astra DB | +|-----------|-----|----------------|---------------------------|------------------| +| PostgreSQL | Low (config) | Low (config) | Low (config) | Low (config) | +| Cassandra | Low (config + SSL) | Low (config + SSL) | Low (config + SSL) | Low (config + SSL) | +| Memcached | Low (config) | Low (config) | Medium (self-managed) | Medium (self-managed) | +| RabbitMQ | Low (config + SSL) | Medium (self-managed) | Medium (self-managed) | Medium (self-managed) | +| Media Storage | None (S3 exists) | Low (S3-compat API) | Medium (new provider) | Medium (new provider) | +| **Overall** | **Low** | **Low-Medium** | **Medium** | **Medium** | + +### Recommendation + +**For fastest time-to-production**: Choose **AWS** +- All managed services available (Keyspaces, Amazon MQ, ElastiCache) +- S3 provider already exists in codebase +- Minimal code changes required + +**For cost optimization with managed services**: Choose **GCP + Astra DB** or **Azure + Astra DB** +- Astra DB serverless pricing scales with usage +- GCP sustained use discounts / Azure Hybrid Benefit reduce costs +- Astra DB works across all three clouds for multi-cloud flexibility + +**For enterprise/hybrid deployments**: Choose **Azure** +- Best integration with Microsoft ecosystem (Azure AD, Office 365) +- Azure Arc for hybrid cloud scenarios +- Strong compliance certifications +- Azure Managed Cassandra is fully CQL-compatible + +**For maximum scalability**: Choose **GCP with Bigtable** +- Bigtable scales to petabytes with consistent performance +- Requires significant code refactoring +- Best for Reddit-scale traffic (billions of requests) + +**For AI/ML workloads**: Choose based on AI platform preference +- **Azure**: Azure OpenAI Service for GPT-4 integration +- **GCP**: Vertex AI + BigQuery + Astra Vector Search +- **AWS**: Amazon Bedrock + SageMaker + +### Hybrid Considerations + +You could also consider: +- **Multi-cloud with Astra DB**: Use Astra DB (available on all three clouds) for database portability +- **Gradual migration**: Start on AWS (easiest), migrate to GCP/Azure later for cost savings +- **Disaster recovery**: Use one cloud as primary, another as DR +- **Geographic distribution**: Use different clouds in different regions based on presence + +--- + +## Additional Resources + +### AWS Resources +- [AWS Well-Architected Framework](https://aws.amazon.com/architecture/well-architected/) +- [Amazon Keyspaces Documentation](https://docs.aws.amazon.com/keyspaces/) +- [Amazon RDS for PostgreSQL](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html) +- [Amazon MQ for RabbitMQ](https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/welcome.html) + +### GCP Resources +- [Google Cloud Architecture Framework](https://cloud.google.com/architecture/framework) +- [Cloud SQL for PostgreSQL](https://cloud.google.com/sql/docs/postgres) +- [Cloud Bigtable Documentation](https://cloud.google.com/bigtable/docs) +- [GKE Best Practices](https://cloud.google.com/kubernetes-engine/docs/best-practices) +- [Memorystore for Memcached](https://cloud.google.com/memorystore/docs/memcached) + +### Azure Resources +- [Azure Well-Architected Framework](https://docs.microsoft.com/azure/architecture/framework/) +- [Azure Database for PostgreSQL](https://docs.microsoft.com/azure/postgresql/) +- [Azure Managed Instance for Apache Cassandra](https://docs.microsoft.com/azure/managed-instance-apache-cassandra/) +- [Azure Kubernetes Service (AKS)](https://docs.microsoft.com/azure/aks/) +- [Azure Cache for Redis](https://docs.microsoft.com/azure/azure-cache-for-redis/) +- [Azure Front Door](https://docs.microsoft.com/azure/frontdoor/) +- [Azure Blob Storage](https://docs.microsoft.com/azure/storage/blobs/) + +### DataStax Astra DB Resources +- [Astra DB Documentation](https://docs.datastax.com/en/astra/docs/) +- [Astra DB on Google Cloud](https://www.datastax.com/products/datastax-astra/google-cloud) +- [Python Driver for Astra DB](https://docs.datastax.com/en/developer/python-driver/latest/cloud/) +- [Astra DB + BigQuery Integration](https://docs.datastax.com/en/astra/docs/integrations/google-bigquery.html) +- [Astra Vector Search](https://docs.datastax.com/en/astra/docs/vector-search-overview.html) + +### Reddit Scaling Resources +- [Original Reddit Scaling Talk](https://www.youtube.com/watch?v=nUcO7n4hek4) - Jeremy Edberg at RAMP Conference +- [High Scalability - Reddit Lessons](http://highscalability.com/blog/2013/8/26/reddit-lessons-learned-from-mistakes-made-scaling-to-1-billi.html) +- [Reddit Engineering (RedditEng subreddit)](https://www.reddit.com/r/RedditEng/) + +--- + +## Kubernetes-Native Deployment + +For production-grade deployments across any cloud provider, Kubernetes provides consistent orchestration. This section provides a cloud-agnostic Kubernetes approach. + +### Why Kubernetes? + +| Advantage | Description | +|-----------|-------------| +| **Automatic failover** | Self-healing restarts failed containers | +| **Zero-downtime updates** | Rolling deployments with health checks | +| **Horizontal autoscaling** | Scale based on CPU, memory, or custom metrics | +| **Resource efficiency** | Better bin-packing than VM-per-service | +| **Service discovery** | Built-in DNS and load balancing | +| **Portability** | Same manifests work on EKS, GKE, AKS, or self-hosted | + +### Helm Chart Structure + +```yaml +# Chart.yaml +apiVersion: v2 +name: tippr +version: 1.0.0 +description: Tippr application deployment + +dependencies: + - name: postgresql + version: "12.x.x" + repository: "https://charts.bitnami.com/bitnami" + condition: postgresql.enabled + - name: rabbitmq + version: "11.x.x" + repository: "https://charts.bitnami.com/bitnami" + condition: rabbitmq.enabled + - name: memcached + version: "6.x.x" + repository: "https://charts.bitnami.com/bitnami" + condition: memcached.enabled +``` + +```yaml +# values.yaml +replicaCount: 3 + +image: + repository: your-registry/tippr + tag: latest + pullPolicy: IfNotPresent + +# External managed database (recommended for production) +database: + external: true + host: tippr-db.xxx.rds.amazonaws.com + port: 5432 + name: tippr + ssl: true + existingSecret: tippr-db-credentials + +# External Cassandra/Keyspaces +cassandra: + external: true + seeds: cassandra.us-east-1.amazonaws.com:9142 + ssl: true + existingSecret: tippr-cassandra-credentials + +# Local memcached (low latency is important) +memcached: + enabled: true + replicaCount: 3 + resources: + requests: + memory: 1Gi + limits: + memory: 2Gi + +# Local RabbitMQ (or external Amazon MQ) +rabbitmq: + enabled: true + replicaCount: 3 + auth: + existingPasswordSecret: tippr-rabbitmq-credentials + +# Autoscaling +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 20 + targetCPUUtilization: 70 + targetMemoryUtilization: 80 + +# Ingress +ingress: + enabled: true + className: nginx # or alb, gce, azure + hosts: + - host: tippr.net + paths: + - path: / + pathType: Prefix + tls: + - secretName: tippr-tls + hosts: + - tippr.net + +# Resources +resources: + requests: + cpu: "500m" + memory: "1Gi" + limits: + cpu: "2" + memory: "4Gi" +``` + +### Application Deployment Manifest + +```yaml +# templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "tippr.fullname" . }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: tippr + template: + metadata: + labels: + app: tippr + spec: + containers: + - name: tippr + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + ports: + - containerPort: 8001 + env: + - name: PYTHONPATH + value: "/app/tippr:/app" + - name: DB_HOST + valueFrom: + secretKeyRef: + name: tippr-db-credentials + key: host + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: tippr-db-credentials + key: password + resources: + {{- toYaml .Values.resources | nindent 10 }} + readinessProbe: + httpGet: + path: /health + port: 8001 + initialDelaySeconds: 10 + periodSeconds: 5 + livenessProbe: + httpGet: + path: /health + port: 8001 + initialDelaySeconds: 30 + periodSeconds: 10 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "tippr.fullname" . }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "tippr.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilization }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilization }} +``` + +### Deployment Commands + +```bash +# Add Bitnami repo for dependencies +helm repo add bitnami https://charts.bitnami.com/bitnami +helm repo update + +# Install with managed databases (production recommended) +helm install tippr ./tippr-chart \ + --namespace tippr \ + --create-namespace \ + --set database.external=true \ + --set cassandra.external=true \ + --set-file secrets.dbPassword=./secrets/db-password.txt + +# Install with all local services (development/testing) +helm install tippr ./tippr-chart \ + --namespace tippr \ + --create-namespace \ + --set postgresql.enabled=true \ + --set database.external=false + +# Upgrade with new image +helm upgrade tippr ./tippr-chart \ + --namespace tippr \ + --set image.tag=v1.2.3 + +# Rollback if needed +helm rollback tippr 1 --namespace tippr +``` + +--- + +## Migration from Development to Production + +If you started with `install-tippr.sh` and need to move to production, follow this guide. + +### Phase 1: Data Export (on development machine) + +```bash +# 1. Stop the application to ensure consistent data +sudo systemctl stop tippr-app + +# 2. Export PostgreSQL +pg_dump -U tippr -h localhost tippr > tippr_dev_backup.sql + +# 3. Export Cassandra data (for each table) +cqlsh localhost 9042 -e "COPY tippr.votes TO 'votes.csv' WITH HEADER=TRUE;" +cqlsh localhost 9042 -e "COPY tippr.activity TO 'activity.csv' WITH HEADER=TRUE;" + +# 4. Package media files (if stored locally) +tar -czvf media_backup.tar.gz /var/lib/tippr/media/ + +# 5. Backup configuration +cp /home/tippr/src/tippr/r2/production.ini production.ini.backup +``` + +### Phase 2: Cloud Setup + +**For AWS:** +```bash +# Create RDS PostgreSQL +aws rds create-db-instance \ + --db-instance-identifier tippr-prod \ + --db-instance-class db.t3.medium \ + --engine postgres \ + --master-username tippr \ + --master-user-password YOUR_PASSWORD \ + --allocated-storage 100 \ + --multi-az + +# Wait for RDS to be available +aws rds wait db-instance-available --db-instance-identifier tippr-prod + +# Get endpoint +aws rds describe-db-instances --db-instance-identifier tippr-prod \ + --query 'DBInstances[0].Endpoint.Address' --output text +``` + +### Phase 3: Data Import + +```bash +# Import PostgreSQL to RDS +psql -h tippr-prod.xxx.rds.amazonaws.com -U tippr -d tippr < tippr_dev_backup.sql + +# Import Cassandra to Keyspaces (use smaller batches) +cqlsh cassandra.us-east-1.amazonaws.com 9142 --ssl \ + -e "COPY tippr.votes FROM 'votes.csv' WITH HEADER=TRUE AND MAXBATCHSIZE=10;" + +# Upload media to S3 +aws s3 sync /var/lib/tippr/media/ s3://your-tippr-media-bucket/ +``` + +### Phase 4: Application Configuration + +Create `production.update` with cloud endpoints: + +```ini +[DEFAULT] +debug = false + +# Cloud database +db_user = tippr +db_pass = YOUR_RDS_PASSWORD +main_db = tippr, tippr-prod.xxx.rds.amazonaws.com, *, *, *, *, * +db_ssl = true + +# Cloud Cassandra +cassandra_seeds = cassandra.us-east-1.amazonaws.com:9142 +cassandra_ssl = true + +# Cloud media storage +media_provider = s3 +s3_media_buckets = your-tippr-media-bucket +``` + +### Phase 5: Verification Checklist + +- [ ] Database connection works: `psql -h -U tippr -c "SELECT 1"` +- [ ] Cassandra connection works: `cqlsh 9142 --ssl -e "DESCRIBE KEYSPACES"` +- [ ] Application starts without errors +- [ ] Login/logout works +- [ ] Posting and voting works +- [ ] Media uploads work +- [ ] All background jobs are processing (check queue depths) + +### Phase 6: DNS Cutover + +```bash +# Update DNS to point to production load balancer +# Reduce TTL beforehand (e.g., to 300 seconds) +# Monitor closely for 24-48 hours after cutover +``` + +--- + +## Backup & Disaster Recovery + +### Backup Strategy + +| Component | Backup Method | Frequency | Retention | +|-----------|--------------|-----------|-----------| +| PostgreSQL | RDS automated snapshots | Daily | 30 days | +| PostgreSQL | Manual snapshots | Before major changes | 90 days | +| Cassandra/Keyspaces | Point-in-time recovery | Continuous | 35 days | +| Media (S3) | Cross-region replication | Real-time | Indefinite | +| Configuration | Git repository | Every change | Indefinite | + +### PostgreSQL Backup (RDS) + +```bash +# Enable automated backups (during RDS creation or modify) +aws rds modify-db-instance \ + --db-instance-identifier tippr-prod \ + --backup-retention-period 30 \ + --preferred-backup-window "03:00-04:00" + +# Create manual snapshot before risky changes +aws rds create-db-snapshot \ + --db-instance-identifier tippr-prod \ + --db-snapshot-identifier tippr-pre-migration-$(date +%Y%m%d) + +# Restore from snapshot (creates new instance) +aws rds restore-db-instance-from-db-snapshot \ + --db-instance-identifier tippr-restored \ + --db-snapshot-identifier tippr-pre-migration-20240115 +``` + +### Cassandra/Keyspaces Backup + +Amazon Keyspaces provides Point-in-Time Recovery (PITR): + +```bash +# Enable PITR on a table +aws keyspaces update-table \ + --keyspace-name tippr \ + --table-name votes \ + --point-in-time-recovery status=ENABLED + +# Restore to a point in time (creates new table) +aws keyspaces restore-table \ + --source-keyspace-name tippr \ + --source-table-name votes \ + --target-keyspace-name tippr \ + --target-table-name votes_restored \ + --restore-timestamp 2024-01-15T12:00:00Z +``` + +### Media Backup (S3) + +```bash +# Enable versioning +aws s3api put-bucket-versioning \ + --bucket your-tippr-media \ + --versioning-configuration Status=Enabled + +# Enable cross-region replication +aws s3api put-bucket-replication \ + --bucket your-tippr-media \ + --replication-configuration file://replication-config.json +``` + +### Disaster Recovery Runbook + +**RTO (Recovery Time Objective)**: 4 hours +**RPO (Recovery Point Objective)**: 1 hour + +| Scenario | Recovery Steps | Estimated Time | +|----------|----------------|----------------| +| Single AZ failure | Automatic RDS failover | 1-2 minutes | +| Application bug | Rollback deployment | 5-10 minutes | +| Database corruption | Restore from snapshot | 30-60 minutes | +| Region outage | Promote DR region | 2-4 hours | +| Accidental deletion | Restore from backup | 30-60 minutes | + +### Recovery Testing + +Schedule quarterly DR tests: + +```markdown +## DR Test Checklist + +1. [ ] Restore PostgreSQL from 24-hour-old snapshot to test instance +2. [ ] Verify data integrity with checksums +3. [ ] Restore Cassandra table from PITR +4. [ ] Test application against restored databases +5. [ ] Document recovery time and any issues +6. [ ] Update runbooks based on findings +``` + +--- + +## Self-Hosted Production Deployment + +## Hybrid Deployment Strategy + +This section describes a hybrid deployment approach that combines local (on-prem) compute and caching with cloud-managed storage and databases. The goal is to keep latency-sensitive services local while moving durable storage and backups to the cloud to reduce operational risk. + +### Hybrid Architecture Diagram + +``` + ┌──────────────────────────────────┐ + │ CLOUD │ + │ │ + │ ┌───────────┐ ┌───────────┐ │ + │ │ Managed │ │ Object │ │ + │ │ Database │ │ Storage │ │ + │ │(Postgres) │ │ (S3/GCS) │ │ + │ └───────────┘ └───────────┘ │ + │ │ + │ ┌───────────┐ │ + │ │ Backups │ │ + │ │(Optional) │ │ + │ └───────────┘ │ + └────────────┬─────────────────────┘ + │ VPN/Direct Connect + │ +┌─────────────────────────────────┼─────────────────────────────────┐ +│ LOCAL │ │ +│ │ │ +│ ┌──────────┐ ┌──────────────┴──────────────┐ │ +│ │ pfSense │───▶│ App Servers │ │ +│ │ HAProxy │ │ (192.168.1.101-103) │ │ +│ └──────────┘ └──────────────┬──────────────┘ │ +│ │ │ +│ ┌──────────────────┼──────────────────┐ │ +│ ▼ ▼ ▼ │ +│ ┌───────────┐ ┌───────────┐ ┌──────────┐ │ +│ │ Memcached │ │ RabbitMQ │ │ Redis │ │ +│ └───────────┘ └───────────┘ └──────────┘ │ +│ │ +└──────────────────────────────────────────────────────────────────┘ +``` + +### Priority Order: What to Cloud First + +#### 1. Object Storage (S3/GCS/Azure Blob) - Do This First + +**Why first:** +- Extremely cheap (~$0.02/GB/month) +- Zero maintenance +- Built-in CDN integration +- Offloads bandwidth from your home connection +- No migration pain - just configure and use + +**Cost:** $5-20/month for typical usage + +**Example `production.update` snippet for media storage:** + +```ini +# production.update +media_provider = s3 +S3KEY_ID = YOUR_KEY +S3SECRET_KEY = YOUR_SECRET +s3_media_buckets = your-tippr-media +s3_media_domain = s3.amazonaws.com +# Or use Cloudflare R2 (S3-compatible, no egress fees) +s3_media_domain = your-account.r2.cloudflarestorage.com +``` + +#### 2. PostgreSQL (RDS/Cloud SQL) - High Value, Moderate Cost + +**Why second:** +- Hardest to manage yourself (backups, replication, failover) +- Data loss here is catastrophic +- Automatic backups, point-in-time recovery +- Easy read replicas when you need them +- Connection from local servers works fine over internet/VPN + +**Cost:** $50-150/month (smallest production tier) + +**Provider comparison (small production tiers):** + +| Provider | Smallest Production Tier | Monthly Cost | +|----------|-------------------------|--------------| +| AWS RDS | db.t3.small (2GB RAM) | ~$50-70 | +| GCP Cloud SQL | db-custom-1-3840 | ~$40-60 | +| Azure PostgreSQL | Burstable B1ms | ~$35-50 | +| Neon (Serverless) | Free tier + usage | ~$0-25 | +| Supabase | Pro plan | ~$25 | + +**Budget Option: Neon or Supabase** +- Serverless PostgreSQL with generous free tiers +- Pay only for what you use +- Great for lower traffic + +**Example `production.update` for Neon:** + +```ini +# production.update for Neon +main_db = tippr, ep-cool-darkness-123456.us-east-2.aws.neon.tech, *, *, *, *, * +db_port = 5432 +# Neon requires SSL +db_ssl = true +``` + +#### 3. Backups to Cloud Storage - Essential, Cheap + +Even if you keep databases local initially, back up to cloud. + +**Cost:** $5-10/month + +| Provider | Cost | Notes | +|----------|------|-------| +| Backblaze B2 | $0.005/GB | Cheapest, S3-compatible | +| Wasabi | $0.0059/GB | No egress fees | +| Cloudflare R2 | $0.015/GB | No egress fees | + +### What to Keep Local (Cost Savings) + +#### Keep Local: Memcached/Redis + +**Why:** +- Latency-sensitive (cloud adds ~10-50ms) +- Easy to run, low maintenance +- Cheap hardware requirement +- Data is ephemeral anyway + +#### Keep Local: RabbitMQ + +**Why:** +- Works fine locally +- Amazon MQ is expensive (~$150/month minimum) +- Queue data is transient +- Easy to migrate later if needed + +#### Keep Local: Cassandra (Initially) + +**Why:** +- Managed Cassandra is expensive +- Can start with single node locally +- Migrate to Astra DB (serverless) later when traffic justifies + +> **Exception**: If Cassandra management is painful, use Astra DB serverless - you only pay for operations, so low traffic = low cost (~$25/month at low usage). + +#### Keep Local: Application Servers + +**Why:** +- Your pfSense/HAProxy handles load balancing well +- Easy to scale by adding machines +- Compute is where self-hosting saves the most money + +### Recommended Hybrid Setup + +#### Phase 1: Minimal Cloud (Start Here) + +| Component | Location | Monthly Cost | +|-----------|----------|--------------| +| App Servers | Local | $0 | +| Memcached | Local | $0 | +| Redis | Local | $0 | +| RabbitMQ | Local | $0 | +| PostgreSQL | Neon/Supabase | $0-25 | +| Cassandra | Local | $0 | +| Media Storage | Cloudflare R2 | $5-15 | +| Backups | Backblaze B2 | $5-10 | +| **Total Cloud** | | **$10-50/month** | + +#### Phase 2: Production-Ready Hybrid + +| Component | Location | Monthly Cost | +|-----------|----------|--------------| +| App Servers | Local | $0 | +| Memcached | Local | $0 | +| Redis | Local | $0 | +| RabbitMQ | Local | $0 | +| PostgreSQL | RDS/Cloud SQL | $50-100 | +| Cassandra | Astra DB Serverless | $25-100 | +| Media Storage | S3 + CloudFront | $20-50 | +| Backups | B2 | $5-10 | +| **Total Cloud** | | **$100-260/month** | + +### Connection Configuration + +#### Secure Connection to Cloud Database + +**Option 1: Direct SSL Connection (Simplest)** + +```ini +# production.update +db_ssl = true +main_db = tippr, your-db.xxx.us-east-1.rds.amazonaws.com, *, *, *, *, * +``` + +**Option 2: WireGuard VPN to Cloud VPC** + +More secure, keeps database off public internet: + +```bash +# On pfSense: Install WireGuard package +# Create tunnel to AWS/GCP VPC +# Route database subnet through tunnel +``` + +**Option 3: AWS Site-to-Site VPN** + +For production-grade connectivity (~$35/month for AWS VPN). + +### Example `production.update` for Hybrid + +```ini +# production.update - Hybrid deployment +[DEFAULT] +debug = false +# Domain +domain = yourdomain.com +https_endpoint = https://yourdomain.com +# PostgreSQL in Cloud (Neon example) +db_user = tippr +db_pass = YOUR_NEON_PASSWORD +db_port = 5432 +db_ssl = true +databases = main, comment, email, authorize, award, hc, traffic +main_db = tippr, ep-xxx.us-east-2.aws.neon.tech, *, *, *, *, * +# Cassandra - Local for now +cassandra_seeds = 192.168.1.111:9042 +cassandra_pool_size = 10 +# Cache - Local (latency sensitive) +lockcaches = 192.168.1.112:11211 +permacache_memcaches = 192.168.1.112:11211 +hardcache_memcaches = 192.168.1.112:11211 +# Queue - Local +amqp_host = 192.168.1.112:5672 +amqp_user = tippr +amqp_pass = YOUR_LOCAL_PASSWORD +# Media - Cloud (Cloudflare R2) +media_provider = s3 +S3KEY_ID = YOUR_R2_ACCESS_KEY +S3SECRET_KEY = YOUR_R2_SECRET_KEY +s3_media_domain = your-account.r2.cloudflarestorage.com +s3_media_buckets = tippr-media +# Or if using AWS S3 + CloudFront +# s3_media_domain = d123xxx.cloudfront.net +# s3_media_buckets = tippr-media-bucket +``` + +### Migration Path Summary + +``` +Phase 1 (Now) Phase 2 (Growth) Phase 3 (Scale) +───────────────── ───────────────── ───────────────── +Local Everything → Hybrid → Full Cloud + + Cloud DB + Cloud Compute + + Cloud Storage + Managed Cache + + Cloud Backups + CDN + +Cost: ~$150/mo Cost: ~$200-350/mo Cost: $1,500+/mo +``` + +> **Key insight**: Cloud databases and storage are cheap and eliminate your biggest operational risks (data loss, complex backups). Keep compute local where you save the most money. + +--- + +For smaller deployments or when you want to start locally before migrating to cloud, self-hosting is a viable option. This approach lets you validate your application and understand traffic patterns before committing to cloud infrastructure costs. + +### When to Self-Host + +**Good candidates for self-hosting:** +- Early-stage projects with limited traffic +- Development/staging environments +- Cost-sensitive deployments +- Learning and experimentation +- Geographic regions without nearby cloud availability zones + +**Consider cloud when:** +- You need 99.9%+ uptime guarantees +- Traffic exceeds your internet bandwidth capacity +- Geographic distribution is required +- Power/internet reliability is a concern +- You need rapid scaling capabilities + +### Self-Hosted Architecture Overview + +``` + Internet + │ + ▼ + ┌──────────────┐ + │ pfSense │ + │ HAProxy │ + │ (Router) │ + └──────┬───────┘ + │ SSL Termination + │ Load Balancing + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ + ┌────────────┐ ┌────────────┐ ┌────────────┐ + │ App Srv 1 │ │ App Srv 2 │ │ App Srv 3 │ + │ 192.168.1.101│ │ 192.168.1.102│ │ 192.168.1.103│ + │ :3000 │ │ :3000 │ │ :3000 │ + └────────────┘ └────────────┘ └────────────┘ + │ │ │ + └───────────────┴───────────────┘ + │ + ┌────────────┼────────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────┐ + │ PostgreSQL│ │ Cassandra│ │ Redis/ │ + │ :5432 │ │ :9042 │ │ Memcached│ + └──────────┘ └──────────┘ └──────────┘ +``` + +### Hardware Requirements + +#### Minimum Single-Server Setup + +| Component | Specification | +|-----------|--------------| +| CPU | 4+ cores (8+ recommended) | +| RAM | 16GB minimum (32GB recommended) | +| Storage | 500GB SSD (NVMe preferred) | +| Network | 100 Mbps symmetric (1 Gbps recommended) | +| UPS | Required for power protection | + +#### Recommended Multi-Server Setup + +| Server Role | Count | Specs | +|-------------|-------|-------| +| Application Servers | 2-3 | 4 cores, 8GB RAM, 100GB SSD | +| Database Server | 1 | 8 cores, 32GB RAM, 500GB NVMe | +| Cache/Queue Server | 1 | 4 cores, 16GB RAM, 100GB SSD | + +### Operating System Setup + +**Recommended: Ubuntu Server 22.04 LTS or 24.04 LTS** + +Ubuntu Server provides: +- Long-term support (5 years) +- Extensive documentation +- Easy migration path to AWS/GCP/Azure (same OS on cloud VMs) +- Good Docker and container support + +```bash +# Initial server setup +sudo apt update && sudo apt upgrade -y + +# Install essential packages +sudo apt install -y \ + curl wget git htop \ + ufw fail2ban \ + docker.io docker-compose-v2 + +# Enable and start Docker +sudo systemctl enable docker +sudo systemctl start docker + +# Add your user to docker group +sudo usermod -aG docker $USER +``` + +### Network Infrastructure with pfSense + HAProxy + +If you're already running pfSense as your router/firewall, you can leverage its built-in HAProxy package for load balancing and SSL termination. + +#### HAProxy Frontend Configuration + +In **Services → HAProxy → Frontend**: + +| Setting | Value | +|---------|-------| +| Name | `tippr_frontend` | +| Listen Address | WAN address | +| Port | 443 | +| SSL Offloading | Enabled | +| Certificate | Let's Encrypt (via ACME package) | +| Default Backend | `tippr_servers` | + +**HTTP to HTTPS Redirect (Port 80):** + +| Setting | Value | +|---------|-------| +| Name | `http_redirect` | +| Listen Address | WAN address | +| Port | 80 | +| Action | HTTP redirect to HTTPS | + +#### HAProxy Backend Configuration + +In **Services → HAProxy → Backend**: + +| Setting | Value | +|---------|-------| +| Name | `tippr_servers` | +| Balance | `roundrobin` or `leastconn` | +| Health Check Method | HTTP | +| Health Check URI | `/health` | +| Health Check Interval | `5000` ms | + +**Server Pool:** + +| Server | Address | Port | Weight | +|--------|---------|------|--------| +| app1 | 192.168.1.101 | 8001 | 100 | +| app2 | 192.168.1.102 | 8001 | 100 | +| app3 | 192.168.1.103 | 8001 | 100 | + +#### Load Balancing Algorithms + +| Algorithm | Best For | +|-----------|----------| +| `roundrobin` | Equal servers, stateless requests | +| `leastconn` | Varying request durations, long-running connections | +| `source` | Session persistence (sticky sessions) | + +#### SSL Certificate with ACME + +Install the ACME package in pfSense for automatic Let's Encrypt certificates: + +1. **System → Package Manager → Available Packages** → Install `acme` +2. **Services → ACME → Certificates** → Add new certificate +3. Configure DNS validation or HTTP-01 challenge +4. Enable auto-renewal + +### Docker Compose Deployment + +#### Single-Server All-in-One Setup + +```yaml +# docker-compose.yml +version: '3.8' + +services: + app: + build: ./tippr + restart: always + ports: + - "8001:8001" + environment: + - PYTHONPATH=/app/tippr:/app + - TIPPR_INI=/app/tippr/r2/production.ini + volumes: + - ./config/production.update:/app/tippr/r2/production.update:ro + depends_on: + - db + - memcached + - rabbitmq + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8001/health"] + interval: 30s + timeout: 10s + retries: 3 + deploy: + resources: + limits: + cpus: '2' + memory: 4G + + db: + image: postgres:15-alpine + restart: always + volumes: + - postgres_data:/var/lib/postgresql/data + - ./backups:/backups + environment: + - POSTGRES_USER=tippr + - POSTGRES_PASSWORD=${DB_PASSWORD} + - POSTGRES_DB=tippr + healthcheck: + test: ["CMD-SHELL", "pg_isready -U tippr"] + interval: 10s + timeout: 5s + retries: 5 + + cassandra: + image: cassandra:4.1 + restart: always + volumes: + - cassandra_data:/var/lib/cassandra + environment: + - CASSANDRA_CLUSTER_NAME=tippr + - MAX_HEAP_SIZE=2G + - HEAP_NEWSIZE=400M + healthcheck: + test: ["CMD", "cqlsh", "-e", "describe keyspaces"] + interval: 30s + timeout: 10s + retries: 5 + + memcached: + image: memcached:1.6-alpine + restart: always + command: ["-m", "1024", "-c", "4096"] + healthcheck: + test: ["CMD", "nc", "-z", "localhost", "11211"] + interval: 10s + timeout: 5s + retries: 3 + + rabbitmq: + image: rabbitmq:3.12-management-alpine + restart: always + volumes: + - rabbitmq_data:/var/lib/rabbitmq + environment: + - RABBITMQ_DEFAULT_USER=tippr + - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD} + ports: + - "15672:15672" # Management UI (internal only) + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"] + interval: 30s + timeout: 10s + retries: 3 + +volumes: + postgres_data: + cassandra_data: + rabbitmq_data: +``` + +#### Multi-Server Setup + +**Application Server (192.168.1.101-103):** + +```yaml +# docker-compose.app.yml +version: '3.8' + +services: + app: + build: ./tippr + restart: always + ports: + - "8001:8001" + environment: + - PYTHONPATH=/app/tippr:/app + - TIPPR_INI=/app/tippr/r2/production.ini + - DATABASE_URL=postgres://tippr:${DB_PASSWORD}@192.168.1.110:5432/tippr + - CASSANDRA_SEEDS=192.168.1.111:9042 + - MEMCACHED_SERVERS=192.168.1.112:11211 + - RABBITMQ_URL=amqp://tippr:${RABBITMQ_PASSWORD}@192.168.1.112:5672/ + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8001/health"] + interval: 30s + timeout: 10s + retries: 3 +``` + +**Database Server (192.168.1.110):** + +```yaml +# docker-compose.db.yml +version: '3.8' + +services: + db: + image: postgres:15-alpine + restart: always + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./backups:/backups + environment: + - POSTGRES_USER=tippr + - POSTGRES_PASSWORD=${DB_PASSWORD} + - POSTGRES_DB=tippr + command: + - "postgres" + - "-c" + - "shared_buffers=8GB" + - "-c" + - "effective_cache_size=24GB" + - "-c" + - "work_mem=256MB" + - "-c" + - "maintenance_work_mem=2GB" + - "-c" + - "max_connections=200" + + cassandra: + image: cassandra:4.1 + restart: always + ports: + - "9042:9042" + volumes: + - cassandra_data:/var/lib/cassandra + environment: + - CASSANDRA_CLUSTER_NAME=tippr + - MAX_HEAP_SIZE=8G + - HEAP_NEWSIZE=1600M + +volumes: + postgres_data: + cassandra_data: +``` + +**Cache/Queue Server (192.168.1.112):** + +```yaml +# docker-compose.cache.yml +version: '3.8' + +services: + memcached: + image: memcached:1.6-alpine + restart: always + ports: + - "11211:11211" + command: ["-m", "4096", "-c", "8192"] + + rabbitmq: + image: rabbitmq:3.12-management-alpine + restart: always + ports: + - "5672:5672" + - "15672:15672" + volumes: + - rabbitmq_data:/var/lib/rabbitmq + environment: + - RABBITMQ_DEFAULT_USER=tippr + - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD} + + redis: + image: redis:7-alpine + restart: always + ports: + - "6379:6379" + volumes: + - redis_data:/data + command: redis-server --appendonly yes --maxmemory 2gb --maxmemory-policy allkeys-lru + +volumes: + rabbitmq_data: + redis_data: +``` + +### Session Management for Load Balancing + +When running multiple application servers, you need a strategy for session handling: + +#### Option 1: Sticky Sessions (Simplest) + +Configure HAProxy backend with cookie-based persistence: + +| Setting | Value | +|---------|-------| +| Persistence | Cookie | +| Cookie Name | `SERVERID` | +| Cookie Mode | Insert | + +This pins users to the same server for the duration of their session. + +#### Option 2: Shared Session Store (Recommended) + +Use Redis for centralized session storage: + +```ini +# production.update +[DEFAULT] +# Redis for session storage +session_store = redis +redis_session_host = 192.168.1.112 +redis_session_port = 6379 +redis_session_db = 0 +``` + +This allows any application server to handle any request. + +### Production Configuration for Self-Hosting + +```ini +# production.update for self-hosted deployment +[DEFAULT] +# CRITICAL: Disable debug mode +debug = false +template_debug = false +reload_templates = false +uncompressedJS = false +sqlprinting = false + +# Production domain +domain = yourdomain.com +media_domain = media.yourdomain.com +https_endpoint = https://yourdomain.com + +# PostgreSQL (self-hosted) +db_user = tippr +db_pass = YOUR_SECURE_PASSWORD +db_port = 5432 +databases = main, comment, email, authorize, award, hc, traffic +main_db = tippr, 192.168.1.110, *, *, *, *, * + +# Cassandra (self-hosted) +cassandra_seeds = 192.168.1.111:9042 +cassandra_pool_size = 10 +cassandra_rcl = LOCAL_QUORUM +cassandra_wcl = LOCAL_QUORUM + +# Memcached (self-hosted) +lockcaches = 192.168.1.112:11211 +permacache_memcaches = 192.168.1.112:11211 +hardcache_memcaches = 192.168.1.112:11211 +num_mc_clients = 20 + +# RabbitMQ (self-hosted) +amqp_host = 192.168.1.112:5672 +amqp_user = tippr +amqp_pass = YOUR_SECURE_PASSWORD +amqp_virtual_host = / + +# Local media storage (or configure S3-compatible like MinIO) +media_provider = filesystem +media_fs_root = /var/tippr/media +media_fs_url = https://media.yourdomain.com + +# Security +trust_local_proxies = true +ENFORCE_RATELIMIT = true + +# Monitoring +statsd_addr = 192.168.1.112:8125 +``` + +### Security Hardening + +#### Firewall Configuration (UFW) + +```bash +# On each application server +sudo ufw default deny incoming +sudo ufw default allow outgoing + +# Allow SSH (change port if using non-standard) +sudo ufw allow 22/tcp + +# Allow HAProxy health checks from pfSense +sudo ufw allow from 192.168.1.1 to any port 8001 + +# Enable firewall +sudo ufw enable +``` + +```bash +# On database server +sudo ufw default deny incoming +sudo ufw default allow outgoing + +sudo ufw allow 22/tcp +sudo ufw allow from 192.168.1.101 to any port 5432 # PostgreSQL +sudo ufw allow from 192.168.1.102 to any port 5432 +sudo ufw allow from 192.168.1.103 to any port 5432 +sudo ufw allow from 192.168.1.101 to any port 9042 # Cassandra +sudo ufw allow from 192.168.1.102 to any port 9042 +sudo ufw allow from 192.168.1.103 to any port 9042 + +sudo ufw enable +``` + +#### SSH Hardening + +```bash +# /etc/ssh/sshd_config +PermitRootLogin no +PasswordAuthentication no +PubkeyAuthentication yes +MaxAuthTries 3 +ClientAliveInterval 300 +ClientAliveCountMax 2 + +# Restart SSH +sudo systemctl restart sshd +``` + +#### Fail2Ban Configuration + +```bash +sudo apt install fail2ban + +# /etc/fail2ban/jail.local +[sshd] +enabled = true +port = ssh +filter = sshd +logpath = /var/log/auth.log +maxretry = 3 +bantime = 3600 +findtime = 600 +``` + +### Backup Strategy + +#### Automated PostgreSQL Backups + +```bash +#!/bin/bash +# /opt/scripts/backup-postgres.sh + +BACKUP_DIR="/backups/postgres" +DATE=$(date +%Y%m%d_%H%M%S) +RETENTION_DAYS=7 + +# Create backup +docker exec postgres pg_dump -U tippr tippr | gzip > "${BACKUP_DIR}/tippr_${DATE}.sql.gz" + +# Upload to offsite storage (Backblaze B2, Wasabi, etc.) +rclone copy "${BACKUP_DIR}/tippr_${DATE}.sql.gz" b2:tippr-backups/postgres/ + +# Remove old local backups +find ${BACKUP_DIR} -type f -mtime +${RETENTION_DAYS} -delete +``` + +```bash +# Crontab entry (daily at 2 AM) +0 2 * * * /opt/scripts/backup-postgres.sh >> /var/log/backup.log 2>&1 +``` + +#### Cassandra Backup with nodetool + +```bash +#!/bin/bash +# /opt/scripts/backup-cassandra.sh + +BACKUP_DIR="/backups/cassandra" +DATE=$(date +%Y%m%d_%H%M%S) + +# Create snapshot +docker exec cassandra nodetool snapshot -t backup_${DATE} tippr + +# Copy snapshot files +docker cp cassandra:/var/lib/cassandra/data/tippr/. "${BACKUP_DIR}/${DATE}/" + +# Compress and upload +tar -czf "${BACKUP_DIR}/cassandra_${DATE}.tar.gz" "${BACKUP_DIR}/${DATE}" +rclone copy "${BACKUP_DIR}/cassandra_${DATE}.tar.gz" b2:tippr-backups/cassandra/ + +# Cleanup +rm -rf "${BACKUP_DIR}/${DATE}" +docker exec cassandra nodetool clearsnapshot -t backup_${DATE} +``` + +### Monitoring for Self-Hosted Deployment + +#### Uptime Kuma (Self-Hosted Uptime Monitoring) + +```yaml +# Add to docker-compose +uptime-kuma: + image: louislam/uptime-kuma:1 + restart: always + ports: + - "3001:3001" + volumes: + - uptime-kuma:/app/data +``` + +Configure monitors for: +- HAProxy frontend (HTTPS endpoint) +- Each application server health endpoint +- PostgreSQL connectivity +- Cassandra connectivity +- RabbitMQ management interface + +#### Netdata (Real-Time Server Metrics) + +```bash +# Install on each server +bash <(curl -Ss https://get.netdata.cloud/kickstart.sh) +``` + +Access at `http://server-ip:19999` for real-time CPU, memory, disk, and network metrics. + +#### Prometheus + Grafana Stack + +```yaml +# docker-compose.monitoring.yml +version: '3.8' + +services: + prometheus: + image: prom/prometheus:latest + restart: always + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus_data:/prometheus + + grafana: + image: grafana/grafana:latest + restart: always + ports: + - "3000:3000" + volumes: + - grafana_data:/var/lib/grafana + environment: + - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD} + + node-exporter: + image: prom/node-exporter:latest + restart: always + ports: + - "9100:9100" + volumes: + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /:/rootfs:ro + command: + - '--path.procfs=/host/proc' + - '--path.sysfs=/host/sys' + - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)' + +volumes: + prometheus_data: + grafana_data: +``` + +### Deployment Scripts + +#### Rolling Deployment Script + +```bash +#!/bin/bash +# /opt/scripts/deploy.sh + +SERVERS=("192.168.1.101" "192.168.1.102" "192.168.1.103") +REPO_PATH="/opt/tippr" +BRANCH="${1:-main}" + +for server in "${SERVERS[@]}"; do + echo "==========================================" + echo "Deploying to ${server}..." + echo "==========================================" + + # Optionally: Drain from HAProxy via pfSense API + # curl -X POST "https://pfsense/api/haproxy/drain/${server}" + + ssh ${server} << 'ENDSSH' + cd ${REPO_PATH} + git fetch origin + git checkout ${BRANCH} + git pull origin ${BRANCH} + + cd ${REPO_PATH} + docker compose build app + docker compose up -d app + + # Wait for health check + sleep 10 + curl -f http://localhost:8001/health || exit 1 +ENDSSH + + if [ $? -ne 0 ]; then + echo "Deployment to ${server} failed!" + exit 1 + fi + + echo "Successfully deployed to ${server}" + + # Wait between servers for graceful rollout + sleep 30 +done + +echo "==========================================" +echo "Deployment complete!" +echo "==========================================" +``` + +### Domain & DNS Setup + +#### Option 1: Static IP + +If your ISP provides a static IP: +1. Register a domain (~$12/year from Namecheap, Cloudflare, etc.) +2. Set A record pointing to your static IP +3. Configure SSL via Let's Encrypt (ACME package in pfSense) + +#### Option 2: Dynamic DNS + +If your IP changes: +1. Use a Dynamic DNS service (Cloudflare, DuckDNS, No-IP) +2. Install ddclient on your server or configure in pfSense + +```bash +# /etc/ddclient.conf for Cloudflare +protocol=cloudflare +use=web +web=checkip.dyndns.org +login=your-cloudflare-email +password=your-api-token +zone=yourdomain.com +yourdomain.com +``` + +#### Cloudflare (Recommended) + +Use Cloudflare's free tier for: +- DNS management +- DDoS protection +- SSL termination (Full Strict mode) +- Caching of static assets +- Hide your origin IP + +### Cost Comparison: Self-Hosted vs Cloud + +| Item | Self-Hosted (One-Time) | Self-Hosted (Monthly) | Cloud (Monthly) | +|------|------------------------|----------------------|-----------------| +| Hardware (3 servers) | $2,000-5,000 | - | - | +| Electricity | - | $30-100 | - | +| Internet (Business) | - | $100-300 | - | +| Domain | - | $1 | $1 | +| Backups (B2/Wasabi) | - | $5-20 | - | +| **Total Monthly** | - | **$136-421** | **$1,325-1,910** | +| **Break-even** | | **6-12 months** | | + +**Note**: Self-hosting requires more hands-on management and expertise. Factor in your time cost. + +### Migration Path to Cloud + +When traffic justifies cloud migration: + +1. **Database First**: Migrate PostgreSQL to RDS/Cloud SQL + - Use pg_dump/pg_restore or AWS DMS + - Update connection strings in production.update + +2. **Cache Layer**: Move to ElastiCache/Memorystore + - Simply update memcached server addresses + +3. **Application Servers**: Move to EC2/GCE/VM Scale Sets + - Same Docker containers work on cloud VMs + - Update HAProxy to point to cloud servers (or switch to ALB) + +4. **Decommission HAProxy**: Switch to cloud load balancer + - ALB/Cloud LB/Application Gateway + +5. **Final Cutover**: Update DNS to point to cloud endpoints + +This gradual migration minimizes risk and allows testing at each stage. + +### Self-Hosted Deployment Checklist + +#### Infrastructure Setup +- [ ] Hardware procured and rack-mounted (or VM hosts configured) +- [ ] Ubuntu Server LTS installed on all servers +- [ ] Static IPs assigned to all servers +- [ ] Network switches configured with proper VLANs +- [ ] UPS installed and configured +- [ ] pfSense configured with HAProxy package + +#### Security +- [ ] SSH key authentication only (passwords disabled) +- [ ] Fail2ban installed and configured +- [ ] UFW firewall rules configured +- [ ] SSL certificates obtained (Let's Encrypt via ACME) +- [ ] All default passwords changed + +#### Services +- [ ] Docker and Docker Compose installed +- [ ] PostgreSQL container running with persistent volume +- [ ] Cassandra container running with persistent volume +- [ ] Memcached container running +- [ ] RabbitMQ container running +- [ ] Redis container running (for sessions) +- [ ] Application containers deployed + +#### HAProxy Configuration +- [ ] Frontend configured for ports 80/443 +- [ ] SSL offloading enabled with valid certificate +- [ ] Backend pool configured with all app servers +- [ ] Health checks configured and passing +- [ ] HTTP to HTTPS redirect enabled + +#### Backup & Recovery +- [ ] PostgreSQL backup script configured +- [ ] Cassandra backup script configured +- [ ] Offsite backup destination configured (B2, Wasabi, etc.) +- [ ] Backup cron jobs scheduled +- [ ] Restore procedure tested + +#### Monitoring +- [ ] Uptime Kuma or similar monitoring deployed +- [ ] Health endpoints monitored for all services +- [ ] Disk space alerts configured +- [ ] Email/Slack notifications configured + +#### DNS & Domain +- [ ] Domain registered and DNS configured +- [ ] A records pointing to your IP +- [ ] Dynamic DNS configured (if needed) +- [ ] Cloudflare or similar CDN/protection enabled (optional) + +#### Documentation +- [ ] Server IP addresses documented +- [ ] Credentials stored securely (password manager) +- [ ] Runbook for common operations written +- [ ] Recovery procedures documented + +--- + +*This document is a starting point. Production deployments should be reviewed by experienced DevOps/SRE engineers and adapted to your specific requirements and traffic patterns.* diff --git a/docs/gold-payments.md b/docs/gold-payments.md new file mode 100644 index 0000000000..c97281e244 --- /dev/null +++ b/docs/gold-payments.md @@ -0,0 +1,77 @@ +# Gold payments — Overview and flow + +This document describes how gold (one-time purchases, gifts, creddits, and subscriptions) are processed in the codebase. + +Files of interest + +- `r2/r2/templates/goldpayment.html` — checkout UI and payment form templates. +- `r2/r2/public/static/js/gold.js` — client-side logic that builds the form and posts to server endpoints. +- `r2/r2/controllers/api.py` — `POST_generate_payment_blob` (creates short-lived payment blob/passthrough token). +- `r2/r2/controllers/ipn.py` — webhook, IPN and shared gold-completion logic (includes Stripe and PayPal handling, `complete_gold_purchase`, `send_gift`, etc.). +- `r2/r2/models/gold.py` — storage schema and helper functions (`create_claimed_gold`, `create_gold_code`, `claim_gold`, accounting helpers). + +Summary flow (high level) + +1. Frontend displays a gold form (from `goldpayment.html`). The client can request a short-lived payment blob by calling `POST /api/generate_payment_blob` which returns a token (`passthrough`). The token encodes the intended gold action (gift/onetime/code) and some metadata. +2. User selects a payment provider (creddits, PayPal, Stripe, Coinbase). The purchase forms carry the `passthrough` token. +3. Provider-specific flows: + - Creddits: client posts to `/api/spendcreddits` handled by `POST_spendcreddits` in `ipn.py` — processed immediately on the server. + - Stripe (cards/subscriptions): client posts card data to `/api/stripecharge/gold` (StripeController) which creates a Stripe customer/charge using `stripe` library and returns status. Stripe then sends webhooks to the server; StripeController processes the webhook and, when successful, calls the shared completion routine. + - PayPal: hosted button flow + IPN callback. PayPal IPNs are received and processed via IPN handlers in `ipn.py` and mapped into the same completion logic. + - Coinbase/Bitcoin: handled by coinbase flow and corresponding webhook endpoint; mapped into the same completion logic. +4. The shared handler (`complete_gold_purchase`) validates the payment, credits creddits or gold, applies subscriptions or gifts, creates a DB transaction (`create_claimed_gold`) and sends notification messages. + +Mermaid sequence diagram + +```mermaid +sequenceDiagram + participant U as User (browser) + participant FE as Frontend (gold form) + participant API as App (`/api`) + participant Proc as Payment Provider (Stripe/PayPal/Coinbase) + participant Webhook as App Webhook Endpoint + participant DB as Database (& Cassandra views) + + U->>FE: open gold form + FE->>API: POST /api/generate_payment_blob (creates `passthrough`) + API-->>FE: passthrough token + U->>FE: choose payment method, submit (passthrough included) + + alt creddits + FE->>API: POST /api/spendcreddits with passthrough + API->>DB: apply creddits / call helpers + API-->>U: success (gold applied) + else stripe (one-time) + FE->>Proc: POST /api/stripecharge/gold (card or token) + Proc->>Webhook: Stripe webhook (charge.succeeded) + Webhook->>API: receive webhook -> validate + API->>DB: create_claimed_gold, adjust expiration, record transaction + API-->>U: notify via system message + else paypal + FE->>Proc: redirect to PayPal hosted button + Proc->>Webhook: PayPal IPN + Webhook->>API: process IPN -> validate + API->>DB: create_claimed_gold, adjust expiration, record transaction + API-->>U: notify via system message + end + + Note over API,DB: Shared helpers in `r2/r2/models/gold.py` perform accounting +``` + +Notes and troubleshooting + +- The server uses `payment_blob` (short-lived blob in hardcache) to pass user/intent data between the UI and backend processing. See `generate_blob` / `get_blob` in `r2/r2/controllers/ipn.py` and `POST_generate_payment_blob` in `r2/r2/controllers/api.py`. +- Stripe flows create a `stripe.Customer` and `Charge`; the webhook handler maps Stripe event types to the internal event types and calls `complete_gold_purchase`. +- For gift codes and unclaimed purchases the code paths in `r2/r2/models/gold.py` (`create_gold_code`, `claim_gold`, `create_unclaimed_gold`) manage code generation and claiming later. +- Creddits are an internal balance (see `gold_creddits` counters) and are cheaper in bulk; spending triggers the same internal helpers to apply gold. + +Quick pointers to trace a real purchase + +- Start at `r2/r2/templates/goldpayment.html` to see the UI and which form fields are posted. +- `r2/r2/public/static/js/gold.js` contains the client event wiring (`generate_payment_blob` call, `modify_payment_blob`, and which endpoints are POSTed). +- For Stripe one-time: `r2/r2/controllers/ipn.py::POST_goldcharge` (creates Stripe customer/charge) and `StripeController.process_response` for webhook processing. +- For PayPal IPN: `r2/r2/controllers/ipn.py` IPN handlers and `check_txn_type` / `check_payment_status` helpers used to normalize inputs. + +If you'd like, I can: +- Add a PNG/SVG version of the diagram under `docs/diagrams/`. +- Expand this doc with a step-by-step example (Stripe one-time gift) including sample payloads and DB rows. diff --git a/docs/local-db.md b/docs/local-db.md index 5542705ee6..0f9b5d81c9 100644 --- a/docs/local-db.md +++ b/docs/local-db.md @@ -14,8 +14,8 @@ docker compose -f docker-compose.local.yml up -d python .\tools\wait_for_dbs.py # Connect examples -# Postgres: you'll be prompted for password 'reddit' -psql -h localhost -U reddit -d reddit +# Postgres: you'll be prompted for password 'tippr' +psql -h localhost -U tippr -d tippr # Cassandra (CQL shell) cqlsh localhost 9042 @@ -29,7 +29,7 @@ Bash (Linux/macOS): ```bash docker compose -f docker-compose.local.yml up -d python tools/wait_for_dbs.py -psql -h localhost -U reddit -d reddit +psql -h localhost -U tippr -d tippr cqlsh localhost 9042 docker compose -f docker-compose.local.yml down -v ``` @@ -38,7 +38,7 @@ Notes: - Cassandra may take a minute or two to finish initial startup; `tools/wait_for_dbs.py` polls TCP ports. Bootstrap via app (auto-create tables) - If you don't have `docker compose`, try `docker-compose`. -- Default Postgres creds: `reddit` / `reddit`. +- Default Postgres creds: `tippr` / `tippr`. - Example configuration keys are in `r2/example.ini` for connecting pools. Next steps (optional): @@ -51,21 +51,21 @@ PowerShell: ```powershell # Create Postgres DB and user (run as an admin account) -sudo -u postgres psql -c "CREATE USER reddit WITH PASSWORD 'reddit';" -sudo -u postgres psql -c "CREATE DATABASE reddit OWNER reddit;" +sudo -u postgres psql -c "CREATE USER tippr WITH PASSWORD 'tippr';" +sudo -u postgres psql -c "CREATE DATABASE tippr OWNER tippr;" # Create Cassandra keyspace (when Cassandra is up). Adjust path if using local tarball. # If using the docker compose above you can instead run `docker exec -it cqlsh`. -cqlsh localhost 9042 -e "CREATE KEYSPACE IF NOT EXISTS reddit WITH replication = {'class':'SimpleStrategy','replication_factor':'1'};" +cqlsh localhost 9042 -e "CREATE KEYSPACE IF NOT EXISTS tippr WITH replication = {'class':'SimpleStrategy','replication_factor':'1'};" ``` Bash: ```bash # Create Postgres DB/user -psql -h 127.0.0.1 -U postgres -c "CREATE USER reddit WITH PASSWORD 'reddit';" -psql -h 127.0.0.1 -U postgres -c "CREATE DATABASE reddit OWNER reddit;" +psql -h 127.0.0.1 -U postgres -c "CREATE USER tippr WITH PASSWORD 'tippr';" +psql -h 127.0.0.1 -U postgres -c "CREATE DATABASE tippr OWNER tippr;" # Create Cassandra keyspace when CQL is available -cqlsh 127.0.0.1 9042 -e "CREATE KEYSPACE IF NOT EXISTS reddit WITH replication = {'class':'SimpleStrategy','replication_factor':'1'};" +cqlsh 127.0.0.1 9042 -e "CREATE KEYSPACE IF NOT EXISTS tippr WITH replication = {'class':'SimpleStrategy','replication_factor':'1'};" ``` diff --git a/install-reddit.sh b/install-tippr.sh old mode 100755 new mode 100644 similarity index 82% rename from install-reddit.sh rename to install-tippr.sh index 06eafadc94..85da7bd125 --- a/install-reddit.sh +++ b/install-tippr.sh @@ -1,15 +1,15 @@ #!/bin/bash ############################################################################### -# reddit dev environment installer +# tippr dev environment installer # -------------------------------- -# This script installs a reddit stack suitable for development. DO NOT run this +# This script installs a tippr stack suitable for development. DO NOT run this # on a system that you use for other purposes as it might delete important # files, truncate your databases, and otherwise do mean things to you. # -# By default, this script will install the reddit code in the current user's +# By default, this script will install the tippr code in the current user's # home directory and all of its dependencies (including libraries and database -# servers) at the system level. The installed reddit will expect to be visited -# on the domain "reddit.local" unless specified otherwise. Configuring name +# servers) at the system level. The installed tippr will expect to be visited +# on the domain "tippr.local" unless specified otherwise. Configuring name # resolution for the domain is expected to be done outside the installed # environment (e.g. in your host machine's /etc/hosts file) and is not # something this script handles. @@ -17,7 +17,7 @@ # Several configuration options (listed in the "Configuration" section below) # are overridable with environment variables. e.g. # -# sudo REDDIT_DOMAIN=example.com ./install/reddit.sh +# sudo TIPPR_DOMAIN=example.com ./install/tippr.sh # ############################################################################### set -e @@ -32,14 +32,14 @@ RUNDIR=$(dirname $0) SCRIPTDIR="$RUNDIR/install" # the canonical source of all installers -GITREPO="https://raw.github.com/acalcutt/reddit/master/install" +GITREPO="https://raw.github.com/TechIdiots-LLC/tippr/master/install" NEEDED=( "done.sh" "install_apt.sh" "install_cassandra.sh" "install_services.sh" "install_zookeeper.sh" - "reddit.sh" + "tippr.sh" "setup_cassandra.sh" "setup_mcrouter.sh" "setup_postgres.sh" @@ -94,7 +94,7 @@ important "you can either edit install/install.cfg or set overrides when running important "(they will be respected)." echo important "Seriously, if this is your first time installing, stop here and read" -important "the script (install/reddit.sh) and that config. It's got some helpful" +important "the script (install/tippr.sh) and that config. It's got some helpful" important "information that can prevent common issues." echo important "Resolving to the appropriate domain name is beyond the scope of this document," @@ -103,5 +103,5 @@ echo read -er -n1 -p "proceed? [Y/n]" response if [[ $response =~ ^[Yy]$ || $response == "" ]]; then echo "Excellent. Here we go!" - $SCRIPTDIR/reddit.sh + $SCRIPTDIR/tippr.sh fi diff --git a/install/README.md b/install/README.md index 7b13c5efbf..200d84d0b1 100644 --- a/install/README.md +++ b/install/README.md @@ -1,15 +1,15 @@ -# Reddit installer development instructions +# Tippr installer development instructions -This folder contains all of the installation scripts required to build reddit on a stock Ubuntu 14.04 (trusty) box. Originally all of this was included in `../install-reddit.sh` but the need to fork the image into an base installer for testing as well as a full installer for local use meant some reorganization and fracturing of the original script. +This folder contains all of the installation scripts required to build tippr on a stock Ubuntu 14.04 (trusty) box. Originally all of this was included in `../install-tippr.sh` but the need to fork the image into an base installer for testing as well as a full installer for local use meant some reorganization and fracturing of the original script. When making updates to any of these files, Travis will test out the minimal install in `travis.sh` but not the full install. *Please test on a fresh VM, and preferably using a modified web-install instructions:* ```bash -wget https://raw.github.com/$GITHUBUSER/reddit/$TESTBRANCH/install-reddit.sh -chmod +x install-reddit.sh -./install-reddit.sh +wget https://raw.github.com/$GITHUBUSER/tippr/$TESTBRANCH/install-tippr.sh +chmod +x install-tippr.sh +./install-tippr.sh ``` This will ensure that the installation is tested in the most basic installation scenario. -**NOTE** if you find yourself adding files to this folder, please update `$NEEDED` in `../install-reddit.sh` to reflect the addition. +**NOTE** if you find yourself adding files to this folder, please update `$NEEDED` in `../install-tippr.sh` to reflect the addition. diff --git a/install/done.sh b/install/done.sh index 4cd9bf668e..5da231ad10 100755 --- a/install/done.sh +++ b/install/done.sh @@ -30,47 +30,47 @@ source $RUNDIR/install.cfg ############################################################################### cat < /etc/apt/preferences.d/reddit Package: * -Pin: release o=LP-PPA-reddit +Pin: release o=LP-PPA-tippr Pin-Priority: 600 HERE diff --git a/install/setup_cassandra.sh b/install/setup_cassandra.sh index 86776909d6..34e38ed5b6 100755 --- a/install/setup_cassandra.sh +++ b/install/setup_cassandra.sh @@ -43,10 +43,10 @@ if [ "$DISTRIB_RELEASE" == "24.04" ]; then apt-get install -y python3-six || true fi - cqlsh -e "CREATE KEYSPACE IF NOT EXISTS reddit WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'};" || true + cqlsh -e "CREATE KEYSPACE IF NOT EXISTS tippr WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'};" || true # Create permacache table - cqlsh -e "CREATE TABLE IF NOT EXISTS reddit.permacache (key text PRIMARY KEY, value blob);" || true + cqlsh -e "CREATE TABLE IF NOT EXISTS tippr.permacache (key text PRIMARY KEY, value blob);" || true echo "Cassandra keyspace and tables created." @@ -63,14 +63,14 @@ else import pycassa sys = pycassa.SystemManager("localhost:9160") -if "reddit" not in sys.list_keyspaces(): - print "creating keyspace 'reddit'" - sys.create_keyspace("reddit", "SimpleStrategy", {"replication_factor": "1"}) +if "tippr" not in sys.list_keyspaces(): + print "creating keyspace 'tippr'" + sys.create_keyspace("tippr", "SimpleStrategy", {"replication_factor": "1"}) print "done" -if "permacache" not in sys.get_keyspace_column_families("reddit"): +if "permacache" not in sys.get_keyspace_column_families("tippr"): print "creating column family 'permacache'" - sys.create_column_family("reddit", "permacache") + sys.create_column_family("tippr", "permacache") print "done" END diff --git a/install/setup_mcrouter.sh b/install/setup_mcrouter.sh index 7b67e01e2a..cc2f7a34df 100755 --- a/install/setup_mcrouter.sh +++ b/install/setup_mcrouter.sh @@ -108,6 +108,18 @@ if [ ! -x /usr/local/bin/mcrouter ]; then if [ ! -d /opt/mcrouter ]; then git clone https://github.com/facebook/mcrouter.git /opt/mcrouter || true fi + # Enable ccache for C/C++ builds if available. Point CCACHE_DIR at the + # install user home (TIPPR_USER) so CI caching of ~/.ccache is used. + if command -v ccache >/dev/null 2>&1; then + CCACHE_DIR=/home/${TIPPR_USER:-runner}/.ccache + export CCACHE_DIR + mkdir -p "$CCACHE_DIR" + chmod 0777 "$CCACHE_DIR" || true + export CC="ccache gcc" + export CXX="ccache g++" + # Set a reasonable max size for cache + ccache -M 5G || true + fi pushd /opt/mcrouter >/dev/null 2>&1 || true # add known upstream fix branch if not already present git remote add markbhasawut https://github.com/markbhasawut/mcrouter.git 2>/dev/null || true diff --git a/install/setup_postgres.sh b/install/setup_postgres.sh index 72f7d604c0..31b740d31d 100755 --- a/install/setup_postgres.sh +++ b/install/setup_postgres.sh @@ -28,7 +28,7 @@ source $RUNDIR/install.cfg ############################################################################### # Configure PostgreSQL ############################################################################### -SQL="SELECT COUNT(1) FROM pg_catalog.pg_database WHERE datname = 'reddit';" +SQL="SELECT COUNT(1) FROM pg_catalog.pg_database WHERE datname = 'tippr';" IS_DATABASE_CREATED=$(sudo -u postgres env LC_ALL=C psql -t -c "$SQL" | tr -d '[:space:]') # Ensure the proper locale is generated and available. Prefer en_US.UTF-8 but @@ -53,34 +53,34 @@ done if [ "$IS_DATABASE_CREATED" != "1" ]; then # Try creating the DB specifying LC_COLLATE/LC_CTYPE if we detected a usable locale. if [ -n "$LOCALE_NAME" ]; then - if sudo -u postgres env LC_ALL=C psql -c "CREATE DATABASE reddit WITH ENCODING = 'UTF8' TEMPLATE template0 LC_COLLATE='${LOCALE_NAME}' LC_CTYPE='${LOCALE_NAME}';" 2>/tmp/createdb.err; then + if sudo -u postgres env LC_ALL=C psql -c "CREATE DATABASE tippr WITH ENCODING = 'UTF8' TEMPLATE template0 LC_COLLATE='${LOCALE_NAME}' LC_CTYPE='${LOCALE_NAME}';" 2>/tmp/createdb.err; then echo "Database created with locale ${LOCALE_NAME}" else echo "Failed to create database with locale ${LOCALE_NAME}, retrying without explicit locale..." cat /tmp/createdb.err || true - sudo -u postgres env LC_ALL=C psql -c "CREATE DATABASE reddit WITH ENCODING = 'UTF8' TEMPLATE template0;" || true + sudo -u postgres env LC_ALL=C psql -c "CREATE DATABASE tippr WITH ENCODING = 'UTF8' TEMPLATE template0;" || true fi else - sudo -u postgres env LC_ALL=C psql -c "CREATE DATABASE reddit WITH ENCODING = 'UTF8' TEMPLATE template0;" || true + sudo -u postgres env LC_ALL=C psql -c "CREATE DATABASE tippr WITH ENCODING = 'UTF8' TEMPLATE template0;" || true fi fi # Create role if it doesn't exist -ROLE_EXISTS=$(sudo -u postgres env LC_ALL=C psql -t -c "SELECT 1 FROM pg_roles WHERE rolname='reddit';" | tr -d '[:space:]') +ROLE_EXISTS=$(sudo -u postgres env LC_ALL=C psql -t -c "SELECT 1 FROM pg_roles WHERE rolname='tippr';" | tr -d '[:space:]') if [ "$ROLE_EXISTS" != "1" ]; then - sudo -u postgres env LC_ALL=C psql -c "CREATE USER reddit WITH PASSWORD 'password';" || true + sudo -u postgres env LC_ALL=C psql -c "CREATE USER tippr WITH PASSWORD 'password';" || true else - sudo -u postgres env LC_ALL=C psql -c "ALTER USER reddit WITH PASSWORD 'password';" || true + sudo -u postgres env LC_ALL=C psql -c "ALTER USER tippr WITH PASSWORD 'password';" || true fi -# Ensure the reddit user owns the reddit database so it can create tables -sudo -u postgres env LC_ALL=C psql -c "ALTER DATABASE reddit OWNER TO reddit;" || true +# Ensure the tippr user owns the tippr database so it can create tables +sudo -u postgres env LC_ALL=C psql -c "ALTER DATABASE tippr OWNER TO tippr;" || true -# Grant privileges on the public schema to the reddit user (needed when the +# Grant privileges on the public schema to the tippr user (needed when the # database owner is different or defaults are restrictive) -sudo -u postgres env LC_ALL=C psql reddit -c "GRANT ALL PRIVILEGES ON SCHEMA public TO reddit;" || true +sudo -u postgres env LC_ALL=C psql tippr -c "GRANT ALL PRIVILEGES ON SCHEMA public TO tippr;" || true -sudo -u postgres env LC_ALL=C psql reddit </dev/null || true else mkdir -p /etc/init.d - cp ${1}/upstart/* /etc/init.d/ + cp ${1}/upstart/tippr-* ${1}/upstart/reddit-* ${1}/upstart/* /etc/init.d/ 2>/dev/null || true # Make copied files executable so they can be used as simple # wrappers or inspected by administrators. chmod +x /etc/init.d/* || true @@ -378,33 +380,54 @@ function copy_upstart { fi } -function clone_reddit_repo { - local destination=$REDDIT_SRC/${1} - local repository_url=https://github.com/${2}.git +function clone_tippr_repo { + local destination=$TIPPR_SRC/${1} + local repo_spec=${2} + local repository_url + + # Accept either an owner/repo spec or a full URL + if echo "$repo_spec" | grep -qE '^https?://'; then + repository_url="$repo_spec" + else + repository_url="https://github.com/${repo_spec}.git" + fi + if [ ! -d $destination ]; then - sudo -u $REDDIT_USER -H git clone $repository_url $destination + # First try an anonymous clone (preserves previous CI behavior). + if sudo -u $TIPPR_USER -H git clone "$repository_url" $destination; then + : # success + else + # Anonymous clone failed; if a token is available, retry with it. + if [ -n "$TIPPR_GITHUB_TOKEN" ] || [ -n "$GITHUB_TOKEN" ]; then + token=${TIPPR_GITHUB_TOKEN:-$GITHUB_TOKEN} + repo_with_token=$(echo "$repository_url" | sed -E "s#https://#https://x-access-token:${token}@#") + sudo -u $TIPPR_USER -H git clone "$repo_with_token" $destination || true + fi + fi fi copy_upstart $destination } -function clone_reddit_service_repo { +function clone_tippr_service_repo { local name=$1 - local repo=${2:-reddit/reddit-service-$1} - clone_reddit_repo $name "$repo" + local repo=${2:-tippr/tippr-service-$1} + clone_tippr_repo $name "$repo" } -clone_reddit_repo reddit acalcutt/reddit -clone_reddit_repo i18n reddit/reddit-i18n -clone_reddit_service_repo websockets "$REDDIT_WEBSOCKETS_REPO" -clone_reddit_service_repo activity "$REDDIT_ACTIVITY_REPO" +clone_tippr_repo tippr TechIdiots-LLC/tippr +# i18n repo lives under the organization owner – ensure we clone the +# TechIdiots-LLC owner (not the old `tippr/` owner). +clone_tippr_repo i18n TechIdiots-LLC/tippr-i18n +clone_tippr_service_repo websockets "$TIPPR_WEBSOCKETS_REPO" +clone_tippr_service_repo activity "$TIPPR_ACTIVITY_REPO" # Patch activity and websockets setup.py to use new baseplate module path # (baseplate.integration was renamed to baseplate.frameworks in baseplate 1.0) for repo in activity websockets; do - if [ -f "$REDDIT_SRC/$repo/setup.py" ]; then - sed -i 's/baseplate\.integration\./baseplate.frameworks./g' "$REDDIT_SRC/$repo/setup.py" + if [ -f "$TIPPR_SRC/$repo/setup.py" ]; then + sed -i 's/baseplate\.integration\./baseplate.frameworks./g' "$TIPPR_SRC/$repo/setup.py" fi done @@ -427,65 +450,131 @@ $RUNDIR/setup_mcrouter.sh $RUNDIR/setup_rabbitmq.sh ############################################################################### -# Install and configure the reddit code +# Install and configure the tippr code ############################################################################### -# Create Python virtual environment for reddit -# Ensure the venv parent exists and is writable by the reddit user +# Create Python virtual environment for tippr +# Ensure the venv parent exists and is writable by the tippr user # Remove any stale venv that is not writable by the runtime user so we can # recreate it as the correct owner. -echo "Creating Python virtual environment at $REDDIT_VENV" -mkdir -p "$(dirname "$REDDIT_VENV")" -chown $REDDIT_USER:$REDDIT_GROUP "$(dirname "$REDDIT_VENV")" || true -if [ -d "$REDDIT_VENV" ] && [ ! -w "$REDDIT_VENV" ]; then - echo "Existing venv at $REDDIT_VENV is not writable by $REDDIT_USER; removing" - rm -rf "$REDDIT_VENV" +echo "Creating Python virtual environment at $TIPPR_VENV" +mkdir -p "$(dirname "$TIPPR_VENV")" +chown $TIPPR_USER:$TIPPR_GROUP "$(dirname "$TIPPR_VENV")" || true +if [ -d "$TIPPR_VENV" ] && [ ! -w "$TIPPR_VENV" ]; then + echo "Existing venv at $TIPPR_VENV is not writable by $TIPPR_USER; removing" + rm -rf "$TIPPR_VENV" fi -sudo -u $REDDIT_USER python3 -m venv $REDDIT_VENV -# Create 'python' symlink for compatibility with Makefiles that expect 'python' -sudo -u $REDDIT_USER ln -sf python3 $REDDIT_VENV/bin/python +# Check if a working venv already exists (e.g., pre-created by CI workflow). +# A working venv has a python executable that can import basic stdlib modules. +VENV_OK=0 +if [ -x "$TIPPR_VENV/bin/python" ]; then + if sudo -u $TIPPR_USER "$TIPPR_VENV/bin/python" -c "import sys, os, math; print('venv ok')" 2>/dev/null; then + echo "Reusing existing working venv at $TIPPR_VENV" + VENV_OK=1 + else + echo "Existing venv at $TIPPR_VENV is broken; removing and recreating" + rm -rf "$TIPPR_VENV" + fi +fi + +# Attempt to create venv; if ensurepip fails (some images lack ensurepip), +# install prerequisites and retry by bootstrapping pip via get-pip.py. +if [ "$VENV_OK" = "1" ]; then + : +elif sudo -u $TIPPR_USER python3 -m venv $TIPPR_VENV; then + : +else + echo "python3 -m venv failed; attempting fallback (installing prerequisites)" + apt-get update || true + + # Detect Python major.minor (e.g. 3.12) and prefer distro-specific packages + PYVER=$(python3 -c 'import sys; print("{}.{}".format(sys.version_info.major, sys.version_info.minor))' 2>/dev/null || echo "") + PKGS="python3-venv python3-pip python3-distutils" + if [ -n "$PYVER" ]; then + PKGS="$PKGS libpython${PYVER}-stdlib python${PYVER}" + fi + + # Try installing candidate packages; tolerate failure and continue + apt-get install -y $PKGS || apt-get install -y python3-venv python3-pip || true + + # Retry creating venv (this may succeed now that stdlib packages are present) + if sudo -u $TIPPR_USER python3 -m venv $TIPPR_VENV; then + : + else + # Fall back to creating venv without pip, then bootstrap pip using get-pip.py + if sudo -u $TIPPR_USER python3 -m venv --without-pip $TIPPR_VENV; then + echo "Bootstrapping pip into venv using get-pip.py" + curl -sS https://bootstrap.pypa.io/get-pip.py -o /tmp/get-pip.py || true + if [ -f /tmp/get-pip.py ]; then + # Ensure libpython is installed before attempting to run get-pip.py + if [ -n "$PYVER" ]; then + apt-get update || true + apt-get install -y libpython${PYVER}-stdlib python${PYVER} || true + fi + if ! sudo -u $TIPPR_USER $TIPPR_VENV/bin/python /tmp/get-pip.py; then + # Retry once more + sudo -u $TIPPR_USER $TIPPR_VENV/bin/python /tmp/get-pip.py || true + fi + rm -f /tmp/get-pip.py || true + else + echo "Failed to download get-pip.py; attempting system pip to install pip into venv" + sudo -u $TIPPR_USER python3 -m pip install --upgrade pip || true + fi + else + echo "Retry to create venv failed; aborting venv setup" >&2 + fi + fi +fi + +# Create 'python' and 'python3' symlinks for compatibility +# Some tools expect 'python', others 'python3'; ensure both exist +if [ -x "$TIPPR_VENV/bin/python3" ] && [ ! -e "$TIPPR_VENV/bin/python" ]; then + sudo -u $TIPPR_USER ln -sf python3 $TIPPR_VENV/bin/python +elif [ -x "$TIPPR_VENV/bin/python" ] && [ ! -e "$TIPPR_VENV/bin/python3" ]; then + sudo -u $TIPPR_USER ln -sf python $TIPPR_VENV/bin/python3 +fi # Upgrade pip and install build tools in venv # Install current setuptools/wheel and ensure `packaging` is recent so editable # installs / metadata generation behave correctly. -sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install --upgrade pip setuptools wheel -sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install --upgrade 'packaging>=23.1' +sudo -u $TIPPR_USER $TIPPR_VENV/bin/python -m pip install --upgrade pip setuptools wheel +sudo -u $TIPPR_USER $TIPPR_VENV/bin/python -m pip install --upgrade 'packaging>=23.1' # Install `baseplate` early so packages that inspect/import it at build time -# (e.g., r2) can detect it. Prefer a local checkout at $REDDIT_SRC/baseplate.py -# when available, otherwise use the configured REDDIT_BASEPLATE_PIP_URL or +# (e.g., r2) can detect it. Prefer a local checkout at $TIPPR_SRC/tippr-baseplate.py +# when available, otherwise use the configured TIPPR_BASEPLATE_PIP_URL or # fall back to PyPI. -if [ -d "$REDDIT_SRC/baseplate.py" ]; then - echo "Installing local baseplate from $REDDIT_SRC/baseplate.py" - sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install -e "$REDDIT_SRC/baseplate.py" -elif [ -n "$REDDIT_BASEPLATE_PIP_URL" ]; then - echo "Installing baseplate from $REDDIT_BASEPLATE_PIP_URL" - sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install "$REDDIT_BASEPLATE_PIP_URL" +if [ -d "$TIPPR_SRC/tippr-baseplate.py" ]; then + echo "Installing local baseplate from $TIPPR_SRC/tippr-baseplate.py" + sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install -e "$TIPPR_SRC/tippr-baseplate.py" +elif [ -n "$TIPPR_BASEPLATE_PIP_URL" ]; then + echo "Installing baseplate from $TIPPR_BASEPLATE_PIP_URL" + sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install "$TIPPR_BASEPLATE_PIP_URL" else echo "Installing baseplate from PyPI" - sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install baseplate + sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install baseplate fi # If provided, prefer a fork of `formenergy-observability` that supports # modern `packaging`, install it early so it cannot force a downgrade of # `packaging` during later bulk installs. -if [ -n "$REDDIT_FORMENERGY_OBSERVABILITY_PIP_URL" ]; then - echo "Installing formenergy-observability from $REDDIT_FORMENERGY_OBSERVABILITY_PIP_URL" - sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install "$REDDIT_FORMENERGY_OBSERVABILITY_PIP_URL" || true +if [ -n "$TIPPR_FORMENERGY_OBSERVABILITY_PIP_URL" ]; then + echo "Installing formenergy-observability from $TIPPR_FORMENERGY_OBSERVABILITY_PIP_URL" + sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install "$TIPPR_FORMENERGY_OBSERVABILITY_PIP_URL" || true fi # Install baseplate and other runtime dependencies # Installation order/options: -# 1. If `REDDIT_BASEPLATE_PIP_URL` is set, install baseplate from that pip +# 1. If `TIPPR_BASEPLATE_PIP_URL` is set, install baseplate from that pip # spec (supports git+ URLs, file://, or local editable installs). -# 2. Else if `REDDIT_BASEPLATE_REPO` is set, install from the given fork +# 2. Else if `TIPPR_BASEPLATE_REPO` is set, install from the given fork # (legacy behavior). # 3. Otherwise install `baseplate` from PyPI. -if [ -n "$REDDIT_BASEPLATE_PIP_URL" ]; then - echo "Installing baseplate from pip spec: $REDDIT_BASEPLATE_PIP_URL" - sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install \ - "$REDDIT_BASEPLATE_PIP_URL" \ +if [ -n "$TIPPR_BASEPLATE_PIP_URL" ]; then + echo "Installing baseplate from pip spec: $TIPPR_BASEPLATE_PIP_URL" + sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install \ + "$TIPPR_BASEPLATE_PIP_URL" \ "gunicorn" \ "whoosh" \ "PasteScript" \ @@ -503,10 +592,10 @@ if [ -n "$REDDIT_BASEPLATE_PIP_URL" ]; then "GeoIP" \ "pika>=1.3.2,<2" \ "sentry-sdk" -elif [ -n "$REDDIT_BASEPLATE_REPO" ]; then - echo "Installing baseplate from fork: $REDDIT_BASEPLATE_REPO" - sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install \ - "git+https://github.com/$REDDIT_BASEPLATE_REPO.git@main#egg=baseplate" \ +elif [ -n "$TIPPR_BASEPLATE_REPO" ]; then + echo "Installing baseplate from fork: $TIPPR_BASEPLATE_REPO" + sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install \ + "git+https://github.com/$TIPPR_BASEPLATE_REPO.git@main#egg=baseplate" \ "gunicorn" \ "whoosh" \ "PasteScript" \ @@ -525,7 +614,7 @@ elif [ -n "$REDDIT_BASEPLATE_REPO" ]; then "pika>=1.3.2,<2" \ "sentry-sdk" else - sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install \ + sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install \ "baseplate" \ "gunicorn" \ "whoosh" \ @@ -547,18 +636,18 @@ else fi # Create a writable directory for Prometheus multiprocess mode if needed -# and make it owned by the reddit user so prometheus-client can write there. -PROMETHEUS_DIR=/var/lib/reddit/prometheus-multiproc +# and make it owned by the tippr user so prometheus-client can write there. +PROMETHEUS_DIR=/var/lib/tippr/prometheus-multiproc if [ ! -d "$PROMETHEUS_DIR" ]; then mkdir -p "$PROMETHEUS_DIR" - chown $REDDIT_USER:$REDDIT_GROUP "$PROMETHEUS_DIR" + chown $TIPPR_USER:$TIPPR_GROUP "$PROMETHEUS_DIR" chmod 0775 "$PROMETHEUS_DIR" fi # Additional packages that `r2` currently lists as runtime/test deps. -# Install them into the venv as the reddit user. Some packages require +# Install them into the venv as the tippr user. Some packages require # system libs (e.g. libpq-dev, libxml2-dev); failures will be reported # but won't abort the installer. -sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install \ +sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install \ bcrypt \ beautifulsoup4 \ boto3 \ @@ -585,46 +674,46 @@ sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install \ # Prefer psycopg2-binary to avoid requiring system postgres headers during # install; if you need the real psycopg2 build from source, install # libpq-dev and python3-dev on the host instead. -sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install psycopg2-binary || true +sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install psycopg2-binary || true # Ensure `packaging` remains at a modern version — some packages may pull # older versions during bulk installs. Force-reinstall without deps to keep # the build-toolchain compatible for later editable installs. -sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install --upgrade --force-reinstall --no-deps 'packaging>=23.1' || true +sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install --upgrade --force-reinstall --no-deps 'packaging>=23.1' || true # Convert legacy Python 2 sources in i18n to Python 3 using lib2to3 -if [ -d "$REDDIT_SRC/i18n" ]; then +if [ -d "$TIPPR_SRC/i18n" ]; then echo "Converting i18n Python files to Python 3 with lib2to3" - for pyf in $(find "$REDDIT_SRC/i18n" -name "*.py"); do - sudo -u $REDDIT_USER PATH="$REDDIT_VENV/bin:$PATH" python3 -m lib2to3 -w "$pyf" || true + for pyf in $(find "$TIPPR_SRC/i18n" -name "*.py"); do + sudo -u $TIPPR_USER PATH="$TIPPR_VENV/bin:$PATH" python3 -m lib2to3 -w "$pyf" || true done fi -function install_reddit_repo { - pushd $REDDIT_SRC/$1 +function install_tippr_repo { + pushd $TIPPR_SRC/$1 # Ensure build-toolchain is pinned so metadata generation won't fail - sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install --upgrade --force-reinstall --no-deps pip setuptools wheel 'packaging>=23.1' || true + sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install --upgrade --force-reinstall --no-deps pip setuptools wheel 'packaging>=23.1' || true - sudo -u $REDDIT_USER $REDDIT_VENV/bin/python setup.py build + sudo -u $TIPPR_USER $TIPPR_VENV/bin/python setup.py build # --no-build-isolation uses the venv's packages (like baseplate) instead of isolated env - sudo -u $REDDIT_USER $REDDIT_VENV/bin/pip install --no-build-isolation -e . + sudo -u $TIPPR_USER $TIPPR_VENV/bin/pip install --no-build-isolation -e . popd } -install_reddit_repo reddit/r2 +install_tippr_repo tippr/r2 # Only install the external `i18n` package if its setup.py contains a # valid version string. Some historical `i18n` checkouts have an empty # version which breaks modern packaging tools; in that case skip the # install and rely on local compatibility shims in the tree. # Ensure i18n is installed; if its setup.py lacks a version, inject a # minimal default to satisfy modern packaging tools. -if [ -f "$REDDIT_SRC/i18n/setup.py" ]; then - if ! grep -Eq "version\s*=\s*['\"][^'\"]+['\"]" "$REDDIT_SRC/i18n/setup.py"; then +if [ -f "$TIPPR_SRC/i18n/setup.py" ]; then + if ! grep -Eq "version\s*=\s*['\"][^'\"]+['\"]" "$TIPPR_SRC/i18n/setup.py"; then echo "Patching i18n/setup.py to add default version 0.0.1" # Backup original for debugging - cp "$REDDIT_SRC/i18n/setup.py" "$REDDIT_SRC/i18n/setup.py.orig" || true + cp "$TIPPR_SRC/i18n/setup.py" "$TIPPR_SRC/i18n/setup.py.orig" || true # Replace with a minimal, safe setup.py to avoid legacy packaging issues - cat > "$REDDIT_SRC/i18n/setup.py" <<'PYSETUP' + cat > "$TIPPR_SRC/i18n/setup.py" <<'PYSETUP' from setuptools import setup, find_packages setup( @@ -634,22 +723,22 @@ setup( ) PYSETUP fi - install_reddit_repo i18n + install_tippr_repo i18n else echo "i18n checkout not present; skipping i18n install" SKIP_I18N=1 fi -for plugin in $REDDIT_AVAILABLE_PLUGINS; do - copy_upstart $REDDIT_SRC/$plugin - install_reddit_repo $plugin +for plugin in $TIPPR_AVAILABLE_PLUGINS; do + copy_upstart $TIPPR_SRC/$plugin + install_tippr_repo $plugin done -install_reddit_repo websockets -install_reddit_repo activity +install_tippr_repo websockets +install_tippr_repo activity # generate binary translation files from source if [ "${SKIP_I18N}" != "1" ]; then # Use venv's python for make commands - sudo -u $REDDIT_USER PATH="$REDDIT_VENV/bin:$PATH" make -C $REDDIT_SRC/i18n clean all + sudo -u $TIPPR_USER PATH="$TIPPR_VENV/bin:$PATH" make -C $TIPPR_SRC/i18n clean all else echo "Skipping i18n message compilation because i18n package was not installed" fi @@ -657,11 +746,11 @@ fi # this builds static files and should be run *after* languages are installed # so that the proper language-specific static files can be generated and after # plugins are installed so all the static files are available. -pushd $REDDIT_SRC/reddit/r2 +pushd $TIPPR_SRC/tippr/r2 # Use venv's python for make commands -sudo -u $REDDIT_USER PATH="$REDDIT_VENV/bin:$PATH" PYTHONPATH="$REDDIT_SRC/reddit:$REDDIT_SRC" make clean pyx +sudo -u $TIPPR_USER PATH="$TIPPR_VENV/bin:$PATH" PYTHONPATH="$TIPPR_SRC/tippr:$TIPPR_SRC" make clean pyx -plugin_str=$(echo -n "$REDDIT_AVAILABLE_PLUGINS" | tr " " ,) +plugin_str=$(echo -n "$TIPPR_AVAILABLE_PLUGINS" | tr " " ,) if [ ! -f development.update ]; then cat > development.update < development.ini" || true + sudo -u $TIPPR_USER bash -lc "PATH=\"$TIPPR_VENV/bin:\$PATH\" PYTHONPATH=\"$TIPPR_SRC/tippr:$TIPPR_SRC\" python updateini.py example.ini development.update > development.ini" || true fi # Ensure run.ini is a symlink to a real ini. Prefer development.ini, fall # back to example.ini if generation failed. if [ -f development.ini ]; then # Create a real file (not a symlink) to avoid broken-link surprises in CI - sudo -u $REDDIT_USER cp -f development.ini run.ini - sudo -u $REDDIT_USER chown $REDDIT_USER run.ini || true + sudo -u $TIPPR_USER cp -f development.ini run.ini + sudo -u $TIPPR_USER chown $TIPPR_USER run.ini || true else echo "Falling back to example.ini for run.ini (development.ini missing)" - sudo -u $REDDIT_USER cp -f example.ini run.ini - sudo -u $REDDIT_USER chown $REDDIT_USER run.ini || true + sudo -u $TIPPR_USER cp -f example.ini run.ini + sudo -u $TIPPR_USER chown $TIPPR_USER run.ini || true fi popd # Ensure generated Mako template cache files are owned by the app user -if [ -d "$REDDIT_SRC/reddit/r2/data/templates" ]; then - chown -R $REDDIT_USER:$REDDIT_GROUP $REDDIT_SRC/reddit/r2/data/templates || true +if [ -d "$TIPPR_SRC/tippr/r2/data/templates" ]; then + chown -R $TIPPR_USER:$TIPPR_GROUP $TIPPR_SRC/tippr/r2/data/templates || true fi ############################################################################### @@ -737,18 +826,18 @@ function helper-script() { chmod 755 $1 } -# Create a Python script for reddit-run that bypasses paster's plugin discovery -cat > $REDDIT_VENV/bin/reddit-run-cmd < $TIPPR_VENV/bin/tippr-run-cmd </dev/null 2>&1; then - initctl emit reddit-start + initctl emit tippr-start elif command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then - # Restart common reddit units if systemd is available - systemctl restart reddit-websockets reddit-activity gunicorn-click gunicorn-geoip || true + # Restart common tippr units if systemd is available + systemctl restart tippr-websockets tippr-activity gunicorn-click gunicorn-geoip || true else echo "No initctl or systemctl found; cannot start services" fi -REDDITSTART +TIPPRSTART -helper-script /usr/local/bin/reddit-stop </dev/null 2>&1; then - initctl emit reddit-stop + initctl emit tippr-stop elif command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then - systemctl stop reddit-websockets reddit-activity gunicorn-click gunicorn-geoip || true + systemctl stop tippr-websockets tippr-activity gunicorn-click gunicorn-geoip || true else echo "No initctl or systemctl found; cannot stop services" fi -REDDITSTOP +TIPPRSTOP -helper-script /usr/local/bin/reddit-restart </dev/null 2>&1; then - initctl emit reddit-restart TARGET=${1:-all} + initctl emit tippr-restart TARGET=${1:-all} elif command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then # If a specific target is provided, attempt to restart matching unit(s) if [ -n "$1" ] && [ "$1" != "all" ]; then systemctl restart "$1" || true else - systemctl restart reddit-websockets reddit-activity gunicorn-click gunicorn-geoip || true + systemctl restart tippr-websockets tippr-activity gunicorn-click gunicorn-geoip || true fi else echo "No initctl or systemctl found; cannot restart services" fi -REDDITRESTART +TIPPRRESTART -helper-script /usr/local/bin/reddit-flush </dev/null 2>&1 && [ -d /run/systemd/system ]; then - cat > /etc/systemd/system/reddit-serve.service < /etc/systemd/system/tippr-serve.service < /etc/gunicorn.d/click.conf </dev/null 2>&1 && [ -d /run/systemd/system ]; then cat > /etc/systemd/system/gunicorn-click.service < /etc/nginx/sites-available/reddit-media < /etc/nginx/sites-available/tippr-media < /etc/nginx/sites-available/reddit-pixel < /etc/nginx/sites-available/tippr-pixel < /etc/nginx/sites-available/reddit-ssl < /etc/nginx/sites-available/tippr-ssl < /etc/nginx/conf.d/reddit-log.conf <<'LOGCONF' +cat > /etc/nginx/conf.d/tippr-log.conf <<'LOGCONF' log_format directlog '$remote_addr - $remote_user [$time_local] ' '"$request_method $request_uri $server_protocol" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"'; LOGCONF # link the ini file for the Flask click tracker -ln -nsf $REDDIT_SRC/reddit/r2/development.ini $REDDIT_SRC/reddit/scripts/production.ini +ln -nsf $TIPPR_SRC/tippr/r2/development.ini $TIPPR_SRC/tippr/scripts/production.ini service nginx restart @@ -1059,9 +1148,9 @@ frontend frontend acl is-click path_beg /click use_backend pixel if is-pixel || is-click - default_backend reddit + default_backend tippr -backend reddit +backend tippr mode http timeout connect 4000 timeout server 30000 @@ -1106,12 +1195,12 @@ service haproxy restart # Only install Upstart jobs if /etc/init exists (do not create it) if [ -d /etc/init ]; then - if [ ! -f /etc/init/reddit-websockets.conf ]; then - cat > /etc/init/reddit-websockets.conf << UPSTART_WEBSOCKETS + if [ ! -f /etc/init/tippr-websockets.conf ]; then + cat > /etc/init/tippr-websockets.conf << UPSTART_WEBSOCKETS description "websockets service" -stop on runlevel [!2345] or reddit-restart all or reddit-restart websockets -start on runlevel [2345] or reddit-restart all or reddit-restart websockets +stop on runlevel [!2345] or tippr-restart all or tippr-restart websockets +start on runlevel [2345] or tippr-restart all or tippr-restart websockets respawn respawn limit 10 5 @@ -1119,38 +1208,38 @@ kill timeout 15 limit nofile 65535 65535 -exec $REDDIT_VENV/bin/baseplate-serve --bind localhost:9001 $REDDIT_SRC/websockets/example.ini +exec $TIPPR_VENV/bin/baseplate-serve --bind localhost:9001 $TIPPR_SRC/websockets/example.ini UPSTART_WEBSOCKETS fi fi # Create a systemd unit for websockets (preferred on modern systems) if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then - cat > /etc/systemd/system/reddit-websockets.service < /etc/systemd/system/tippr-websockets.service < /etc/init/reddit-activity.conf << UPSTART_ACTIVITY + if [ ! -f /etc/init/tippr-activity.conf ]; then + cat > /etc/init/tippr-activity.conf << UPSTART_ACTIVITY description "activity service" -stop on runlevel [!2345] or reddit-restart all or reddit-restart activity -start on runlevel [2345] or reddit-restart all or reddit-restart activity +stop on runlevel [!2345] or tippr-restart all or tippr-restart activity +start on runlevel [2345] or tippr-restart all or tippr-restart activity respawn respawn limit 10 5 kill timeout 15 -exec $REDDIT_VENV/bin/baseplate-serve --bind localhost:9002 $REDDIT_SRC/activity/example.ini +exec $TIPPR_VENV/bin/baseplate-serve --bind localhost:9002 $TIPPR_SRC/activity/example.ini UPSTART_ACTIVITY fi fi # Create a systemd unit for activity service if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then - cat > /etc/systemd/system/reddit-activity.service < /etc/systemd/system/tippr-activity.service < /etc/gunicorn.d/geoip.conf </dev/null 2>&1 && [ -d /run/systemd/system ]; then cat > /etc/systemd/system/gunicorn-geoip.service < /etc/default/reddit < /etc/default/tippr </dev/null 2>&1; then - initctl emit reddit-stop - initctl emit reddit-start + initctl emit tippr-stop + initctl emit tippr-start elif command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then - systemctl stop reddit-websockets reddit-activity gunicorn-click gunicorn-geoip || true - systemctl start reddit-websockets reddit-activity gunicorn-click gunicorn-geoip || true + systemctl stop tippr-websockets tippr-activity gunicorn-click gunicorn-geoip || true + systemctl start tippr-websockets tippr-activity gunicorn-click gunicorn-geoip || true else echo "No init system found (initctl/systemctl); services not started." fi @@ -1337,27 +1426,27 @@ fi ############################################################################### # Cron Jobs ############################################################################### -if [ ! -f /etc/cron.d/reddit ]; then - cat > /etc/cron.d/reddit < /etc/cron.d/tippr <