Sync a local Linux folder to a SharePoint Online document library using Microsoft Graph and certificate-based Entra ID app authentication.
- Uploads local files to SharePoint Online
- Creates remote folders as needed
- Supports nested folders
- Uses certificate-based app authentication
- Supports Docker or local Python execution
- Can optionally delete remote files not present locally
- Linux
- Git
- Python 3 or Docker
- OpenSSL
- Microsoft Entra app registration
- Microsoft Graph
Sites.Selected - Write access granted to the approved SharePoint site
git clone <the repository>
cd sharepoint-folder-syncmkdir -p ~/sharepoint-cert
cd ~/sharepoint-cert
openssl req -x509 -newkey rsa:4096 \
-keyout sharepoint-sync.key.pem \
-out sharepoint-sync.cert.pem \
-days 365 \
-nodes \
-subj "/CN=sharepoint-folder-sync"
openssl x509 -outform der \
-in sharepoint-sync.cert.pem \
-out sharepoint-sync.cert.cer
cat sharepoint-sync.key.pem sharepoint-sync.cert.pem > sharepoint-sync.combined.pem
chmod 600 sharepoint-sync.key.pem sharepoint-sync.combined.pem
chmod 644 sharepoint-sync.cert.cerSend only this file to the Microsoft 365 administrator:
sharepoint-sync.cert.cer
Never share:
sharepoint-sync.key.pem
sharepoint-sync.combined.pem
The Microsoft 365 administrator will provide:
AZURE_TENANT_ID="<tenant-id>"
AZURE_CLIENT_ID="<client-id>"
SHAREPOINT_SITE_URL="<sharepoint-site-url>"
SHAREPOINT_LIBRARY="<document-library>"
SHAREPOINT_REMOTE_FOLDER="<target-folder>"Example:
export AZURE_TENANT_ID="00000000-0000-0000-0000-000000000000"
export AZURE_CLIENT_ID="11111111-1111-1111-1111-111111111111"
export AZURE_CLIENT_CERTIFICATE_PATH="$HOME/sharepoint-cert/sharepoint-sync.combined.pem"
export SHAREPOINT_SITE_URL="https://tenant.sharepoint.com/sites/site"
export SHAREPOINT_LIBRARY="Documents"
export SHAREPOINT_REMOTE_FOLDER="Incoming/LinuxUpload"Build:
docker build -t sharepoint-folder-sync:latest .Run:
docker run --rm \
--user "$(id -u):$(id -g)" \
-e HOME="/tmp" \
-e AZURE_TENANT_ID="$AZURE_TENANT_ID" \
-e AZURE_CLIENT_ID="$AZURE_CLIENT_ID" \
-e AZURE_CLIENT_CERTIFICATE_PATH="/certs/sharepoint-sync.combined.pem" \
-v "$HOME/sharepoint-cert:/certs:ro" \
-v "/path/to/local/folder:/data:rw" \
sharepoint-folder-sync:latest \
--site-url "$SHAREPOINT_SITE_URL" \
--library "$SHAREPOINT_LIBRARY" \
--local-folder "/data" \
--remote-folder "$SHAREPOINT_REMOTE_FOLDER"python3 -m venv .venv
source .venv/bin/activate
pip install azure-identity requestspython3 sync_sharepoint_library.py \
--site-url "$SHAREPOINT_SITE_URL" \
--library "$SHAREPOINT_LIBRARY" \
--local-folder "/path/to/local/folder" \
--remote-folder "$SHAREPOINT_REMOTE_FOLDER"python3 sync_sharepoint_library.py \
--site-url "$SHAREPOINT_SITE_URL" \
--library "$SHAREPOINT_LIBRARY" \
--local-folder "/path/to/local/folder" \
--remote-folder "$SHAREPOINT_REMOTE_FOLDER" \
--dry-runDeletes remote files under the target folder that no longer exist locally.
python3 sync_sharepoint_library.py \
--site-url "$SHAREPOINT_SITE_URL" \
--library "$SHAREPOINT_LIBRARY" \
--local-folder "/path/to/local/folder" \
--remote-folder "$SHAREPOINT_REMOTE_FOLDER" \
--delete-remote-extraUse with care.
- Create an Entra app registration.
- Upload the public certificate.
- Add Microsoft Graph application permission:
Sites.Selected
- Grant admin consent.
- Grant the app
Writeaccess to the approved SharePoint site.
Example with PnP.PowerShell:
$siteUrl = "https://tenant.sharepoint.com/sites/site"
$appId = "<application-client-id>"
Connect-PnPOnline -Url $siteUrl -Interactive
Grant-PnPEntraIDAppSitePermission `
-AppId $appId `
-DisplayName "sharepoint-folder-sync" `
-Site $siteUrl `
-Permissions WriteIf you never used PnP.PowerShell before, register it
Register-PnPEntraIDAppForInteractiveLogin `
-ApplicationName "PnP PowerShell" `
-SharePointDelegatePermissions "AllSites.FullControl" `
-Tenant "<tenant-id>"you can then login with
Connect-PnPOnline -Url https://<tenant>.sharepoint.com -ClientId <client-id>- The private key stays on the Linux machine.
- The app should use
Sites.Selected, not tenant wide SharePoint permissions. - The script writes
.spo_sync_manifest.jsoninto the local folder to track uploaded files. - Docker runs should (often) include
--user "$(id -u):$(id -g)"so the manifest can be written.