mirror of
https://github.com/rybbit-io/rybbit.git
synced 2025-05-10 20:05:38 +02:00
Add caddy (#90)
* Add Nginx and Certbot services to Docker Compose - Introduced Nginx service with custom configuration for handling HTTP and HTTPS traffic, including volume mounts for templates and entrypoint script. - Added Certbot service for automated SSL certificate management, with a renewal loop and environment variable support for domain and email. - Updated existing services to include restart policies and removed unnecessary port mappings for backend and client services. - Enhanced volume management by adding certbot-conf and certbot-www for certificate storage and challenge handling. * remove volume * Replace Nginx and Certbot with Caddy in Docker Compose - Removed Nginx and Certbot services, streamlining the configuration for SSL management. - Introduced Caddy service with automatic HTTPS support and simplified volume management. - Updated environment variables for domain and email configuration, ensuring compatibility with Caddy's setup. - Enhanced volume definitions for persistent data and configuration storage. * Fix * Fix * Remove Nginx and Certbot configurations from Docker setup - Deleted Nginx and Certbot Dockerfiles, entrypoint scripts, and associated configuration files to streamline the project. - Updated docker-compose.yml to remove Certbot email configuration, reflecting the transition to Caddy for SSL management. * Remove version declaration from docker-compose.yml to simplify configuration * Refactor login page to simplify account creation prompt - Removed conditional rendering for the sign-up link, ensuring it is always displayed for better user accessibility. * Update self-hosting documentation for Frogstats - Revamped the self-hosting guide to provide clearer instructions for setting up Frogstats. - Added prerequisites section detailing requirements such as a Linux VPS, domain name, Docker, and Git. - Enhanced setup steps with detailed commands for installing Docker and Git, cloning the repository, and running the setup script. - Included a callout for compatibility with Ubuntu 24 LTS and emphasized the importance of HTTPS for tracking scripts.
This commit is contained in:
parent
f890ec9b2b
commit
d10017686f
14 changed files with 178 additions and 122 deletions
24
Caddyfile
Normal file
24
Caddyfile
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Caddyfile
|
||||
# Use the domain name passed from docker-compose environment
|
||||
{$DOMAIN_NAME} {
|
||||
# Enable compression
|
||||
encode zstd gzip
|
||||
|
||||
# Proxy API requests to the backend service
|
||||
handle_path /api/* {
|
||||
reverse_proxy backend:3001
|
||||
}
|
||||
|
||||
# Proxy all other requests to the client service
|
||||
handle {
|
||||
reverse_proxy client:3002
|
||||
}
|
||||
|
||||
# Optional: Add security headers (example)
|
||||
# header {
|
||||
# Strict-Transport-Security max-age=31536000;
|
||||
# X-Content-Type-Options nosniff
|
||||
# X-Frame-Options DENY
|
||||
# Referrer-Policy strict-origin-when-cross-origin
|
||||
# }
|
||||
}
|
|
@ -17,7 +17,6 @@ COPY . .
|
|||
|
||||
# Next.js collects completely anonymous telemetry data about general usage.
|
||||
# Learn more here: https://nextjs.org/telemetry
|
||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
ARG NEXT_PUBLIC_BACKEND_URL
|
||||
ENV NEXT_PUBLIC_BACKEND_URL=${NEXT_PUBLIC_BACKEND_URL}
|
||||
|
|
|
@ -147,14 +147,12 @@ export default function Page() {
|
|||
</Alert>
|
||||
)}
|
||||
|
||||
{IS_CLOUD && (
|
||||
<div className="text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<Link href="/signup" className="underline">
|
||||
Sign up
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<Link href="/signup" className="underline">
|
||||
Sign up
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
caddy:
|
||||
image: caddy:latest
|
||||
container_name: caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp" # Needed for HTTP/3
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile # Mount Caddy config file
|
||||
- caddy_data:/data # Mount persistent data volume for certs etc.
|
||||
- caddy_config:/config # Mount persistent config volume
|
||||
environment:
|
||||
# Pass domain name for use in Caddyfile
|
||||
# Email is configured via Caddyfile global options
|
||||
- DOMAIN_NAME=${DOMAIN_NAME}
|
||||
depends_on:
|
||||
- backend
|
||||
- client
|
||||
|
||||
clickhouse:
|
||||
container_name: clickhouse
|
||||
image: clickhouse/clickhouse-server:latest
|
||||
|
@ -19,6 +37,7 @@ services:
|
|||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
|
@ -27,18 +46,15 @@ services:
|
|||
POSTGRES_USER: frog
|
||||
POSTGRES_PASSWORD: frog
|
||||
POSTGRES_DB: analytics
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
|
||||
backend:
|
||||
container_name: backend
|
||||
build:
|
||||
context: ./server
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3001:3001"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- CLICKHOUSE_HOST=http://clickhouse:8123
|
||||
|
@ -59,6 +75,7 @@ services:
|
|||
condition: service_healthy
|
||||
postgres:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
|
||||
client:
|
||||
container_name: client
|
||||
|
@ -67,15 +84,16 @@ services:
|
|||
dockerfile: Dockerfile
|
||||
args:
|
||||
NEXT_PUBLIC_BACKEND_URL: ${BASE_URL}
|
||||
ports:
|
||||
- "3002:3002"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- NEXT_PUBLIC_BACKEND_URL=${BASE_URL}
|
||||
- CLOUD=${CLOUD}
|
||||
depends_on:
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
clickhouse-data:
|
||||
postgres-data:
|
||||
caddy_data: # Persistent volume for Caddy's certificates and state
|
||||
caddy_config: # Persistent volume for Caddy's configuration cache (optional but good practice)
|
||||
|
|
|
@ -1,105 +1,68 @@
|
|||
import { Steps } from 'nextra/components'
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# Self Hosting
|
||||
# Self Hosting Frogstats
|
||||
|
||||
You can self-host Frogstats by following the steps below.
|
||||
This guide will walk you through setting up your own instance of Frogstats.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
## Create manually
|
||||
Before you begin, ensure you have the following:
|
||||
|
||||
Nextra works like a Next.js plugin, and it accepts a theme config (layout) to
|
||||
render the page. To start: [^3]
|
||||
- **A Linux VPS:** We use [Hetzner Cloud](https://hetzner.cloud/?ref=QEdVqVpTLBDP) for everything (referral link - but Hetzner is legitimately the best value). CX11 for ~$4/month will be fine for small-medium sized projects.
|
||||
- **A Domain Name:** You'll need a domain or subdomain (e.g., `tracking.yourdomain.com`) pointed to your VPS's IP address. HTTPS is required because browsers block tracking scripts served over insecure HTTP.
|
||||
- **Docker Engine:** Docker is used to run the application stack. Installation instructions are in Step 1.
|
||||
- **Git:** Needed to clone the repository.
|
||||
|
||||
<Callout type="info">
|
||||
This guide has been tested on Ubuntu 24 LTS. While it should work on other distributions, your mileage may vary.
|
||||
</Callout>
|
||||
|
||||
## Setup Steps
|
||||
|
||||
<Steps>
|
||||
### Install Next.js, Nextra and React [^1]
|
||||
### 1. Install Docker and Git
|
||||
|
||||
{/* ```sh npm2yarn
|
||||
npm i react react-dom next nextra
|
||||
``` */}
|
||||
First, connect to your VPS via SSH.
|
||||
|
||||
### Install the docs theme [^2]
|
||||
**Install Docker Engine:** Follow the official instructions for your Linux distribution:
|
||||
[https://docs.docker.com/engine/install/](https://docs.docker.com/engine/install/)
|
||||
|
||||
```sh npm2yarn
|
||||
npm i nextra-theme-docs
|
||||
Make sure to complete the [post-installation steps for Linux](https://docs.docker.com/engine/install/linux-postinstall/) as well, particularly adding your user to the `docker` group so you don't have to use `sudo` for every Docker command.
|
||||
|
||||
**Install Git:** If Git is not already installed, install it using your distribution's package manager. For Debian/Ubuntu:
|
||||
```bash
|
||||
sudo apt update && sudo apt install -y git
|
||||
```
|
||||
|
||||
### Create the following Next.js config and theme config under the root directory
|
||||
### 2. Clone the Frogstats Repository
|
||||
|
||||
```js filename="next.config.mjs"
|
||||
import nextra from 'nextra'
|
||||
|
||||
const withNextra = nextra({
|
||||
theme: 'nextra-theme-blog',
|
||||
themeConfig: './theme.config.js'
|
||||
})
|
||||
export default withNextra()
|
||||
Clone the project repository from GitHub:
|
||||
```bash
|
||||
git clone https://github.com/goldflag/frogstats.git
|
||||
cd frogstats
|
||||
```
|
||||
|
||||
### Create a `theme.config.js` file for the docs theme
|
||||
### 3. Run the Setup Script
|
||||
|
||||
```js filename="theme.config.js"
|
||||
export default {
|
||||
project: {
|
||||
link: 'https://github.com/shuding/nextra' // GitHub link in the navbar
|
||||
},
|
||||
docsRepositoryBase: 'https://github.com/shuding/nextra/blob/master', // base URL for the docs repository
|
||||
getNextSeoProps: () => ({ titleTemplate: '%s – Nextra' }),
|
||||
navigation: true,
|
||||
darkMode: true,
|
||||
footer: {
|
||||
text: `MIT ${new Date().getFullYear()} © Shu Ding.`
|
||||
},
|
||||
editLink: {
|
||||
text: 'Edit this page on GitHub'
|
||||
},
|
||||
logo: (
|
||||
<>
|
||||
<svg>...</svg>
|
||||
<span>Next.js Static Site Generator</span>
|
||||
</>
|
||||
),
|
||||
head: (
|
||||
<>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Nextra: the next docs builder" />
|
||||
<meta name="og:title" content="Nextra: the next docs builder" />
|
||||
</>
|
||||
),
|
||||
primaryHue: {
|
||||
dark: 204,
|
||||
light: 212
|
||||
}
|
||||
}
|
||||
The repository includes a setup script that configures the necessary environment variables (including generating a secure secret) and starts the application using Docker Compose.
|
||||
|
||||
Run the script, replacing `your.domain.name` with the domain or subdomain you configured in the prerequisites:
|
||||
```bash
|
||||
chmod +x setup.sh
|
||||
./setup.sh your.domain.name
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> More configuration options for the docs theme can be found
|
||||
> [here](/themes/docs/configuration).
|
||||
The script will create a `.env` file and then build and start the containers. This might take a few minutes the first time.
|
||||
|
||||
### You are good to go! Run `next dev` to start
|
||||
### 4. Sign Up
|
||||
|
||||
Once the services are running, Caddy (the webserver) will automatically obtain an SSL certificate for your domain.
|
||||
|
||||
Open your browser and navigate to `https://your.domain.name/signup` (using the domain you provided to the setup script).
|
||||
|
||||
Create your admin account. You can then log in and start adding your websites!
|
||||
|
||||
</Steps>
|
||||
|
||||
---
|
||||
|
||||
<span id="sidebar-and-anchor-links" />
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Any `.md` or `.mdx` file will turn into a doc page and be displayed in
|
||||
> sidebar. You can also create a `_meta.js` file to customize the page order and
|
||||
> title. <br /> Check the source code: https://github.com/shuding/nextra for
|
||||
> more information.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> You can also use
|
||||
> [`<style jsx>`](https://nextjs.org/docs/basic-features/built-in-css-support#css-in-js)
|
||||
> to style elements inside `theme.config.js`.
|
||||
|
||||
[^1]: Install Next.js, Nextra and React.
|
||||
|
||||
[^2]: Install the docs theme.
|
||||
|
||||
[^3]: To start.
|
||||
Congratulations! You have successfully self-hosted Frogstats.
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Docker Compose already ensures services are ready using healthchecks
|
||||
# and dependency conditions in the docker-compose.yml file
|
||||
|
||||
# Run migrations explicitly using the npm script
|
||||
echo "Running database migrations...."
|
||||
npm run db:push
|
||||
|
|
|
@ -4,6 +4,7 @@ import { db } from "../../db/postgres/postgres.js";
|
|||
import { member } from "../../db/postgres/schema.js";
|
||||
import { getSitesUserHasAccessTo } from "../../lib/auth-utils.js";
|
||||
import { getSubscriptionInner } from "../stripe/getSubscription.js";
|
||||
import { IS_CLOUD } from "../../lib/const.js";
|
||||
|
||||
// Default event limit for users without an active subscription
|
||||
const DEFAULT_EVENT_LIMIT = 10_000;
|
||||
|
@ -13,12 +14,20 @@ export async function getSites(req: FastifyRequest, reply: FastifyReply) {
|
|||
// Get sites the user has access to
|
||||
const sitesData = await getSitesUserHasAccessTo(req);
|
||||
|
||||
// Enhance sites data - removing usage limit information for now
|
||||
const enhancedSitesData = await Promise.all(
|
||||
sitesData.map(async (site) => {
|
||||
let isOwner = false;
|
||||
let ownerId = "";
|
||||
|
||||
if (!IS_CLOUD) {
|
||||
return {
|
||||
...site,
|
||||
monthlyEventCount: 0,
|
||||
eventLimit: Infinity,
|
||||
overMonthlyLimit: false,
|
||||
isOwner: true,
|
||||
};
|
||||
}
|
||||
// Determine ownership if organization ID exists
|
||||
if (site.organizationId) {
|
||||
const orgOwnerResult = await db
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import { stripe } from "../../lib/stripe.js";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { FastifyReply, FastifyRequest } from "fastify";
|
||||
import Stripe from "stripe";
|
||||
import { db } from "../../db/postgres/postgres.js";
|
||||
import { user as userSchema } from "../../db/postgres/schema.js";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { stripe } from "../../lib/stripe.js";
|
||||
|
||||
interface CheckoutRequestBody {
|
||||
priceId: string;
|
||||
|
@ -49,7 +50,7 @@ export async function createCheckoutSession(
|
|||
|
||||
// 2. If the user doesn't have a Stripe Customer ID, create one
|
||||
if (!stripeCustomerId) {
|
||||
const customer = await stripe.customers.create({
|
||||
const customer = await (stripe as Stripe).customers.create({
|
||||
email: user.email,
|
||||
metadata: {
|
||||
userId: user.id, // Link Stripe customer to your internal user ID
|
||||
|
@ -65,7 +66,7 @@ export async function createCheckoutSession(
|
|||
}
|
||||
|
||||
// 4. Create a Stripe Checkout Session
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
const session = await (stripe as Stripe).checkout.sessions.create({
|
||||
payment_method_types: ["card"],
|
||||
mode: "subscription",
|
||||
customer: stripeCustomerId,
|
||||
|
|
|
@ -3,6 +3,7 @@ import { stripe } from "../../lib/stripe.js";
|
|||
import { db } from "../../db/postgres/postgres.js";
|
||||
import { user as userSchema } from "../../db/postgres/schema.js";
|
||||
import { eq } from "drizzle-orm";
|
||||
import Stripe from "stripe";
|
||||
|
||||
interface PortalRequestBody {
|
||||
returnUrl: string;
|
||||
|
@ -44,7 +45,9 @@ export async function createPortalSession(
|
|||
}
|
||||
|
||||
// 2. Create a Stripe Billing Portal Session
|
||||
const portalSession = await stripe.billingPortal.sessions.create({
|
||||
const portalSession = await (
|
||||
stripe as Stripe
|
||||
).billingPortal.sessions.create({
|
||||
customer: user.stripeCustomerId,
|
||||
return_url: returnUrl, // The user will be redirected here after managing their billing
|
||||
});
|
||||
|
|
|
@ -34,7 +34,7 @@ export async function getSubscriptionInner(userId: string) {
|
|||
}
|
||||
|
||||
// 2. List active subscriptions for the customer from Stripe
|
||||
const subscriptions = await stripe.subscriptions.list({
|
||||
const subscriptions = await (stripe as Stripe).subscriptions.list({
|
||||
customer: user.stripeCustomerId,
|
||||
status: "active", // Only fetch active subscriptions
|
||||
limit: 1, // Users should only have one active subscription in this model
|
||||
|
|
|
@ -29,7 +29,7 @@ export async function handleWebhook(
|
|||
return reply.status(400).send("Webhook error: No raw body available");
|
||||
}
|
||||
|
||||
event = stripe.webhooks.constructEvent(
|
||||
event = (stripe as Stripe).webhooks.constructEvent(
|
||||
rawBody,
|
||||
sig as string,
|
||||
webhookSecret
|
||||
|
|
|
@ -5,6 +5,7 @@ import { eq, inArray, and } from "drizzle-orm";
|
|||
import { db } from "../db/postgres/postgres.js";
|
||||
import { processResults } from "../api/analytics/utils.js";
|
||||
import { stripe } from "../lib/stripe.js";
|
||||
import Stripe from "stripe";
|
||||
|
||||
// Default event limit for users without an active subscription
|
||||
const DEFAULT_EVENT_LIMIT = 10_000;
|
||||
|
@ -69,7 +70,7 @@ async function getUserSubscriptionInfo(userData: {
|
|||
|
||||
try {
|
||||
// Fetch active subscriptions for the customer from Stripe
|
||||
const subscriptions = await stripe.subscriptions.list({
|
||||
const subscriptions = await (stripe as Stripe).subscriptions.list({
|
||||
customer: userData.stripeCustomerId,
|
||||
status: "active",
|
||||
limit: 1,
|
||||
|
|
|
@ -5,13 +5,8 @@ dotenv.config();
|
|||
|
||||
const secretKey = process.env.STRIPE_SECRET_KEY;
|
||||
|
||||
if (!secretKey) {
|
||||
throw new Error(
|
||||
"Stripe secret key is not defined in environment variables. Please set STRIPE_SECRET_KEY."
|
||||
);
|
||||
}
|
||||
|
||||
export const stripe = new Stripe(secretKey, {
|
||||
// apiVersion: "2024-06-20", // Use the latest API version - Removed to use SDK default
|
||||
typescript: true, // Enable TypeScript support
|
||||
});
|
||||
export const stripe = secretKey
|
||||
? new Stripe(secretKey, {
|
||||
typescript: true, // Enable TypeScript support
|
||||
})
|
||||
: null;
|
||||
|
|
48
setup.sh
Normal file
48
setup.sh
Normal file
|
@ -0,0 +1,48 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Exit immediately if a command exits with a non-zero status.
|
||||
set -e
|
||||
|
||||
# Check if domain name argument is provided
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <domain_name>"
|
||||
echo "Example: $0 myapp.example.com"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DOMAIN_NAME="$1"
|
||||
BASE_URL="https://${DOMAIN_NAME}"
|
||||
|
||||
# Generate a secure random secret for BETTER_AUTH_SECRET
|
||||
# Uses OpenSSL if available, otherwise falls back to /dev/urandom
|
||||
if command -v openssl &> /dev/null; then
|
||||
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
|
||||
elif [ -e /dev/urandom ]; then
|
||||
BETTER_AUTH_SECRET=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32)
|
||||
else
|
||||
echo "Error: Could not generate secure secret. Please install openssl or ensure /dev/urandom is available." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create or overwrite the .env file
|
||||
echo "Creating .env file..."
|
||||
cat > .env << EOL
|
||||
# Variables configured by setup.sh
|
||||
DOMAIN_NAME=${DOMAIN_NAME}
|
||||
BASE_URL=${BASE_URL}
|
||||
BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
||||
|
||||
# Defaulting to empty strings to suppress docker-compose warnings
|
||||
STRIPE_SECRET_KEY=
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
CLOUD=
|
||||
EOL
|
||||
|
||||
echo ".env file created successfully with domain ${DOMAIN_NAME}."
|
||||
|
||||
# Build and start the Docker Compose stack
|
||||
echo "Building and starting Docker services..."
|
||||
docker compose up --build -d
|
||||
|
||||
echo "Setup complete. Services are starting in the background."
|
||||
echo "You can monitor logs with: docker compose logs -f"
|
Loading…
Add table
Add a link
Reference in a new issue