On disk, a fresh page comes from growing the file. Today you make the file pager's AllocPage hand out the next page by extending the file, keeping page 0 reserved for the meta and tracking the page count.
Allocate a new page by extending the file, returning increasing ids starting after the reserved meta page.
Allocation on disk is growth: the next page is the one just past the current end of
the file, so AllocPage returns the current page count and bumps it. Page 0 is
always the meta, so a brand-new file starts with a page count of 1 and the first
node lands at id 1. Keeping the count in the pager (and, on the meta page, on disk)
means a reopened file knows exactly where its used region ends.
Growing forever would waste space as keys are deleted, which is why the next lesson adds a free list: freed pages get recycled before the file is extended again. For now, allocation is the simplest thing that can work - bump a counter, make sure the file is big enough - and it is enough to run the whole tree on disk. The recycling is a pure optimization layered on top, using the free-list head the meta page already reserves.
func (p *filePager) AllocPage() PageID {// if the free list is non-empty, reuse (next lesson); else:id := p.pageCount // next id past the endp.pageCount++// ensure the file is at least pageCount*PageSize bytesreturn id}