Google does this natively and it is better than anything worth building.
Open calendar.google.com, signed in.
Top left, the Create button. Choose Appointment schedule.
Give it a title and a length. Mine is “Twenty minutes with Paul,” 20 minutes.
Set the days and hours you are willing to be booked. It checks your real calendar,
so nobody can book over something you already have.
Under location, pick Google Meet. It generates a fresh link for every booking, so
there is no standing room anyone can wander into. That matters more than it sounds.
Next, then Save.
The schedule now appears on your calendar as shaded blocks during your available
hours. Click one, then Open booking page or Share, and copy the link.
That link is the whole thing — hand it out as it is. I also put a short address in front
of mine so it is easy to say out loud: wholetech.com/book simply redirects to the
Google page. One line of web server config, and there is still only one page to keep correct.
The busy calendar — more work, and worth it
This one answers “when is he free” without answering “what is he doing.”
Google Calendar → Settings → click your own calendar in the left list.
Scroll to Integrate calendar and find Secret address in iCal format.
A script on my server fetches that feed hourly and writes a file containing
four things per event and nothing else: start, end, all-day, and the word
“Busy.”
The web page reads that file and draws a month grid.
Treat the secret address as a password. Anyone holding it can read your entire
calendar — every title, attendee and location. It lives in a root-only file on my
server, never in a web folder, and it should never be pasted anywhere public.
Why the middle step exists
A straight mirror would publish where I am, who I am seeing, and when I am away from the
property. Filtering it down to busy blocks means people can find a gap without learning
anything about my life. The allow-list is enforced in code rather than by habit: four keys,
re-checked before every write, and the script refuses to publish if anything unexpected
appears.
The one exception is deliberate — an event titled starting with PUBLIC:
shows its name, so I can advertise something when I actually want to.
An honest caveat
Complicated repeating events — “third Thursday of the month” and similar —
are left out rather than guessed at. So a slot can show free when it is not. The script
counts them and says so on every run. Better than quietly getting it wrong, but worth knowing
before you rely on it.