improve: prepare for coolify and general self-hosting improvements (#175)

* fix(self-hosting): improve docker compose, add healthchecks, rename env SELF_HOSTED

* improve(db): improve initial migration when no data exists

* fix(db): misstakes were made

* improve(dashboard): better curl preview depending on project type

* fix(db): fix migrations

* fix(onboarding): ensure we publish event correctly

* wip

* fix: curl preview

* add coolify template

* fix(dashboard): page -> route

* fix

* fix env
This commit is contained in:
Carl-Gerhard Lindesvärd
2025-06-23 22:21:11 +02:00
committed by GitHub
parent 4a2dbc5c4d
commit 92d62c3e5c
22 changed files with 382 additions and 60 deletions

View File

@@ -20,6 +20,7 @@ ENV PATH="$PNPM_HOME:$PATH"
RUN apt-get update && apt-get install -y \
openssl \
libssl3 \
curl \
&& rm -rf /var/lib/apt/lists/*
RUN corepack enable
@@ -62,6 +63,7 @@ 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__"
ENV NEXT_PUBLIC_SELF_HOSTED="__NEXT_PUBLIC_SELF_HOSTED__"
RUN pnpm run build

View File

@@ -4,7 +4,7 @@ set -e
echo "> Replace env variable placeholders with runtime values..."
# Define environment variables to check (space-separated string)
variables_to_replace="NEXT_PUBLIC_DASHBOARD_URL NEXT_PUBLIC_API_URL"
variables_to_replace="NEXT_PUBLIC_DASHBOARD_URL NEXT_PUBLIC_API_URL NEXT_PUBLIC_SELF_HOSTED"
# Replace env variable placeholders with real values
for key in $variables_to_replace; do

View File

@@ -99,7 +99,7 @@ export default function LayoutMenu({
</div>
</ProjectLink>
)}
{process.env.SELF_HOSTED && (
{process.env.NEXT_PUBLIC_SELF_HOSTED === 'true' && (
<a
className="rounded p-2 row items-center gap-2 hover:bg-def-200"
href="https://openpanel.dev/supporter"
@@ -231,7 +231,7 @@ export default function LayoutMenu({
))}
</div>
</div>
{process.env.SELF_HOSTED && (
{process.env.NEXT_PUBLIC_SELF_HOSTED === 'true' && (
<div className="mt-auto w-full ">
<div className={cn('text-sm w-full text-center')}>
Self-hosted instance

View File

@@ -27,7 +27,7 @@ export default async function Page({
params: { organizationSlug: organizationId },
searchParams,
}: PageProps) {
const isBillingEnabled = !process.env.SELF_HOSTED;
const isBillingEnabled = process.env.NEXT_PUBLIC_SELF_HOSTED !== 'true';
const tab = parseAsStringEnum(['org', 'billing', 'members', 'invites'])
.withDefault('org')
.parseServerSide(searchParams.tab);

View File

@@ -104,22 +104,34 @@ function CurlPreview({ project }: { project: IServiceProjectWithClients }) {
return null;
}
const payload: Record<string, any> = {
type: 'track',
payload: {
name: 'screen_view',
properties: {
__title: `Testing OpenPanel - ${project.name}`,
__path: `${project.domain}`,
__referrer: `${process.env.NEXT_PUBLIC_DASHBOARD_URL}`,
},
},
};
if (project.types.includes('app')) {
payload.payload.properties.__path = '/';
delete payload.payload.properties.__referrer;
}
if (project.types.includes('backend')) {
payload.payload.name = 'test_event';
payload.payload.properties = {};
}
const code = `curl -X POST ${process.env.NEXT_PUBLIC_API_URL}/track \\
-H "Content-Type: application/json" \\
-H "openpanel-client-id: ${client.id}" \\
-H "openpanel-client-secret: ${secret}" \\
-H "User-Agent: ${window.navigator.userAgent}" \\
-d '{
"type": "track",
"payload": {
"name": "screen_view",
"properties": {
"__title": "Testing OpenPanel - ${project.name}",
"__path": "${project.domain}",
"__referrer": "${process.env.NEXT_PUBLIC_DASHBOARD_URL}"
}
}
}'`;
-H "User-Agent: ${typeof window !== 'undefined' ? window.navigator.userAgent : ''}" \\
-d '${JSON.stringify(payload)}'`;
return (
<div className="card">

View File

@@ -27,7 +27,7 @@ const Verify = async ({ params: { projectId } }: Props) => {
const [project, events] = await Promise.all([
await getProjectWithClients(projectId),
getEvents(
`SELECT * FROM ${TABLE_NAMES.events} WHERE project_id = ${escape(projectId)} LIMIT 100`,
`SELECT * FROM ${TABLE_NAMES.events} WHERE project_id = ${escape(projectId)} ORDER BY created_at DESC LIMIT 100`,
),
]);
@@ -35,7 +35,7 @@ const Verify = async ({ params: { projectId } }: Props) => {
return <div>Hmm, something fishy is going on. Please reload the page.</div>;
}
return <OnboardingVerify project={project} events={events} />;
return <OnboardingVerify project={project} events={events.reverse()} />;
};
export default Verify;

View File

@@ -1,7 +0,0 @@
export const runtime = 'edge';
export const dynamic = 'force-dynamic'; // no caching
export async function GET(request: Request) {
const headers = Object.fromEntries(request.headers.entries());
return Response.json({ headers, region: process.env.VERCEL_REGION });
}

View File

@@ -0,0 +1,5 @@
export const dynamic = 'force-dynamic'; // no caching
export async function GET(request: Request) {
return Response.json({ status: 'ok' });
}

View File

@@ -3,6 +3,14 @@ title: Changelog for self-hosting
description: This is a list of changes that have been made to the self-hosting setup.
---
## 1.2.0
We have renamed `SELF_HOSTED` to `NEXT_PUBLIC_SELF_HOSTED`. It's important to rename this env before your upgrade to this version.
## 1.1.1
Packed with new features since our first stable release.
## 1.0.0 (stable)
OpenPanel self-hosting is now in a stable state and should not be any breaking changes in the future.

View File

@@ -15,6 +15,7 @@ RUN corepack enable && \
apt-get install -y --no-install-recommends \
ca-certificates \
openssl \
curl \
libssl3 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

View File

@@ -36,7 +36,11 @@ export async function bootCron() {
},
];
if (process.env.SELF_HOSTED && process.env.NODE_ENV === 'production') {
if (
(process.env.NEXT_PUBLIC_SELF_HOSTED === 'true' ||
process.env.SELF_HOSTED) &&
process.env.NODE_ENV === 'production'
) {
jobs.push({
name: 'ping',
type: 'ping',

View File

@@ -57,6 +57,10 @@ async function start() {
});
});
app.get('/healthcheck', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(PORT, () => {
console.log(`For the UI, open http://localhost:${PORT}/`);
});

View File

@@ -46,9 +46,10 @@ export async function deleteProjects(job: Job<CronQueuePayload>) {
];
for (const table of tables) {
const query = process.env.SELF_HOSTED
? `ALTER TABLE ${table} DELETE WHERE ${where};`
: `ALTER TABLE ${table}_replicated ON CLUSTER '{cluster}' DELETE WHERE ${where};`;
const query =
process.env.NEXT_PUBLIC_SELF_HOSTED === 'true'
? `ALTER TABLE ${table} DELETE WHERE ${where};`
: `ALTER TABLE ${table}_replicated ON CLUSTER '{cluster}' DELETE WHERE ${where};`;
await ch.command({
query,