committed by
GitHub
parent
f0b7526847
commit
df05e2dab3
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ dump-*
|
||||
# Logs
|
||||
|
||||
logs
|
||||
!self-hosting/logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
|
||||
10
README.md
10
README.md
@@ -66,8 +66,12 @@ Openpanel is a simple analytics tool for logging events on web, apps and backend
|
||||
- tRPC - will probably migrate this to server actions
|
||||
- Clerk - for authentication
|
||||
|
||||
## Self hosting
|
||||
## Self-hosting
|
||||
|
||||
I'll fill out this section when we're out of beta (might be sooner than that).
|
||||
OpenPanel can be self-hosted and we have tried to make it as simple as possible.
|
||||
|
||||
But it will probably be a CapRover recipe and Docker Compose scheme.
|
||||
You can find the how to [here](https://docs.openpanel.dev/docs/self-hosting)
|
||||
|
||||
**Give us a star if you like it!**
|
||||
|
||||
[](https://star-history.com/#Openpanel-dev/openpanel&Date)
|
||||
|
||||
@@ -1,86 +1,95 @@
|
||||
# Dockerfile that builds the web app only
|
||||
ARG NODE_VERSION=20.15.1
|
||||
FROM --platform=linux/amd64 node:${NODE_VERSION}-slim AS base
|
||||
|
||||
FROM node:${NODE_VERSION}-slim AS base
|
||||
|
||||
RUN corepack enable && apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
openssl \
|
||||
libssl3 \
|
||||
&& apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG DATABASE_URL
|
||||
ENV DATABASE_URL=$DATABASE_URL
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
RUN apt update \
|
||||
&& apt install -y curl python3 make g++ \
|
||||
&& curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n \
|
||||
&& bash n $NODE_VERSION \
|
||||
&& rm n \
|
||||
&& npm install -g n \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package.json
|
||||
COPY pnpm-lock.yaml pnpm-lock.yaml
|
||||
COPY pnpm-workspace.yaml pnpm-workspace.yaml
|
||||
COPY apps/api/package.json apps/api/package.json
|
||||
COPY packages/db/package.json packages/db/package.json
|
||||
COPY packages/redis/package.json packages/redis/package.json
|
||||
COPY packages/trpc/package.json packages/trpc/package.json
|
||||
COPY packages/queue/package.json packages/queue/package.json
|
||||
COPY packages/common/package.json packages/common/package.json
|
||||
COPY packages/constants/package.json packages/constants/package.json
|
||||
COPY packages/validation/package.json packages/validation/package.json
|
||||
COPY packages/sdks/sdk/package.json packages/sdks/sdk/package.json
|
||||
# Workspace
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
# Apps
|
||||
COPY apps/api/package.json ./apps/api/
|
||||
# Packages
|
||||
COPY packages/db/package.json packages/db/
|
||||
COPY packages/trpc/package.json packages/trpc/
|
||||
COPY packages/queue/package.json packages/queue/
|
||||
COPY packages/redis/package.json packages/redis/
|
||||
COPY packages/common/package.json packages/common/
|
||||
COPY packages/sdks/sdk/package.json packages/sdks/sdk/
|
||||
COPY packages/constants/package.json packages/constants/
|
||||
COPY packages/validation/package.json packages/validation/
|
||||
COPY packages/sdks/sdk/package.json packages/sdks/sdk/
|
||||
|
||||
# Patches
|
||||
COPY patches patches
|
||||
|
||||
# BUILD
|
||||
FROM base AS build
|
||||
|
||||
WORKDIR /app/apps/api
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
make \
|
||||
g++
|
||||
|
||||
WORKDIR /app
|
||||
COPY apps/api apps/api
|
||||
COPY packages packages
|
||||
COPY tooling tooling
|
||||
RUN pnpm db:codegen
|
||||
WORKDIR /app
|
||||
RUN pnpm install --frozen-lockfile && \
|
||||
pnpm store prune
|
||||
|
||||
WORKDIR /app/apps/api
|
||||
RUN pnpm run build
|
||||
COPY apps/api ./apps/api
|
||||
COPY packages ./packages
|
||||
COPY tooling ./tooling
|
||||
|
||||
RUN pnpm db:codegen && \
|
||||
pnpm --filter api run build
|
||||
|
||||
# PROD
|
||||
FROM base AS prod
|
||||
|
||||
WORKDIR /app/apps/api
|
||||
RUN pnpm install --frozen-lockfile --prod
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
make \
|
||||
g++
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/package.json ./
|
||||
COPY --from=build /app/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --prod && \
|
||||
pnpm store prune
|
||||
|
||||
# FINAL
|
||||
FROM base AS runner
|
||||
|
||||
COPY --from=build /app/package.json /app/package.json
|
||||
COPY --from=prod /app/node_modules /app/node_modules
|
||||
ENV NODE_ENV=production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/package.json ./
|
||||
COPY --from=prod /app/node_modules ./node_modules
|
||||
|
||||
# Apps
|
||||
COPY --from=build /app/apps/api /app/apps/api
|
||||
|
||||
# Apps node_modules
|
||||
COPY --from=prod /app/apps/api/node_modules /app/apps/api/node_modules
|
||||
COPY --from=build /app/apps/api ./apps/api
|
||||
|
||||
# Packages
|
||||
COPY --from=build /app/packages/db /app/packages/db
|
||||
COPY --from=build /app/packages/redis /app/packages/redis
|
||||
COPY --from=build /app/packages/trpc /app/packages/trpc
|
||||
COPY --from=build /app/packages/queue /app/packages/queue
|
||||
COPY --from=build /app/packages/common /app/packages/common
|
||||
|
||||
# Packages node_modules
|
||||
COPY --from=prod /app/packages/db/node_modules /app/packages/db/node_modules
|
||||
COPY --from=prod /app/packages/redis/node_modules /app/packages/redis/node_modules
|
||||
COPY --from=prod /app/packages/trpc/node_modules /app/packages/trpc/node_modules
|
||||
COPY --from=prod /app/packages/queue/node_modules /app/packages/queue/node_modules
|
||||
COPY --from=prod /app/packages/common/node_modules /app/packages/common/node_modules
|
||||
COPY --from=build /app/packages/db ./packages/db
|
||||
COPY --from=build /app/packages/trpc ./packages/trpc
|
||||
COPY --from=build /app/packages/queue ./packages/queue
|
||||
COPY --from=build /app/packages/redis ./packages/redis
|
||||
COPY --from=build /app/packages/common ./packages/common
|
||||
COPY --from=build /app/packages/sdks/sdk ./packages/sdks/sdk
|
||||
COPY --from=build /app/packages/constants ./packages/constants
|
||||
COPY --from=build /app/packages/validation ./packages/validation
|
||||
|
||||
RUN pnpm db:codegen
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import icoToPng from 'ico-to-png';
|
||||
import sharp from 'sharp';
|
||||
|
||||
import { createHash } from '@openpanel/common';
|
||||
import { ch, TABLE_NAMES } from '@openpanel/db';
|
||||
import { getRedisCache } from '@openpanel/redis';
|
||||
|
||||
interface GetFaviconParams {
|
||||
@@ -110,3 +111,37 @@ export async function clearFavicons(
|
||||
}
|
||||
return reply.status(404).send('OK');
|
||||
}
|
||||
|
||||
export async function ping(
|
||||
request: FastifyRequest<{
|
||||
Body: {
|
||||
domain: string;
|
||||
count: number;
|
||||
};
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
await ch.insert({
|
||||
table: TABLE_NAMES.self_hosting,
|
||||
values: [
|
||||
{
|
||||
domain: request.body.domain,
|
||||
count: request.body.count,
|
||||
created_at: new Date(),
|
||||
},
|
||||
],
|
||||
format: 'JSONEachRow',
|
||||
});
|
||||
reply.status(200).send({
|
||||
message: `Success`,
|
||||
count: request.body.count,
|
||||
domain: request.body.domain,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e, 'Failed to insert ping');
|
||||
reply.status(500).send({
|
||||
error: 'Failed to insert ping',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export async function clerkWebhook(
|
||||
|
||||
if (payload.type === 'user.created') {
|
||||
const email = payload.data.email_addresses[0]?.email_address;
|
||||
const emails = payload.data.email_addresses.map((e) => e.email_address);
|
||||
|
||||
if (!email) {
|
||||
return Response.json(
|
||||
@@ -63,14 +64,16 @@ export async function clerkWebhook(
|
||||
|
||||
const memberships = await db.member.findMany({
|
||||
where: {
|
||||
email,
|
||||
email: {
|
||||
in: emails,
|
||||
},
|
||||
userId: null,
|
||||
},
|
||||
});
|
||||
|
||||
for (const membership of memberships) {
|
||||
const access = pathOr<string[]>([], ['meta', 'access'], membership);
|
||||
db.$transaction([
|
||||
await db.$transaction([
|
||||
// Update the member to link it to the user
|
||||
// This will remove the item from invitations
|
||||
db.member.update({
|
||||
@@ -123,7 +126,6 @@ export async function clerkWebhook(
|
||||
deletedAt: new Date(),
|
||||
firstName: null,
|
||||
lastName: null,
|
||||
email: `deleted+${payload.data.id}@openpanel.dev`,
|
||||
},
|
||||
}),
|
||||
db.projectAccess.deleteMany({
|
||||
|
||||
@@ -2,6 +2,12 @@ import * as controller from '@/controllers/misc.controller';
|
||||
import type { FastifyPluginCallback } from 'fastify';
|
||||
|
||||
const miscRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
||||
fastify.route({
|
||||
method: 'POST',
|
||||
url: '/ping',
|
||||
handler: controller.ping,
|
||||
});
|
||||
|
||||
fastify.route({
|
||||
method: 'GET',
|
||||
url: '/favicon',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
ARG NODE_VERSION=20.15.1
|
||||
|
||||
FROM --platform=linux/amd64 node:${NODE_VERSION}-slim AS base
|
||||
FROM node:${NODE_VERSION}-slim AS base
|
||||
|
||||
ENV SKIP_ENV_VALIDATION="1"
|
||||
|
||||
@@ -11,17 +11,15 @@ ARG ENABLE_INSTRUMENTATION_HOOK
|
||||
ENV ENABLE_INSTRUMENTATION_HOOK=$ENABLE_INSTRUMENTATION_HOOK
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
RUN corepack enable
|
||||
# Install necessary dependencies for prisma
|
||||
RUN apt-get update && apt-get install -y \
|
||||
openssl \
|
||||
libssl3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt update \
|
||||
&& apt install -y curl \
|
||||
&& curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n \
|
||||
&& bash n $NODE_VERSION \
|
||||
&& rm n \
|
||||
&& npm install -g n
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -39,14 +37,14 @@ COPY packages/common/package.json packages/common/package.json
|
||||
COPY packages/constants/package.json packages/constants/package.json
|
||||
COPY packages/validation/package.json packages/validation/package.json
|
||||
COPY packages/sdks/sdk/package.json packages/sdks/sdk/package.json
|
||||
COPY patches patches
|
||||
|
||||
# BUILD
|
||||
FROM base AS build
|
||||
|
||||
WORKDIR /app/apps/dashboard
|
||||
WORKDIR /app
|
||||
RUN pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
WORKDIR /app
|
||||
COPY apps/dashboard apps/dashboard
|
||||
COPY packages packages
|
||||
COPY tooling tooling
|
||||
@@ -57,48 +55,44 @@ WORKDIR /app/apps/dashboard
|
||||
# Will be replaced on runtime
|
||||
ENV NEXT_PUBLIC_DASHBOARD_URL="__NEXT_PUBLIC_DASHBOARD_URL__"
|
||||
ENV NEXT_PUBLIC_API_URL="__NEXT_PUBLIC_API_URL__"
|
||||
# Check entrypoint for this little fellow
|
||||
ENV NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_eW9sby5jb20k"
|
||||
# Does not need to be replaced
|
||||
ENV NEXT_PUBLIC_CLERK_SIGN_IN_URL="/login"
|
||||
ENV NEXT_PUBLIC_CLERK_SIGN_UP_URL="/register"
|
||||
ENV NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/"
|
||||
ENV NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/"
|
||||
|
||||
RUN pnpm run build
|
||||
|
||||
# PROD
|
||||
FROM base AS prod
|
||||
|
||||
WORKDIR /app/apps/dashboard
|
||||
RUN pnpm install --frozen-lockfile --prod --ignore-scripts
|
||||
|
||||
# FINAL
|
||||
# RUNNER
|
||||
FROM base AS runner
|
||||
|
||||
COPY --from=build /app/package.json /app/package.json
|
||||
COPY --from=prod /app/node_modules /app/node_modules
|
||||
# Apps
|
||||
COPY --from=build /app/apps/dashboard /app/apps/dashboard
|
||||
# Apps node_modules
|
||||
COPY --from=prod /app/apps/dashboard/node_modules /app/apps/dashboard/node_modules
|
||||
# Packages
|
||||
COPY --from=build /app/packages/db /app/packages/db
|
||||
COPY --from=build /app/packages/redis /app/packages/redis
|
||||
COPY --from=build /app/packages/common /app/packages/common
|
||||
COPY --from=build /app/packages/queue /app/packages/queue
|
||||
COPY --from=build /app/packages/constants /app/packages/constants
|
||||
COPY --from=build /app/packages/validation /app/packages/validation
|
||||
COPY --from=build /app/packages/sdks/sdk /app/packages/sdks/sdk
|
||||
# Packages node_modules
|
||||
COPY --from=prod /app/packages/db/node_modules /app/packages/db/node_modules
|
||||
COPY --from=prod /app/packages/redis/node_modules /app/packages/redis/node_modules
|
||||
COPY --from=prod /app/packages/common/node_modules /app/packages/common/node_modules
|
||||
COPY --from=prod /app/packages/validation/node_modules /app/packages/validation/node_modules
|
||||
COPY --from=prod /app/packages/queue/node_modules /app/packages/queue/node_modules
|
||||
WORKDIR /app
|
||||
|
||||
RUN pnpm db:codegen
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
WORKDIR /app/apps/dashboard
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# Set the correct permission for prerender cache
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
# Set the correct permissions for the entire /app directory
|
||||
COPY --from=build --chown=nextjs:nodejs /app/apps/dashboard/.next/standalone ./
|
||||
COPY --from=build --chown=nextjs:nodejs /app/apps/dashboard/.next/static ./apps/dashboard/.next/static
|
||||
COPY --from=build --chown=nextjs:nodejs /app/apps/dashboard/public ./apps/dashboard/public
|
||||
|
||||
# Copy and set permissions for the entrypoint script
|
||||
COPY --from=build --chown=nextjs:nodejs /app/apps/dashboard/entrypoint.sh ./entrypoint.sh
|
||||
RUN chmod +x ./entrypoint.sh
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
# CMD ["pnpm", "start"]
|
||||
COPY --from=build /app/apps/dashboard/entrypoint.sh /usr/bin/
|
||||
RUN chmod +x /usr/bin/entrypoint.sh
|
||||
ENTRYPOINT ["entrypoint.sh", "pnpm", "start"]
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
|
||||
ENTRYPOINT [ "/app/entrypoint.sh", "node", "/app/apps/dashboard/server.js"]
|
||||
@@ -1,32 +1,39 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "> Replace env variable placeholders with runtime values..."
|
||||
# Define an array of environment variables to check
|
||||
variables_to_replace=(
|
||||
"NEXT_PUBLIC_DASHBOARD_URL"
|
||||
"NEXT_PUBLIC_API_URL"
|
||||
"NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY"
|
||||
)
|
||||
|
||||
# Define environment variables to check (space-separated string)
|
||||
variables_to_replace="NEXT_PUBLIC_DASHBOARD_URL NEXT_PUBLIC_API_URL NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY"
|
||||
|
||||
# Replace env variable placeholders with real values
|
||||
for key in "${variables_to_replace[@]}"; do
|
||||
value=$(printenv $key)
|
||||
if [ ! -z "$value" ]; then
|
||||
echo " - Searching for $key with value $value..."
|
||||
# Use a custom placeholder for 'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY' or use the actual key otherwise
|
||||
if [ "$key" = "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY" ]; then
|
||||
placeholder="pk_test_eW9sby5jb20k"
|
||||
for key in $variables_to_replace; do
|
||||
value=$(eval echo \$"$key")
|
||||
if [ -n "$value" ]; then
|
||||
echo " - Searching for $key with value $value..."
|
||||
# Use a custom placeholder for 'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY' or use the actual key otherwise
|
||||
case "$key" in
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY)
|
||||
placeholder="pk_test_eW9sby5jb20k"
|
||||
;;
|
||||
*)
|
||||
placeholder="__${key}__"
|
||||
;;
|
||||
esac
|
||||
# Run the replacement
|
||||
find /app -type f \( -name "*.js" -o -name "*.html" \) | while read -r file; do
|
||||
if grep -q "$placeholder" "$file"; then
|
||||
echo " - Replacing in file: $file"
|
||||
sed -i "s|$placeholder|$value|g" "$file"
|
||||
fi
|
||||
done
|
||||
else
|
||||
placeholder="__${key}__"
|
||||
echo " - Skipping $key as it has no value set."
|
||||
fi
|
||||
# Run the replacement
|
||||
find /app/apps/dashboard/.next/ -type f \( -name "*.js" -o -name "*.html" \) -exec sed -i "s|$placeholder|$value|g" {} \;
|
||||
|
||||
else
|
||||
echo " - Skipping $key as it has no value set."
|
||||
fi
|
||||
done
|
||||
|
||||
echo "> Done!"
|
||||
echo "> Running $@"
|
||||
|
||||
# Execute the container's main process (CMD in Dockerfile)
|
||||
exec "$@"
|
||||
@@ -9,6 +9,7 @@ await import('./src/env.mjs');
|
||||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const config = {
|
||||
output: 'standalone',
|
||||
webpack: (config, { isServer }) => {
|
||||
if (isServer) {
|
||||
config.plugins = [...config.plugins, new PrismaPlugin()];
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"dev": "rm -rf .next && pnpm with-env next dev",
|
||||
"testing": "pnpm dev",
|
||||
"build": "next build",
|
||||
"build": "pnpm with-env next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --check \"**/*.{tsx,mjs,ts,md,json}\"",
|
||||
|
||||
@@ -16,9 +16,8 @@ const SkipOnboarding = () => {
|
||||
res.refetch();
|
||||
}, [pathname]);
|
||||
|
||||
console.log(res.data);
|
||||
|
||||
if (!pathname.startsWith('/onboarding')) return null;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -30,6 +29,7 @@ const SkipOnboarding = () => {
|
||||
text: 'Are you sure you want to skip onboarding? Since you do not have any projects, you will be logged out.',
|
||||
onConfirm() {
|
||||
auth.signOut();
|
||||
router.replace(process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { MetadataRoute } from 'next';
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
id: process.env.NEXT_PUBLIC_DASHBOARD_URL,
|
||||
name: 'Openpanel.dev',
|
||||
short_name: 'Openpanel.dev',
|
||||
description: '',
|
||||
@@ -9,12 +10,5 @@ export default function manifest(): MetadataRoute.Manifest {
|
||||
display: 'standalone',
|
||||
background_color: '#fff',
|
||||
theme_color: '#fff',
|
||||
icons: [
|
||||
{
|
||||
src: '/favicon.ico',
|
||||
sizes: 'any',
|
||||
type: 'image/x-icon',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ $openpanel->event(
|
||||
<strong>Usage</strong>
|
||||
<p>Create a custom event called "my_event".</p>
|
||||
<Syntax
|
||||
code={`curl 'https://api.openpanel.dev/track' \\
|
||||
code={`curl '${process.env.NEXT_PUBLIC_API_URL}/track' \\
|
||||
-H 'content-type: application/json' \\
|
||||
-H 'openpanel-client-id: ${clientId}' \\
|
||||
-H 'openpanel-client-secret: ${clientSecret}' \\
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 424 KiB |
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"index": {
|
||||
"title": "Introduction",
|
||||
"title": "Home",
|
||||
"type": "page"
|
||||
},
|
||||
"docs": {
|
||||
"title": "Documentation",
|
||||
"type": "page"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,6 @@
|
||||
{
|
||||
"index": "Get Started",
|
||||
"-- Frameworks": {
|
||||
"type": "separator",
|
||||
"title": "Frameworks"
|
||||
},
|
||||
"script": "Web (Script Tag)",
|
||||
"web": "Web (Module)",
|
||||
"nextjs": "Next.js",
|
||||
"react": "React",
|
||||
"react-native": "React-Native",
|
||||
"remix": "Remix",
|
||||
"vue": "Vue",
|
||||
"astro": "Astro",
|
||||
"node": "Node",
|
||||
"express": "Express (backend)",
|
||||
"-- Others": {
|
||||
"type": "separator",
|
||||
"title": "Others"
|
||||
},
|
||||
"javascript": "JavaScript",
|
||||
"api": "API",
|
||||
"export": "Export",
|
||||
"migration": "Migrations"
|
||||
"sdks": "SDKs",
|
||||
"migration": "Migrations",
|
||||
"self-hosting": "Self-hosting"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
# Astro
|
||||
|
||||
You can use <Link href="/docs/script">script tag</Link> or <Link href="/docs/web">Web SDK</Link> to track events in Astro.
|
||||
@@ -68,7 +68,7 @@ Clears the current user identifier and ends the session.
|
||||
<BrandLogo src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/240px-HTML5_logo_and_wordmark.svg.png" />
|
||||
}
|
||||
title="HTML / Script"
|
||||
href="/docs/script"
|
||||
href="/docs/sdks/script"
|
||||
>
|
||||
{' '}
|
||||
</Card>
|
||||
@@ -77,7 +77,7 @@ Clears the current user identifier and ends the session.
|
||||
<BrandLogo src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/2300px-React-icon.svg.png" />
|
||||
}
|
||||
title="React"
|
||||
href="/docs/react"
|
||||
href="/docs/sdks/react"
|
||||
>
|
||||
{' '}
|
||||
</Card>
|
||||
@@ -86,7 +86,7 @@ Clears the current user identifier and ends the session.
|
||||
<BrandLogo src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/2300px-React-icon.svg.png" />
|
||||
}
|
||||
title="React-Native"
|
||||
href="/docs/react-native"
|
||||
href="/docs/sdks/react-native"
|
||||
>
|
||||
{' '}
|
||||
</Card>
|
||||
@@ -94,11 +94,11 @@ Clears the current user identifier and ends the session.
|
||||
icon={
|
||||
<BrandLogo
|
||||
isDark
|
||||
src="https://static-00.iconduck.com/assets.00/nextjs-icon-512x512-y563b8iq.png"
|
||||
src="https://pbs.twimg.com/profile_images/1565710214019444737/if82cpbS_400x400.jpg"
|
||||
/>
|
||||
}
|
||||
title="Next.js"
|
||||
href="/docs/nextjs"
|
||||
href="/docs/sdks/nextjs"
|
||||
>
|
||||
{' '}
|
||||
</Card>
|
||||
@@ -110,7 +110,7 @@ Clears the current user identifier and ends the session.
|
||||
/>
|
||||
}
|
||||
title="Remix"
|
||||
href="/docs/remix"
|
||||
href="/docs/sdks/remix"
|
||||
>
|
||||
{' '}
|
||||
</Card>
|
||||
@@ -119,7 +119,7 @@ Clears the current user identifier and ends the session.
|
||||
<BrandLogo src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1024px-Vue.js_Logo_2.svg.png" />
|
||||
}
|
||||
title="Vue"
|
||||
href="/docs/vue"
|
||||
href="/docs/sdks/vue"
|
||||
>
|
||||
{' '}
|
||||
</Card>
|
||||
@@ -131,7 +131,7 @@ Clears the current user identifier and ends the session.
|
||||
/>
|
||||
}
|
||||
title="Astro"
|
||||
href="/docs/astro"
|
||||
href="/docs/sdks/astro"
|
||||
>
|
||||
{' '}
|
||||
</Card>
|
||||
@@ -140,3 +140,28 @@ Clears the current user identifier and ends the session.
|
||||
## Unofficial SDKs
|
||||
|
||||
While not officially supported, the following community-contributed SDKs are available.
|
||||
|
||||
<Cards>
|
||||
<Card
|
||||
icon={
|
||||
<BrandLogo
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Laravel.svg/1200px-Laravel.svg.png"
|
||||
/>
|
||||
}
|
||||
title="Laravel"
|
||||
href="https://github.com/tbleckert/openpanel-laravel"
|
||||
>
|
||||
{' '}
|
||||
</Card>
|
||||
<Card
|
||||
icon={
|
||||
<BrandLogo
|
||||
src="https://storage.googleapis.com/cms-storage-bucket/0dbfcc7a59cd1cf16282.png"
|
||||
/>
|
||||
}
|
||||
title="Flutter"
|
||||
href="https://github.com/stevenosse/openpanel_flutter"
|
||||
>
|
||||
{' '}
|
||||
</Card>
|
||||
</Cards>
|
||||
@@ -1,5 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
# Node
|
||||
|
||||
Use <Link href="/docs/javascript">Javascript SDK</Link> to track events in Node.
|
||||
@@ -1,5 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
# React
|
||||
|
||||
Use <Link href="/docs/script">script tag</Link> or <Link href="/docs/web">Web SDK</Link> for now. We'll add a dedicated react sdk soon.
|
||||
@@ -1,5 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
# Remix
|
||||
|
||||
Use <Link href="/docs/script">script tag</Link> or <Link href="/docs/web">Web SDK</Link> for now. We'll add a dedicated remix sdk soon.
|
||||
19
apps/docs/src/pages/docs/sdks/_meta.json
Normal file
19
apps/docs/src/pages/docs/sdks/_meta.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"script": "Web (Script Tag)",
|
||||
"web": "Web (Module)",
|
||||
"nextjs": "Next.js",
|
||||
"react": "React",
|
||||
"react-native": "React-Native",
|
||||
"remix": "Remix",
|
||||
"vue": "Vue",
|
||||
"astro": "Astro",
|
||||
"node": "Node",
|
||||
"express": "Express (backend)",
|
||||
"-- Others": {
|
||||
"type": "separator",
|
||||
"title": "Others"
|
||||
},
|
||||
"javascript": "JavaScript",
|
||||
"api": "API",
|
||||
"export": "Export"
|
||||
}
|
||||
5
apps/docs/src/pages/docs/sdks/astro.mdx
Normal file
5
apps/docs/src/pages/docs/sdks/astro.mdx
Normal file
@@ -0,0 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
# Astro
|
||||
|
||||
You can use <Link href="/docs/sdks/script">script tag</Link> or <Link href="/docs/sdks/web">Web SDK</Link> to track events in Astro.
|
||||
@@ -7,7 +7,7 @@ import CommonSdkConfig from 'src/components/common-sdk-config.mdx';
|
||||
|
||||
# Express
|
||||
|
||||
The Express middleware is a basic wrapper around [Javascript SDK](/docs/javascript). It provides a simple way to add the SDK to your Express application.
|
||||
The Express middleware is a basic wrapper around [Javascript SDK](/docs/sdks/javascript). It provides a simple way to add the SDK to your Express application.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -121,7 +121,7 @@ export const op = new Openpanel({
|
||||
op.track('my_event', { foo: 'bar' });
|
||||
```
|
||||
|
||||
Refer to the [Javascript SDK](/docs/javascript#usage) for usage instructions.
|
||||
Refer to the [Javascript SDK](/docs/sdks/javascript#usage) for usage instructions.
|
||||
|
||||
### Tracking Events
|
||||
|
||||
5
apps/docs/src/pages/docs/sdks/node.mdx
Normal file
5
apps/docs/src/pages/docs/sdks/node.mdx
Normal file
@@ -0,0 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
# Node
|
||||
|
||||
Use <Link href="/docs/sdks/javascript">Javascript SDK</Link> to track events in Node.
|
||||
@@ -108,4 +108,4 @@ op.track('my_event', { foo: 'bar' });
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
|
||||
For more information on how to use the SDK, check out the [Javascript SDK](/docs/javascript#usage).
|
||||
For more information on how to use the SDK, check out the [Javascript SDK](/docs/sdks/javascript#usage).
|
||||
5
apps/docs/src/pages/docs/sdks/react.mdx
Normal file
5
apps/docs/src/pages/docs/sdks/react.mdx
Normal file
@@ -0,0 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
# React
|
||||
|
||||
Use <Link href="/docs/sdks/script">script tag</Link> or <Link href="/docs/sdks/web">Web SDK</Link> for now. We'll add a dedicated react sdk soon.
|
||||
5
apps/docs/src/pages/docs/sdks/remix.mdx
Normal file
5
apps/docs/src/pages/docs/sdks/remix.mdx
Normal file
@@ -0,0 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
# Remix
|
||||
|
||||
Use <Link href="/docs/sdks/script">script tag</Link> or <Link href="/docs/sdks/web">Web SDK</Link> for now. We'll add a dedicated remix sdk soon.
|
||||
5
apps/docs/src/pages/docs/sdks/vue.mdx
Normal file
5
apps/docs/src/pages/docs/sdks/vue.mdx
Normal file
@@ -0,0 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
# Vue
|
||||
|
||||
Use <Link href="/docs/sdks/script">script tag</Link> or <Link href="/docs/sdks/web">Web SDK</Link> for now. We'll add a dedicated react sdk soon.
|
||||
@@ -45,4 +45,4 @@ op.track('my_event', { foo: 'bar' });
|
||||
|
||||
## Usage
|
||||
|
||||
Refer to the [Javascript SDK](/docs/javascript#usage) for usage instructions.
|
||||
Refer to the [Javascript SDK](/docs/sdks/javascript#usage) for usage instructions.
|
||||
3
apps/docs/src/pages/docs/self-hosting/_meta.json
Normal file
3
apps/docs/src/pages/docs/self-hosting/_meta.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"index": "Get started"
|
||||
}
|
||||
121
apps/docs/src/pages/docs/self-hosting/index.mdx
Normal file
121
apps/docs/src/pages/docs/self-hosting/index.mdx
Normal file
@@ -0,0 +1,121 @@
|
||||
import { Callout, Tabs, Steps } from 'nextra/components';
|
||||
|
||||
# Self-hosting
|
||||
|
||||
<Callout>OpenPanel is not stable yet. If you still want to self-host you can go ahead. Bear in mind that new changes might give a little headache to keep up with.</Callout>
|
||||
|
||||
This is a simple guide how to get started with OpenPanel on your own VPS.
|
||||
|
||||
## Instructions
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- VPS of any kind (only tested on Ubuntu 24.04)
|
||||
- [Clerk.com](https://clerk.com) account (they have a free tier)
|
||||
|
||||
### Quickstart
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Openpanel-dev/openpanel && cd openpanel/self-hosting && ./start
|
||||
# After setup is complete run `./start` to start OpenPanel
|
||||
```
|
||||
|
||||
<Steps>
|
||||
|
||||
### Clone
|
||||
|
||||
Clone the repository to your VPS
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Openpanel-dev/openpanel.git
|
||||
```
|
||||
|
||||
### Run the setup script
|
||||
|
||||
The setup script will do 3 things
|
||||
|
||||
1. Install node (if you accept)
|
||||
2. Install docker (if you accept)
|
||||
3. Execute a node script that will ask some questions about your setup
|
||||
4. After this is done you'll need to point a webhook inside Clerk (https://your-domain.com/api/webhook/clerk)
|
||||
|
||||
> Setup takes 5-10 minutes depending on your VPS. It'll build all the docker images.
|
||||
|
||||
```bash
|
||||
cd openpanel/self-hosting
|
||||
./setup
|
||||
```
|
||||
|
||||
### Start 🚀
|
||||
|
||||
Run the `./start` script located inside the self-hosting folder
|
||||
|
||||
```bash
|
||||
./start
|
||||
```
|
||||
</Steps>
|
||||
|
||||
## Clerk.com
|
||||
|
||||
<Callout>
|
||||
Some might wonder why we use Clerk.com for authentication. The main reason for this is that Clerk have great support for iOS and Android apps. We're in the process of building an iOS app and we want to have a seamless experience for our users.
|
||||
|
||||
**next-auth** is great, but lacks good support for mobile apps.
|
||||
</Callout>
|
||||
|
||||
You'll need to create an account at [Clerk.com](https://clerk.com) and create a new project. You'll need the 3 keys that Clerk provides you with.
|
||||
|
||||
- **Publishable key** `pk_live_xxx`
|
||||
- **Secret key** `sk_live_xxx`
|
||||
- **Signing secret** `"whsec_xxx"`
|
||||
|
||||
### Webhooks
|
||||
|
||||
You'll also need to add a webhook to your domain. We listen on some events from Clerk to keep our database in sync.
|
||||
|
||||
#### URL
|
||||
|
||||
- **Path**: `/api/webhook/clerk`
|
||||
- **Example**: `https://your-domain.com/api/webhook/clerk`
|
||||
|
||||
#### Events we listen to
|
||||
|
||||
- `organizationMembership.created`
|
||||
- `user.created`
|
||||
- `organizationMembership.deleted`
|
||||
- `user.updated`
|
||||
- `user.deleted`
|
||||
|
||||
## Good to know
|
||||
|
||||
### Always use correct api url
|
||||
|
||||
When self-hosting you'll need to provide your api url when initializing the SDK.
|
||||
|
||||
The path should be `/api` and the domain should be your domain.
|
||||
|
||||
```html filename="index.html" {4}
|
||||
<script>
|
||||
window.op = window.op||function(...args){(window.op.q=window.op.q||[]).push(args);};
|
||||
window.op('init', {
|
||||
apiUrl: 'https://your-domain.com/api',
|
||||
clientId: 'YOUR_CLIENT_ID',
|
||||
trackScreenViews: true,
|
||||
trackOutgoingLinks: true,
|
||||
trackAttributes: true,
|
||||
});
|
||||
</script>
|
||||
<script src="https://openpanel.dev/op1.js" defer async></script>
|
||||
```
|
||||
|
||||
```js filename="op.ts" {4}
|
||||
import { OpenPanel } from '@openpanel/sdk';
|
||||
|
||||
const op = new OpenPanel({
|
||||
apiUrl: 'https://your-domain.com/api',
|
||||
clientId: 'YOUR_CLIENT_ID',
|
||||
trackScreenViews: true,
|
||||
trackOutgoingLinks: true,
|
||||
trackAttributes: true,
|
||||
});
|
||||
```
|
||||
@@ -1,5 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
# Vue
|
||||
|
||||
Use <Link href="/docs/script">script tag</Link> or <Link href="/docs/web">Web SDK</Link> for now. We'll add a dedicated vue sdk soon.
|
||||
@@ -1,3 +1,5 @@
|
||||
<img src="https://openpanel.dev/ogimage.png" />
|
||||
|
||||
# Introduction
|
||||
|
||||
Openpanel is an open-source alternative to Mixpanel. Combining the power of Mixpanel with the ease of Plausible, Openpanel is a privacy-focused analytics tool that gives you the insights you need to make data-driven decisions.
|
||||
|
||||
@@ -19,7 +19,7 @@ export default {
|
||||
height="32"
|
||||
width="32"
|
||||
/>
|
||||
<strong style={{ marginLeft: '8px' }}>openpanel</strong>
|
||||
<strong style={{ marginLeft: '8px' }}>OpenPanel</strong>
|
||||
</>
|
||||
),
|
||||
head: () => {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 428 KiB |
@@ -1,85 +1,77 @@
|
||||
ARG NODE_VERSION=20.15.1
|
||||
|
||||
FROM --platform=linux/amd64 node:${NODE_VERSION}-slim AS base
|
||||
FROM node:${NODE_VERSION}-slim AS base
|
||||
|
||||
ARG DATABASE_URL
|
||||
ENV DATABASE_URL=$DATABASE_URL
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
RUN apt update \
|
||||
&& apt install -y curl \
|
||||
&& curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n \
|
||||
&& bash n $NODE_VERSION \
|
||||
&& rm n \
|
||||
&& npm install -g n
|
||||
RUN corepack enable && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
openssl \
|
||||
libssl3 && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package.json
|
||||
COPY pnpm-lock.yaml pnpm-lock.yaml
|
||||
COPY pnpm-workspace.yaml pnpm-workspace.yaml
|
||||
COPY apps/worker/package.json apps/worker/package.json
|
||||
COPY packages/db/package.json packages/db/package.json
|
||||
COPY packages/redis/package.json packages/redis/package.json
|
||||
COPY packages/logger/package.json packages/logger/package.json
|
||||
COPY packages/queue/package.json packages/queue/package.json
|
||||
COPY packages/common/package.json packages/common/package.json
|
||||
COPY packages/constants/package.json packages/constants/package.json
|
||||
COPY packages/validation/package.json packages/validation/package.json
|
||||
COPY packages/sdks/sdk/package.json packages/sdks/sdk/package.json
|
||||
COPY patches patches
|
||||
# Workspace
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
# Apps
|
||||
COPY apps/worker/package.json ./apps/worker/
|
||||
# Packages
|
||||
COPY packages/db/package.json ./packages/db/
|
||||
COPY packages/redis/package.json ./packages/redis/
|
||||
COPY packages/queue/package.json ./packages/queue/
|
||||
COPY packages/logger/package.json ./packages/logger/
|
||||
COPY packages/common/package.json ./packages/common/
|
||||
COPY packages/constants/package.json ./packages/constants/
|
||||
# Patches
|
||||
COPY patches ./patches
|
||||
|
||||
# BUILD
|
||||
FROM base AS build
|
||||
|
||||
WORKDIR /app/apps/worker
|
||||
RUN pnpm install --frozen-lockfile --ignore-scripts
|
||||
WORKDIR /app
|
||||
RUN pnpm install --frozen-lockfile && \
|
||||
pnpm store prune
|
||||
|
||||
WORKDIR /app
|
||||
COPY apps/worker apps/worker
|
||||
COPY packages packages
|
||||
COPY tooling tooling
|
||||
RUN pnpm db:codegen
|
||||
COPY apps/worker ./apps/worker
|
||||
COPY packages ./packages
|
||||
COPY tooling ./tooling
|
||||
|
||||
WORKDIR /app/apps/worker
|
||||
RUN pnpm run build
|
||||
RUN pnpm db:codegen && \
|
||||
pnpm --filter worker run build
|
||||
|
||||
# PROD
|
||||
FROM base AS prod
|
||||
|
||||
WORKDIR /app/apps/worker
|
||||
RUN pnpm install --frozen-lockfile --prod --ignore-scripts
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/package.json ./
|
||||
COPY --from=build /app/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile --prod && \
|
||||
pnpm store prune
|
||||
|
||||
# FINAL
|
||||
FROM base AS runner
|
||||
|
||||
COPY --from=build /app/package.json /app/package.json
|
||||
COPY --from=prod /app/node_modules /app/node_modules
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/package.json ./
|
||||
COPY --from=prod /app/node_modules ./node_modules
|
||||
|
||||
# Apps
|
||||
COPY --from=build /app/apps/worker /app/apps/worker
|
||||
|
||||
# Apps node_modules
|
||||
COPY --from=prod /app/apps/worker/node_modules /app/apps/worker/node_modules
|
||||
COPY --from=build /app/apps/worker ./apps/worker
|
||||
|
||||
# Packages
|
||||
COPY --from=build /app/packages/db /app/packages/db
|
||||
COPY --from=build /app/packages/redis /app/packages/redis
|
||||
COPY --from=build /app/packages/logger /app/packages/logger
|
||||
COPY --from=build /app/packages/queue /app/packages/queue
|
||||
COPY --from=build /app/packages/common /app/packages/common
|
||||
|
||||
# Packages node_modules
|
||||
COPY --from=prod /app/packages/db/node_modules /app/packages/db/node_modules
|
||||
COPY --from=prod /app/packages/redis/node_modules /app/packages/redis/node_modules
|
||||
COPY --from=prod /app/packages/logger/node_modules /app/packages/logger/node_modules
|
||||
COPY --from=prod /app/packages/queue/node_modules /app/packages/queue/node_modules
|
||||
COPY --from=prod /app/packages/common/node_modules /app/packages/common/node_modules
|
||||
COPY --from=build /app/packages/db ./packages/db
|
||||
COPY --from=build /app/packages/redis ./packages/redis
|
||||
COPY --from=build /app/packages/logger ./packages/logger
|
||||
COPY --from=build /app/packages/queue ./packages/queue
|
||||
COPY --from=build /app/packages/common ./packages/common
|
||||
|
||||
RUN pnpm db:codegen
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@baselime/pino-transport": "^0.1.5",
|
||||
"@bull-board/api": "^5.21.0",
|
||||
"@bull-board/express": "^5.21.0",
|
||||
"@bull-board/api": "5.21.0",
|
||||
"@bull-board/express": "5.21.0",
|
||||
"@openpanel/common": "workspace:*",
|
||||
"@openpanel/db": "workspace:*",
|
||||
"@openpanel/logger": "workspace:*",
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { WorkerOptions } from 'bullmq';
|
||||
import { Worker } from 'bullmq';
|
||||
import express from 'express';
|
||||
|
||||
import { createInitialSalts } from '@openpanel/db';
|
||||
import { cronQueue, eventsQueue, sessionsQueue } from '@openpanel/queue';
|
||||
import { getRedisQueue } from '@openpanel/redis';
|
||||
|
||||
@@ -15,7 +16,7 @@ import { register } from './metrics';
|
||||
|
||||
const PORT = parseInt(process.env.WORKER_PORT || '3000', 10);
|
||||
const serverAdapter = new ExpressAdapter();
|
||||
serverAdapter.setBasePath(process.env.SELF_HOSTED ? '/worker' : '/');
|
||||
serverAdapter.setBasePath('/');
|
||||
const app = express();
|
||||
|
||||
const workerOptions: WorkerOptions = {
|
||||
@@ -162,10 +163,28 @@ async function start() {
|
||||
}
|
||||
);
|
||||
|
||||
if (process.env.SELF_HOSTED && process.env.NODE_ENV === 'production') {
|
||||
await cronQueue.add(
|
||||
'ping',
|
||||
{
|
||||
type: 'ping',
|
||||
payload: undefined,
|
||||
},
|
||||
{
|
||||
jobId: 'ping',
|
||||
repeat: {
|
||||
pattern: '0 0 * * *',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const repeatableJobs = await cronQueue.getRepeatableJobs();
|
||||
|
||||
console.log('Repeatable jobs:');
|
||||
console.log(repeatableJobs);
|
||||
|
||||
await createInitialSalts();
|
||||
}
|
||||
|
||||
start();
|
||||
|
||||
26
apps/worker/src/jobs/cron.ping.ts
Normal file
26
apps/worker/src/jobs/cron.ping.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { chQuery, TABLE_NAMES } from '@openpanel/db';
|
||||
|
||||
export async function ping() {
|
||||
const [res] = await chQuery<{ count: number }>(
|
||||
`SELECT COUNT(*) as count FROM ${TABLE_NAMES.events}`
|
||||
);
|
||||
|
||||
if (typeof res?.count === 'number') {
|
||||
const response = await fetch('https://api.openpanel.com/misc/ping', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
domain: process.env.NEXT_PUBLIC_DASHBOARD_URL,
|
||||
count: res?.count,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
throw new Error('Failed to ping the server');
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import type { Job } from 'bullmq';
|
||||
import { eventBuffer, profileBuffer } from '@openpanel/db';
|
||||
import type { CronQueuePayload } from '@openpanel/queue';
|
||||
|
||||
import { ping } from './cron.ping';
|
||||
import { salt } from './cron.salt';
|
||||
|
||||
export async function cronJob(job: Job<CronQueuePayload>) {
|
||||
@@ -16,5 +17,8 @@ export async function cronJob(job: Job<CronQueuePayload>) {
|
||||
case 'flushProfiles': {
|
||||
return await profileBuffer.flush();
|
||||
}
|
||||
case 'ping': {
|
||||
return await ping();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
CREATE DATABASE IF NOT EXISTS openpanel;
|
||||
|
||||
CREATE TABLE openpanel.events_v2 (
|
||||
CREATE TABLE IF NOT EXISTS openpanel.self_hosting
|
||||
(
|
||||
created_at Date,
|
||||
domain String,
|
||||
count UInt64
|
||||
)
|
||||
ENGINE = MergeTree()
|
||||
ORDER BY (domain, created_at)
|
||||
PARTITION BY toYYYYMM(created_at);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS openpanel.events_v2 (
|
||||
`id` UUID DEFAULT generateUUIDv4(),
|
||||
`name` String,
|
||||
`sdk_name` String,
|
||||
@@ -80,7 +91,7 @@ SELECT
|
||||
uniqState(profile_id) as profile_id,
|
||||
project_id
|
||||
FROM
|
||||
events
|
||||
events_v2
|
||||
GROUP BY
|
||||
date,
|
||||
project_id;
|
||||
@@ -5,6 +5,7 @@ export const TABLE_NAMES = {
|
||||
events: 'events_v2',
|
||||
profiles: 'profiles',
|
||||
alias: 'profile_aliases',
|
||||
self_hosting: 'self_hosting',
|
||||
};
|
||||
|
||||
export const originalCh = createClient({
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { generateSalt } from '@openpanel/common';
|
||||
|
||||
import { db } from '../prisma-client';
|
||||
|
||||
export async function getCurrentSalt() {
|
||||
@@ -27,7 +29,7 @@ export async function getSalts() {
|
||||
}
|
||||
|
||||
if (!prev) {
|
||||
throw new Error('No previous salt found');
|
||||
throw new Error('No salt found');
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -35,3 +37,44 @@ export async function getSalts() {
|
||||
previous: prev.salt,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createInitialSalts() {
|
||||
const MAX_RETRIES = 5;
|
||||
const BASE_DELAY = 1000; // 1 second
|
||||
const createSaltsWithRetry = async (retryCount = 0): Promise<void> => {
|
||||
try {
|
||||
await getSalts();
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'No salt found') {
|
||||
console.log('Creating salts for the first time');
|
||||
await db.salt.create({
|
||||
data: {
|
||||
salt: generateSalt(),
|
||||
createdAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 24),
|
||||
},
|
||||
});
|
||||
await db.salt.create({
|
||||
data: {
|
||||
salt: generateSalt(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.log('Error getting salts', error);
|
||||
if (retryCount < MAX_RETRIES) {
|
||||
const delay = BASE_DELAY * Math.pow(2, retryCount);
|
||||
console.log(
|
||||
`Retrying in ${delay}ms... (Attempt ${retryCount + 1}/${MAX_RETRIES})`
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
return createSaltsWithRetry(retryCount + 1);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Failed to create salts after ${MAX_RETRIES} attempts`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await createSaltsWithRetry();
|
||||
}
|
||||
|
||||
@@ -56,10 +56,15 @@ export type CronQueuePayloadFlushProfiles = {
|
||||
type: 'flushProfiles';
|
||||
payload: undefined;
|
||||
};
|
||||
export type CronQueuePayloadPing = {
|
||||
type: 'ping';
|
||||
payload: undefined;
|
||||
};
|
||||
export type CronQueuePayload =
|
||||
| CronQueuePayloadSalt
|
||||
| CronQueuePayloadFlushEvents
|
||||
| CronQueuePayloadFlushProfiles;
|
||||
| CronQueuePayloadFlushProfiles
|
||||
| CronQueuePayloadPing;
|
||||
|
||||
export const eventsQueue = new Queue<EventsQueuePayload>('events', {
|
||||
connection: getRedisQueue(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const api = {
|
||||
logo: 'https://cdn-icons-png.flaticon.com/512/10169/10169724.png',
|
||||
name: 'Rest API',
|
||||
href: 'https://docs.openpanel.dev/docs/api',
|
||||
href: 'https://docs.openpanel.dev/docs/sdks/api',
|
||||
} as const;
|
||||
|
||||
export const frameworks = {
|
||||
@@ -9,32 +9,32 @@ export const frameworks = {
|
||||
{
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/240px-HTML5_logo_and_wordmark.svg.png',
|
||||
name: 'HTML / Script',
|
||||
href: 'https://docs.openpanel.dev/docs/script',
|
||||
href: 'https://docs.openpanel.dev/docs/sdks/script',
|
||||
},
|
||||
{
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/2300px-React-icon.svg.png',
|
||||
name: 'React',
|
||||
href: 'https://docs.openpanel.dev/docs/react',
|
||||
href: 'https://docs.openpanel.dev/docs/sdks/react',
|
||||
},
|
||||
{
|
||||
logo: 'https://static-00.iconduck.com/assets.00/nextjs-icon-512x512-y563b8iq.png',
|
||||
name: 'Next.js',
|
||||
href: 'https://docs.openpanel.dev/docs/nextjs',
|
||||
href: 'https://docs.openpanel.dev/docs/sdks/nextjs',
|
||||
},
|
||||
{
|
||||
logo: 'https://www.datocms-assets.com/205/1642515307-square-logo.svg',
|
||||
name: 'Remix',
|
||||
href: 'https://docs.openpanel.dev/docs/remix',
|
||||
href: 'https://docs.openpanel.dev/docs/sdks/remix',
|
||||
},
|
||||
{
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Vue.js_Logo_2.svg/1024px-Vue.js_Logo_2.svg.png',
|
||||
name: 'Vue',
|
||||
href: 'https://docs.openpanel.dev/docs/vue',
|
||||
href: 'https://docs.openpanel.dev/docs/sdks/vue',
|
||||
},
|
||||
{
|
||||
logo: 'https://astro.build/assets/press/astro-icon-dark.png',
|
||||
name: 'Astro',
|
||||
href: 'https://docs.openpanel.dev/docs/astro',
|
||||
href: 'https://docs.openpanel.dev/docs/sdks/astro',
|
||||
},
|
||||
api,
|
||||
],
|
||||
@@ -42,7 +42,7 @@ export const frameworks = {
|
||||
{
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/2300px-React-icon.svg.png',
|
||||
name: 'React-Native',
|
||||
href: 'https://docs.openpanel.dev/docs/react-native',
|
||||
href: 'https://docs.openpanel.dev/docs/sdks/react-native',
|
||||
},
|
||||
api,
|
||||
],
|
||||
@@ -50,12 +50,12 @@ export const frameworks = {
|
||||
{
|
||||
logo: 'https://static-00.iconduck.com/assets.00/node-js-icon-454x512-nztofx17.png',
|
||||
name: 'Node',
|
||||
href: 'https://docs.openpanel.dev/docs/node',
|
||||
href: 'https://docs.openpanel.dev/docs/sdks/node',
|
||||
},
|
||||
{
|
||||
logo: 'https://expressjs.com/images/favicon.png',
|
||||
name: 'Express',
|
||||
href: 'https://docs.openpanel.dev/docs/express',
|
||||
href: 'https://docs.openpanel.dev/docs/sdks/express',
|
||||
},
|
||||
{
|
||||
logo: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Laravel.svg/1969px-Laravel.svg.png',
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -735,10 +735,10 @@ importers:
|
||||
specifier: ^0.1.5
|
||||
version: 0.1.5
|
||||
'@bull-board/api':
|
||||
specifier: ^5.21.0
|
||||
specifier: 5.21.0
|
||||
version: 5.21.0(patch_hash=25udjn3ygs6h4rrgl46tnrqrn4)(@bull-board/ui@5.21.0)
|
||||
'@bull-board/express':
|
||||
specifier: ^5.21.0
|
||||
specifier: 5.21.0
|
||||
version: 5.21.0
|
||||
'@openpanel/common':
|
||||
specifier: workspace:*
|
||||
@@ -8194,6 +8194,7 @@ packages:
|
||||
/are-we-there-yet@2.0.0:
|
||||
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: This package is no longer supported.
|
||||
dependencies:
|
||||
delegates: 1.0.0
|
||||
readable-stream: 3.6.2
|
||||
@@ -11674,6 +11675,7 @@ packages:
|
||||
/gauge@3.0.2:
|
||||
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: This package is no longer supported.
|
||||
dependencies:
|
||||
aproba: 2.0.0
|
||||
color-support: 1.1.3
|
||||
@@ -12307,6 +12309,7 @@ packages:
|
||||
|
||||
/inflight@1.0.6:
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
wrappy: 1.0.2
|
||||
@@ -15036,6 +15039,7 @@ packages:
|
||||
|
||||
/npmlog@5.0.1:
|
||||
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
|
||||
deprecated: This package is no longer supported.
|
||||
dependencies:
|
||||
are-we-there-yet: 2.0.0
|
||||
console-control-strings: 1.1.0
|
||||
@@ -16924,6 +16928,7 @@ packages:
|
||||
|
||||
/rimraf@3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
hasBin: true
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
|
||||
22
self-hosting/.env.template
Normal file
22
self-hosting/.env.template
Normal file
@@ -0,0 +1,22 @@
|
||||
NODE_ENV="production"
|
||||
SELF_HOSTED="true"
|
||||
GEO_IP_HOST="http://op-geo:8080"
|
||||
NEXT_PUBLIC_CLERK_SIGN_IN_URL="/login"
|
||||
NEXT_PUBLIC_CLERK_SIGN_UP_URL="/register"
|
||||
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/"
|
||||
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/"
|
||||
BATCH_SIZE="5000"
|
||||
BATCH_INTERVAL="10000"
|
||||
# Will be replaced with the setup script
|
||||
REDIS_URL="$REDIS_URL"
|
||||
CLICKHOUSE_URL="$CLICKHOUSE_URL"
|
||||
CLICKHOUSE_DB="$CLICKHOUSE_DB"
|
||||
CLICKHOUSE_USER="$CLICKHOUSE_USER"
|
||||
CLICKHOUSE_PASSWORD="$CLICKHOUSE_PASSWORD"
|
||||
DATABASE_URL="$DATABASE_URL"
|
||||
DATABASE_URL_DIRECT="$DATABASE_URL_DIRECT"
|
||||
NEXT_PUBLIC_DASHBOARD_URL="$NEXT_PUBLIC_DASHBOARD_URL"
|
||||
NEXT_PUBLIC_API_URL="$NEXT_PUBLIC_API_URL"
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="$NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY"
|
||||
CLERK_SECRET_KEY="$CLERK_SECRET_KEY"
|
||||
CLERK_SIGNING_SECRET="$CLERK_SIGNING_SECRET"
|
||||
3
self-hosting/.gitignore
vendored
Normal file
3
self-hosting/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.env
|
||||
docker-compose.yml
|
||||
caddy/Caddyfile
|
||||
19
self-hosting/caddy/Caddyfile.template
Normal file
19
self-hosting/caddy/Caddyfile.template
Normal file
@@ -0,0 +1,19 @@
|
||||
$DOMAIN_NAME {$SSL_CONFIG
|
||||
encode gzip
|
||||
|
||||
handle_path /api* {
|
||||
reverse_proxy op-api:3000
|
||||
}
|
||||
|
||||
reverse_proxy /* op-dashboard:3000
|
||||
}
|
||||
|
||||
worker.$DOMAIN_NAME {$SSL_CONFIG
|
||||
encode gzip
|
||||
|
||||
basic_auth {
|
||||
admin $BASIC_AUTH_PASSWORD
|
||||
}
|
||||
|
||||
reverse_proxy op-worker:3000
|
||||
}
|
||||
25
self-hosting/clickhouse/clickhouse-config.xml
Normal file
25
self-hosting/clickhouse/clickhouse-config.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<clickhouse>
|
||||
<logger>
|
||||
<level>warning</level>
|
||||
<console>true</console>
|
||||
</logger>
|
||||
|
||||
<keep_alive_timeout>10</keep_alive_timeout>
|
||||
<!--
|
||||
Avoid the warning: "Listen [::]:9009 failed: Address family for hostname not supported".
|
||||
If Docker has IPv6 disabled, bind ClickHouse to IPv4 to prevent this issue.
|
||||
Add this to the configuration to ensure it listens on all IPv4 interfaces:
|
||||
<listen_host>0.0.0.0</listen_host>
|
||||
-->
|
||||
|
||||
<!-- Stop all the unnecessary logging -->
|
||||
<query_thread_log remove="remove"/>
|
||||
<query_log remove="remove"/>
|
||||
<text_log remove="remove"/>
|
||||
<trace_log remove="remove"/>
|
||||
<metric_log remove="remove"/>
|
||||
<asynchronous_metric_log remove="remove"/>
|
||||
<session_log remove="remove"/>
|
||||
<part_log remove="remove"/>
|
||||
|
||||
</clickhouse>
|
||||
8
self-hosting/clickhouse/clickhouse-user-config.xml
Normal file
8
self-hosting/clickhouse/clickhouse-user-config.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<clickhouse>
|
||||
<profiles>
|
||||
<default>
|
||||
<log_queries>0</log_queries>
|
||||
<log_query_threads>0</log_query_threads>
|
||||
</default>
|
||||
</profiles>
|
||||
</clickhouse>
|
||||
45
self-hosting/danger_wipe_everything
Executable file
45
self-hosting/danger_wipe_everything
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set the project name if it's not the directory name
|
||||
# COMPOSE_PROJECT_NAME=your_project_name
|
||||
|
||||
# Use the directory name as the project name if not set
|
||||
PROJECT_NAME=${COMPOSE_PROJECT_NAME:-$(basename "$(pwd)")}
|
||||
|
||||
echo "Cleaning up Docker resources for project: $PROJECT_NAME"
|
||||
|
||||
# Stop and remove containers, networks, and volumes
|
||||
echo "Stopping and removing containers, networks, and volumes..."
|
||||
docker-compose down --volumes --remove-orphans
|
||||
|
||||
# Remove any remaining project-specific volumes
|
||||
echo "Removing any remaining project volumes..."
|
||||
project_volumes=$(docker volume ls --filter name="$PROJECT_NAME" -q)
|
||||
if [ -n "$project_volumes" ]; then
|
||||
docker volume rm $project_volumes
|
||||
fi
|
||||
|
||||
# Remove project-specific images
|
||||
echo "Removing project-specific images..."
|
||||
project_images=$(docker-compose config --images)
|
||||
if [ -n "$project_images" ]; then
|
||||
docker rmi $project_images
|
||||
fi
|
||||
|
||||
# Remove any dangling images
|
||||
echo "Removing dangling images..."
|
||||
docker image prune -f
|
||||
|
||||
# Remove any dangling volumes
|
||||
echo "Removing dangling volumes..."
|
||||
docker volume prune -f
|
||||
|
||||
echo "Cleanup complete. All project containers, images, volumes, and related resources have been removed."
|
||||
|
||||
# List remaining containers, images, and volumes
|
||||
echo "Remaining containers:"
|
||||
docker ps -a
|
||||
echo "Remaining images:"
|
||||
docker images
|
||||
echo "Remaining volumes:"
|
||||
docker volume ls
|
||||
150
self-hosting/docker-compose.template.yml
Normal file
150
self-hosting/docker-compose.template.yml
Normal file
@@ -0,0 +1,150 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
op-proxy:
|
||||
image: caddy:2-alpine
|
||||
restart: always
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
volumes:
|
||||
- op-proxy-data:/data
|
||||
- op-proxy-config:/config
|
||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile
|
||||
depends_on:
|
||||
- op-dashboard
|
||||
- op-api
|
||||
|
||||
op-db:
|
||||
image: postgres:14-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- op-db-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_PASSWORD
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U postgres']
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
ports:
|
||||
- 5431:5432
|
||||
|
||||
op-kv:
|
||||
image: redis:7.2.5-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- op-kv-data:/data
|
||||
command:
|
||||
[
|
||||
'redis-server',
|
||||
'--requirepass',
|
||||
'${REDIS_PASSWORD}',
|
||||
'--maxmemory-policy',
|
||||
'noeviction',
|
||||
]
|
||||
ports:
|
||||
- 6378:6379
|
||||
environment:
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
|
||||
op-geo:
|
||||
image: observabilitystack/geoip-api:latest
|
||||
restart: always
|
||||
|
||||
op-ch:
|
||||
image: clickhouse/clickhouse-server:23.3.7.5-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- op-ch-data:/var/lib/clickhouse
|
||||
- op-ch-logs:/var/log/clickhouse-server
|
||||
- ./clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/op-config.xml:ro
|
||||
- ./clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/op-user-config.xml:ro
|
||||
environment:
|
||||
- CLICKHOUSE_DB
|
||||
- CLICKHOUSE_USER
|
||||
- CLICKHOUSE_PASSWORD
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'clickhouse-client --query "SELECT 1"']
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
ports:
|
||||
- 8999:9000
|
||||
- 8122:8123
|
||||
|
||||
op-ch-migrator:
|
||||
image: clickhouse/clickhouse-server:23.3.7.5-alpine
|
||||
depends_on:
|
||||
- op-ch
|
||||
volumes:
|
||||
- ../packages/db/clickhouse_init.sql:/migrations/clickhouse_init.sql
|
||||
environment:
|
||||
- CLICKHOUSE_DB
|
||||
- CLICKHOUSE_USER
|
||||
- CLICKHOUSE_PASSWORD
|
||||
entrypoint: /bin/sh -c
|
||||
command: >
|
||||
"
|
||||
echo 'Waiting for ClickHouse to start...';
|
||||
while ! clickhouse-client --host op-ch --user=$CLICKHOUSE_USER --password=$CLICKHOUSE_PASSWORD --query 'SELECT 1;' 2>/dev/null; do
|
||||
echo 'ClickHouse is unavailable - sleeping 1s...';
|
||||
sleep 1;
|
||||
done;
|
||||
|
||||
echo 'ClickHouse started. Running migrations...';
|
||||
clickhouse-client --host op-ch --database=$CLICKHOUSE_DB --user=$CLICKHOUSE_USER --password=$CLICKHOUSE_PASSWORD --queries-file /migrations/clickhouse_init.sql;
|
||||
"
|
||||
|
||||
op-api:
|
||||
image: lindesvard/openpanel-api:latest
|
||||
restart: always
|
||||
command: sh -c "sleep 10 && pnpm -r run migrate:deploy && pnpm start"
|
||||
depends_on:
|
||||
- op-db
|
||||
- op-ch
|
||||
- op-kv
|
||||
- op-geo
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
op-dashboard:
|
||||
image: lindesvard/openpanel-dashboard:latest
|
||||
restart: always
|
||||
depends_on:
|
||||
- op-db
|
||||
- op-ch
|
||||
- op-kv
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
op-worker:
|
||||
image: lindesvard/openpanel-worker:latest
|
||||
restart: always
|
||||
depends_on:
|
||||
- op-db
|
||||
- op-ch
|
||||
- op-kv
|
||||
env_file:
|
||||
- .env
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: $OP_WORKER_REPLICAS
|
||||
|
||||
volumes:
|
||||
op-db-data:
|
||||
driver: local
|
||||
op-kv-data:
|
||||
driver: local
|
||||
op-ch-data:
|
||||
driver: local
|
||||
op-ch-logs:
|
||||
driver: local
|
||||
op-proxy-data:
|
||||
driver: local
|
||||
op-proxy-config:
|
||||
driver: local
|
||||
3
self-hosting/logs
Executable file
3
self-hosting/logs
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker compose logs -f
|
||||
22
self-hosting/package.json
Normal file
22
self-hosting/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@openpanel/self-hosting",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/inquirer": "^9.0.7",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"bcrypt": "^5.1.1",
|
||||
"inquirer": "^9.3.1",
|
||||
"jiti": "^1.21.6",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^5.0.2"
|
||||
}
|
||||
}
|
||||
716
self-hosting/pnpm-lock.yaml
generated
Normal file
716
self-hosting/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,716 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
'@types/inquirer':
|
||||
specifier: ^9.0.7
|
||||
version: 9.0.7
|
||||
'@types/js-yaml':
|
||||
specifier: ^4.0.9
|
||||
version: 4.0.9
|
||||
bcrypt:
|
||||
specifier: ^5.1.1
|
||||
version: 5.1.1
|
||||
inquirer:
|
||||
specifier: ^9.3.1
|
||||
version: 9.3.1
|
||||
jiti:
|
||||
specifier: ^1.21.6
|
||||
version: 1.21.6
|
||||
js-yaml:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
|
||||
devDependencies:
|
||||
'@types/bcrypt':
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2
|
||||
|
||||
packages:
|
||||
|
||||
/@inquirer/figures@1.0.3:
|
||||
resolution: {integrity: sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==}
|
||||
engines: {node: '>=18'}
|
||||
dev: false
|
||||
|
||||
/@mapbox/node-pre-gyp@1.0.11:
|
||||
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
detect-libc: 2.0.3
|
||||
https-proxy-agent: 5.0.1
|
||||
make-dir: 3.1.0
|
||||
node-fetch: 2.7.0
|
||||
nopt: 5.0.0
|
||||
npmlog: 5.0.1
|
||||
rimraf: 3.0.2
|
||||
semver: 7.6.3
|
||||
tar: 6.2.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@types/bcrypt@5.0.2:
|
||||
resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==}
|
||||
dependencies:
|
||||
'@types/node': 20.14.9
|
||||
dev: true
|
||||
|
||||
/@types/inquirer@9.0.7:
|
||||
resolution: {integrity: sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==}
|
||||
dependencies:
|
||||
'@types/through': 0.0.33
|
||||
rxjs: 7.8.1
|
||||
dev: false
|
||||
|
||||
/@types/js-yaml@4.0.9:
|
||||
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
|
||||
dev: false
|
||||
|
||||
/@types/node@20.14.9:
|
||||
resolution: {integrity: sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
/@types/through@0.0.33:
|
||||
resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==}
|
||||
dependencies:
|
||||
'@types/node': 20.14.9
|
||||
dev: false
|
||||
|
||||
/abbrev@1.1.1:
|
||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
dev: false
|
||||
|
||||
/agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
dependencies:
|
||||
debug: 4.3.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/ansi-escapes@4.3.2:
|
||||
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
type-fest: 0.21.3
|
||||
dev: false
|
||||
|
||||
/ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
dev: false
|
||||
|
||||
/aproba@2.0.0:
|
||||
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
|
||||
dev: false
|
||||
|
||||
/are-we-there-yet@2.0.0:
|
||||
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: This package is no longer supported.
|
||||
dependencies:
|
||||
delegates: 1.0.0
|
||||
readable-stream: 3.6.2
|
||||
dev: false
|
||||
|
||||
/argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
dev: false
|
||||
|
||||
/balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: false
|
||||
|
||||
/base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
dev: false
|
||||
|
||||
/bcrypt@5.1.1:
|
||||
resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@mapbox/node-pre-gyp': 1.0.11
|
||||
node-addon-api: 5.1.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
dependencies:
|
||||
buffer: 5.7.1
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
dev: false
|
||||
|
||||
/brace-expansion@1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
dev: false
|
||||
|
||||
/buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
dev: false
|
||||
|
||||
/chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
dev: false
|
||||
|
||||
/chardet@0.7.0:
|
||||
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
|
||||
dev: false
|
||||
|
||||
/chownr@2.0.0:
|
||||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/cli-cursor@3.1.0:
|
||||
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
restore-cursor: 3.1.0
|
||||
dev: false
|
||||
|
||||
/cli-spinners@2.9.2:
|
||||
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/cli-width@4.1.0:
|
||||
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
|
||||
engines: {node: '>= 12'}
|
||||
dev: false
|
||||
|
||||
/clone@1.0.4:
|
||||
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
|
||||
engines: {node: '>=0.8'}
|
||||
dev: false
|
||||
|
||||
/color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
dev: false
|
||||
|
||||
/color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
dev: false
|
||||
|
||||
/color-support@1.1.3:
|
||||
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
dev: false
|
||||
|
||||
/console-control-strings@1.1.0:
|
||||
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
|
||||
dev: false
|
||||
|
||||
/debug@4.3.6:
|
||||
resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
dev: false
|
||||
|
||||
/defaults@1.0.4:
|
||||
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
|
||||
dependencies:
|
||||
clone: 1.0.4
|
||||
dev: false
|
||||
|
||||
/delegates@1.0.0:
|
||||
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
|
||||
dev: false
|
||||
|
||||
/detect-libc@2.0.3:
|
||||
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
dev: false
|
||||
|
||||
/external-editor@3.1.0:
|
||||
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
chardet: 0.7.0
|
||||
iconv-lite: 0.4.24
|
||||
tmp: 0.0.33
|
||||
dev: false
|
||||
|
||||
/fs-minipass@2.1.0:
|
||||
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
|
||||
engines: {node: '>= 8'}
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
dev: false
|
||||
|
||||
/fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
dev: false
|
||||
|
||||
/gauge@3.0.2:
|
||||
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: This package is no longer supported.
|
||||
dependencies:
|
||||
aproba: 2.0.0
|
||||
color-support: 1.1.3
|
||||
console-control-strings: 1.1.0
|
||||
has-unicode: 2.0.1
|
||||
object-assign: 4.1.1
|
||||
signal-exit: 3.0.7
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wide-align: 1.1.5
|
||||
dev: false
|
||||
|
||||
/glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
inherits: 2.0.4
|
||||
minimatch: 3.1.2
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
dev: false
|
||||
|
||||
/has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/has-unicode@2.0.1:
|
||||
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
|
||||
dev: false
|
||||
|
||||
/https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.3.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/iconv-lite@0.4.24:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
dev: false
|
||||
|
||||
/ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
dev: false
|
||||
|
||||
/inflight@1.0.6:
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
wrappy: 1.0.2
|
||||
dev: false
|
||||
|
||||
/inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
dev: false
|
||||
|
||||
/inquirer@9.3.1:
|
||||
resolution: {integrity: sha512-A5IdVr1I04XqPlwrGgTJMKmzRg5ropqNpSeqo0vj1ZmluSCNSFaPZz4eazdPrhVcZfej7fCEYvD2NYa1KjkTJA==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
'@inquirer/figures': 1.0.3
|
||||
ansi-escapes: 4.3.2
|
||||
cli-width: 4.1.0
|
||||
external-editor: 3.1.0
|
||||
mute-stream: 1.0.0
|
||||
ora: 5.4.1
|
||||
picocolors: 1.0.1
|
||||
run-async: 3.0.0
|
||||
rxjs: 7.8.1
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 6.2.0
|
||||
dev: false
|
||||
|
||||
/is-fullwidth-code-point@3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/is-interactive@1.0.0:
|
||||
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/is-unicode-supported@0.1.0:
|
||||
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/jiti@1.21.6:
|
||||
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/js-yaml@4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
dev: false
|
||||
|
||||
/log-symbols@4.1.0:
|
||||
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
is-unicode-supported: 0.1.0
|
||||
dev: false
|
||||
|
||||
/make-dir@3.1.0:
|
||||
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
semver: 6.3.1
|
||||
dev: false
|
||||
|
||||
/mimic-fn@2.1.0:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
dev: false
|
||||
|
||||
/minipass@3.3.6:
|
||||
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
yallist: 4.0.0
|
||||
dev: false
|
||||
|
||||
/minipass@5.0.0:
|
||||
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/minizlib@2.1.2:
|
||||
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
|
||||
engines: {node: '>= 8'}
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
yallist: 4.0.0
|
||||
dev: false
|
||||
|
||||
/mkdirp@1.0.4:
|
||||
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/ms@2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
dev: false
|
||||
|
||||
/mute-stream@1.0.0:
|
||||
resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
dev: false
|
||||
|
||||
/node-addon-api@5.1.0:
|
||||
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
|
||||
dev: false
|
||||
|
||||
/node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
dev: false
|
||||
|
||||
/nopt@5.0.0:
|
||||
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
abbrev: 1.1.1
|
||||
dev: false
|
||||
|
||||
/npmlog@5.0.1:
|
||||
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
|
||||
deprecated: This package is no longer supported.
|
||||
dependencies:
|
||||
are-we-there-yet: 2.0.0
|
||||
console-control-strings: 1.1.0
|
||||
gauge: 3.0.2
|
||||
set-blocking: 2.0.0
|
||||
dev: false
|
||||
|
||||
/object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
dev: false
|
||||
|
||||
/onetime@5.1.2:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
dev: false
|
||||
|
||||
/ora@5.4.1:
|
||||
resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
bl: 4.1.0
|
||||
chalk: 4.1.2
|
||||
cli-cursor: 3.1.0
|
||||
cli-spinners: 2.9.2
|
||||
is-interactive: 1.0.0
|
||||
is-unicode-supported: 0.1.0
|
||||
log-symbols: 4.1.0
|
||||
strip-ansi: 6.0.1
|
||||
wcwidth: 1.0.1
|
||||
dev: false
|
||||
|
||||
/os-tmpdir@1.0.2:
|
||||
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/path-is-absolute@1.0.1:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/picocolors@1.0.1:
|
||||
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
|
||||
dev: false
|
||||
|
||||
/readable-stream@3.6.2:
|
||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
string_decoder: 1.3.0
|
||||
util-deprecate: 1.0.2
|
||||
dev: false
|
||||
|
||||
/restore-cursor@3.1.0:
|
||||
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
onetime: 5.1.2
|
||||
signal-exit: 3.0.7
|
||||
dev: false
|
||||
|
||||
/rimraf@3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
hasBin: true
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
dev: false
|
||||
|
||||
/run-async@3.0.0:
|
||||
resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: false
|
||||
|
||||
/rxjs@7.8.1:
|
||||
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
|
||||
dependencies:
|
||||
tslib: 2.6.3
|
||||
dev: false
|
||||
|
||||
/safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
dev: false
|
||||
|
||||
/safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
dev: false
|
||||
|
||||
/semver@6.3.1:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/semver@7.6.3:
|
||||
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/set-blocking@2.0.0:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
dev: false
|
||||
|
||||
/signal-exit@3.0.7:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
dev: false
|
||||
|
||||
/string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
is-fullwidth-code-point: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
dev: false
|
||||
|
||||
/string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
dev: false
|
||||
|
||||
/supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
dev: false
|
||||
|
||||
/tar@6.2.1:
|
||||
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
chownr: 2.0.0
|
||||
fs-minipass: 2.1.0
|
||||
minipass: 5.0.0
|
||||
minizlib: 2.1.2
|
||||
mkdirp: 1.0.4
|
||||
yallist: 4.0.0
|
||||
dev: false
|
||||
|
||||
/tmp@0.0.33:
|
||||
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
|
||||
engines: {node: '>=0.6.0'}
|
||||
dependencies:
|
||||
os-tmpdir: 1.0.2
|
||||
dev: false
|
||||
|
||||
/tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
dev: false
|
||||
|
||||
/tslib@2.6.3:
|
||||
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
|
||||
dev: false
|
||||
|
||||
/type-fest@0.21.3:
|
||||
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
/util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
dev: false
|
||||
|
||||
/wcwidth@1.0.1:
|
||||
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
|
||||
dependencies:
|
||||
defaults: 1.0.4
|
||||
dev: false
|
||||
|
||||
/webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
dev: false
|
||||
|
||||
/whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
dev: false
|
||||
|
||||
/wide-align@1.1.5:
|
||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
dev: false
|
||||
|
||||
/wrap-ansi@6.2.0:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
dev: false
|
||||
|
||||
/wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
dev: false
|
||||
|
||||
/yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
dev: false
|
||||
476
self-hosting/quiz.ts
Normal file
476
self-hosting/quiz.ts
Normal file
@@ -0,0 +1,476 @@
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import bcrypt from 'bcrypt';
|
||||
import inquirer from 'inquirer';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
function generatePassword(length: number) {
|
||||
const charset =
|
||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let password = '';
|
||||
for (let i = 0, n = charset.length; i < length; ++i) {
|
||||
password += charset.charAt(Math.floor(Math.random() * n));
|
||||
}
|
||||
return password;
|
||||
}
|
||||
|
||||
function writeCaddyfile(domainName: string, basicAuthPassword: string) {
|
||||
const caddyfileTemplatePath = path.resolve(
|
||||
__dirname,
|
||||
'caddy',
|
||||
'Caddyfile.template'
|
||||
);
|
||||
const caddyfilePath = path.resolve(__dirname, 'caddy', 'Caddyfile');
|
||||
fs.writeFileSync(
|
||||
caddyfilePath,
|
||||
fs
|
||||
.readFileSync(caddyfileTemplatePath, 'utf-8')
|
||||
.replaceAll('$DOMAIN_NAME', domainName.replace(/https?:\/\//, ''))
|
||||
.replaceAll(
|
||||
'$BASIC_AUTH_PASSWORD',
|
||||
bcrypt.hashSync(basicAuthPassword, 10)
|
||||
)
|
||||
.replaceAll(
|
||||
'$SSL_CONFIG',
|
||||
domainName.includes('localhost:443') ? '\n\ttls internal' : ''
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export interface DockerComposeFile {
|
||||
version: string;
|
||||
services: Record<
|
||||
string,
|
||||
{
|
||||
image: string;
|
||||
restart: string;
|
||||
ports: string[];
|
||||
volumes: string[];
|
||||
depends_on: string[];
|
||||
}
|
||||
>;
|
||||
volumes?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const stripTrailingSlash = (str: string) =>
|
||||
str.endsWith('/') ? str.slice(0, -1) : str;
|
||||
|
||||
function searchAndReplaceDockerCompose(replacements: [string, string][]) {
|
||||
const dockerComposePath = path.resolve(__dirname, 'docker-compose.yml');
|
||||
const dockerComposeContent = fs.readFileSync(dockerComposePath, 'utf-8');
|
||||
const dockerComposeReplaced = replacements.reduce(
|
||||
(acc, [search, replace]) => acc.replaceAll(search, replace),
|
||||
dockerComposeContent
|
||||
);
|
||||
|
||||
fs.writeFileSync(dockerComposePath, dockerComposeReplaced);
|
||||
}
|
||||
|
||||
function removeServiceFromDockerCompose(serviceName: string) {
|
||||
const dockerComposePath = path.resolve(__dirname, 'docker-compose.yml');
|
||||
const dockerComposeContent = fs.readFileSync(dockerComposePath, 'utf-8');
|
||||
|
||||
// Parse the YAML file
|
||||
const dockerCompose = yaml.load(dockerComposeContent) as DockerComposeFile;
|
||||
|
||||
// Remove the service
|
||||
if (dockerCompose.services && dockerCompose.services[serviceName]) {
|
||||
delete dockerCompose.services[serviceName];
|
||||
console.log(`Service '${serviceName}' has been removed.`);
|
||||
} else {
|
||||
console.log(`Service '${serviceName}' not found.`);
|
||||
// return;
|
||||
}
|
||||
|
||||
// filter depends_on
|
||||
Object.keys(dockerCompose.services).forEach((service) => {
|
||||
if (dockerCompose.services[service]?.depends_on) {
|
||||
// @ts-expect-error
|
||||
dockerCompose.services[service].depends_on = dockerCompose.services[
|
||||
service
|
||||
].depends_on.filter((dep) => dep !== serviceName);
|
||||
}
|
||||
});
|
||||
|
||||
// filter volumes
|
||||
Object.keys(dockerCompose.volumes ?? {}).forEach((volume) => {
|
||||
if (dockerCompose.volumes && volume.startsWith(serviceName)) {
|
||||
delete dockerCompose.volumes[volume];
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(dockerCompose.volumes ?? {}).length === 0) {
|
||||
delete dockerCompose.volumes;
|
||||
}
|
||||
|
||||
// Convert the object back to YAML
|
||||
const newYaml = yaml.dump(dockerCompose, {
|
||||
lineWidth: -1,
|
||||
});
|
||||
fs.writeFileSync(dockerComposePath, newYaml);
|
||||
}
|
||||
|
||||
function writeEnvFile(envs: {
|
||||
POSTGRES_PASSWORD: string | undefined;
|
||||
REDIS_PASSWORD: string | undefined;
|
||||
CLICKHOUSE_URL: string;
|
||||
CLICKHOUSE_DB: string;
|
||||
CLICKHOUSE_USER: string;
|
||||
CLICKHOUSE_PASSWORD: string;
|
||||
REDIS_URL: string;
|
||||
DATABASE_URL: string;
|
||||
DOMAIN_NAME: string;
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: string;
|
||||
CLERK_SECRET_KEY: string;
|
||||
CLERK_SIGNING_SECRET: string;
|
||||
}) {
|
||||
const envTemplatePath = path.resolve(__dirname, '.env.template');
|
||||
const envPath = path.resolve(__dirname, '.env');
|
||||
const envTemplate = fs.readFileSync(envTemplatePath, 'utf-8');
|
||||
|
||||
let newEnvFile = envTemplate
|
||||
.replace('$CLICKHOUSE_URL', envs.CLICKHOUSE_URL)
|
||||
.replace('$CLICKHOUSE_DB', envs.CLICKHOUSE_DB)
|
||||
.replace('$CLICKHOUSE_USER', envs.CLICKHOUSE_USER)
|
||||
.replace('$CLICKHOUSE_PASSWORD', envs.CLICKHOUSE_PASSWORD)
|
||||
.replace('$REDIS_URL', envs.REDIS_URL)
|
||||
.replace('$DATABASE_URL', envs.DATABASE_URL)
|
||||
.replace('$DATABASE_URL_DIRECT', envs.DATABASE_URL)
|
||||
.replace('$NEXT_PUBLIC_DASHBOARD_URL', stripTrailingSlash(envs.DOMAIN_NAME))
|
||||
.replace(
|
||||
'$NEXT_PUBLIC_API_URL',
|
||||
`${stripTrailingSlash(envs.DOMAIN_NAME)}/api`
|
||||
)
|
||||
.replace(
|
||||
'$NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY',
|
||||
envs.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
|
||||
)
|
||||
.replace('$CLERK_SECRET_KEY', envs.CLERK_SECRET_KEY)
|
||||
.replace('$CLERK_SIGNING_SECRET', envs.CLERK_SIGNING_SECRET);
|
||||
|
||||
if (envs.POSTGRES_PASSWORD) {
|
||||
newEnvFile += `\nPOSTGRES_PASSWORD=${envs.POSTGRES_PASSWORD}`;
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
envPath,
|
||||
newEnvFile
|
||||
.split('\n')
|
||||
.filter((line) => {
|
||||
return !line.includes('=""');
|
||||
})
|
||||
.join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
async function initiateOnboarding() {
|
||||
const T = ' ';
|
||||
const message = [
|
||||
'',
|
||||
'DISCLAIMER: This script is provided as-is and without warranty. Use at your own risk.',
|
||||
'',
|
||||
'',
|
||||
'WORTH MENTIONING: This is an early version of the script and it may not cover all scenarios.',
|
||||
' We recommend using our cloud service for production workloads until we release a stable version of self-hosting.',
|
||||
'',
|
||||
'',
|
||||
"With that said let's get started! 🤠",
|
||||
'',
|
||||
`Hey and welcome to Openpanel's self-hosting setup! 🚀\n`,
|
||||
`Before you continue, please make sure you have the following:`,
|
||||
`${T}1. Docker and Docker Compose installed on your machine.`,
|
||||
`${T}2. A domain name that you can use for this setup and point it to this machine's ip`,
|
||||
`${T}3. A Clerk.com account`,
|
||||
`${T}${T}- If you don't have one, you can create one at https://clerk.dev`,
|
||||
`${T}${T}- We'll need NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY, CLERK_SIGNING_SECRET`,
|
||||
`${T}${T}- Create a webhook pointing to https://your_domain/api/webhook/clerk\n`,
|
||||
'For more information you can read our article on self-hosting at https://docs.openpanel.dev/docs/self-hosting\n',
|
||||
];
|
||||
|
||||
console.log(
|
||||
'******************************************************************************\n'
|
||||
);
|
||||
console.log(message.join('\n'));
|
||||
console.log(
|
||||
'\n******************************************************************************'
|
||||
);
|
||||
|
||||
// Domain name
|
||||
|
||||
const domainNameResponse = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'domainName',
|
||||
message: "What's the domain name you want to use?",
|
||||
default: process.env.DEBUG ? 'http://localhost' : undefined,
|
||||
prefix: '🌐',
|
||||
validate: (value) => {
|
||||
if (value.startsWith('http://') || value.startsWith('https://')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Please enter a valid domain name. Should start with "http://" or "https://"';
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Dependencies
|
||||
|
||||
const dependenciesResponse = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'dependencies',
|
||||
message: 'Which of these dependencies will you need us to install?',
|
||||
choices: ['Clickhouse', 'Redis', 'Postgres'],
|
||||
default: ['Clickhouse', 'Redis', 'Postgres'],
|
||||
prefix: '📦',
|
||||
},
|
||||
]);
|
||||
|
||||
let envs: Record<string, string> = {};
|
||||
if (!dependenciesResponse.dependencies.includes('Clickhouse')) {
|
||||
const clickhouseResponse = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'CLICKHOUSE_URL',
|
||||
message: 'Enter your ClickHouse URL:',
|
||||
default: process.env.DEBUG ? 'http://clickhouse:8123' : undefined,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'CLICKHOUSE_DB',
|
||||
message: 'Enter your ClickHouse DB name:',
|
||||
default: process.env.DEBUG ? 'db_openpanel' : undefined,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'CLICKHOUSE_USER',
|
||||
message: 'Enter your ClickHouse user name:',
|
||||
default: process.env.DEBUG ? 'user_openpanel' : undefined,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'CLICKHOUSE_PASSWORD',
|
||||
message: 'Enter your ClickHouse password:',
|
||||
default: process.env.DEBUG ? 'ch_password' : undefined,
|
||||
},
|
||||
]);
|
||||
|
||||
envs = {
|
||||
...envs,
|
||||
...clickhouseResponse,
|
||||
};
|
||||
}
|
||||
|
||||
if (!dependenciesResponse.dependencies.includes('Redis')) {
|
||||
const redisResponse = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'REDIS_URL',
|
||||
message: 'Enter your Redis URL:',
|
||||
default: process.env.DEBUG ? 'redis://redis:6379' : undefined,
|
||||
},
|
||||
]);
|
||||
envs = {
|
||||
...envs,
|
||||
...redisResponse,
|
||||
};
|
||||
}
|
||||
|
||||
if (!dependenciesResponse.dependencies.includes('Postgres')) {
|
||||
const dbResponse = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'DATABASE_URL',
|
||||
message: 'Enter your Database URL:',
|
||||
default: process.env.DEBUG
|
||||
? 'postgresql://postgres:postgres@postgres:5432/postgres?schema=public'
|
||||
: undefined,
|
||||
},
|
||||
]);
|
||||
envs = {
|
||||
...envs,
|
||||
...dbResponse,
|
||||
};
|
||||
}
|
||||
|
||||
// Proxy
|
||||
|
||||
const proxyResponse = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'proxy',
|
||||
message:
|
||||
'Do you already have a web service setup or would you like us to install Caddy with SSL?',
|
||||
choices: ['Install Caddy with SSL', 'Bring my own'],
|
||||
},
|
||||
]);
|
||||
|
||||
// Clerk
|
||||
|
||||
const clerkResponse = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY',
|
||||
message: 'Enter your Clerk Publishable Key:',
|
||||
default: process.env.DEBUG ? 'pk_test_1234567890' : undefined,
|
||||
validate: (value) => {
|
||||
if (value.startsWith('pk_live_') || value.startsWith('pk_test_')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Please enter a valid Clerk Publishable Key. Should start with "pk_live_" or "pk_test_"';
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'CLERK_SECRET_KEY',
|
||||
message: 'Enter your Clerk Secret Key:',
|
||||
default: process.env.DEBUG ? 'sk_test_1234567890' : undefined,
|
||||
validate: (value) => {
|
||||
if (value.startsWith('sk_live_') || value.startsWith('sk_test_')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Please enter a valid Clerk Secret Key. Should start with "sk_live_" or "sk_test_"';
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'CLERK_SIGNING_SECRET',
|
||||
message: 'Enter your Clerk Signing Secret:',
|
||||
default: process.env.DEBUG ? 'whsec_1234567890' : undefined,
|
||||
validate: (value) => {
|
||||
if (value.startsWith('whsec_')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Please enter a valid Clerk Signing Secret. Should start with "whsec_"';
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// OS
|
||||
|
||||
const cpus = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'CPUS',
|
||||
default: os.cpus().length,
|
||||
message: 'How many CPUs do you have?',
|
||||
validate: (value) => {
|
||||
const parsed = parseInt(value, 10);
|
||||
|
||||
if (Number.isNaN(parsed)) {
|
||||
return 'Please enter a valid number';
|
||||
}
|
||||
|
||||
if (parsed < 1) {
|
||||
return 'Please enter a number greater than 0';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const basicAuth = await inquirer.prompt<{
|
||||
password: string;
|
||||
}>([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'password',
|
||||
default: generatePassword(12),
|
||||
message: 'Give a password for basic auth',
|
||||
validate: (value) => {
|
||||
if (!value) {
|
||||
return 'Please enter a valid password';
|
||||
}
|
||||
|
||||
if (value.length < 5) {
|
||||
return 'Password should be atleast 5 characters';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
console.log('');
|
||||
console.log('Creating .env file...\n');
|
||||
const POSTGRES_PASSWORD = generatePassword(20);
|
||||
const REDIS_PASSWORD = generatePassword(20);
|
||||
|
||||
writeEnvFile({
|
||||
POSTGRES_PASSWORD: envs.DATABASE_URL ? undefined : POSTGRES_PASSWORD,
|
||||
REDIS_PASSWORD: envs.REDIS_URL ? undefined : REDIS_PASSWORD,
|
||||
CLICKHOUSE_URL: envs.CLICKHOUSE_URL || 'http://op-ch:8123',
|
||||
CLICKHOUSE_DB: envs.CLICKHOUSE_DB || 'openpanel',
|
||||
CLICKHOUSE_USER: envs.CLICKHOUSE_USER || 'openpanel',
|
||||
CLICKHOUSE_PASSWORD: envs.CLICKHOUSE_PASSWORD || generatePassword(20),
|
||||
REDIS_URL: envs.REDIS_URL || 'redis://op-kv:6379',
|
||||
DATABASE_URL:
|
||||
envs.DATABASE_URL ||
|
||||
`postgresql://postgres:${POSTGRES_PASSWORD}@op-db:5432/postgres?schema=public`,
|
||||
DOMAIN_NAME: domainNameResponse.domainName,
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY:
|
||||
clerkResponse.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY || '',
|
||||
CLERK_SECRET_KEY: clerkResponse.CLERK_SECRET_KEY || '',
|
||||
CLERK_SIGNING_SECRET: clerkResponse.CLERK_SIGNING_SECRET || '',
|
||||
});
|
||||
|
||||
console.log('Updating docker-compose.yml file...\n');
|
||||
fs.copyFileSync(
|
||||
path.resolve(__dirname, 'docker-compose.template.yml'),
|
||||
path.resolve(__dirname, 'docker-compose.yml')
|
||||
);
|
||||
|
||||
if (envs.CLICKHOUSE_URL) {
|
||||
removeServiceFromDockerCompose('op-ch');
|
||||
removeServiceFromDockerCompose('op-ch-migrator');
|
||||
}
|
||||
|
||||
if (envs.REDIS_URL) {
|
||||
removeServiceFromDockerCompose('op-kv');
|
||||
}
|
||||
|
||||
if (envs.DATABASE_URL) {
|
||||
removeServiceFromDockerCompose('op-db');
|
||||
}
|
||||
|
||||
if (proxyResponse.proxy === 'Bring my own') {
|
||||
removeServiceFromDockerCompose('op-proxy');
|
||||
} else {
|
||||
writeCaddyfile(domainNameResponse.domainName, basicAuth.password);
|
||||
}
|
||||
|
||||
searchAndReplaceDockerCompose([['$OP_WORKER_REPLICAS', cpus.CPUS]]);
|
||||
|
||||
console.log(
|
||||
[
|
||||
'======================================================================',
|
||||
'Here are some good things to know before you continue:',
|
||||
'',
|
||||
`1. Make sure that your webhook is pointing at ${domainNameResponse.domainName}/api/webhook/clerk`,
|
||||
'',
|
||||
'2. Commands:',
|
||||
'\t- ./start (example: ./start)',
|
||||
'\t- ./stop (example: ./stop)',
|
||||
'\t- ./logs (example: ./logs)',
|
||||
'\t- ./rebuild (example: ./rebuild op-dashboard)',
|
||||
'',
|
||||
'3. Danger zone!',
|
||||
'\t- ./danger_wipe_everything (example: ./danger_wipe_everything)',
|
||||
'',
|
||||
'4. More about self-hosting: https://docs.openpanel.dev/docs/self-hosting',
|
||||
'======================================================================',
|
||||
'',
|
||||
`Start OpenPanel with "./start" inside the self-hosting directory`,
|
||||
'',
|
||||
'',
|
||||
].join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
initiateOnboarding();
|
||||
6
self-hosting/rebuild
Executable file
6
self-hosting/rebuild
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
NAME=$1
|
||||
|
||||
docker compose build $NAME
|
||||
docker compose up -d --no-deps --force-recreate $NAME
|
||||
96
self-hosting/setup
Executable file
96
self-hosting/setup
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/bin/bash
|
||||
|
||||
NODE_VERSION=20.15.0
|
||||
|
||||
# Function to install Node.js
|
||||
install_nvm_and_node() {
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
|
||||
source ~/.bashrc
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
||||
nvm install $NODE_VERSION
|
||||
nvm use $NODE_VERSION
|
||||
}
|
||||
|
||||
# Function to install pnpm
|
||||
install_pnpm() {
|
||||
echo "Installing pnpm..."
|
||||
npm install -g pnpm
|
||||
}
|
||||
|
||||
# Function to install Docker
|
||||
install_docker() {
|
||||
echo "Installing Docker..."
|
||||
# Add Docker's official GPG key:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ca-certificates curl gnupg
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
|
||||
# Add the repository to Apt sources:
|
||||
echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
|
||||
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
|
||||
# Install Docker packages:
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
# Add current user to docker group
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
echo "Docker installed successfully. You may need to log out and back in for group changes to take effect."
|
||||
}
|
||||
|
||||
# Check if Node.js is installed
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
echo "********************************************************************************"
|
||||
echo "********************************************************************************"
|
||||
echo "Do you wish to automatically install Node.js version $NODE_VERSION using NVM? (yes/no)"
|
||||
echo "********************************************************************************"
|
||||
echo "********************************************************************************"
|
||||
read user_choice
|
||||
|
||||
case $user_choice in
|
||||
[Yy]* )
|
||||
install_nvm_and_node;;
|
||||
[Nn]* )
|
||||
echo "Please install Node.js version $NODE_VERSION by yourself as per your preference. Exiting script."
|
||||
exit 1;;
|
||||
* )
|
||||
echo "Invalid input. Please answer yes or no."
|
||||
exit 1;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Check if Docker is installed
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "********************************************************************************"
|
||||
echo "********************************************************************************"
|
||||
echo "Docker is not installed. Do you wish to install Docker? (yes/no)"
|
||||
echo "********************************************************************************"
|
||||
echo "********************************************************************************"
|
||||
read docker_choice
|
||||
|
||||
case $docker_choice in
|
||||
[Yy]* )
|
||||
install_docker;;
|
||||
[Nn]* )
|
||||
echo "Skipping Docker installation.";;
|
||||
* )
|
||||
echo "Invalid input. Skipping Docker installation.";;
|
||||
esac
|
||||
else
|
||||
echo "Docker is already installed."
|
||||
fi
|
||||
|
||||
|
||||
# Check if pnpm is installed
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
install_pnpm
|
||||
fi
|
||||
|
||||
pnpm --ignore-workspace install
|
||||
./node_modules/.bin/jiti quiz.ts
|
||||
3
self-hosting/start
Executable file
3
self-hosting/start
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker compose up -d
|
||||
3
self-hosting/stop
Executable file
3
self-hosting/stop
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker compose down
|
||||
28
self-hosting/tsconfig.json
Normal file
28
self-hosting/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"exclude": ["node_modules", "build", "dist"],
|
||||
"include": ["."]
|
||||
}
|
||||
54
sh/docker-build
Executable file
54
sh/docker-build
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
|
||||
APP=$1
|
||||
VERSION=$2
|
||||
|
||||
if [ -z "$APP" ]; then
|
||||
echo "Please provide an app name as an argument."
|
||||
echo "Usage: $0 <app_name> <version>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if version is provided
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "Please provide a version number as an argument."
|
||||
echo "Usage: $0 $APP <version>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure Docker Buildx is available and set up a builder
|
||||
docker buildx create --use --name multi-arch-builder || true
|
||||
|
||||
# Function to build a multi-architecture image
|
||||
build_image() {
|
||||
local app=$1
|
||||
local image_name="lindesvard/openpanel-$app"
|
||||
|
||||
echo "Building multi-architecture image for $image_name:$VERSION"
|
||||
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
-t "$image_name:$VERSION" \
|
||||
-t "$image_name:latest" \
|
||||
--build-arg DATABASE_URL="postgresql://p@p:5432/p" \
|
||||
-f "apps/$app/Dockerfile" \
|
||||
--push \
|
||||
.
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to build $image_name:$VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully built and pushed multi-architecture image for $image_name:$VERSION"
|
||||
}
|
||||
|
||||
if [ "$APP" == "all" ]; then
|
||||
build_image "dashboard"
|
||||
build_image "worker"
|
||||
build_image "api"
|
||||
echo "All multi-architecture images have been built and pushed successfully."
|
||||
else
|
||||
build_image $APP
|
||||
echo "Multi-architecture image for $APP has been built and pushed successfully."
|
||||
fi
|
||||
49
sh/docker-publish
Executable file
49
sh/docker-publish
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
|
||||
APP=$1
|
||||
VERSION=$2
|
||||
|
||||
if [ -z "$APP" ]; then
|
||||
echo "Please provide an app name as an argument."
|
||||
echo "Usage: $0 <app_name> <version>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if version is provided
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "Please provide a version number as an argument."
|
||||
echo "Usage: $0 $APP <version>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to push a multi-architecture image
|
||||
push_image() {
|
||||
local app=$1
|
||||
local image_name="lindesvard/openpanel-$app"
|
||||
|
||||
echo "Pushing multi-architecture image for $image_name:$VERSION"
|
||||
|
||||
# Push the versioned tag
|
||||
docker buildx imagetools create -t "$image_name:$VERSION" "$image_name:$VERSION"
|
||||
|
||||
# Push the latest tag
|
||||
docker buildx imagetools create -t "$image_name:latest" "$image_name:$VERSION"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to push $image_name:$VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully pushed multi-architecture image for $image_name:$VERSION and latest"
|
||||
}
|
||||
|
||||
# Push each image
|
||||
if [ "$APP" == "all" ]; then
|
||||
push_image "dashboard"
|
||||
push_image "worker"
|
||||
push_image "api"
|
||||
echo "All multi-architecture images have been pushed successfully."
|
||||
else
|
||||
push_image $APP
|
||||
echo "Multi-architecture image for $APP has been pushed successfully."
|
||||
fi
|
||||
Reference in New Issue
Block a user