Skip to content

Commit

Permalink
(self-host|3): self-hostable platform! (#982)
Browse files Browse the repository at this point in the history
* feat: allow local file uploads with minio

* feat: allow different email providers to be used when self-hosting, or allow users to disable email

* fix: handle errors and fix types

* fix: make minio slightly easier to start

* fix: add upload test

* chore: format

* chore: format?

* fix: get docker compose stuff in reasonable state

* fix: hopefully make ci run correctly

* fix: env vars?

* fix: unfix env vars

* fix: remove unecessary option

* fix: shuffle things around more

* fix: add manual database_url back

* fix: don't use non-existint postgres_name var, use postgres_db

* fix: log on cancel, and max fail 3 tests

* fix: change db url

* fix: actually pass in correct env vars this time??

* fix: change a bit how i'm doing env vars (shocking)

* fix: don't use env.docker-compose in ci directly

* fix: okay i have learned how env vars work. you cannot set 'environment:' vars using vars from 'env_file'

* fix: minio_root_user instead

* chore: restore dirty

* docs: update self-host docs

* chore: clean up test file

* chore: remove file staged for later commit

* feat: add docker-compose file for self hosting

* feat: add caddy file config and all that jazz

* feat: add way to add admin user

* docs: update environment variable docs

* docs: provide better documentation

* chore: remove console.log

* fix: better env vars for db

* chore: add explanatory comment

* fix: improve order of .env.example a bit

* chore: do not log out password after account creation

* chore: start minio for test again (oops)
  • Loading branch information
tefkah authored Feb 27, 2025
1 parent 626e97e commit 61a0012
Show file tree
Hide file tree
Showing 9 changed files with 578 additions and 13 deletions.
94 changes: 94 additions & 0 deletions core/prisma/create-admin-user.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/* eslint-disable no-console */

import { createEnv } from "@t3-oss/env-nextjs";
import { Kysely, PostgresDialect } from "kysely";
import * as pg from "pg";
import { z } from "zod";

import { Database } from "db/Database";

import { isUniqueConstraintError } from "../kysely/errors";
import { createPasswordHash } from "../lib/authentication/password";

const env = createEnv({
server: {
ADMIN_EMAIL: z.string().email(),
ADMIN_PASSWORD: z.string().min(8),
ADMIN_FIRSTNAME: z.string(),
ADMIN_LASTNAME: z.string(),
DATABASE_URL: z.string(),
},
client: {},
experimental__runtimeEnv: {},
});

const dialect = new PostgresDialect({
pool: new pg.Pool({
connectionString: env.DATABASE_URL,
}),
});

const db = new Kysely<Database>({
dialect,
});

async function createAdminUser({
email,
password,
firstName,
lastName,
}: {
email: string;
password: string;
firstName: string;
lastName: string;
}) {
const values = {
slug: email.split("@")[0],
email,
firstName,
lastName,
passwordHash: await createPasswordHash(password),
isSuperAdmin: true,
};

return db.insertInto("users").values(values).returningAll().executeTakeFirstOrThrow();
}

async function main() {
const adminEmail = env.ADMIN_EMAIL;
const adminPassword = env.ADMIN_PASSWORD;
const adminFirstName = env.ADMIN_FIRSTNAME;
const adminLastName = env.ADMIN_LASTNAME;

if (!adminEmail || !adminPassword) {
throw new Error("ADMIN_EMAIL and ADMIN_PASSWORD must be set for admin initialization");
}

try {
await createAdminUser({
email: adminEmail,
password: adminPassword,
firstName: adminFirstName,
lastName: adminLastName,
});
console.log("✨ Admin user created successfully!");
console.log(`You can now log in with:`);
console.log(`${adminEmail}`);
} catch (e) {
if (isUniqueConstraintError(e)) {
console.log("⚠️ Admin user already exists, skipping initialization");
return;
}
throw e;
}
}

if (require.main === module) {
main()
.then(() => process.exit(0))
.catch((e) => {
console.error(e);
process.exit(1);
});
}
44 changes: 44 additions & 0 deletions self-host/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# the default url of the platform
PUBPUB_URL=http://localhost:3000 # the url of the platform
# change this to eg
# PUBPUB_URL=https://platform.example.com
# for a production environment


# configure these things with safe values
# or the values of a remote postgres database
POSTGRES_USER=my-postgres-user # change this!
POSTGRES_PASSWORD=my-postgres-password # change this!
POSTGRES_DB=my-postgres-db # change this! this is hard to change after the database has been created
POSTGRES_HOST=db # change this to the name of the service in docker-compose.yml, or the domain of a remote postgres database if you're using that instead
POSTGRES_PORT=5432 # don't forget to update the port in docker-compose.yml if you change this

# not needed if you're using a remote file server like AWS S3
MINIO_ROOT_USER= # change this! this is the username for your file server!
MINIO_ROOT_PASSWORD= # change this! this is the password for your file server!

ASSETS_BUCKET_NAME=assets
ASSETS_UPLOAD_KEY= # change this! example: asset-user
ASSETS_UPLOAD_SECRET_KEY= # change this!
ASSETS_REGION=us-east-1 # leave this unchanged, unless you are hosting files on a different region on actual AWS

# this is the default value but you ideally should set this up more nicely using our caddy service
ASSETS_STORAGE_ENDPOINT="http://localhost:9000"
# you could also set this to the secured endpoint of your file server
# ASSETS_STORAGE_ENDPOINT="https://example.com/assets"

MAILGUN_SMTP_HOST=localhost
MAILGUN_SMTP_PORT=54325
MAILGUN_SMTP_PASSWORD="xxx"
MAILGUN_SMTP_USERNAME="xxx"

API_KEY="super_secret_key"

OTEL_SERVICE_NAME="pubpub-v7-dev" # should be shared across components but not environments
HONEYCOMB_API_KEY="xxx"

# KYSELY_DEBUG="true"

GCLOUD_KEY_FILE='xxx'

SELF_HOST="true"
2 changes: 2 additions & 0 deletions self-host/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
minio/.minio.sys
Loading

0 comments on commit 61a0012

Please sign in to comment.