-
Notifications
You must be signed in to change notification settings - Fork 0
Description
midclt (the official JSON-RPC websocket tool) seems to be the only reasonable way to interact with TrueNAS programmatically at the moment.
- The REST API is deprecated, and its features are mismatched with the JSON-RPC documentation.
- The official CLI (
midcli, or justcliwhen logged into TrueNAS) doesn't seem to be scriptable. - There are no official SDKs and the Websocket protocol documentation isn't great
Create a User
You must get the midclt API id (NOT the POSIX gid) for any groups you'd like to use:
midclt call group.query | jqb_group="custom_group"
midclt call group.query '[["group", "=", "'"$b_group"'"]]' '{"select": ["id", "gid", "group"]}' \
| jq '.[0].id'midclt call user.create '{
"username": "jsmith",
"password": "securepass123",
"full_name": "John Smith",
"email": "john.smith@example.com",
"group_create": false,
"group": 112,
"groups": [111, 110],
"home_create": true,
"home": "/mnt/tank1/smbhomes",
"smb": true,
"shell": "/usr/bin/zsh"
}' | jqhomeis the parent whenhome_createis true- otherwise
homewill be used as home, and have its permissions changed groupandgroupsuse the APIid, not the POSIXgid- see https://api.truenas.com/v26.04.0/api_methods_user.create.html# for more info
Example: Creating from CSV
#!/bin/sh
set -e
set -u
g_home="/mnt/tank1/smbhomes"
b_group="acme_corp"
b_group_id="$(
midclt call group.query '[["group", "=", "'"$b_group"'"]]' '{"select": ["id", "gid", "group"]}' \
| jq '.[0].id'
)"
# Loop over CSV: UID, Full Name, Username, Password, Comments, Etc
while IFS=, read -r a_uid a_full_name a_username a_password _ignore; do
echo >&2 "Creating '${a_full_name}' as '${a_uid}:${a_username}'"
midclt call user.create '{
"uid": '"${a_uid}"',
"username": "'"${a_username}"'",
"password": "'"${a_password}"'",
"full_name": "'"${a_full_name}"'",
"group_create": false,
"group": '"${b_group_id}"',
"groups": [],
"home_create": true,
"home": "'"${g_home}"'",
"smb": true,
"shell": "/usr/bin/zsh"
}' | jq
# sometimes tasks get skipped if too many are queued,
# so we sleep a bit to give any danglish tasks time to finish
sleep 3
#exit 1
done < ./users.csvGet User and Group IDs (POSIX and API)
User
b_username="jsmith"
midclt call user.query '[["username", "=", "'"$b_username"'"]]' '{"select": ["id", "uid", "username"]}'[{"id": 75, "uid": 3001, "username": "jsmith"}]Group
b_group="co_office"
midclt call group.query '[["group", "=", "'"$b_group"'"]]' '{"select": ["id", "gid", "group"]}'[{"id": 112, "gid": 3003, "group": "co_office"}]Create a Group
midclt call group.create '{ "name": "custom_group" }'
100Check the task
b_task='100'
midclt call core.get_jobs '[["id", "=", '"$b_task"']]'Create a Dataset
midclt call pool.dataset.create '{
"name": "tank1/company/scans/scans-jsmith",
"type": "FILESYSTEM",
"share_type": "SMB",
"aclmode": "RESTRICTED",
"acltype": "NFSV4"
}' | jq- uses ZFS dataset name, NOT path
Create a Share
midclt call sharing.smb.create '{
"name": "scans-jsmith",
"path": "/mnt/tank1/company/scans/scans-jsmith",
"purpose": "DEFAULT_SHARE"
}' | jq- uses filesystem path, not dataset name
Update Share Parameters
Turn on or off recycle bin, snapshots, limit enumeration to shares the user has permission to, or turn off enumeration entirely, etc.
midclt call sharing.smb.update '<api-id>' '{"purpose": "NO_PRESET", "browsable": true, "access_based_share_enumeration": true}'
midclt call sharing.smb.update '<api-id>' '{"purpose": "NO_PRESET", "browsable": false, "access_based_share_enumeration": false}'- set
NO_PRESETto change parameters -DEFAULT_SHAREwill prevent changingbrowsable,abe, and others abeisaccess_based_share_enumeration, meaning that only people with access to the share see the network share- see also https://api.truenas.com/v26.04.0/api_methods_sharing.smb.update.html
Set POSIX Share Owner
midclt call filesystem.chown '{
"path": "/mnt/tank1/company/scans/scans-jsmith",
"uid": null,
"user": "jsmith",
"gid": null,
"group": "co_scanner",
"options": {"recursive": true, "traverse": false}
}'Set ACL Share Perms
- IMPORTANT:
owner@andgroup@are NOT inherited for newly created files - they will become the user and primary group of the user that creates them. You must add aUSERand/orGROUPto ensure the dataset members retain access to files added by others.
midclt call filesystem.setacl '{
"path": "/mnt/tank1/company/scans/scans-jsmith",
"dacl": [
{"tag": "owner@", "type": "ALLOW", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"tag": "group@", "type": "ALLOW", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"tag": "USER", "who": "jsmith", "type": "ALLOW", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"tag": "GROUP", "who": "co_office", "type": "ALLOW", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"tag": "GROUP", "who": "co_admin", "type": "ALLOW", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"tag": "GROUP", "who": "builtin_administrators", "type": "ALLOW", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}}
],
"options": {"stripacl": false, "recursive": true, "traverse": false, "canonicalize": true, "validate_effective_acl": true},
"uid": null,
"user": "jsmith",
"gid": null,
"group": "co_scanner",
"acltype": null
}'- returns a Task ID, with inputs largely unchecked, not a result, not an indication of success
- use
midclt call filesystem.getacl /mnt/tank1/company/scans/scans-jsmith/scan.pdfto see ACLs on new files
List all SMB Shares
midclt call sharing.smb.query | jqList all shares matching a particular name (or path) pattern
midclt call sharing.smb.query | jq -r '.[] | select(.name | startswith("backups-") or startswith("scans-")) | " ",.id,.path,.name'Switch all SMB Shares to NO_PRESET, non-browsable
midclt call sharing.smb.query | jq -r '.[] | select(.name | startswith("backups-") or startswith("scans-")) | .id' | while read -r b_id;
do
midclt call sharing.smb.update "$b_id" '{"purpose": "NO_PRESET", "browsable": false}'
echo "Updated share id: $b_id"
doneReplication Recipes
Create a user for replication on the sending system
b_group="zfs_senders"
b_api_id="$( midclt call group.query '[["group", "=", "'"$b_group"'"]]' '{"select": ["id", "gid", "group"]}' | jq '.[0].id' )"
echo "$b_api_id"midclt call user.create '{
"username": "repl_mydestinationhost",
"password": "xxxxxxxxxxxx",
"full_name": "MyDataset Replication Sender",
"email": "truenas+repl.sender@example.com",
"group_create": false,
"group": '"$b_api_id"',
"groups": [],
"home_create": true,
"home": "/mnt/tank1/replication/home",
"smb": false,
"shell": "/usr/bin/sh",
"sshpubkey": null,
"sudo_commands": []
}'curl https://webi.sh/ssh-pubkey | shsudo zfs allow -u 'repl_mydestinationhost' snapshot,send,hold tank1/mydataset~/.ssh/authorized_keys:
command="/sbin/zfs send tank1/mydataset",no-agent-forwarding,no-port-forwarding,no-pty ssh-ed25519 <public-key> repl_mydestinationhost@example.com
Create a user for replication on the receiving system
b_group="zfs_receivers"
b_api_id="$( midclt call group.query '[["group", "=", "'"$b_group"'"]]' '{"select": ["id", "gid", "group"]}' | jq '.[0].id' )"
echo "$b_api_id"midclt call user.create '{
"username": "repl_mysourcehost",
"password": "xxxxxxxxxxxx",
"full_name": "MyDataset Replication Receiver",
"email": "truenas+repl.receiver@example.com",
"group_create": false,
"group": '"$b_api_id"',
"groups": [],
"home_create": true,
"home": "/mnt/tank1/replication/home",
"smb": false,
"shell": "/usr/bin/sh",
"sshpubkey": null,
"sudo_commands": []
}'curl https://webi.sh/ssh-pubkey | sh# TODO figure this out more exactly
sudo zfs allow -u 'repl_mysourcehost' create,diff,mount,readonly,receive,release,send,mountpoint,sharenfs,userprop,xattr,quota,sharesmb,refreservation,copies,refquota,reservation,setuid,aclmode,acltype,atime,aclinherit tank1/replication/mydataset
# https://www.reddit.com/r/truenas/comments/119k6kd/replication_credentials_root_required/
sudo zfs allow -u 'repl_mysourcehost' create,destroy,receive,mount,refreservation,rollback tank1/replication/mydataset
# https://www.truenas.com/community/threads/is-there-a-zfs-allow-equivalent-in-the-gui.94429/#post-724288~/.ssh/authorized_keys:
command="/sbin/zfs receive tank1/replication/mydataset",no-agent-forwarding,no-port-forwarding,no-pty ssh-ed25519 <public-key> repl_mysourcehost@example.com
ACL Recipes
ACL for C-Suite Only
A share that's owned by the corporate executive group and can only be accessed by them and IT.
owner@will be whoever creates the filegroup@should always beco_corporate, if only users whose primary group is that are creating files - but it may beco_adminif IT sets up some folders or filesGROUPco_corporateensures that all members have access, even if someone with a different primary group creates the filesGROUPco_adminensures that IT has access (same forbultin_administrators)
#!/bin/sh
set -e
set -u
fn_set_acl_corporate() { (
b_path="/mnt/tank1/company/corporate"
b_group="co_corporate"
echo "# Updating ACLs for ${b_path} for ${b_group}"
midclt call filesystem.setacl '{
"path": "'"${b_path}"'",
"dacl": [
{"type": "ALLOW", "tag": "owner@", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"type": "ALLOW", "tag": "group@", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}, "who": "co_admin"},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}, "who": "co_corporate"},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}, "who": "builtin_administrators"}
],
"options": {"stripacl": false, "recursive": true, "traverse": false, "canonicalize": true, "validate_effective_acl": true},
"uid": null,
"user": "root",
"gid": null,
"group": "'"$b_group"'",
"acltype": "NFS4"
}'
); }
fn_set_acl_corporateACL for IT & Support
Shares only for IT Admins
owner@will betruenas_admin, or whoever creates the filegroup@will generally beco_admin(only IT support with that as their primary group are creating files)GROUPco_adminensures that all IT support staff always have full accessGROUPco_office(on the support datashare) gives READ only access to all office members (andco_corporateas well)
#!/bin/sh
set -e
set -u
fn_set_acl_admin() { (
b_path="/mnt/tank1/admin"
b_group="co_admin"
echo "# Updating ACLs for ${b_path} for ${b_group}"
midclt call filesystem.setacl '{
"path": "'"${b_path}"'",
"dacl": [
{"type": "ALLOW", "tag": "owner@", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"type": "ALLOW", "tag": "group@", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}, "who": "co_admin"},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}, "who": "builtin_administrators"}
],
"options": {"stripacl": false, "recursive": true, "traverse": false, "canonicalize": true, "validate_effective_acl": true},
"uid": null,
"user": "truenas_admin",
"gid": null,
"group": "'"$b_group"'",
"acltype": "NFS4"
}'
); }
fn_set_acl_adminShares accessible by End Users, editable by IT Support
#!/bin/sh
set -e
set -u
fn_set_acl_support() { (
b_path="/mnt/tank1/company/support"
b_group="co_admin"
echo "# Updating ACLs for ${b_path} for ${b_group}"
midclt call filesystem.setacl '{
"path": "'"${b_path}"'",
"dacl": [
{"type": "ALLOW", "tag": "owner@", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"type": "ALLOW", "tag": "group@", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}, "who": "co_admin"},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}, "who": "co_corporate"},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "READ"}, "flags": {"BASIC": "INHERIT"}, "who": "c_office"},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "READ"}, "flags": {"BASIC": "INHERIT"}, "who": "builtin_administrators"}
],
"options": {"stripacl": false, "recursive": true, "traverse": false, "canonicalize": true, "validate_effective_acl": true},
"uid": null,
"user": "truenas_admin",
"gid": null,
"group": "'"$b_group"'",
"acltype": "NFS4"
}'
); }
fn_set_acl_supportACL for Office-wide Shares
A share that can be used by anyone, company-wide.
owner@will be whoever creates the filegroup@will be the primary group of whoever creates the file (co_office,co_corporate, orco_admin)GROUPco_officeensures that all office members always have full access (as well asco_corporateandco_admin)GROUPbuiltin_administratorskeeps all files fully accessible totruenas_admin(and other NAS admins)
#!/bin/sh
set -e
set -u
fn_set_acl_office() { (
b_path="/mnt/tank1/company/office"
b_group="co_office"
echo "# Updating ACLs for ${b_path} for ${b_group}"
midclt call filesystem.setacl '{
"path": "'"${b_path}"'",
"dacl": [
{"type": "ALLOW", "tag": "owner@", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"type": "ALLOW", "tag": "group@", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}, "who": "co_admin"},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}, "who": "co_corporate"},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}, "who": "co_office"},
{"type": "ALLOW", "tag": "GROUP", "perms": {"BASIC": "FULL_CONTROL"}, "flags": {"BASIC": "INHERIT"}, "who": "builtin_administrators"}
],
"options": {"stripacl": false, "recursive": true, "traverse": false, "canonicalize": true, "validate_effective_acl": true},
"uid": null,
"user": "root",
"gid": null,
"group": "'"$b_group"'",
"acltype": "NFS4"
}'
); }
fn_set_acl_office