# Restoring from a backup

This is the disaster-recovery procedure: your Windows machine died, or you're
setting the books up on a brand-new machine, and all you have is a backup
file from `Documents\Accounting Backups\` (or wherever `BACKUP_DIR` pointed).

**Read this before you type anything.** The single most important rule:

> **Restore into a FRESH, EMPTY database. Never restore over the live one.**
>
> A restore does not "sync" or "update" anything — it replays a full
> `CREATE TABLE ... INSERT ...` script. If you run it against a database that
> already has data, you will get duplicate-key errors at best, and a
> silently corrupted mix of old and new data at worst. This document
> therefore restores into a brand-new database with a different name, and
> only swaps it into place at the end, once you've seen it load cleanly. The
> old database is renamed out of the way rather than deleted — you throw it
> away yourself, later, once you're sure.

You will not need Docker, and you will not need to install PostgreSQL.
Balanced Sum brings its own copy, and every command below uses it.

---

## 1. Get Node.js and the application code onto the machine

If this is a brand-new machine, do sections **2.1** and **2.2** of
[GETTING-STARTED.md](GETTING-STARTED.md) first: install Node.js, then unzip
(or clone) the application folder and run `npm install` inside it.

Stop there. Do **not** go on to use the setup wizard — the whole point of
this document is that your books come from the backup, not from a wizard.

## 2. Run the app once, then stop it

From the application folder, in PowerShell:

```powershell
npm run app
```

On a new machine this is the run that downloads PostgreSQL (about 25 MB) and
sets it up — a couple of minutes, and it narrates every step. Let it finish
until it prints:

```
  Balanced Sum is running at http://localhost:3000
  Press Ctrl+C in this window to close it and shut the database down.
```

Then press **Ctrl+C** and let it shut the database down. Ignore the browser
tab it opened; don't create a company.

**This step is not optional, and it is not just "checking it works."** On a
new machine it is what puts PostgreSQL on the computer at all — there is
nothing to restore into until it has run. It also creates the login account
the app uses day to day, which your backup refers to by name; skip it and the
restore in section 7 stops partway through complaining that a role called
`app` does not exist.

## 3. Set up your PowerShell session

The three things every command below needs: where PostgreSQL's tools are,
where the database lives, and the password. Paste this block once, into the
PowerShell window you're going to use (it does **not** survive into a new
window — re-paste it if you open one):

```powershell
$pgbin  = "$env:LOCALAPPDATA\BalancedSum\runtime\postgresql-16.10-win32-x64\bin"
$pgdata = "$env:LOCALAPPDATA\BalancedSum\data\pg16"
$env:PGPASSWORD = Get-Content "$env:LOCALAPPDATA\BalancedSum\secret" -Raw
```

About that last line: **the database has a password, and it was generated for
your machine alone.** It is not written in `.env`, it is not in the
application folder, and there is no default to guess. It lives in a single
file, `secret`, readable only by your Windows account, next to the database
itself. That line reads it into this PowerShell session so `psql` stops asking
you for it. Check it worked before going on — this should print `32`:

```powershell
$env:PGPASSWORD.Length
```

Two paths in that block have version numbers in them, and one day they won't
be these ones. If either fails with "path not found," look at what's actually
there and adjust:

```powershell
Get-ChildItem "$env:LOCALAPPDATA\BalancedSum\runtime"   # -> postgresql-16.10-win32-x64
Get-ChildItem "$env:LOCALAPPDATA\BalancedSum\data"      # -> pg16
```

The same goes for the port used throughout this document, **54329**. If you
ever need to confirm it rather than trust it, your machine wrote it down:

```powershell
Get-Content "$env:LOCALAPPDATA\BalancedSum\instance.json"
```

## 4. Start the database

`npm run app` normally starts and stops the database for you, but for this
procedure you need it running *without* the app connected to it. Start it by
hand:

```powershell
& "$pgbin\pg_ctl.exe" -D $pgdata -l "$env:LOCALAPPDATA\BalancedSum\logs\postgresql.log" -o "-p 54329 -c listen_addresses=127.0.0.1" start
```

```
waiting for server to start.... done
server started
```

The `-o` part is not decoration: **54329**, not PostgreSQL's usual 5432, is
the port Balanced Sum uses, and `listen_addresses=127.0.0.1` keeps the
database reachable only from this computer. Without them you would start a
second, differently-configured server that the app can't find.

Confirm it's up:

```powershell
& "$pgbin\pg_isready.exe" -h 127.0.0.1 -p 54329
```

```
127.0.0.1:54329 - accepting connections
```

**Leave Balanced Sum itself closed for the rest of this document.** A running
app holds connections to the database you're about to rename, and PostgreSQL
will refuse to rename a database anyone is connected to.

## 5. Create a FRESH, empty database for the restore

```powershell
& "$pgbin\psql.exe" -h 127.0.0.1 -p 54329 -U postgres -d postgres -c "CREATE DATABASE accounting_restored;"
```

```
CREATE DATABASE
```

(Pick whatever name you like — `accounting_restored` is just a suggestion.
The important part is that it did not exist a moment ago. Your real books
live in a database called `accounting`, and nothing so far has touched it.)

## 6. Un-gzip the backup

Copy your backup file (`accounting-YYYYMMDD-HHMMSS.sql.gz`) to the machine if
it isn't there already, then move the **same** PowerShell window into the
folder containing it — the settings from section 3 travel with you, they're
attached to the window, not to the folder:

```powershell
Set-Location "$env:USERPROFILE\Documents\Accounting Backups"
```

Now un-gzip it to a plain `.sql` file next to itself. Windows PowerShell has
no built-in gzip support, but Node.js (which you already installed in section
1) does, via its `zlib` module — and doing it this way (reading and writing
raw bytes) keeps the file's exact bytes intact:

```powershell
node -e "require('fs').writeFileSync('accounting-20260101-030000.sql', require('zlib').gunzipSync(require('fs').readFileSync('accounting-20260101-030000.sql.gz')))"
```

(Replace the filename with your actual backup's name, in both places.)

## 7. Load it

**Use `psql -f`, and do NOT pipe the file in.** It's tempting to write
`Get-Content -Raw ... | psql`, but PowerShell pipes text through its own
string encoding on the way — any account name, memo, or customer name with an
accented or non-English character (e.g. "Café", "Müller") comes out the other
side silently mangled (e.g. "CafÃ©"), with **no error at all** to warn you.
`-f` hands `psql` the filename and lets it read the bytes itself, which is the
only safe way to do this:

```powershell
& "$pgbin\psql.exe" -h 127.0.0.1 -p 54329 -U postgres -d accounting_restored -v ON_ERROR_STOP=1 -f "accounting-20260101-030000.sql"
```

`ON_ERROR_STOP=1` is the difference between a restore that fails and one that
*looks* like it worked. Without it, `psql` prints an error, carries on to the
next statement, and finishes with a cheerful-looking wall of output hiding a
missing table somewhere in the middle. With it, the first error stops
everything.

A clean restore ends with a stream of `CREATE TABLE` / `ALTER TABLE` /
`COPY ... rows` / `GRANT` lines and no `ERROR:` lines. Check the exit code:

```powershell
$LASTEXITCODE
```

`0` means it loaded. Anything else means **stop** — do not point the app at
this database. The likeliest causes are that you restored into a database
that wasn't actually empty (go back to step 5 with a genuinely new name), or
that the file didn't un-gzip cleanly.

Have a quick look at what arrived before you commit to it:

```powershell
& "$pgbin\psql.exe" -h 127.0.0.1 -p 54329 -U postgres -d accounting_restored -c "SELECT name FROM companies ORDER BY name;"
```

The companies you expect should be listed. If that comes back empty and your
books were not empty, something is wrong — stop here.

## 8. Swap it into place

Now, and only now, put the restored database where the app looks for it. The
old one is **renamed, not dropped**, so it's still there if you need it:

```powershell
& "$pgbin\psql.exe" -h 127.0.0.1 -p 54329 -U postgres -d postgres -c "ALTER DATABASE accounting RENAME TO accounting_before_restore;"
& "$pgbin\psql.exe" -h 127.0.0.1 -p 54329 -U postgres -d postgres -c "ALTER DATABASE accounting_restored RENAME TO accounting;"
```

```
ALTER DATABASE
ALTER DATABASE
```

If the first one complains that the database is being accessed by other
users, Balanced Sum is still running somewhere — close it (Ctrl+C in its
PowerShell window) and try again. Do not force it.

Nothing else needs configuring. You do **not** edit `.env`, and you do not
set `DATABASE_URL`: the app finds its own database by name, and the name is
now correct.

## 9. Stop the database

Hand it back to the app:

```powershell
& "$pgbin\pg_ctl.exe" -D $pgdata -m fast stop
```

```
waiting for server to shut down.... done
server stopped
```

## 10. Verify by actually running the app

There is no automated verification step here — the real test is starting the
application and looking at your books:

```powershell
npm run app
```

It will start the database again, apply any migrations the backup predates,
and open the dashboard. Confirm:

- The company (or companies) you expect are listed in the company switcher.
- The dashboard tiles (cash, YTD in, YTD profit) show numbers that look
  right for when the backup was taken.
- Open Reports → Trial Balance and spot-check a few account balances
  against what you remember, or against the last reconciliation.
- Open Year-end Close and confirm previously-locked years still show as
  locked.
- If any account, customer, or memo names include accented or non-English
  characters, confirm they display correctly (not as garbled symbols) —
  that's the specific failure mode step 7's `-f` avoids.

If all of that looks right, the restore succeeded.

---

## Afterwards: throwing away the old database

`accounting_before_restore` is still sitting there, costing you nothing but
disk space. Leave it for a few days of normal use. When you're confident, and
with Balanced Sum closed, sections 3 and 4 will get you a running database
again, and then:

```powershell
& "$pgbin\psql.exe" -h 127.0.0.1 -p 54329 -U postgres -d postgres -c "DROP DATABASE accounting_before_restore;"
```

This is the irreversible step, and it is deliberately the only one in this
document. Nothing above it destroys anything.

## If the restore went wrong

Everything is recoverable up until you run that `DROP`. To put the old
database back exactly as it was, with Balanced Sum closed and the database
started per sections 3 and 4:

```powershell
& "$pgbin\psql.exe" -h 127.0.0.1 -p 54329 -U postgres -d postgres -c "ALTER DATABASE accounting RENAME TO accounting_bad_restore;"
& "$pgbin\psql.exe" -h 127.0.0.1 -p 54329 -U postgres -d postgres -c "ALTER DATABASE accounting_before_restore RENAME TO accounting;"
```

Then stop the database (section 9), start the app, and you are back where you
began — with the failed attempt still on disk as `accounting_bad_restore` if
anyone needs to look at it.
