Migriere Persistenz von SQLite zu PostgreSQL (postgresql.lan:5432/essens)
- Dockerfile: pdo_pgsql installieren - migrations: Postgres-Dialekt (SERIAL, TIMESTAMP(0), TEXT) - compose/.env(.dist): postgresql://DATABASE_URL (Passwort via .env.local) - bin/migrate-sqlite-to-pg.sh: einmaliger Datenimport SQLite->PG
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
# Referenzwerte für die Produktion (docker1: /root/stacks/svc-essensplanung/.env,
|
# Referenzwerte für die Produktion (docker1: /root/stacks/svc-essensplanung/.env.local,
|
||||||
# als env_file über compose gemountet – siehe Doku/plan.md M5).
|
# als env_file über compose gemountet – siehe Doku/plan.md M5).
|
||||||
# Lokale Entwicklung nutzt stattdessen .env (var/data.db).
|
# Lokale Entwicklung nutzt die gleiche DB (postgresql.lan).
|
||||||
APP_ENV=prod
|
APP_ENV=prod
|
||||||
APP_DEBUG=0
|
APP_DEBUG=0
|
||||||
APP_SECRET=<random-64-hex>
|
APP_SECRET=<random-64-hex>
|
||||||
DATABASE_URL=sqlite:////data/essens.db
|
DATABASE_URL=postgresql://essens:<passwort>@postgresql.lan:5432/essens?serverVersion=15&charset=utf8
|
||||||
KEYCLOAK_ISSUER=https://iam.ljh31.de/realms/family
|
KEYCLOAK_ISSUER=https://iam.ljh31.de/realms/family
|
||||||
KEYCLOAK_CLIENT_ID=essens
|
KEYCLOAK_CLIENT_ID=essens
|
||||||
KEYCLOAK_CLIENT_SECRET=<keycloak-client-secret>
|
KEYCLOAK_CLIENT_SECRET=<keycloak-client-secret>
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ RUN apt-get update \
|
|||||||
&& apt-get install -y --no-install-recommends git unzip sqlite3 libicu-dev \
|
&& apt-get install -y --no-install-recommends git unzip sqlite3 libicu-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN docker-php-ext-install bcmath intl
|
RUN docker-php-ext-install bcmath intl pdo_pgsql
|
||||||
|
|
||||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
|||||||
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Einmalige Migration: SQLite (/data/essens.db, Volume essens_data) -> PostgreSQL (postgresql.lan/essens)
|
||||||
|
# Läuft auf docker1 im Stack-Verzeichnis. Erwartet: .env.local enthält DATABASE_URL="postgresql://essens:<pw>@postgresql.lan:5432/essens?..."
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
DB_URL=$(grep -m1 '^DATABASE_URL=' .env.local | sed 's/^DATABASE_URL=//; s/^"//; s/"$//')
|
||||||
|
PG_USER=$(sed -n 's|.*://\([^:@]*\):.*|\1|p' <<<"$DB_URL")
|
||||||
|
PG_PASS=$(sed -n 's|.*://[^:@]*:\([^@]*\)@.*|\1|p' <<<"$DB_URL")
|
||||||
|
PG_HOST=$(sed -n 's|.*@\([^:/?]*\).*|\1|p' <<<"$DB_URL")
|
||||||
|
PG_DB=$(sed -n 's|.*:/\([^?]*\).*|\1|p' <<<"$DB_URL")
|
||||||
|
export PGPASSWORD="$PG_PASS"
|
||||||
|
|
||||||
|
echo "== 1) SQLite-Export (CSV) aus dem laufenden Container"
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMP"' EXIT
|
||||||
|
docker exec svc-essensplanung sh -c 'sqlite3 -cmd ".nullvalue NULL" -csv /data/essens.db "SELECT * FROM essens_user;"' > "$TMP/essens_user.csv"
|
||||||
|
docker exec svc-essensplanung sh -c 'sqlite3 -cmd ".nullvalue NULL" -csv /data/essens.db "SELECT * FROM essens_mahlzeiten;"' > "$TMP/essens_mahlzeiten.csv"
|
||||||
|
echo " essens_user: $(wc -l < "$TMP/essens_user.csv") Zeilen"
|
||||||
|
echo " essens_mahlzeiten: $(wc -l < "$TMP/essens_mahlzeiten.csv") Zeilen"
|
||||||
|
|
||||||
|
echo "== 2) Schema-Migrations auf PostgreSQL"
|
||||||
|
docker compose run --rm --no-deps app bin/console doctrine:migrations:migrate --no-interaction
|
||||||
|
|
||||||
|
echo "== 3) Datenimport via COPY"
|
||||||
|
psql -h "$PG_HOST" -U "$PG_USER" -d "$PG_DB" <<EOF
|
||||||
|
BEGIN;
|
||||||
|
COPY essens_user (id, username, email, kc_sub, created_am, updated_am) FROM STDIN WITH (FORMAT csv);
|
||||||
|
$(cat "$TMP/essens_user.csv")
|
||||||
|
\.
|
||||||
|
COPY essens_mahlzeiten (id, name, notiz, wochentage, sort_order, status, gegessen_am, erstellt_am, aktualisiert_am) FROM STDIN WITH (FORMAT csv);
|
||||||
|
$(cat "$TMP/essens_mahlzeiten.csv")
|
||||||
|
\.
|
||||||
|
SELECT setval('essens_user_id_seq', COALESCE(MAX(id), 0) + 1, false) FROM essens_user;
|
||||||
|
SELECT setval('essens_mahlzeiten_id_seq', COALESCE(MAX(id), 0) + 1, false) FROM essens_mahlzeiten;
|
||||||
|
COMMIT;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "== 4) Verifikation"
|
||||||
|
psql -h "$PG_HOST" -U "$PG_USER" -d "$PG_DB" -c 'SELECT (SELECT count(*) FROM essens_user) AS users, (SELECT count(*) FROM essens_mahlzeiten) AS mahlzeiten;'
|
||||||
@@ -9,7 +9,6 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
APP_ENV: prod
|
APP_ENV: prod
|
||||||
APP_DEBUG: '0'
|
APP_DEBUG: '0'
|
||||||
DATABASE_URL: sqlite:////data/essens.db
|
|
||||||
DEFAULT_URI: https://essen.ljh31.de
|
DEFAULT_URI: https://essen.ljh31.de
|
||||||
KEYCLOAK_ISSUER: https://iam.ljh31.de/realms/family
|
KEYCLOAK_ISSUER: https://iam.ljh31.de/realms/family
|
||||||
KEYCLOAK_CLIENT_ID: essen
|
KEYCLOAK_CLIENT_ID: essen
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"php": ">=8.2",
|
"php": ">=8.2",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
|
"ext-pgsql": "*",
|
||||||
"doctrine/doctrine-bundle": "^3.3",
|
"doctrine/doctrine-bundle": "^3.3",
|
||||||
"doctrine/doctrine-migrations-bundle": "^4.0",
|
"doctrine/doctrine-migrations-bundle": "^4.0",
|
||||||
"doctrine/orm": "^3.7",
|
"doctrine/orm": "^3.7",
|
||||||
|
|||||||
@@ -8,26 +8,24 @@ use Doctrine\DBAL\Schema\Schema;
|
|||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
* Postgres-Port der ursprünglichen SQLite-Migration (2026-09-13).
|
||||||
*/
|
*/
|
||||||
final class Version20260913204759 extends AbstractMigration
|
final class Version20260913204759 extends AbstractMigration
|
||||||
{
|
{
|
||||||
public function getDescription(): string
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
return '';
|
return 'essens_user (Postgres)';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
$this->addSql('CREATE TABLE essens_user (id SERIAL PRIMARY KEY, username VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, kc_sub VARCHAR(64) NOT NULL, created_am TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_am TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL)');
|
||||||
$this->addSql('CREATE TABLE essens_user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, kc_sub VARCHAR(64) NOT NULL, created_am DATETIME NOT NULL, updated_am DATETIME NOT NULL)');
|
|
||||||
$this->addSql('CREATE UNIQUE INDEX uniq_email ON essens_user (email)');
|
$this->addSql('CREATE UNIQUE INDEX uniq_email ON essens_user (email)');
|
||||||
$this->addSql('CREATE UNIQUE INDEX uniq_kc_sub ON essens_user (kc_sub)');
|
$this->addSql('CREATE UNIQUE INDEX uniq_kc_sub ON essens_user (kc_sub)');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP TABLE essens_user');
|
$this->addSql('DROP TABLE essens_user');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,26 +8,24 @@ use Doctrine\DBAL\Schema\Schema;
|
|||||||
use Doctrine\Migrations\AbstractMigration;
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
* Postgres-Port der ursprünglichen SQLite-Migration (2026-09-18).
|
||||||
*/
|
*/
|
||||||
final class Version20260918213517 extends AbstractMigration
|
final class Version20260918213517 extends AbstractMigration
|
||||||
{
|
{
|
||||||
public function getDescription(): string
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
return '';
|
return 'essens_mahlzeiten (Postgres)';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
public function up(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
$this->addSql('CREATE TABLE essens_mahlzeiten (id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, notiz TEXT DEFAULT NULL, wochentage VARCHAR(32) DEFAULT NULL, sort_order INTEGER NOT NULL, status VARCHAR(32) NOT NULL, gegessen_am TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, erstellt_am TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, aktualisiert_am TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL)');
|
||||||
$this->addSql('CREATE TABLE essens_mahlzeiten (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, notiz CLOB DEFAULT NULL, wochentage VARCHAR(32) DEFAULT NULL, sort_order INTEGER NOT NULL, status VARCHAR(32) NOT NULL, gegessen_am DATETIME DEFAULT NULL, erstellt_am DATETIME NOT NULL, aktualisiert_am DATETIME NOT NULL)');
|
|
||||||
$this->addSql('CREATE INDEX idx_status ON essens_mahlzeiten (status)');
|
$this->addSql('CREATE INDEX idx_status ON essens_mahlzeiten (status)');
|
||||||
$this->addSql('CREATE INDEX idx_sort_order ON essens_mahlzeiten (sort_order)');
|
$this->addSql('CREATE INDEX idx_sort_order ON essens_mahlzeiten (sort_order)');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
public function down(Schema $schema): void
|
||||||
{
|
{
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql('DROP TABLE essens_mahlzeiten');
|
$this->addSql('DROP TABLE essens_mahlzeiten');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user