No lock-in

Exporting your directory.

Every user, group, and SSH key in your Dirless directory can be exported with a single command - as JSON, as standard LDIF you can load into any LDAP server, or as flat passwd-style files. Leaving Dirless must never be harder than joining it. Your directory is yours.

On this page
Overview

Why an exporter

Identity systems are the stickiest infrastructure there is: once your UIDs, groups, and SSH keys live somewhere, moving them is usually painful enough that nobody does it. We think that is a bug, not a business model. Dirless users are standard Linux identities - real UIDs, nsswitch.conf, getent - and the exporter completes the picture: everything you put in comes back out in open formats, including LDIF that loads directly into OpenLDAP, 389-ds, or FreeIPA.

The export runs on any enrolled host and includes both your cloud-synced users (from AWS IAM Identity Center) and your portal-managed local users, merged exactly the way your hosts see them through NSS. It is read-only: exporting changes nothing on the backend.

Privacy note. Like every Dirless operation, the export happens on your machine: the encrypted snapshots are fetched from the backend and decrypted locally with your age key. The server never sees your directory in plaintext, not even to export it.

Formats

The three formats

FormatWhat you getUse it for
json (default) One document with users, groups, and ssh_keys Backups, scripting, restoring later with dirless-cli import
ldif Standard posixAccount / posixGroup entries, SSH keys as sshPublicKey Moving to OpenLDAP, 389-ds, or FreeIPA
passwd passwd + group files and one authorized_keys file per user Plain local users, air-gapped hosts, "just give me files"
Password fields are always x. Dirless never stores password hashes - authentication is SSH-key and certificate based - so there are no password hashes to export. Set passwords on the destination system if you need them.

Step 1

Running the export

On any host already enrolled with dirless-cli enroll, the backend URL, credentials, and your age key are read from /etc/dirless/ - no flags needed. Requires dirless-cli 0.6.0 or newer.

Shell (enrolled host) - JSON to stdout
dirless-cli export
Output
{
  "users": [
    {
      "username": "alice",
      "uid": 100001,
      "gid": 100000,
      "gecos": "Alice Anderson",
      "home": "/home/alice",
      "shell": "/bin/zsh",
      "email": "alice@example.com"
    }
  ],
  "groups": [
    { "name": "devs", "gid": 100010, "members": ["alice"] }
  ],
  "ssh_keys": {
    "alice": "ssh-ed25519 AAAAC3... alice@laptop"
  }
}
Exported 1 user(s), 1 group(s), 1 SSH key file(s) [source: merged, format: json]

LDIF

Shell (enrolled host)
dirless-cli export --format ldif --base-dn dc=example,dc=com --out export.ldif

Flat files

The passwd format writes multiple files, so it takes a directory:

Shell (enrolled host)
dirless-cli export --format passwd --out ./directory-export
tree directory-export
directory-export/
├── passwd            # username:x:uid:gid:gecos:home:shell
├── group             # name:x:gid:member1,member2
└── authorized_keys/
    ├── alice         # one file per user, ready for ~/.ssh/
    └── bob

Choosing what to export

By default the export merges your cloud-synced and local users the same way enrolled hosts do (local wins on a username conflict). Use --source cloud or --source local to export just one side. For backups you plan to restore with dirless-cli import, use --source local: the cloud side re-syncs from your IdP automatically and does not belong in the local snapshot.


Step 2

Loading the LDIF into an LDAP server

The LDIF export uses only standard schema: inetOrgPerson + posixAccount for users, posixGroup for groups, and the widely deployed openssh-lpk ldapPublicKey class for SSH keys. Entries are placed under ou=users and ou=groups beneath the base DN you pass with --base-dn.

The export does not create the parent OUs (on an existing tree they would collide), so create them first if your destination tree is empty:

bootstrap-ous.ldif
dn: ou=users,dc=example,dc=com
objectClass: organizationalUnit
ou: users

dn: ou=groups,dc=example,dc=com
objectClass: organizationalUnit
ou: groups
Shell (LDAP server)
ldapadd -x -D cn=admin,dc=example,dc=com -W -f bootstrap-ous.ldif
ldapadd -x -D cn=admin,dc=example,dc=com -W -f export.ldif

Then verify a user came through intact, SSH keys and all:

Shell (LDAP server)
ldapsearch -x -D cn=admin,dc=example,dc=com -W \
  -b ou=users,dc=example,dc=com "(uid=alice)" \
  uidNumber loginShell sshPublicKey

dn: uid=alice,ou=users,dc=example,dc=com
uidNumber: 100001
loginShell: /bin/zsh
sshPublicKey: ssh-ed25519 AAAAC3... alice@laptop
SSH key schema. If your LDAP server rejects objectClass: ldapPublicKey, load the two-line openssh-lpk schema first (many distributions and Docker images, including osixia/openldap, already ship it). Users without SSH keys do not reference the class at all, so they load on a stock server either way.

Reference

Command options

FlagEffect
--format FORMAT json (default), ldif, or passwd
--source SOURCE merged (default), cloud (IdP-synced users only), or local (portal-managed users only)
--out PATH Output file (default: stdout). For --format passwd this is a directory and is required
--base-dn DN Base DN for LDIF entries (default: dc=example,dc=com)
--config PATH Agent config file (default: /etc/dirless/dirless-agent.toml)
--server / --token / --tenant-id / --age-key Run from a non-enrolled machine by passing the backend URL, bearer token, tenant ID, and age private key explicitly
Keep the export file safe. It contains no passwords, but it is a complete listing of your users, groups, and SSH public keys. Delete it once it has served its purpose.

What's next