{ backupFile ? "" }: with import { }; pkgs.mkShell { name = "Solid Scribe Env"; buildInputs = [ nodejs nodemon pnpm pm2 mariadb openssl ]; 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." mysql -u root --socket="$MYSQL_SOCKET" \ -e "CREATE DATABASE IF NOT EXISTS application;" 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= # 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 ''; }