Skip to content

Section X: TrueNAS: Automate User + Share Creation #36

@coolaj86

Description

@coolaj86

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 just cli when 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 | jq
b_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"
   }' | jq

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.csv

Get 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" }'
100

Check 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 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@ and group@ are NOT inherited for newly created files - they will become the user and primary group of the user that creates them. You must add a USER and/or GROUP to 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.pdf to see ACLs on new files

List all SMB Shares

midclt call sharing.smb.query | jq

List 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"
done

Replication 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 | sh
sudo 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 file
  • group@ should always be co_corporate, if only users whose primary group is that are creating files - but it may be co_admin if IT sets up some folders or files
  • GROUP co_corporate ensures that all members have access, even if someone with a different primary group creates the files
  • GROUP co_admin ensures that IT has access (same for bultin_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_corporate

ACL for IT & Support

Shares only for IT Admins

  • owner@ will be truenas_admin, or whoever creates the file
  • group@ will generally be co_admin (only IT support with that as their primary group are creating files)
  • GROUP co_admin ensures that all IT support staff always have full access
  • GROUP co_office (on the support datashare) gives READ only access to all office members (and co_corporate as 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_admin

Shares 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_support

ACL for Office-wide Shares

A share that can be used by anyone, company-wide.

  • owner@ will be whoever creates the file
  • group@ will be the primary group of whoever creates the file (co_office, co_corporate, or co_admin)
  • GROUP co_office ensures that all office members always have full access (as well as co_corporate and co_admin)
  • GROUP builtin_administrators keeps all files fully accessible to truenas_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

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions