├── LICENSE.md ├── README.md └── aur /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Austin Hyde 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ansible-aur 2 | 3 | An Ansible module for installing [AUR](https://aur.archlinux.org/) packages. 4 | 5 | If [cower](https://github.com/falconindy/cower) is available, it will be used to download packages. Otherwise, cURL will be used. Packages are installed via [makepkg](https://wiki.archlinux.org/index.php/Makepkg). 6 | 7 | ## Dependencies (Managed Node) 8 | 9 | * [Arch Linux](https://www.archlinux.org/) (Obviously) 10 | * [cower](https://github.com/falconindy/cower) (Optional) 11 | 12 | ## Installation 13 | 14 | 1. Clone this repo 15 | 2. Copy or link the `aur` file into your global Ansible library (usually `/usr/share/ansible`) or into the `./library` folder alongside your top-level playbook 16 | 17 | ## Usage 18 | 19 | ### Options 20 | 21 | * `name` - required, name of the AUR package to install 22 | * `user` - required, name of the user you want to install the package as 23 | * `dir` - optional, name of the directory you want to download AUR packages into. If this is left blank, the module will try to use `~/aur` for the specified user. 24 | * `skip_pgp` - optional, whether makepkg should skip verification of PGP signatures of source files. Defaults to false. This may be useful when installing packages on a target node with no keyring established. 25 | 26 | ### Examples 27 | 28 | ```yaml 29 | # Install package foo 30 | - aur: name=foo user=someone 31 | 32 | # Install package foo with a specific download directory 33 | - aur: name=foo dir=/opt/packages/aur user=someone 34 | 35 | # Install package foo without checking PGP signature 36 | - aur: name=foo user=someone skip_pgp=yes 37 | 38 | ``` 39 | 40 | If you wouldn't like to install AUR packages by normal user, you can create a system user `aurman` (or another one which you like) as AUR manager: 41 | 42 | ```yaml 43 | - name: New user for AUR management 44 | user: 45 | name: aurman 46 | comment: "AUR manager" 47 | shell: /bin/nologin 48 | home: /var/lib/aurman 49 | system: yes 50 | ``` 51 | -------------------------------------------------------------------------------- /aur: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2014 Austin Hyde 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | 25 | import os 26 | import pwd 27 | import platform 28 | 29 | 30 | def cower_in_path(module): 31 | """ 32 | Determine if cower is available. 33 | """ 34 | rc, stdout, stderr = module.run_command('which cower', check_rc=False) 35 | return rc == 0 36 | 37 | 38 | def pacman_in_path(module): 39 | """ 40 | Determine if pacman is available. 41 | """ 42 | rc, stdout, stderr = module.run_command('which pacman', check_rc=False) 43 | return rc == 0 44 | 45 | 46 | def package_installed(module, pkg): 47 | """ 48 | Determine if a package is already installed. 49 | """ 50 | rc, stdout, stderr = module.run_command('pacman -Q %s' % pkg, check_rc=False) 51 | return rc == 0 52 | 53 | 54 | def check_packages(module, pkgs): 55 | """ 56 | Inform the user what would change if the module were run. 57 | """ 58 | would_be_changed = [] 59 | 60 | for pkg in pkgs: 61 | installed = package_installed(module, pkg) 62 | if not installed: 63 | would_be_changed.append(pkg) 64 | 65 | if would_be_changed: 66 | module.exit_json(changed=True, msg='%s package(s) would be installed' % (len(would_be_changed))) 67 | else: 68 | module.exit_json(changed=False, msg='all packages are already installed') 69 | 70 | 71 | def download_packages(module, pkgs, dir, user): 72 | """ 73 | Download the specified packages. 74 | """ 75 | # Use cower, if available. 76 | if cower_in_path(module): 77 | cmds = ['sudo -u %s cower -dqf %s', ] 78 | # Otherwise, fall back to cURL 79 | else: 80 | cmds = ['sudo -u %s curl -O https://aur.archlinux.org/cgit/aur.git/snapshot/%s.tar.gz', 81 | 'sudo -u %s tar xzf %s.tar.gz'] 82 | for pkg in pkgs: 83 | # If the package is already installed, skip the download. 84 | if package_installed(module, pkg): 85 | continue 86 | # Change into the specified directory for download. 87 | os.chdir(dir) 88 | # Attempt to install the package. 89 | for cmd in cmds: 90 | rc, stdout, stderr = module.run_command(cmd % (user, pkg), check_rc=False) 91 | if rc != 0: 92 | module.fail_json(msg='failed to download package %s, because: %s' % (pkg,stderr)) 93 | 94 | 95 | def install_packages(module, pkgs, dir, user, virtual): 96 | """ 97 | Install the specified packages via makepkg. 98 | """ 99 | num_installed = 0 100 | 101 | if platform.machine().startswith('arm') or platform.machine().startswith('aarch64'): 102 | makepkg_args = '-Acsrf' 103 | else: 104 | makepkg_args = '-csrf' 105 | cmd = 'sudo -u %s PKGEXT=".pkg.tar" makepkg %s --noconfirm --needed --noprogressbar' % (user, makepkg_args) 106 | if module.params['skip_pgp']: 107 | cmd += ' --skippgpcheck' 108 | for pkg in pkgs: 109 | # If the package is already installed, skip the install. 110 | if package_installed(module, pkg): 111 | continue 112 | 113 | # Change into the package directory. 114 | # Check if the package is a virtual package 115 | if virtual: 116 | os.chdir(os.path.join(dir, virtual)) 117 | else: 118 | os.chdir(os.path.join(dir, pkg)) 119 | 120 | # Attempt to build the directory. 121 | rc, stdout, stderr = module.run_command(cmd, check_rc=False) 122 | if rc != 0: 123 | module.fail_json(msg='failed to build package %s, because: %s' % (pkg,stderr)) 124 | 125 | # If the package was succesfully built, install it. 126 | rc, stdout, stderr = module.run_command('pacman -U --noconfirm *.pkg.tar*', check_rc=False, use_unsafe_shell=True) 127 | if rc != 0: 128 | module.fail_json(msg='failed to install package %s, because: %s' % (pkg,stderr)) 129 | else: 130 | num_installed += 1 131 | 132 | # Exit with the number of packages succesfully installed. 133 | if num_installed > 0: 134 | module.exit_json(changed=True, msg='installed %s package(s)' % num_installed) 135 | else: 136 | module.exit_json(changed=False, msg='all packages were already installed') 137 | 138 | 139 | def main(): 140 | module = AnsibleModule( 141 | argument_spec = dict( 142 | name = dict(required=True, type='list'), 143 | user = dict(required=True), 144 | dir = dict(), 145 | skip_pgp = dict(default=False, type='bool'), 146 | virtual = dict(), 147 | ), 148 | supports_check_mode = True 149 | ) 150 | 151 | # Fail of pacman is not available. 152 | if not pacman_in_path(module): 153 | module.fail_json(msg="could not locate pacman executable") 154 | 155 | p = module.params 156 | 157 | # Get all the requested package names. 158 | pkgs = p['name'] 159 | 160 | # Fail if the specified user does not exist. 161 | try: 162 | pwd.getpwnam(p['user']) 163 | except KeyError: 164 | module.fail_json(msg="user %s does not exist" % p['user']) 165 | else: 166 | user = p['user'] 167 | 168 | # If no directory was given, assume the packages should be downloaded to 169 | # ~user/aur. 170 | if not p['dir']: 171 | home = os.path.expanduser('~%s' % user) 172 | if not os.path.exists(home): 173 | module.fail_json(msg="%s's home directory %s does not exist" % (user, home)) 174 | 175 | dir = os.path.join(home, 'aur') 176 | if not os.path.exists(dir): 177 | os.makedirs(dir) 178 | uid = pwd.getpwnam(user).pw_uid 179 | os.chown(dir, uid, -1) 180 | else: 181 | dir = os.path.expanduser(p['dir']) 182 | 183 | # Fail if the specified directory does not exist. 184 | if not os.path.exists(dir): 185 | module.fail_json(msg="directory %s does not exist" % dir) 186 | 187 | if module.check_mode: 188 | check_packages(module, pkgs) 189 | 190 | download_packages(module, pkgs, dir, user) 191 | # Check if the package is virtual 192 | if p['virtual']: 193 | virtual = p['virtual'] 194 | else: 195 | virtual = False 196 | 197 | install_packages(module, pkgs, dir, user, virtual) 198 | 199 | 200 | from ansible.module_utils.basic import * 201 | main() 202 | --------------------------------------------------------------------------------