├── README.md ├── defaults └── main.yml ├── handlers └── main.yml ├── meta └── main.yml ├── tasks └── main.yml ├── templates ├── znc-base.conf ├── znc-child.conf └── znc-monit.conf └── vars └── main.yml /README.md: -------------------------------------------------------------------------------- 1 | znc-on-znc 2 | ========== 3 | 4 | This role makes it easy to create a set of connected ZNC bouncer 5 | instances, so that each IRC client has its own scrollback. 6 | 7 | This work is based on the work of Sean Dague and Dan Smith, as 8 | described in https://dague.net/2014/09/13/my-irc-proxy-setup/ 9 | 10 | A base ZNC instance is configured to talk to the IRC servers 11 | upstream. Then separate child servers are created and exposed for 12 | clients to connect to. Each child server has its own self-signed SSL 13 | certificate and ZNC configuration file. A monit service is configured 14 | for each ZNC server to ensure that the bouncer itself stays running. 15 | 16 | Requirements 17 | ------------ 18 | 19 | You need accounts on whatever IRC server(s) you use. 20 | 21 | You need to generate hashed password values for the user connecting to 22 | the ZNC servers. Generate these values using "znc --makepass". 23 | 24 | You will need to manage the firewall settings for the host you run 25 | this on to expose the ports configured for each client service. 26 | 27 | Role Variables 28 | -------------- 29 | 30 | * znc_system_user 31 | 32 | User to use on the system to run znc. By default it will be the same user as 33 | what Ansible uses to connect to the system. 34 | 35 | * znc_system_group 36 | 37 | Group to use on the system to run znc. By default it will be the same group 38 | as what Ansible uses to connect to the system. 39 | 40 | * znc_config_dir 41 | 42 | Base directory for the personal ZNC configuration files. A separate 43 | subdirectory is created under this path for each server. Must be 44 | writable by the user running the ZNC services. Must be a full 45 | path. Defaults to ~/znc. 46 | 47 | * znc_base_port 48 | 49 | The TCP port for the base ZNC server to listen on. Clients do not 50 | usually connect to this server, so the port doesn't really matter, 51 | but it's configurable anyway. Defaults to 6666. 52 | 53 | * znc_max_buffer_size 54 | 55 | The MaxBufferSize for the server. 56 | 57 | * znc_user 58 | 59 | Dictionary containing credentials to be used to connect to the ZNC 60 | bouncer. Generate these values using "znc --makepass". 61 | 62 | * name 63 | 64 | The user name. 65 | 66 | * hash 67 | 68 | The hashed password. 69 | 70 | * method 71 | 72 | The hash encryption method used. Defaults to SHA256. 73 | 74 | * salt 75 | 76 | The salt value used for the encryption. 77 | 78 | * password 79 | 80 | The unencrypted password value, used to let the child servers 81 | connect to the base server. 82 | 83 | * znc_nick 84 | 85 | The nickname to use on the IRC server. 86 | 87 | * znc_alt_nick 88 | 89 | The alternate nickname to fall back to if znc_nick is 90 | taken. Defaults to znc_nick + "_". 91 | 92 | * znc_buffer 93 | 94 | The number of lines to buffer. Defaults to 500. 95 | 96 | * znc_quit_msg 97 | 98 | Message to use when ZNC shuts down. 99 | 100 | * znc_real_name 101 | 102 | A fuller name than the nick or ident. 103 | 104 | * znc_extra_modules 105 | 106 | A list of module names to be added to the base server. For example, 107 | include the "log" module to log all conversations and include 108 | "webadmin" to enable the web UI. 109 | 110 | * znc_networks 111 | 112 | The IRC networks to connect to. For each network, specify: 113 | 114 | * name 115 | 116 | The unique name of the network. For example, "freenode". 117 | 118 | * ident 119 | 120 | The confirmed identity on the IRC service. Frequently this is the 121 | same as the nick, but multiple nicks can be associated with a single 122 | identity. (Changed from the single value "znc_ident" as part of 2.x.) 123 | 124 | * server_name 125 | 126 | The hostname or IP of the IRC server. For example, 127 | "chat.freenode.net". 128 | 129 | * server_port 130 | 131 | The port on which to connect. For SSL connections, append "+" to 132 | the port number. For example, "6667" or "6667+". 133 | 134 | * znc_server_passwords 135 | 136 | Mapping of network names to passwords for connecting to them as the 137 | confirmed identity in the ident field. Replaces the 138 | "server_password" parameter to allow the passwords to be stored 139 | separately in a file managed with ansible-vault. 140 | 141 | * znc_channels 142 | 143 | List of names of channels to join by default. 144 | 145 | * znc_clients 146 | 147 | List of ZNC instances to run for different clients. 148 | 149 | * name 150 | 151 | The name of the client. Avoid spaces and punctuation because the 152 | name is used to identify the service and name configuration files 153 | and directories. 154 | 155 | * host 156 | 157 | The host/IP on which the client should listen. 158 | 159 | * port 160 | 161 | The port on which the client should listen. This port needs to be 162 | exposed through your firewall. The service runs as the the user 163 | ansible is using, so the port shouldn't be privileged. (See 164 | znc_firewall_bypass_port.) 165 | 166 | * ipv4 167 | 168 | Enable IPv4. Defaults to true. 169 | 170 | * ipv6 171 | 172 | Enable IPv6. Defaults to false. 173 | 174 | * buffer 175 | 176 | Override znc_buffer for this connection. Optional, defaults to 177 | value of znc_buffer. 178 | 179 | * use_ssl 180 | 181 | Boolean flag to control whether SSL is used. Defaults to true. 182 | 183 | * ssl_protocols 184 | 185 | String passed to the SSLProtocols variable in the ZNC 186 | config. Defaults to empty, which does not set the value and uses 187 | the default for SSLProtocols. 188 | 189 | * znc_firewall_bypass_port 190 | 191 | Many corporate and public wifi networks block outgoing connections 192 | to "arbitrary" or IRC ports. To bypass these, many users configure 193 | their IRC bouncer to listen on a port that is more likely to be 194 | open, such as 443. Because this playbook configures services to run 195 | as a regular non-root user, the services cannot be bound directly to 196 | port 443. Instead, this option can be used to specify one port 197 | number that should be mapped to 443 using rinetd. 198 | 199 | Configuring Your IRC Client 200 | --------------------------- 201 | 202 | Configure your client to connect to your server using one of the 203 | settings from the `znc_clients` variable. SSL is always enabled for 204 | all connections. 205 | 206 | Dependencies 207 | ------------ 208 | 209 | None 210 | 211 | Example Playbook 212 | ---------------- 213 | 214 | Including an example of how to use your role (for instance, with 215 | variables passed in as parameters) is always nice for users too: 216 | 217 | - hosts: znc 218 | roles: 219 | - znc-on-znc 220 | vars: 221 | # By default we will run znc as the same user/group Ansible uses to 222 | # connect to the system. If you prefer to specify a different 223 | # user/group or if Ansible uses the root user you can specify the 224 | # user/group to run znc. To run znc as the user 'znc' and group 'znc' 225 | # uncomment the following two lines: 226 | #znc_system_user: znc 227 | #znc_system_group: znc 228 | # znc_user and znc_server_passwords can go into a vault-encrypted file. 229 | znc_user: 230 | name: dhellmann 231 | hash: hashhashhash 232 | method: SHA256 233 | salt: "saltsaltsalt" 234 | password: unencryptedpass 235 | znc_server_passwords: 236 | freenode: supersecretvalue 237 | tech404: evenmoresecretvalue 238 | # The remaining values are safe to leave in the playbook in clear text. 239 | znc_nick: dhellmann 240 | znc_quit_msg: disconnecting 241 | znc_real_name: Doug Hellmann 242 | znc_networks: 243 | - name: freenode 244 | server_name: chat.freenode.net 245 | server_port: 6667 246 | ident: dhellmann 247 | channels: 248 | - "#openstack" 249 | - "#openstack-dev" 250 | - "#openstack-infra" 251 | - "#openstack-meeting" 252 | - "#openstack-meeting-3" 253 | - "#openstack-meeting-alt" 254 | - "#openstack-oslo" 255 | - "#wsme" 256 | - name: tech404 257 | ident: dhellmann 258 | server_name: tech404.irc.slack.com 259 | server_port: "+6697" 260 | channels: 261 | - "#python" 262 | - "#openstack" 263 | znc_firewall_bypass_port: 6672 264 | znc_clients: 265 | - name: hubert 266 | port: 6667 267 | - name: lrrr 268 | port: 6672 269 | auto_clear_chan_buffer: true 270 | - name: phone 271 | port: 6673 272 | buffer: 100 273 | auto_clear_chan_buffer: true 274 | - name: ipad 275 | port: 6677 276 | buffer: 100 277 | auto_clear_chan_buffer: true 278 | 279 | License 280 | ------- 281 | 282 | BSD 283 | 284 | Author Information 285 | ------------------ 286 | 287 | Doug Hellmann 288 | 289 | 290 | TODO 291 | ---- 292 | 293 | * Restart ZNC instances when their configuration files change. 294 | * Support using vault for passwords. 295 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for multiznc 3 | znc_system_user: "{{ansible_env.USER}}" 4 | znc_system_group: "{{ansible_env.USER}}" 5 | znc_system_user_dir: "/home/{{znc_system_user}}" 6 | znc_config_dir: "{{znc_system_user_dir|expanduser}}/znc" 7 | znc_base_port: 6666 8 | znc_max_buffer_size: 5000 9 | znc_auto_clear_chan_buffer: "false" 10 | znc_buffer: 500 11 | znc_alt_nick: "{{znc_nick}}_" 12 | znc_extra_modules: [] 13 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: znc-restart-monit 3 | service: name=monit state=restarted 4 | become: yes 5 | 6 | - name: restart rinetd 7 | service: name=rinetd state=restarted 8 | become: yes 9 | 10 | - name: znc-hup-instances 11 | shell: for f in {{ znc_config_dir }}/*/znc.pid; do /bin/kill -HUP `cat $f`; done 12 | ignore_errors: yes 13 | become: yes 14 | become_user: "{{ znc_system_user }}" 15 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | author: Doug Hellmann 4 | description: "Deploy ZNC bouncer for IRC, configured for multiple clients." 5 | license: BSD 6 | min_ansible_version: 1.8.2 7 | platforms: 8 | - name: Ubuntu 9 | versions: 10 | - precise 11 | - trusty 12 | categories: 13 | - cloud 14 | dependencies: [] 15 | # List your role dependencies here, one per line. Only 16 | # dependencies available via galaxy should be listed here. 17 | # Be sure to remove the '[]' above if you add dependencies 18 | # to this list. 19 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Ensure not installing znc to root user 4 | fail: msg="Trying to install znc to the root user. znc refuses to run as root. Please set values for znc_system_user and znc_system_group" 5 | when: "'znc_system_user' == 'root'" 6 | 7 | - name: Install packages 8 | package: 9 | state: latest 10 | name: 11 | - znc 12 | - znc-dev 13 | - znc-perl 14 | - znc-python 15 | - znc-tcl 16 | - openssl 17 | - monit 18 | become: yes 19 | tags: 20 | - zncinstall 21 | - monitinstall 22 | 23 | - name: Install znc-dbg packages 24 | become: yes 25 | package: 26 | state: latest 27 | name: 28 | - znc-dbg 29 | tags: 30 | - zncinstall 31 | - monitinstall 32 | when: ansible_distribution == 'Debian' and ansible_distribution_release == 'stretch' 33 | 34 | # Create the ZNC user if needed 35 | - name: Create znc group 36 | group: name={{ znc_system_group }} 37 | become: yes 38 | - name: Create znc user 39 | user: name={{ znc_system_user }} group={{ znc_system_group }} 40 | become: yes 41 | tags: 42 | - zncconfig 43 | 44 | - name: "Base config directory" 45 | file: path={{ znc_config_dir }}/base/configs state=directory mode=0755 46 | become: yes 47 | become_user: "{{ znc_system_user }}" 48 | tags: 49 | - zncconfig 50 | 51 | - name: "Child config directories" 52 | file: path={{ znc_config_dir }}/{{ item.name }}/configs state=directory mode=0755 53 | with_items: "{{ znc_clients }}" 54 | become: yes 55 | become_user: "{{ znc_system_user }}" 56 | tags: 57 | - zncconfig 58 | 59 | # based on https://github.com/willshersystems/ansible-znc/blob/master/tasks/ssl.yml 60 | - name: "Base SSL certificate" 61 | command: znc --datadir={{ znc_config_dir }}/base --makepem 62 | args: 63 | creates: "{{ znc_config_dir }}/base/znc.pem" 64 | become: yes 65 | become_user: "{{ znc_system_user }}" 66 | tags: 67 | - zncconfig 68 | - ssl 69 | 70 | - name: "Base SSL certificate fingerprint" # noqa 301 71 | become: yes 72 | become_user: "{{ znc_system_user }}" 73 | shell: | 74 | set -o pipefail 75 | cat {{ znc_config_dir }}/base/znc.pem \ 76 | | openssl x509 -fingerprint -sha256 \ 77 | | grep Fingerprint= \ 78 | | cut -f2 -d= 79 | args: 80 | executable: /bin/bash 81 | register: znc_base_ssl_fingerprint 82 | tags: 83 | - zncconfig 84 | - ssl 85 | 86 | # based on https://github.com/willshersystems/ansible-znc/blob/master/tasks/ssl.yml 87 | - name: "SSL certificates" 88 | command: znc --datadir={{ znc_config_dir }}/{{ item.name }} --makepem 89 | args: 90 | creates: "{{ znc_config_dir }}/{{ item.name }}/znc.pem" 91 | with_items: "{{ znc_clients }}" 92 | become: yes 93 | become_user: "{{ znc_system_user }}" 94 | tags: 95 | - zncconfig 96 | - ssl 97 | 98 | - name: "Base ZNC config file" 99 | template: 100 | src: znc-base.conf 101 | dest: "{{ znc_config_dir }}/base/configs/znc.conf" 102 | mode: 0644 103 | notify: 104 | - znc-hup-instances 105 | become: yes 106 | become_user: "{{ znc_system_user }}" 107 | tags: 108 | - zncconfig 109 | 110 | - name: "Child ZNC config files" 111 | template: 112 | src: znc-child.conf 113 | dest: "{{ znc_config_dir }}/{{ item.name }}/configs/znc.conf" 114 | mode: 0644 115 | with_items: "{{ znc_clients }}" 116 | notify: 117 | - znc-hup-instances 118 | become: yes 119 | become_user: "{{ znc_system_user }}" 120 | tags: 121 | - zncconfig 122 | 123 | - name: "Base monit config" 124 | template: src=znc-monit.conf 125 | dest=/etc/monit/conf.d/znc-{{ item.name }}.conf 126 | notify: 127 | - znc-restart-monit 128 | with_items: 129 | - name: base 130 | become: yes 131 | tags: 132 | - monitconfig 133 | 134 | - name: "Child monit configs" 135 | become: yes 136 | template: src=znc-monit.conf 137 | dest=/etc/monit/conf.d/znc-{{ item.name }}.conf 138 | notify: 139 | - znc-restart-monit 140 | with_items: "{{ znc_clients }}" 141 | tags: 142 | - monitconfig 143 | 144 | - name: Make sure monit is running 145 | service: name=monit state=started enabled=yes 146 | become: yes 147 | tags: 148 | - monitconfig 149 | 150 | # Use rinetd to bypass firewalls that block regular IRC ports (or 151 | # arbitrary ports) by connecting *one* client to port 443. 152 | - name: Install rinetd 153 | become: yes 154 | apt: name=rinetd 155 | when: znc_firewall_bypass_port is defined 156 | tags: 157 | - rinetdinstall 158 | 159 | - name: Configure rinetd 160 | become: yes 161 | lineinfile: dest=/etc/rinetd.conf 162 | insertafter="# bindadress bindport connectaddress connectport" 163 | line="0.0.0.0 443 127.0.0.1 {{ znc_firewall_bypass_port }}" 164 | when: znc_firewall_bypass_port is defined 165 | notify: 166 | - restart rinetd 167 | tags: 168 | - rinedtconfig 169 | -------------------------------------------------------------------------------- /templates/znc-base.conf: -------------------------------------------------------------------------------- 1 | // WARNING 2 | // 3 | // Do NOT edit this file while ZNC is running! 4 | // Use webadmin or *controlpanel instead. 5 | // 6 | // Altering this file by hand will forfeit all support. 7 | // 8 | // But if you feel risky, you might want to read help on /znc saveconfig and /znc rehash. 9 | // Also check http://en.znc.in/wiki/Configuration 10 | 11 | AnonIPLimit = 10 12 | ConnectDelay = 5 13 | MaxBufferSize = {{znc_max_buffer_size}} 14 | ProtectWebSessions = true 15 | SSLCertFile = {{znc_config_dir}}/base/znc.pem 16 | ServerThrottle = 30 17 | Version = 1.6 18 | HideVersion = false 19 | PidFile = {{znc_config_dir}}/base/znc.pid 20 | 21 | 22 | AllowIRC = true 23 | AllowWeb = true 24 | IPv4 = true 25 | IPv6 = true 26 | Port = {{znc_base_port}} 27 | SSL = true 28 | 29 | 30 | 31 | Admin = true 32 | AltNick = {{znc_alt_nick}} 33 | AppendTimestamp = false 34 | AutoClearChanBuffer = {{znc_auto_clear_chan_buffer}} 35 | Buffer = {{znc_buffer}} 36 | ChanModes = +stn 37 | DenyLoadMod = false 38 | DenySetBindHost = false 39 | JoinTries = 10 40 | LoadModule = chansaver 41 | LoadModule = controlpanel 42 | LoadModule = perform 43 | {% for mod in znc_extra_modules %} 44 | LoadModule = {{mod}} 45 | {% endfor %} 46 | MaxJoins = 0 47 | MaxNetworks = 1 48 | MultiClients = true 49 | Nick = {{znc_nick}} 50 | PrependTimestamp = true 51 | QuitMsg = {{znc_quit_msg}} 52 | RealName = {{znc_real_name}} 53 | StatusPrefix = * 54 | TimestampFormat = [%H:%M:%S] 55 | 56 | {% for net in znc_networks %} 57 | 58 | FloodBurst = 4 59 | FloodRate = 1.00 60 | IRCConnectEnabled = true 61 | LoadModule = chansaver 62 | LoadModule = keepnick 63 | LoadModule = kickrejoin 64 | LoadModule = nickserv 65 | LoadModule = perform 66 | Ident = {{net.ident}} 67 | Server = {{net.server_name}} {{net.server_port}} {{znc_server_passwords[net.name]}} 68 | 69 | {% for c in net.channels %} 70 | 71 | 72 | {% endfor %} 73 | 74 | 75 | {% endfor %} 76 | 77 | 78 | Hash = {{znc_user.hash}} 79 | Method = {{znc_user.method | default('SHA256')}} 80 | Salt = {{znc_user.salt}} 81 | 82 | 83 | -------------------------------------------------------------------------------- /templates/znc-child.conf: -------------------------------------------------------------------------------- 1 | // WARNING 2 | // 3 | // Do NOT edit this file while ZNC is running! 4 | // Use webadmin or *controlpanel instead. 5 | // 6 | // Altering this file by hand will forfeit all support. 7 | // 8 | // But if you feel risky, you might want to read help on /znc saveconfig and /znc rehash. 9 | // Also check http://en.znc.in/wiki/Configuration 10 | 11 | AnonIPLimit = 10 12 | ConnectDelay = 5 13 | MaxBufferSize = {{znc_max_buffer_size}} 14 | ProtectWebSessions = true 15 | {% if item.get('use_ssl', True) %} 16 | SSLCertFile = {{znc_config_dir}}/{{item.name}}/znc.pem 17 | {% endif %} 18 | ServerThrottle = 30 19 | Version = 1.6 20 | HideVersion = false 21 | PidFile = {{znc_config_dir}}/{{item.name}}/znc.pid 22 | {% if item.get('ssl_protocols') %} 23 | SSLProtocols = {{item['ssl_protocols']}} 24 | {% endif %} 25 | 26 | 27 | AllowIRC = true 28 | AllowWeb = true 29 | {% if item.get('ipv4', True) %} 30 | IPv4 = true 31 | {% endif %} 32 | {% if item.get('ipv6', False) %} 33 | IPv6 = true 34 | {% endif %} 35 | Port = {{item.port}} 36 | SSL = {{item.get('use_ssl', 'true')}} 37 | {% if item.get('host', False) %} 38 | Host = {{ item.host }} 39 | {% endif %} 40 | 41 | 42 | 43 | Admin = true 44 | AltNick = {{znc_alt_nick}} 45 | AppendTimestamp = false 46 | AutoClearChanBuffer = {{item.auto_clear_chan_buffer | default(znc_auto_clear_chan_buffer)}} 47 | Buffer = {{item.buffer | default(znc_buffer)}} 48 | ChanModes = +stn 49 | DenyLoadMod = false 50 | DenySetBindHost = false 51 | JoinTries = 10 52 | LoadModule = chansaver 53 | LoadModule = controlpanel 54 | LoadModule = perform 55 | MaxJoins = 0 56 | MaxNetworks = 1 57 | MultiClients = true 58 | Nick = {{znc_nick}} 59 | PrependTimestamp = true 60 | QuitMsg = {{znc_quit_msg}} 61 | RealName = {{znc_real_name}} 62 | StatusPrefix = * 63 | TimestampFormat = [%H:%M:%S] 64 | 65 | {% for net in znc_networks %} 66 | 67 | FloodBurst = 4 68 | FloodRate = 1.00 69 | IRCConnectEnabled = true 70 | LoadModule = chansaver 71 | LoadModule = keepnick 72 | LoadModule = kickrejoin 73 | LoadModule = nickserv 74 | LoadModule = perform 75 | // Combine the ZNC username and the network name to ensure the 76 | // connection is for the specific network. 77 | // http://wiki.znc.in/Connecting_to_ZNC 78 | Ident = {{znc_user.name}}/{{net.name}} 79 | Server = 127.0.0.1 +{{znc_base_port}} {{znc_user.password}} 80 | // We generated this SSL certificate ourselves, so set the 81 | // flag to trust it. 82 | TrustedServerFingerprint = {{znc_base_ssl_fingerprint.stdout}} 83 | 84 | {% for c in net.channels %} 85 | 86 | 87 | {% endfor %} 88 | 89 | 90 | {% endfor %} 91 | 92 | 93 | Hash = {{znc_user.hash}} 94 | Method = {{znc_user.method | default('SHA256')}} 95 | Salt = {{znc_user.salt}} 96 | 97 | 98 | -------------------------------------------------------------------------------- /templates/znc-monit.conf: -------------------------------------------------------------------------------- 1 | check process znc-{{item.name}} with pidfile {{znc_config_dir}}/{{item.name}}/znc.pid 2 | start program = "/usr/bin/znc -d {{znc_config_dir}}/{{item.name}}" 3 | as uid {{znc_system_user}} and gid {{znc_system_group}} 4 | stop program = "/bin/bash -c 'kill -s SIGTERM `cat {{znc_config_dir}}/{{item.name}}/znc.pid`'" 5 | if totalmem is greater than 300 MB for 10 cycles then restart 6 | -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | --------------------------------------------------------------------------------