Documentation
¶
Overview ¶
Package m365 executes Microsoft 365 backup jobs on the agent: it talks to Microsoft Graph, keeps a per-unit index of what the previous snapshot held, presents each user, site or team as a virtual tree whose unchanged items are never fetched, and uploads one snapshot per unit through the shared backup engine.
Index ¶
- Constants
- func BuildTree(root string, entries []Entry, dirs []Dir, now time.Time) (*scan.Node, error)
- func IsResync(err error) bool
- func IsStatus(err error, statuses ...int) bool
- func List[T any](ctx context.Context, c *Client, path string) ([]T, error)
- func Run(ctx context.Context, o RunOptions) (*temporal.BackupExecutionResult, error)
- type BrowseEntry
- type ChannelIndex
- type Client
- func (c *Client) Download(ctx context.Context, url string) (io.ReadCloser, int64, error)
- func (c *Client) Each(ctx context.Context, path string, headers map[string]string, ...) (deltaLink string, err error)
- func (c *Client) Get(ctx context.Context, path string, out any, headers map[string]string) error
- func (c *Client) Post(ctx context.Context, path string, body, out any) error
- func (c *Client) ReadAll(ctx context.Context, url string, limit int64) ([]byte, error)
- func (c *Client) URL(path string) string
- type Dir
- type DriveEntry
- type DriveIndex
- type Entry
- type FolderIndex
- type GraphError
- type HostedEntry
- type ItemEntry
- type MailboxIndex
- type Page
- type Principal
- type RunOptions
- type Site
- type State
- type Stats
- type Team
- type TeamsIndex
- type Unit
- type UnitIndex
- type UploadFunc
Constants ¶
const ( ArchiveUser = "user" // mailbox/ + onedrive/ ArchiveSite = "site" // files/<library>/ ArchiveTeam = "team" // messages/, hosted/, files/<library>/ ArchiveEntra = spec.ArchiveEntra )
Archive names: one archive per unit snapshot
const IndexVersion = 1
IndexVersion is bumped when the index layout changes incompatibly (old files are then ignored and the unit is enumerated afresh)
Variables ¶
This section is empty.
Functions ¶
func BuildTree ¶
BuildTree assembles the virtual directory named root from entries and explicit directories. Parents are created as needed; files sort with the directory (gopbs sorts children again). A path component that is not a valid archive name is rewritten by safeComponent.
func Run ¶
func Run(ctx context.Context, o RunOptions) (*temporal.BackupExecutionResult, error)
Run executes one Microsoft 365 backup: precheck → select units → one snapshot per unit (Concurrency in flight) → the directory snapshot with the run manifest
Types ¶
type BrowseEntry ¶
type BrowseEntry struct {
Subject string `json:"subject"`
From string `json:"from,omitempty"`
Received string `json:"received,omitempty"`
HasAttachments bool `json:"has_attachments,omitempty"`
}
BrowseEntry is the browsing index line of a message
type ChannelIndex ¶
type ChannelIndex struct {
Name string `json:"name"`
// Months: "2026-10" → message id → message JSON as Graph returned it
Months map[string]map[string]json.RawMessage `json:"months"`
}
ChannelIndex groups a channel's messages by month of creation
type Client ¶
type Client struct {
// Stats counts what the client did (read after the run)
Stats Stats
// contains filtered or unexported fields
}
Client is a Graph HTTP client with bearer auth, paging and throttling retries
func NewClient ¶
func NewClient(ctx context.Context, creds spec.Credentials, authority, graphURL string) (*Client, error)
NewClient builds a client from the tenant credentials. graphURL "" means GraphURL; authority "" means AuthorityURL.
func (*Client) Download ¶
Download streams a content URL. Graph content endpoints answer with a redirect to a pre-authenticated URL on another host; the bearer header must not follow it there, which Go's client already guarantees for cross-host redirects. Returns the body and the Content-Length (-1 when unknown).
func (*Client) Each ¶
func (c *Client) Each(ctx context.Context, path string, headers map[string]string, fn func(raw json.RawMessage) error) (deltaLink string, err error)
Each walks a collection page by page (nextLink) calling fn for every element; it returns the deltaLink of the last page (delta queries) or ""
type DriveEntry ¶
type DriveEntry struct {
Path string `json:"path"`
Dir bool `json:"dir,omitempty"`
Size int64 `json:"size"`
Mtime int64 `json:"mtime"`
CTag string `json:"ctag,omitempty"`
// Metadata for the .fallbck/items.json sidecar
WebURL string `json:"web_url,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
ModifiedBy string `json:"modified_by,omitempty"`
}
DriveEntry is one drive item (file or folder)
type DriveIndex ¶
type DriveIndex struct {
ID string `json:"id"`
Name string `json:"name"`
WebURL string `json:"web_url,omitempty"`
// Prefix is the archive directory the drive's items live under (onedrive, or
// files/<library>)
Prefix string `json:"prefix"`
Delta string `json:"delta,omitempty"`
Items map[string]*DriveEntry `json:"items"`
}
DriveIndex is one OneDrive or document library
type Entry ¶
type Entry struct {
// Path is the archive path with '/' separators, no leading slash
Path string
Size int64
Mtime time.Time
Open func() (io.ReadCloser, error)
}
Entry is one file of a unit's virtual tree. Open is called at most once, when gopbs reads the content; it is never called for an item reused from the previous snapshot.
type FolderIndex ¶
type FolderIndex struct {
ID string `json:"id"`
Name string `json:"name"`
ParentID string `json:"parent_id,omitempty"`
Class string `json:"class,omitempty"`
// Path is the folder's archive path below mailbox/ (computed from the parents)
Path string `json:"path"`
ItemsDelta string `json:"items_delta,omitempty"`
Items map[string]*ItemEntry `json:"items"`
// Browse is the human-readable index of the folder (subject, from, dates) kept
// through the standard messages delta; BrowseDelta its link
BrowseDelta string `json:"browse_delta,omitempty"`
Browse map[string]*BrowseEntry `json:"browse,omitempty"`
}
FolderIndex is one mail folder with its items
type GraphError ¶
type GraphError struct {
Status int
Code string
Message string
URL string
// contains filtered or unexported fields
}
GraphError is a non-2xx answer from Graph
func (*GraphError) Error ¶
func (e *GraphError) Error() string
type HostedEntry ¶
type HostedEntry struct {
URL string `json:"url"`
Size int64 `json:"size"`
Mtime int64 `json:"mtime"`
}
HostedEntry is one hosted content blob of a message
type ItemEntry ¶
type ItemEntry struct {
// Size is the export stream size (what the archive declares); -1 until exported
Size int64 `json:"size"`
ChangeKey string `json:"change_key"`
// Mtime is lastModifiedDateTime (unix seconds); Created is createdDateTime, used
// in the file name
Mtime int64 `json:"mtime"`
Created int64 `json:"created"`
Class string `json:"class"`
// EstSize is the mailboxItem.size Graph reported (packing estimate for exports)
EstSize int64 `json:"est_size,omitempty"`
// Dirty: the item must be exported again (new, changed or a failed export)
Dirty bool `json:"dirty,omitempty"`
}
ItemEntry is one exported item as the previous snapshot holds it
type MailboxIndex ¶
type MailboxIndex struct {
MailboxID string `json:"mailbox_id"`
FoldersDelta string `json:"folders_delta,omitempty"`
Folders map[string]*FolderIndex `json:"folders"`
}
MailboxIndex is the mailbox of a user unit
type Page ¶
type Page struct {
Value []json.RawMessage `json:"value"`
NextLink string `json:"@odata.nextLink"`
DeltaLink string `json:"@odata.deltaLink"`
}
Page is one page of a collection
type Principal ¶
type Principal struct {
ID string `json:"id"`
UPN string `json:"userPrincipalName"`
DisplayName string `json:"displayName"`
Mail string `json:"mail"`
UserType string `json:"userType"`
Enabled bool `json:"accountEnabled"`
Licenses []struct {
SKU string `json:"skuId"`
} `json:"assignedLicenses"`
}
Principal is a user of the tenant as the job sees it
func SelectUsers ¶
func SelectUsers(ctx context.Context, c *Client, sel spec.UserSelection) ([]Principal, []string, error)
SelectUsers lists the tenant's users and applies the job's selection: members of the named groups (or everyone), licensed accounts plus shared mailboxes (accounts with a mail address but no licence) unless Unlicensed asks for every account, never guests, then the UPN include/exclude globs
type RunOptions ¶
type RunOptions struct {
Session *temporal.BackupSessionInfo
Spec *spec.Spec
Credentials spec.Credentials
// StateDir holds the job's unit indexes (<data>/m365-state/<job id>)
StateDir string
// SpoolDir receives exported mailbox items while a unit uploads (<spool>/<run id>)
SpoolDir string
Upload UploadFunc
Progress func(phase string)
Items func(items map[string]rundetail.ProgressItem)
Hostname string
Version string
// Client overrides the Graph client (tests); Authority / GraphURL point at a fake
Client *Client
Authority string
GraphURL string
Now func() time.Time
}
RunOptions parameterise Run
type Site ¶
type Site struct {
ID string `json:"id"`
Name string `json:"name"`
DisplayName string `json:"displayName"`
WebURL string `json:"webUrl"`
}
Site is a SharePoint site
type State ¶
type State struct{ Dir string }
State is the per-job index directory (<data>/m365-state/<job id>)
type TeamsIndex ¶
type TeamsIndex struct {
// Since is the newest lastModifiedDateTime seen (the next run filters from here)
Since string `json:"since,omitempty"`
Channels map[string]*ChannelIndex `json:"channels"`
// Hosted are inline images and files by archive path (hosted/<message>-<id>)
Hosted map[string]*HostedEntry `json:"hosted,omitempty"`
}
TeamsIndex holds a team's channel messages (the backup data itself: messages are small and must be re-emitted unchanged for chunk reuse)
type Unit ¶
type Unit struct {
Kind string
Name string
ObjectID string
Group string
// Archive is the pxar name of the unit
Archive string
// Principal / Site / Team carry the Graph object
Principal *Principal
Site *Site
Team *Team
}
Unit is one snapshot of the run: a user, a site, a team or the directory
type UnitIndex ¶
type UnitIndex struct {
Version int `json:"version"`
Kind string `json:"kind"`
Name string `json:"name"`
ObjectID string `json:"object_id"`
Group string `json:"group"`
// LastRunID / LastStatus / LastSnapshot describe the newest finished snapshot; an
// activity retry skips units whose LastRunID is the current run
LastRunID string `json:"last_run_id,omitempty"`
LastStatus string `json:"last_status,omitempty"`
LastSnapshot int64 `json:"last_snapshot,omitempty"`
Mailbox *MailboxIndex `json:"mailbox,omitempty"`
Drives map[string]*DriveIndex `json:"drives,omitempty"`
Teams *TeamsIndex `json:"teams,omitempty"`
}
UnitIndex is what the agent remembers about one unit (user, site, team or the directory) between runs: the Graph delta links and, per item, the metadata the previous snapshot recorded. Saved only after the unit's snapshot finished, so a failed run repeats the same delta round instead of skipping anything.