Introduction
Samba provides SMB/CIFS file sharing between Linux and Windows systems. Ansible automates the full setup — install Samba, template smb.conf for shares, manage users and passwords, configure access control, join Active Directory domains, and set up printer sharing. This is essential for mixed Linux/Windows environments.
Install Samba
---
- name: Deploy Samba file server
hosts: file_servers
become: true
vars:
samba_workgroup: WORKGROUP
samba_server_string: "Ansible-managed file server"
samba_shares:
- name: shared
path: /srv/samba/shared
comment: Shared files
browseable: true
read_only: false
valid_users: "@smbusers"
create_mask: "0664"
directory_mask: "0775"
- name: public
path: /srv/samba/public
comment: Public read-only
browseable: true
read_only: true
guest_ok: true
tasks:
- name: Install Samba
ansible.builtin.package:
name:
- samba
- samba-common
- samba-client
state: present
- name: Deploy smb.conf
ansible.builtin.template:
src: smb.conf.j2
dest: /etc/samba/smb.conf
mode: '0644'
validate: "testparm -s %s"
notify: restart smbd
- name: Create share directories
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
mode: "{{ item.directory_mask | default('0775') }}"
group: smbusers
loop: "{{ samba_shares }}"
- name: Allow Samba through firewall
ansible.posix.firewalld:
service: samba
permanent: true
state: enabled
immediate: true
- name: Start Samba services
ansible.builtin.service:
name: "{{ item }}"
state: started
enabled: true
loop:
- smbd
- nmbd
handlers:
- name: restart smbd
ansible.builtin.service:
name: smbd
state: restarted
smb.conf Template
# templates/smb.conf.j2
# Managed by Ansible — do not edit manually
[global]
workgroup = {{ samba_workgroup }}
server string = {{ samba_server_string }}
security = user
map to guest = Bad User
dns proxy = no
# Logging
log file = /var/log/samba/log.%m
max log size = 1000
logging = file
# Performance
socket options = TCP_NODELAY IPTOS_LOWDELAY
read raw = yes
write raw = yes
use sendfile = yes
aio read size = 16384
aio write size = 16384
# macOS compatibility
min protocol = SMB2
vfs objects = fruit streams_xattr
fruit:metadata = stream
fruit:model = MacSamba
fruit:nfs_aces = no
{% for share in samba_shares %}
[{{ share.name }}]
path = {{ share.path }}
comment = {{ share.comment | default('') }}
browseable = {{ 'yes' if share.browseable | default(true) else 'no' }}
read only = {{ 'yes' if share.read_only | default(false) else 'no' }}
{% if share.valid_users is defined %}
valid users = {{ share.valid_users }}
{% endif %}
{% if share.write_list is defined %}
write list = {{ share.write_list }}
{% endif %}
{% if share.guest_ok is defined and share.guest_ok %}
guest ok = yes
{% endif %}
create mask = {{ share.create_mask | default('0664') }}
directory mask = {{ share.directory_mask | default('0775') }}
{% if share.force_group is defined %}
force group = {{ share.force_group }}
{% endif %}
{% endfor %}
User Management
- name: Create system group for Samba
ansible.builtin.group:
name: smbusers
state: present
- name: Create Samba users
block:
- name: Create system accounts
ansible.builtin.user:
name: "{{ item.name }}"
groups: smbusers
append: true
shell: /usr/sbin/nologin
create_home: false
loop: "{{ samba_users }}"
- name: Set Samba passwords
ansible.builtin.shell: |
(echo '{{ item.password }}'; echo '{{ item.password }}') | smbpasswd -s -a {{ item.name }}
loop: "{{ samba_users }}"
no_log: true
changed_when: true
- name: Enable Samba users
ansible.builtin.command: smbpasswd -e {{ item.name }}
loop: "{{ samba_users }}"
changed_when: true
# vars/samba-users.yml (vault-encrypted)
samba_users:
- { name: alice, password: "{{ vault_alice_smb_password }}" }
- { name: bob, password: "{{ vault_bob_smb_password }}" }
- { name: charlie, password: "{{ vault_charlie_smb_password }}" }
Department Shares
samba_shares:
- name: engineering
path: /srv/samba/engineering
comment: Engineering department
valid_users: "@engineering"
write_list: "@engineering"
force_group: engineering
create_mask: "0660"
directory_mask: "0770"
- name: finance
path: /srv/samba/finance
comment: Finance department (restricted)
valid_users: "@finance"
write_list: "@finance-write"
force_group: finance
create_mask: "0660"
directory_mask: "0770"
browseable: false
- name: management
path: /srv/samba/management
comment: Management only
valid_users: "@management"
create_mask: "0600"
directory_mask: "0700"
Join Active Directory
---
- name: Join Samba to Active Directory
hosts: file_servers
become: true
vars:
ad_domain: EXAMPLE.COM
ad_realm: example.com
ad_admin_user: administrator
tasks:
- name: Install AD packages
ansible.builtin.package:
name:
- samba
- winbind
- libpam-winbind
- libnss-winbind
- krb5-user
state: present
- name: Configure Kerberos
ansible.builtin.template:
src: krb5.conf.j2
dest: /etc/krb5.conf
mode: '0644'
- name: Configure Samba as AD member
ansible.builtin.template:
src: smb-ad.conf.j2
dest: /etc/samba/smb.conf
mode: '0644'
notify: restart smbd
- name: Join domain
ansible.builtin.command: >
net ads join -U {{ ad_admin_user }}%{{ vault_ad_admin_password }}
register: domain_join
changed_when: "'Joined' in domain_join.stdout"
failed_when: domain_join.rc != 0 and 'Already' not in domain_join.stderr
no_log: true
- name: Start winbind
ansible.builtin.service:
name: winbind
state: started
enabled: true
# templates/smb-ad.conf.j2
[global]
workgroup = {{ ad_domain.split('.')[0] }}
realm = {{ ad_realm | upper }}
security = ADS
encrypt passwords = yes
winbind use default domain = yes
winbind enum users = yes
winbind enum groups = yes
idmap config * : backend = tdb
idmap config * : range = 10000-20000
idmap config {{ ad_domain.split('.')[0] }} : backend = rid
idmap config {{ ad_domain.split('.')[0] }} : range = 20001-99999
template shell = /bin/bash
template homedir = /home/%U
Mount SMB Shares on Linux Clients
- name: Install CIFS client
ansible.builtin.package:
name: cifs-utils
state: present
- name: Create credentials file
ansible.builtin.copy:
dest: /root/.smbcredentials
content: |
username={{ smb_user }}
password={{ vault_smb_password }}
domain={{ smb_domain | default('WORKGROUP') }}
mode: '0600'
no_log: true
- name: Mount SMB share
ansible.posix.mount:
src: "//fileserver.example.com/shared"
path: /mnt/shared
fstype: cifs
opts: "credentials=/root/.smbcredentials,iocharset=utf8,uid=1000,gid=1000,file_mode=0664,dir_mode=0775"
state: mounted
Troubleshooting
Test Config
- name: Validate smb.conf
ansible.builtin.command: testparm -s
register: testparm
changed_when: false
- name: List shares
ansible.builtin.command: smbclient -L localhost -N
register: shares
changed_when: false
Permission Issues
- name: Fix share permissions recursively
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
recurse: true
group: smbusers
mode: u=rwX,g=rwX,o=rX
loop: "{{ samba_shares }}"
Related Articles
Conclusion
Ansible templates smb.conf from share definitions in variables — add shares by adding items to a list, manage users with smbpasswd, join AD domains with net ads join, and mount shares on clients with ansible.posix.mount. Validate with testparm before applying. Samba + Ansible gives you declarative file sharing that works across Linux, Windows, and macOS.