Having done bespoke software development for about 20 years, here's what I'd do in your shoes:
- If I hadn't done so, determine an agreed-upon list of the deliverable software changes - individual features, bug fixes, etc.
- Estimate how long I think each of those tasks will take, individually. You get a much better estimate that way than looking at the project as a whole. In fact, the more you can break it down, the better your estimate will be. But don't give those individual task estimates to the customer.
- Add those up, and double that to account for unknowns. Call that doubled amount X.
Then I'd send a simple email or letter saying:
- Here's the list of changes we agreed on. I estimate this will take at most a total of X hours.
- I work on a time-and-materials basis, with time billed at $Y per hour.
- If - due to unforeseen factors - the project reaches 90% of X and it's not complete, I will stop work and contact you to discuss whether to proceed.
I've found this approach is a win for both sides. The customer gets the fixed-price advantage of a "cap" on their financial exposure, and you aren't stuck doing an endless amount of work for a fixed price.
This is also very easy to embody in a contract. In the US, you can typically use a very simple two-page contract that refers to a "scope-of-work" addendum. Then that addendum is a simple, single page that lists the changes and an estimate of Y hours maximum.
Other than your hourly rate, the only objection most reasonable people can raise would be that X is too long. The counter to that is to explain that you don't actually expect the project to take that long, but you wanted to account for unknowns and still give them a cap you're comfortable that you won't exceed. Under no circumstances should you tell them your original, unmodified estimate; it could become a bargaining point (e.g. something the customer can argue about) that would not be to your advantage.
As other people have mentioned, a common problem with fixed-price projects is the customer can "one more little thing" you into the poorhouse. A nice thing about this approach is that for simple requests, you can just get verbal agreement to bill them for a few more hours, and for more complicated requests, you can add more scope-of-work pages as further contract addenda.
You're in a very fortunate position: they need you more than you need them, so you can pretty much dictate the contract terms. If you propose something reasonable like this, they should be happy. If they're not happy - and especially if they demand a fixed-price contract despite the cap I suggested above - that's an early warning that they're going to be a pain in the neck to deal with, so I'd just say, "Sorry we can't come to an agreement," and walk away.