{ backupFile ? "" }: with import { }; pkgs.mkShell { name = "Solid Scribe Env"; buildInputs = [ nodejs nodemon pnpm pm2 mariadb openssl # Schema migrations and DB scaffolding only -- the running server never # imports Prisma. The nixpkgs wrapper is self-configuring: it exports # PRISMA_SCHEMA_ENGINE_BINARY pointing at a nix-built prisma-engines and # execs the bundled CLI, so there is no engine download and none of the # usual NixOS prebuilt-binary trouble. # # Pinned to prisma_7 deliberately, NOT the `prisma` alias: Prisma 8 has no # MariaDB support yet, so if nixpkgs moves that alias to v8 this project's # database would stop being supported underneath us. prisma_7 ]; shellHook = '' echo "Welcome to Solid Scribe" alias npm=pnpm MYSQL_DATADIR="$PWD/.mysql/data" MYSQL_SOCKET="$PWD/.mysql/mysql.sock" MYSQL_PIDFILE="$PWD/.mysql/mysql.pid" # # Work out what database situation we are in BEFORE touching anything. # This shell is used on the production server too, where a system MariaDB # is already running -- we must not initialise a datadir, start a second # server on port 3306, or inject dev credentials over the real ones. # # ours - the project-local server is already up (another nix-shell) # existing - some other server is listening; not ours to manage # start - nothing is running; this is a dev box, bring one up # SS_DB_MODE="start" if mysqladmin --socket="$MYSQL_SOCKET" ping &>/dev/null 2>&1; then SS_DB_MODE="ours" elif (echo >/dev/tcp/127.0.0.1/3306) &>/dev/null \ || [ -S /run/mysqld/mysqld.sock ] \ || [ -S /var/run/mysqld/mysqld.sock ] \ || [ -S /tmp/mysql.sock ]; then SS_DB_MODE="existing" fi if [ "$SS_DB_MODE" = "existing" ]; then echo "Existing database detected -- not starting the project one." echo "Leaving DB_* and JSON_KEY alone so the server's own config wins." fi STARTED_MYSQL=0 if [ "$SS_DB_MODE" = "start" ]; then mkdir -p "$MYSQL_DATADIR" # Initialize data directory on first run if [ ! -d "$MYSQL_DATADIR/mysql" ]; then echo "Initializing MySQL data directory (first run)..." mysql_install_db \ --datadir="$MYSQL_DATADIR" \ --skip-test-db \ --auth-root-authentication-method=normal \ 2>/dev/null echo "Initialization complete." fi STARTED_MYSQL=1 echo "Starting MySQL..." mysqld --no-defaults \ --datadir="$MYSQL_DATADIR" \ --socket="$MYSQL_SOCKET" \ --pid-file="$MYSQL_PIDFILE" \ --port=3306 \ --bind-address=127.0.0.1 \ --log-error="$PWD/.mysql/error.log" & until mysqladmin --socket="$MYSQL_SOCKET" ping &>/dev/null 2>&1; do sleep 0.5 done echo "MySQL ready." # # Collation is explicit because the server default here is # utf8mb4_unicode_ci while every application table is # utf8mb4_general_ci. Without this, a table created later without an # explicit COLLATE would silently inherit the wrong one, changing string # comparison and FULLTEXT ranking. Prisma does not model collation and # `prisma migrate diff` does not report the difference. # # Existing databases need a one-time, non-destructive fix: # ALTER DATABASE application # CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # mysql -u root --socket="$MYSQL_SOCKET" \ -e "CREATE DATABASE IF NOT EXISTS application \ CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" 2>/dev/null fi # # Dev credentials, injected ONLY when this shell is providing the database # and there is no ~/.env supplying real config. # # This matters because dotenv does NOT override variables already present in # process.env (server/config/database.js loads ~/.env). So anything exported # here silently beats the server's real configuration -- including JSON_KEY, # which would invalidate every existing user session. # if [ "$SS_DB_MODE" != "existing" ] && [ ! -f "$HOME/.env" ]; then export DB_HOST=127.0.0.1 export DB_USER=root export DB_PASS= # # Prisma CLI connection string, for migrations and scaffolding. # # The warning above about shadowing ~/.env does NOT apply here: # DATABASE_URL is read only by the Prisma CLI, never by # server/config/database.js, so it cannot affect the running server. # # Deliberately NOT derived from DB_USER/DB_PASS. Prisma needs DDL # rights, and in production those variables hold the least-privilege # `app` account. Admin credentials are composed by migrateDatabase.sh # instead, which is why this export lives only in the dev branch. # export DATABASE_URL="mysql://root@127.0.0.1:3306/application" # Stop the Prisma CLI phoning checkpoint.prisma.io for update checks. # Keeps the schema tests network-free and the output uncluttered. export CHECKPOINT_DISABLE=1 # Stable dev JWT key, persisted across shell sessions if [ -z "''${JSON_KEY:-}" ]; then if [ -s "$PWD/.mysql/jwt_key" ]; then export JSON_KEY=$(cat "$PWD/.mysql/jwt_key") else export JSON_KEY=$(openssl rand -hex 32) echo "$JSON_KEY" > "$PWD/.mysql/jwt_key" echo "Generated dev JSON_KEY (saved to .mysql/jwt_key)" fi fi elif [ -f "$HOME/.env" ]; then echo "~/.env found -- using its DB_* and JSON_KEY, not dev defaults." fi # Restore a SQL dump — handles both plain .sql and .gz files restore-db() { local file="''${1:-}" if [ -z "$file" ]; then echo "Usage: restore-db " return 1 fi # Refuse to run against a database this shell does not manage. The dumps # are --all-databases: restoring one would overwrite every schema on the # server and reset root's password. That is fine for a local dev copy and # catastrophic on the production box. if [ "$SS_DB_MODE" = "existing" ]; then echo "Refusing: this shell is talking to a pre-existing database." >&2 echo "restore-db only runs against the project-local dev database." >&2 return 1 fi echo "Restoring database from $file ..." if [[ "$file" == *.gz ]]; then gunzip -c "$file" | mysql -u root --socket="$MYSQL_SOCKET" else mysql -u root --socket="$MYSQL_SOCKET" < "$file" fi # The dump is --all-databases, so it includes the `mysql` schema and # overwrites local root with production's credentials. In-memory grants # are still the old ones until FLUSH, so fix root now while we can # still authenticate. mysql -u root --socket="$MYSQL_SOCKET" -e " ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('''); GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION; FLUSH PRIVILEGES;" echo "Restore complete." } export -f restore-db # Auto-restore if --argstr backupFile was passed to nix-shell # (restore-db itself refuses when the database is not ours) if [ -n "${backupFile}" ]; then restore-db "${backupFile}" fi if [ "$STARTED_MYSQL" = "1" ]; then trap "echo 'Stopping MySQL...'; mysqladmin -u root --socket=$MYSQL_SOCKET shutdown 2>/dev/null" EXIT else echo "MySQL already running (started by another shell) -- leaving it up on exit." fi # Do not start the app here. # This same shell.nix is used for hosting the app on dev # use the startDevServerAndClient.sh script ''; }