Remove obsolete scripts and configuration files for self-hosting and database migrations

- Deleted `pnpm-workspace.yaml` as it is no longer needed.
- Removed `apply-migrations.sh` script that handled database migrations.
- Eliminated `generate-full-stack-env.mjs` script for environment variable generation.
- Deleted `selfhost-backup.sh` script for creating backups of the self-hosted environment.
- Removed `selfhost-doctor.sh` script for health checks of the self-hosted services.
- Deleted `selfhost-restore.sh` script for restoring backups in the self-hosted environment.
This commit is contained in:
Poyraz
2026-06-16 09:57:32 +03:00
parent da8c55d1ad
commit 72a0b38f3e
48 changed files with 2463 additions and 13020 deletions
-176
View File
@@ -1,176 +0,0 @@
#!/usr/bin/env sh
set -eu
ROOT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
PSQL_IMAGE="${NETA_PSQL_IMAGE:-postgres:16-alpine}"
if [ -z "${DATABASE_URL:-}" ]; then
echo "DATABASE_URL is required." >&2
echo "Example:" >&2
echo " DATABASE_URL='postgresql://postgres:password@host:5432/postgres' sh ./scripts/apply-migrations.sh" >&2
exit 1
fi
run_sql() {
sql="$1"
if command -v psql >/dev/null 2>&1; then
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -Atc "$sql"
elif command -v docker >/dev/null 2>&1; then
docker run --rm -i "$PSQL_IMAGE" \
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -Atc "$sql"
else
echo "Neither psql nor docker is available to run migrations." >&2
exit 1
fi
}
query_scalar() {
run_sql "$1" | tail -n 1 | tr -d '[:space:]'
}
ensure_migration_table() {
run_sql "
create schema if not exists neta_internal;
create table if not exists neta_internal.schema_migrations (
version text primary key,
file_path text not null,
applied_at timestamp with time zone default timezone('utc'::text, now()) not null
);
revoke all on schema neta_internal from public;
revoke all on all tables in schema neta_internal from public;
" >/dev/null
}
is_migration_recorded() {
migration_id="$1"
result="$(query_scalar "select case when exists (select 1 from neta_internal.schema_migrations where version = '$migration_id') then 'yes' else 'no' end;")"
[ "$result" = "yes" ]
}
record_migration() {
migration_id="$1"
file_path="$2"
run_sql "
insert into neta_internal.schema_migrations (version, file_path)
values ('$migration_id', '$file_path')
on conflict (version) do update set
file_path = excluded.file_path,
applied_at = timezone('utc'::text, now());
" >/dev/null
}
existing_objects_cover_migration() {
migration_id="$1"
case "$migration_id" in
0001_schema)
query_scalar "select case when to_regclass('public.profiles') is not null and to_regclass('public.tasks') is not null then 'yes' else 'no' end;"
;;
0002_freelancer_os_core)
query_scalar "select case when to_regclass('public.clients') is not null and to_regclass('public.projects') is not null and to_regclass('public.calendar_events') is not null and to_regclass('public.finance_transactions') is not null and to_regclass('public.daily_logs') is not null and exists (select 1 from information_schema.columns where table_schema = 'public' and table_name = 'tasks' and column_name = 'priority') then 'yes' else 'no' end;"
;;
0003_project_planning_assets)
query_scalar "select case when to_regclass('public.project_planning_sections') is not null and exists (select 1 from information_schema.columns where table_schema = 'public' and table_name = 'projects' and column_name = 'cover_image_path') then 'yes' else 'no' end;"
;;
0004_business_os_tables)
query_scalar "select case when to_regclass('public.proposals') is not null and to_regclass('public.contracts') is not null and to_regclass('public.invoices') is not null and to_regclass('public.subscriptions') is not null then 'yes' else 'no' end;"
;;
0005_advanced_crm)
query_scalar "select case when to_regclass('public.client_activities') is not null and exists (select 1 from information_schema.columns where table_schema = 'public' and table_name = 'clients' and column_name = 'pipeline_stage') then 'yes' else 'no' end;"
;;
0006_pgvector_embeddings)
query_scalar "select case when to_regclass('public.document_embeddings') is not null and exists (select 1 from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.proname = 'match_documents') then 'yes' else 'no' end;"
;;
0007_client_portal)
query_scalar "select case when to_regclass('public.project_revisions') is not null and exists (select 1 from information_schema.columns where table_schema = 'public' and table_name = 'profiles' and column_name = 'role') and exists (select 1 from information_schema.columns where table_schema = 'public' and table_name = 'clients' and column_name = 'client_auth_id') then 'yes' else 'no' end;"
;;
0008_project_progress_quota)
query_scalar "select case when exists (select 1 from information_schema.columns where table_schema = 'public' and table_name = 'projects' and column_name = 'progress_type') and exists (select 1 from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.proname = 'update_project_progress_on_task_change') then 'yes' else 'no' end;"
;;
0009_first_admin_registration_lock)
echo "no"
;;
0010_internal_auth_creation)
echo "no"
;;
0011_service_role_claims_storage)
echo "no"
;;
*)
echo "no"
;;
esac
}
run_sql_file() {
file_path="$1"
absolute_path="$ROOT_DIR/$file_path"
if [ ! -f "$absolute_path" ]; then
echo "Missing SQL file: $file_path" >&2
exit 1
fi
echo "Applying $file_path"
if command -v psql >/dev/null 2>&1; then
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f "$absolute_path"
elif command -v docker >/dev/null 2>&1; then
docker run --rm -i "$PSQL_IMAGE" \
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f - < "$absolute_path"
else
echo "Neither psql nor docker is available to run migrations." >&2
exit 1
fi
}
run_migration() {
migration_id="$1"
file_path="$2"
if is_migration_recorded "$migration_id"; then
echo "Skipping $file_path; migration $migration_id is already recorded."
return
fi
if [ "$(existing_objects_cover_migration "$migration_id")" = "yes" ]; then
echo "Detected existing database objects for $migration_id; recording without replay."
record_migration "$migration_id" "$file_path"
return
fi
run_sql_file "$file_path"
record_migration "$migration_id" "$file_path"
}
reload_postgrest_schema_cache() {
echo "Reloading PostgREST schema cache."
run_sql "notify pgrst, 'reload schema';" >/dev/null
sleep "${NETA_POSTGREST_RELOAD_WAIT_SECONDS:-2}"
}
ensure_migration_table
while IFS='|' read -r migration_id sql_file; do
[ -n "$migration_id" ] || continue
run_migration "$migration_id" "$sql_file"
done <<'SQL_FILES'
0001_schema|supabase/schema.sql
0002_freelancer_os_core|supabase/migrations/0002_add_freelancer_os_core_tables.sql
0003_project_planning_assets|supabase/migrations/0003_add_project_planning_assets.sql
0004_business_os_tables|supabase/migrations/0004_add_business_os_tables.sql
0005_advanced_crm|supabase/migrations/0005_add_advanced_crm_tables.sql
0006_pgvector_embeddings|supabase/migrations/0006_add_pgvector_and_embeddings.sql
0007_client_portal|supabase/migrations/0007_add_client_portal_tables.sql
0008_project_progress_quota|supabase/migrations/0008_add_project_progress_and_quota.sql
0009_first_admin_registration_lock|supabase/migrations/0009_lock_registration_after_first_admin.sql
0010_internal_auth_creation|supabase/migrations/0010_allow_internal_auth_user_creation.sql
0011_service_role_claims_storage|supabase/migrations/0011_fix_service_role_claims_and_storage_policies.sql
SQL_FILES
reload_postgrest_schema_cache
echo "All Neta migrations were applied."
-54
View File
@@ -1,54 +0,0 @@
#!/usr/bin/env node
import crypto from "node:crypto";
const jwtSecret = process.env.JWT_SECRET || randomSecret();
const values = {
NETA_INSTALL_MODE: "full-stack",
NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000",
NETA_PORT: process.env.NETA_PORT || "3000",
NEXT_PUBLIC_SUPABASE_URL:
process.env.NEXT_PUBLIC_SUPABASE_URL || "http://localhost:8000",
SUPABASE_API_PORT: process.env.SUPABASE_API_PORT || "8000",
NEXT_PUBLIC_SUPABASE_ANON_KEY:
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || generateSupabaseJwt("anon", jwtSecret),
SUPABASE_SERVICE_ROLE_KEY:
process.env.SUPABASE_SERVICE_ROLE_KEY ||
generateSupabaseJwt("service_role", jwtSecret),
JWT_SECRET: jwtSecret,
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD || randomSecret(),
POSTGRES_PORT: process.env.POSTGRES_PORT || "54322",
JWT_EXPIRY: process.env.JWT_EXPIRY || "3600",
SMTP_ADMIN_EMAIL: process.env.SMTP_ADMIN_EMAIL || "admin@neta.local",
};
for (const [key, value] of Object.entries(values)) {
console.log(`${key}=${value}`);
}
function randomSecret() {
return crypto.randomBytes(32).toString("hex");
}
function generateSupabaseJwt(role, secret) {
const header = base64UrlJson({ alg: "HS256", typ: "JWT" });
const payload = base64UrlJson({
iss: "supabase",
ref: "neta",
role,
iat: 1700000000,
exp: 4102444800,
});
const unsigned = `${header}.${payload}`;
const signature = crypto
.createHmac("sha256", secret)
.update(unsigned)
.digest("base64url");
return `${unsigned}.${signature}`;
}
function base64UrlJson(value) {
return Buffer.from(JSON.stringify(value)).toString("base64url");
}
-58
View File
@@ -1,58 +0,0 @@
#!/usr/bin/env sh
set -eu
BACKUP_ROOT="${NETA_BACKUP_DIR:-./backups}"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_DIR="$BACKUP_ROOT/$STAMP"
info() {
printf "%s\n" "$1"
}
fail() {
echo "Error: $1" >&2
exit 1
}
require_command() {
command -v "$1" >/dev/null 2>&1 || fail "$1 is required."
}
require_container() {
name="$1"
docker inspect "$name" >/dev/null 2>&1 || fail "Container '$name' was not found."
}
require_command docker
require_command tar
require_container neta-db
require_container neta-storage
mkdir -p "$BACKUP_DIR"
BACKUP_DIR_ABS="$(cd "$BACKUP_DIR" && pwd)"
info "Creating Postgres backup"
docker exec neta-db pg_dump \
-U postgres \
-d postgres \
--format=custom \
--clean \
--if-exists \
--file=/tmp/neta-postgres.dump
docker cp neta-db:/tmp/neta-postgres.dump "$BACKUP_DIR_ABS/postgres.dump"
docker exec neta-db rm -f /tmp/neta-postgres.dump
info "Creating Storage backup"
docker cp neta-storage:/var/lib/storage "$BACKUP_DIR_ABS/storage"
tar -czf "$BACKUP_DIR_ABS/storage.tar.gz" -C "$BACKUP_DIR_ABS/storage" .
rm -rf "$BACKUP_DIR_ABS/storage"
cat > "$BACKUP_DIR_ABS/manifest.txt" <<EOF
neta_backup_version=1
created_at_utc=$STAMP
postgres_dump=postgres.dump
storage_archive=storage.tar.gz
EOF
info "Backup created: $BACKUP_DIR_ABS"
-161
View File
@@ -1,161 +0,0 @@
#!/usr/bin/env sh
set -u
FAILURES=0
pass() {
printf "ok %s\n" "$1"
}
fail() {
printf "fail %s\n" "$1"
FAILURES=$((FAILURES + 1))
}
check_container_running() {
name="$1"
if docker inspect -f '{{.State.Running}}' "$name" 2>/dev/null | grep -q true; then
pass "$name is running"
else
fail "$name is not running"
fi
}
check_container_exit_zero() {
name="$1"
if [ "$(docker inspect -f '{{.State.ExitCode}}' "$name" 2>/dev/null)" = "0" ]; then
pass "$name exited successfully"
else
fail "$name did not exit successfully"
fi
}
check_container_health() {
name="$1"
status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$name" 2>/dev/null || true)"
if [ "$status" = "healthy" ]; then
pass "$name health is $status"
else
fail "$name health is $status"
fi
}
check_exec() {
label="$1"
shift
if "$@" >/dev/null 2>&1; then
pass "$label"
else
fail "$label"
fi
}
if ! command -v docker >/dev/null 2>&1; then
echo "fail docker is not installed"
exit 1
fi
if ! docker info >/dev/null 2>&1; then
echo "fail docker daemon is not reachable"
exit 1
fi
for container in neta-db neta-auth neta-rest neta-storage neta-supabase-proxy neta-web; do
check_container_running "$container"
done
check_container_exit_zero neta-migrations
check_container_health neta-db
check_container_health neta-web
check_container_health neta-supabase-proxy
check_exec "web health endpoint returns JSON" \
docker exec neta-web wget -qO- http://127.0.0.1:3000/api/health
check_exec "supabase proxy health endpoint responds" \
docker exec neta-supabase-proxy wget -qO- http://127.0.0.1:8000/health
check_exec "auth settings endpoint responds through proxy" \
docker exec neta-supabase-proxy wget -qO- http://127.0.0.1:8000/auth/v1/settings
check_exec "database contains Neta tables" \
docker exec neta-db sh -c "psql -U postgres -d postgres -tAc \"select to_regclass('public.profiles') is not null and to_regclass('public.projects') is not null\" | grep -q t"
if [ "${NETA_DOCTOR_AUTH_SMOKE:-0}" = "1" ]; then
profile_count="$(docker exec neta-db sh -c "psql -U postgres -d postgres -tAc \"select count(*) from public.profiles\"" 2>/dev/null | tr -d "[:space:]")"
if [ "$profile_count" = "0" ]; then
check_exec "auth signup/login smoke succeeds" \
docker exec neta-web node -e '
const apiUrl = process.env.SUPABASE_INTERNAL_URL || process.env.NEXT_PUBLIC_SUPABASE_URL;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
const email = `doctor-${Date.now()}@neta.local`;
const password = "Test123456!";
const headers = {
apikey: anonKey,
authorization: `Bearer ${anonKey}`,
"content-type": "application/json",
};
async function request(path, body) {
const response = await fetch(`${apiUrl}${path}`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`${path} returned ${response.status}: ${await response.text()}`);
}
return response.json();
}
(async () => {
const signup = await request("/auth/v1/signup", { email, password });
const login = await request("/auth/v1/token?grant_type=password", { email, password });
if (!signup.user?.id || !login.access_token || signup.user.id !== login.user?.id) {
throw new Error("signup/login response did not include a matching user and token");
}
})().catch((error) => {
console.error(error.message);
process.exit(1);
});
'
else
check_exec "auth password endpoint rejects invalid login without database errors" \
docker exec neta-web node -e '
const apiUrl = process.env.SUPABASE_INTERNAL_URL || process.env.NEXT_PUBLIC_SUPABASE_URL;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
const email = `doctor-${Date.now()}@neta.local`;
const headers = {
apikey: anonKey,
authorization: `Bearer ${anonKey}`,
"content-type": "application/json",
};
(async () => {
const response = await fetch(`${apiUrl}/auth/v1/token?grant_type=password`, {
method: "POST",
headers,
body: JSON.stringify({ email, password: "NotARealPassword123!" }),
});
if (response.status >= 500) {
throw new Error(`password endpoint returned ${response.status}: ${await response.text()}`);
}
if (response.ok) {
throw new Error("invalid credentials unexpectedly succeeded");
}
})().catch((error) => {
console.error(error.message);
process.exit(1);
});
'
fi
fi
if [ "$FAILURES" -gt 0 ]; then
printf "\n%d check(s) failed.\n" "$FAILURES"
exit 1
fi
printf "\nAll self-host checks passed.\n"
-73
View File
@@ -1,73 +0,0 @@
#!/usr/bin/env sh
set -eu
BACKUP_DIR="${1:-}"
info() {
printf "%s\n" "$1"
}
fail() {
echo "Error: $1" >&2
exit 1
}
require_command() {
command -v "$1" >/dev/null 2>&1 || fail "$1 is required."
}
require_container() {
name="$1"
docker inspect "$name" >/dev/null 2>&1 || fail "Container '$name' was not found."
}
if [ -z "$BACKUP_DIR" ]; then
fail "Backup directory is required. Usage: sh ./scripts/selfhost-restore.sh ./backups/20260101T120000Z"
fi
require_command docker
require_command tar
require_container neta-db
require_container neta-storage
[ -f "$BACKUP_DIR/postgres.dump" ] || fail "Missing $BACKUP_DIR/postgres.dump"
[ -f "$BACKUP_DIR/storage.tar.gz" ] || fail "Missing $BACKUP_DIR/storage.tar.gz"
BACKUP_DIR_ABS="$(cd "$BACKUP_DIR" && pwd)"
STORAGE_RESTORE_DIR="$BACKUP_DIR_ABS/.storage-restore"
if [ "${NETA_RESTORE_FORCE:-}" != "1" ]; then
echo "This will overwrite the current full-stack Neta database and local storage."
printf "Type RESTORE to continue: "
read answer
[ "$answer" = "RESTORE" ] || fail "Restore cancelled."
fi
info "Stopping application services"
docker stop neta-web neta-supabase-proxy neta-storage neta-rest neta-auth >/dev/null 2>&1 || true
info "Restoring Postgres"
docker cp "$BACKUP_DIR_ABS/postgres.dump" neta-db:/tmp/neta-postgres-restore.dump
docker exec neta-db pg_restore \
-U postgres \
-d postgres \
--clean \
--if-exists \
/tmp/neta-postgres-restore.dump
docker exec neta-db rm -f /tmp/neta-postgres-restore.dump
info "Restoring Storage"
docker start neta-storage >/dev/null
rm -rf "$STORAGE_RESTORE_DIR"
mkdir -p "$STORAGE_RESTORE_DIR"
tar -xzf "$BACKUP_DIR_ABS/storage.tar.gz" -C "$STORAGE_RESTORE_DIR"
docker exec neta-storage sh -c "rm -rf /var/lib/storage/* /var/lib/storage/.[!.]* /var/lib/storage/..?* 2>/dev/null || true; mkdir -p /var/lib/storage"
docker cp "$STORAGE_RESTORE_DIR/." neta-storage:/var/lib/storage
rm -rf "$STORAGE_RESTORE_DIR"
docker restart neta-storage >/dev/null
info "Starting application services"
docker start neta-auth neta-rest neta-supabase-proxy neta-web >/dev/null
info "Restore completed."