calculate your view-projection matrix for the main camera, and invert it
transform the 8 corners of the NDC cube through the inverted projection-matrix (with -1 for near-z if opengl, instead of 0 IIRC?)
perspective divide the 8 resulting coordinates
that gives you the 8 corners of your view frustum in worldspace
take the average of all 8, that gives you the midpoint of the frustum
generate an up & right vector perpendicular to your light direction
create a view matrix centred on the origin, using your light direction and these up/right vectors
the projection matrix is an ortho matrix where the left is the minimum of the dot product of all 8 corners and the right vector, the right is the maximum of the dot product of all 8 corners and the right vector, the top is the maximum of the dot product of all 8 corners and the up vector, and you should be able to guess how to derive the other 3 values
That gives the tightest standard projection to fit the entire frustum.
You generally want to pull the near plane of the ortho matrix back to fit all shadow casters in the scene or "pancake" them at the near plane in the vertex shader.
The shadowmap sampling matrix is the viewprojection for the shadow rendering, but with a scale and bias afterwards as transforming by the matrix will give values between -1 and 1, whereas the texture sampling will need 0 to 1
For cascaded shadows, the process is the same as above, but you split the camera frustum up into multiple z slices, which gives tighter projections closer to the camera.