Result:

Geometry Nodes:



Note: In most seeds, most points are random, those who aren't are perceptually random.
What the Point Index Randomizer node group does is first generates a random index for every point, and if a point got and index that is used by an previous point, it gets an index that was not generated from a list containing all not generated indexes.

$$\text{Explanation}$$
First, for every point, a random index is generated with limits equal to: $\left(\text{min}=0,\text{max}=C-1\right)$, where $C$ is equal to the point count. I will call these rIndex.
Now points with an rIndex that is used by previous points are identified by accumulating $1$ with Group Input equal to rIndex. The trailing value is compared verified if it is equal to $0$, if it is, then the point have an unique $rIndex$ compared to preceding points. I will call the resulting boolean field notRepeated.
Example result of trailing:

Now it accumulates notRepeated, the trailing result is the same as the index of a repeating point if it were on a list with only the repeating points. I will call these indexRepeat
Here's an example:

indexRepeat is what will be used in repeating points to get from the unused index list an index that wasn't generated by the Random Value node. But first it needs to calculate that list.
To calculate the list, it first branch the points geometry, set the position of the points to $\left(0,0,\text{rIndex}\right)$ and then separates points where notRepeat is $true$ (will call these usedIndexes). After that it creates a Mesh line with Count equal to the point count of the main points and vertex position equal to $\left(0,0,\text{Index}\right)$, then it deletes all vertices with a distance to points of usedIndexes equal to $0$, resulting in a mesh where all vertices have at their $Z$ coordinate an unused index. I will call this mesh unusedIndexes.
Now, for a point in the base points, if notRepeat is $true$, the Randomized Index is equal to rIndex. If not, a value for Randomized Index is transferred from unusedIndexes using indexRepeat as index. It could transfer sequentially, but, to look more random, for even indexRepeat values, it gets a value starting from index $0$, and for odd values it starts from the last unused index.