This document describes all the maintenance scripts included in the Server Deployment Package (SDP). These scripts are located in the $SDP/Maintenance directory and provide tools for routine Perforce server administration tasks.
Most maintenance scripts read their configuration from maintenance.cfg in the Maintenance directory. Key configuration parameters include:
| Parameter | Description |
|---|---|
weeks |
Default inactivity threshold in weeks |
deleteweeks |
Threshold for permanent deletion |
userweeks |
Inactivity threshold for user accounts |
weeks1warn |
First warning threshold |
weeks2warn |
Second warning threshold |
administrator |
Email from address |
mailhost |
SMTP server hostname |
mailport |
SMTP server port |
mailsecure |
Use TLS (1 or 0) |
mailuser |
SMTP username |
mailpass |
SMTP password |
default_user_password |
Default password for new users |
Generate lists of inactive Perforce specs (branches, clients, labels, users) that haven't been accessed within a specified number of weeks.
python accessdates.py [instance]
Output Files:
branches.txtclients.txtlabels.txtusers.txtCreate Perforce users in bulk from a CSV file.
python createusers.py <userlist.csv> [instance]
CSV Format:
user,email,fullname
jsmith,jsmith@company.com,John Smith
Remove users that haven't accessed Perforce within the configured threshold.
python delusers.py [instance]
Uses the weeks threshold from maintenance.cfg.
Remove Perforce users and their associated metadata (clients, shelves).
python p4deleteuser.py [instance] <user_or_file> [--simulate]
Arguments:
instance: SDP instance identifier (default: '1')user_or_file: Username or file containing usernames (one per line)--simulate: Print actions without executingFeatures:
p4 user -FDy commandSet a user's password to the default password configured in maintenance.cfg.
python setpass.py [instance] <username>
Find group members without corresponding user accounts (orphaned members).
python checkusers.py [instance]
Output: removeusersfromgroups.txt
Find users that are not members of any Perforce group.
python checkusers_not_in_group.py [instance]
Sync users based on group membership - creates missing users and removes users not in any group.
python maintain_user_from_groups.py [instance]
Send warning emails to users whose accounts are scheduled for deletion due to inactivity.
python email_pending_user_deletes.py [instance]
Uses userweeks threshold from maintenance.cfg.
Generate a CSV report of Perforce users across multiple servers.
python totalusers.py [cfgfile] [--output <outfile>] [--verbose]
Configuration File (totalusers.cfg):
case_sensitive=1
ROBOT,p4robot
ROBOT,jenkins
SERVER,server1.example.com:1666,p4admin,1
SERVER,server2.example.com:1666,p4admin,2
Create Perforce groups from a configuration file.
python creategroups.py [instance]
Input File Format (groups.txt):
group,groupname1
username1
username2
group,groupname2
username3
Add user(s) to a Perforce group.
python addusertogroup.py [instance] <user_or_file> <group>
Examples:
python addusertogroup.py jsmith developers
python addusertogroup.py 2 userlist.txt developers
Remove user(s) from all their Perforce groups.
python removeuserfromgroups.py [instance] <user_or_file>
Remove users from a specific list of groups.
python removeuserfromgroups_file.py <instance> <user_or_file> <groups_file>
Remove users from a specific group.
python removeusersfromgroup.py [instance] <user_or_file> <groupname>
Mirror user access from one user to others (add target users to all groups the source user belongs to).
python mirroraccess.py <instance> <source_user> <target_user> [<user3> ... <userN>]
Send quarterly audit reminders to group owners asking them to validate group membership.
python group_audit.py [instance]
Unload inactive Perforce clients to the unload depot.
python unload_clients.py [instance]
Uses weeks threshold from maintenance.cfg. Skips Swarm clients.
Unload inactive clients, but forcibly delete those with exclusive file locks (which can't be unloaded).
python unload_clients_with_delete.py [instance]
Delete inactive unloaded clients from the unload depot.
python delete_unload_clients.py [instance]
Uses deleteweeks threshold from maintenance.cfg.
Generate lists of inactive unloaded clients and labels.
python unloadaccessdates.py [instance]
Output Files:
unload_clients.txtunload_labels.txtSend warning emails for inactive clients scheduled for unload.
python email_pending_client_deletes.py [instance]
Sends two-tier warnings using weeks1warn and weeks2warn thresholds.
Delete shelves and inactive clients.
python del_shelve.py [instance]
Unload labels that haven't been accessed within the configured threshold.
python unload_labels.py [instance]
Convert Perforce labels from noautoreload to autoreload mode.
python convert_label_to_autoreload.py [instance] <label_or_file>
Check if a label exists in Perforce.
python isitalabel.py [instance] <labelname>
Exit Codes:
0: Label exists1: Label not foundLock a Perforce label.
python p4lock.py [instance] <labelname>
Unlock a Perforce label.
python p4unlock.py [instance] <labelname>
Note: Saves owner info to
owner.txtfor restoration byp4lock.py.
Send email to all Perforce users.
./email.sh "Subject line"
Prerequisites:
message.txt must exist containing the email bodypymail.py must be availableSend email from command line using SMTP.
python pymail.py -t <to-address or file> -s <subject> -i <input-file> [instance]
Arguments:
-t: Recipient email address or path to file with addresses-s: Email subject-i: Path to file containing email bodyExamples:
python pymail.py -t admin@example.com -s "Test" -i message.txt
python pymail.py -t recipients.txt -s "Announcement" -i body.txt 2
Find groups in the protection table that don't exist on the server.
# First, extract groups from protect table
p4 protect -o | grep group | cut -d " " -f 3 | sort | uniq > protect_groups.txt
# Then find non-existent groups
python protect_groups.py [instance] protect_groups.txt > remove_groups.txt
Remove groups from the Perforce protection table.
# Generate protection table
p4 protect -o > p4.protect
# Clean up invalid groups
python clean_protect.py remove_groups.txt p4.protect
# Review and apply
p4 protect -i < new.p4.protect
Count total files and revisions from a Perforce files list.
p4 files //... > files.txt
python countrevs.py files.txt
Find proxy servers in Perforce server log.
python proxysearch.py <logfile>
Output: Unique proxy IP addresses (one per line)
Generate a combined file list from all Perforce depots.
./create_p4_filelist.sh
Output: p4_file_list.txt
Iterates through depots one directory level deep to avoid memory spikes.
Copy a directory tree to a target with lowercase names.
cd /p4/1/depots
python lowercp.py depot
Note: Configure
TARGETDEPOTPATHvariable at top of script.
Rename a directory tree to all lowercase (in place).
python lowertree.py <directory>
Useful for converting from case-sensitive to case-insensitive Perforce servers.
Delete all empty pending changelists.
python remove_empty_pending_changes.py [instance]
Note: Changelists with
#do-not-deletein description are protected.
Remove Perforce jobs and their associated fixes.
p4 jobs > jobs.txt
python remove_jobs.py [instance] jobs.txt
Force update on changelists (useful with form triggers).
p4 changes | cut -d " " -f 2 > changes.txt
python update-changes.py [instance] changes.txt
Core utility module used by most other maintenance scripts. Provides:
SDPUtils class for Perforce interactionsmaintenance.cfgp4 command execution via run_p4()Example Usage:
from sdputils import SDPUtils
utils = SDPUtils("1")
utils.login()
users = utils.get_all_users()
| Category | Script Count |
|---|---|
| User Management | 10 |
| Group Management | 7 |
| Client Management | 6 |
| Label Management | 5 |
| Email/Notification | 3 |
| Protection Table | 2 |
| Reporting | 2 |
| File/Archive Operations | 5 |
| Utility | 1 |
| Total | 41 |
# 1. Generate list of inactive users
python accessdates.py 1
# 2. Review users.txt, then delete
python p4deleteuser.py 1 users.txt --simulate # Review first
python p4deleteuser.py 1 users.txt # Execute
# 1. Send warnings to users
python email_pending_client_deletes.py 1
# 2. Unload inactive clients
python unload_clients.py 1
# 3. Later, delete unloaded clients
python delete_unload_clients.py 1
# 1. Extract groups from protect table
p4 protect -o | grep group | cut -d " " -f 3 | sort | uniq > protect_groups.txt
# 2. Find non-existent groups
python protect_groups.py 1 protect_groups.txt > remove_groups.txt
# 3. Export current protect table
p4 protect -o > p4.protect
# 4. Remove invalid groups
python clean_protect.py remove_groups.txt p4.protect
# 5. Review and apply
diff p4.protect new.p4.protect
p4 protect -i < new.p4.protect
# Find users in groups that don't exist
python checkusers.py 1
# Review and remove orphaned entries
python removeuserfromgroups.py 1 removeusersfromgroups.txt# SDP Maintenance Scripts Reference
This document describes all the maintenance scripts included in the Server Deployment Package (SDP). These scripts are located in the `$SDP/Maintenance` directory and provide tools for routine Perforce server administration tasks.
---
## Table of Contents
- [Configuration](#configuration)
- [User Management Scripts](#user-management-scripts)
- [Group Management Scripts](#group-management-scripts)
- [Client Management Scripts](#client-management-scripts)
- [Label Management Scripts](#label-management-scripts)
- [Email and Notification Scripts](#email-and-notification-scripts)
- [Protection Table Scripts](#protection-table-scripts)
- [Reporting Scripts](#reporting-scripts)
- [File and Archive Operations](#file-and-archive-operations)
- [Utility Scripts](#utility-scripts)
---
## Configuration
Most maintenance scripts read their configuration from `maintenance.cfg` in the Maintenance directory. Key configuration parameters include:
| Parameter | Description |
|-----------|-------------|
| `weeks` | Default inactivity threshold in weeks |
| `deleteweeks` | Threshold for permanent deletion |
| `userweeks` | Inactivity threshold for user accounts |
| `weeks1warn` | First warning threshold |
| `weeks2warn` | Second warning threshold |
| `administrator` | Email from address |
| `mailhost` | SMTP server hostname |
| `mailport` | SMTP server port |
| `mailsecure` | Use TLS (1 or 0) |
| `mailuser` | SMTP username |
| `mailpass` | SMTP password |
| `default_user_password` | Default password for new users |
---
## User Management Scripts
### accessdates.py
Generate lists of inactive Perforce specs (branches, clients, labels, users) that haven't been accessed within a specified number of weeks.
```bash
python accessdates.py [instance]
```
**Output Files:**
- `branches.txt`
- `clients.txt`
- `labels.txt`
- `users.txt`
---
### createusers.py
Create Perforce users in bulk from a CSV file.
```bash
python createusers.py <userlist.csv> [instance]
```
**CSV Format:**
```csv
user,email,fullname
jsmith,jsmith@company.com,John Smith
```
---
### delusers.py
Remove users that haven't accessed Perforce within the configured threshold.
```bash
python delusers.py [instance]
```
Uses the `weeks` threshold from `maintenance.cfg`.
---
### p4deleteuser.py
Remove Perforce users and their associated metadata (clients, shelves).
```bash
python p4deleteuser.py [instance] <user_or_file> [--simulate]
```
**Arguments:**
- `instance`: SDP instance identifier (default: '1')
- `user_or_file`: Username or file containing usernames (one per line)
- `--simulate`: Print actions without executing
**Features:**
- Protects service accounts (p4admin, perforce, swarm) from deletion
- Supports batch deletion from files
- Uses `p4 user -FDy` command
---
### setpass.py
Set a user's password to the default password configured in `maintenance.cfg`.
```bash
python setpass.py [instance] <username>
```
---
### checkusers.py
Find group members without corresponding user accounts (orphaned members).
```bash
python checkusers.py [instance]
```
**Output:** `removeusersfromgroups.txt`
---
### checkusers_not_in_group.py
Find users that are not members of any Perforce group.
```bash
python checkusers_not_in_group.py [instance]
```
---
### maintain_user_from_groups.py
Sync users based on group membership - creates missing users and removes users not in any group.
```bash
python maintain_user_from_groups.py [instance]
```
---
### email_pending_user_deletes.py
Send warning emails to users whose accounts are scheduled for deletion due to inactivity.
```bash
python email_pending_user_deletes.py [instance]
```
Uses `userweeks` threshold from `maintenance.cfg`.
---
### totalusers.py
Generate a CSV report of Perforce users across multiple servers.
```bash
python totalusers.py [cfgfile] [--output <outfile>] [--verbose]
```
**Configuration File (totalusers.cfg):**
```
case_sensitive=1
ROBOT,p4robot
ROBOT,jenkins
SERVER,server1.example.com:1666,p4admin,1
SERVER,server2.example.com:1666,p4admin,2
```
---
## Group Management Scripts
### creategroups.py
Create Perforce groups from a configuration file.
```bash
python creategroups.py [instance]
```
**Input File Format (groups.txt):**
```
group,groupname1
username1
username2
group,groupname2
username3
```
---
### addusertogroup.py
Add user(s) to a Perforce group.
```bash
python addusertogroup.py [instance] <user_or_file> <group>
```
**Examples:**
```bash
python addusertogroup.py jsmith developers
python addusertogroup.py 2 userlist.txt developers
```
---
### removeuserfromgroups.py
Remove user(s) from all their Perforce groups.
```bash
python removeuserfromgroups.py [instance] <user_or_file>
```
---
### removeuserfromgroups_file.py
Remove users from a specific list of groups.
```bash
python removeuserfromgroups_file.py <instance> <user_or_file> <groups_file>
```
---
### removeusersfromgroup.py
Remove users from a specific group.
```bash
python removeusersfromgroup.py [instance] <user_or_file> <groupname>
```
---
### mirroraccess.py
Mirror user access from one user to others (add target users to all groups the source user belongs to).
```bash
python mirroraccess.py <instance> <source_user> <target_user> [<user3> ... <userN>]
```
---
### group_audit.py
Send quarterly audit reminders to group owners asking them to validate group membership.
```bash
python group_audit.py [instance]
```
---
## Client Management Scripts
### unload_clients.py
Unload inactive Perforce clients to the unload depot.
```bash
python unload_clients.py [instance]
```
Uses `weeks` threshold from `maintenance.cfg`. Skips Swarm clients.
---
### unload_clients_with_delete.py
Unload inactive clients, but forcibly delete those with exclusive file locks (which can't be unloaded).
```bash
python unload_clients_with_delete.py [instance]
```
---
### delete_unload_clients.py
Delete inactive unloaded clients from the unload depot.
```bash
python delete_unload_clients.py [instance]
```
Uses `deleteweeks` threshold from `maintenance.cfg`.
---
### unloadaccessdates.py
Generate lists of inactive unloaded clients and labels.
```bash
python unloadaccessdates.py [instance]
```
**Output Files:**
- `unload_clients.txt`
- `unload_labels.txt`
---
### email_pending_client_deletes.py
Send warning emails for inactive clients scheduled for unload.
```bash
python email_pending_client_deletes.py [instance]
```
Sends two-tier warnings using `weeks1warn` and `weeks2warn` thresholds.
---
### del_shelve.py
Delete shelves and inactive clients.
```bash
python del_shelve.py [instance]
```
---
## Label Management Scripts
### unload_labels.py
Unload labels that haven't been accessed within the configured threshold.
```bash
python unload_labels.py [instance]
```
---
### convert_label_to_autoreload.py
Convert Perforce labels from `noautoreload` to `autoreload` mode.
```bash
python convert_label_to_autoreload.py [instance] <label_or_file>
```
---
### isitalabel.py
Check if a label exists in Perforce.
```bash
python isitalabel.py [instance] <labelname>
```
**Exit Codes:**
- `0`: Label exists
- `1`: Label not found
---
### p4lock.py
Lock a Perforce label.
```bash
python p4lock.py [instance] <labelname>
```
---
### p4unlock.py
Unlock a Perforce label.
```bash
python p4unlock.py [instance] <labelname>
```
> **Note:** Saves owner info to `owner.txt` for restoration by `p4lock.py`.
---
## Email and Notification Scripts
### email.sh
Send email to all Perforce users.
```bash
./email.sh "Subject line"
```
**Prerequisites:**
- `message.txt` must exist containing the email body
- `pymail.py` must be available
---
### pymail.py
Send email from command line using SMTP.
```bash
python pymail.py -t <to-address or file> -s <subject> -i <input-file> [instance]
```
**Arguments:**
- `-t`: Recipient email address or path to file with addresses
- `-s`: Email subject
- `-i`: Path to file containing email body
**Examples:**
```bash
python pymail.py -t admin@example.com -s "Test" -i message.txt
python pymail.py -t recipients.txt -s "Announcement" -i body.txt 2
```
---
## Protection Table Scripts
### protect_groups.py
Find groups in the protection table that don't exist on the server.
```bash
# First, extract groups from protect table
p4 protect -o | grep group | cut -d " " -f 3 | sort | uniq > protect_groups.txt
# Then find non-existent groups
python protect_groups.py [instance] protect_groups.txt > remove_groups.txt
```
---
### clean_protect.py
Remove groups from the Perforce protection table.
```bash
# Generate protection table
p4 protect -o > p4.protect
# Clean up invalid groups
python clean_protect.py remove_groups.txt p4.protect
# Review and apply
p4 protect -i < new.p4.protect
```
---
## Reporting Scripts
### countrevs.py
Count total files and revisions from a Perforce files list.
```bash
p4 files //... > files.txt
python countrevs.py files.txt
```
---
### proxysearch.py
Find proxy servers in Perforce server log.
```bash
python proxysearch.py <logfile>
```
**Output:** Unique proxy IP addresses (one per line)
---
## File and Archive Operations
### create_p4_filelist.sh
Generate a combined file list from all Perforce depots.
```bash
./create_p4_filelist.sh
```
**Output:** `p4_file_list.txt`
Iterates through depots one directory level deep to avoid memory spikes.
---
### lowercp.py
Copy a directory tree to a target with lowercase names.
```bash
cd /p4/1/depots
python lowercp.py depot
```
> **Note:** Configure `TARGETDEPOTPATH` variable at top of script.
---
### lowertree.py
Rename a directory tree to all lowercase (in place).
```bash
python lowertree.py <directory>
```
Useful for converting from case-sensitive to case-insensitive Perforce servers.
---
### remove_empty_pending_changes.py
Delete all empty pending changelists.
```bash
python remove_empty_pending_changes.py [instance]
```
> **Note:** Changelists with `#do-not-delete` in description are protected.
---
### remove_jobs.py
Remove Perforce jobs and their associated fixes.
```bash
p4 jobs > jobs.txt
python remove_jobs.py [instance] jobs.txt
```
---
### update-changes.py
Force update on changelists (useful with form triggers).
```bash
p4 changes | cut -d " " -f 2 > changes.txt
python update-changes.py [instance] changes.txt
```
---
## Utility Scripts
### sdputils.py
Core utility module used by most other maintenance scripts. Provides:
- `SDPUtils` class for Perforce interactions
- Configuration loading from `maintenance.cfg`
- Safe `p4` command execution via `run_p4()`
- Secure password management
- Common Perforce operations (get users, groups, etc.)
**Example Usage:**
```python
from sdputils import SDPUtils
utils = SDPUtils("1")
utils.login()
users = utils.get_all_users()
```
---
# Summary
| Category | Script Count |
|----------|--------------|
| User Management | 10 |
| Group Management | 7 |
| Client Management | 6 |
| Label Management | 5 |
| Email/Notification | 3 |
| Protection Table | 2 |
| Reporting | 2 |
| File/Archive Operations | 5 |
| Utility | 1 |
| **Total** | **41** |
---
# Common Workflows
## Remove Inactive Users
```bash
# 1. Generate list of inactive users
python accessdates.py 1
# 2. Review users.txt, then delete
python p4deleteuser.py 1 users.txt --simulate # Review first
python p4deleteuser.py 1 users.txt # Execute
```
## Clean Up Inactive Clients
```bash
# 1. Send warnings to users
python email_pending_client_deletes.py 1
# 2. Unload inactive clients
python unload_clients.py 1
# 3. Later, delete unloaded clients
python delete_unload_clients.py 1
```
## Clean Up Protection Table
```bash
# 1. Extract groups from protect table
p4 protect -o | grep group | cut -d " " -f 3 | sort | uniq > protect_groups.txt
# 2. Find non-existent groups
python protect_groups.py 1 protect_groups.txt > remove_groups.txt
# 3. Export current protect table
p4 protect -o > p4.protect
# 4. Remove invalid groups
python clean_protect.py remove_groups.txt p4.protect
# 5. Review and apply
diff p4.protect new.p4.protect
p4 protect -i < new.p4.protect
```
## Audit Group Membership
```bash
# Find users in groups that don't exist
python checkusers.py 1
# Review and remove orphaned entries
python removeuserfromgroups.py 1 removeusersfromgroups.txt
```
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #1 | 32388 | Russell C. Jackson (Rusty) | Updates using Claude.ai to clean up the code, reduce duplication, enhanace security, and use current standards. |