diff --git a/pillar/base/haproxy.sls b/pillar/base/haproxy.sls index 55e9809e..9754b6df 100644 --- a/pillar/base/haproxy.sls +++ b/pillar/base/haproxy.sls @@ -156,10 +156,6 @@ haproxy: hsts_preload: False listens: - hg_ssh: - bind: :20100 - service: hg-ssh - buildbot_worker: bind: :20101 service: buildbot-master-worker diff --git a/salt/hg/config/hg.apache.conf.jinja b/salt/hg/config/hg.apache.conf.jinja index 04a60c3d..316ff85e 100644 --- a/salt/hg/config/hg.apache.conf.jinja +++ b/salt/hg/config/hg.apache.conf.jinja @@ -32,30 +32,14 @@ Require all granted - # The lookup app is better run with few processes, since it uses a cache. - WSGIDaemonProcess hglookup user=hg group=hg \ - threads=2 processes=1 maximum-requests=1000 \ - display-name=hglookup - # The Location hack ensures the lookup app is run within - # this process group - WSGIProcessGroup hglookup + ProxyPass http://localhost:8000/lookup - WSGIScriptAlias /lookup /srv/hg/wsgi/lookup.wsgi - - # A lightweight standin for revision app to maintain support for /lookup - WSGIDaemonProcess hgrev user=hg group=hg \ - threads=1 processes=6 maximum-requests=100 \ - display-name=hgrev - # The Location hack ensures the lookup app is run within - # this process group - WSGIProcessGroup hgrev + ProxyPass http://localhost:8000 - WSGIScriptAliasMatch "^(.*)/rev/([A-Fa-f0-9]{12,40})/?" /srv/hg/wsgi/rev.wsgi - # Staticly serve hg repos over HTTP DocumentRoot /srv/hg/hg-static/ diff --git a/salt/hg/config/hgmin.service.jinja b/salt/hg/config/hgmin.service.jinja new file mode 100644 index 00000000..f9eadaf6 --- /dev/null +++ b/salt/hg/config/hgmin.service.jinja @@ -0,0 +1,17 @@ +[Unit] +Description=Minimal HG service +After=network.target + +[Service] +Environment=LC_ALL=en_US.UTF-8 +Environment=LANG=en_US.UTF-8 +WorkingDirectory=/srv/hg/src +ExecStart=/srv/hg/env/bin/gunicorn app:app -w 4 --access-logfile - --error-logfile - +ExecReload=/bin/kill -HUP $MAINPID +ExecStop = /bin/kill -s TERM $MAINPID +Restart=on-failure +User=hg +Group=hg + +[Install] +WantedBy=multi-user.target diff --git a/salt/hg/config/repos.conf b/salt/hg/config/repos.conf deleted file mode 100644 index 5836b595..00000000 --- a/salt/hg/config/repos.conf +++ /dev/null @@ -1,19 +0,0 @@ -[paths] -/ = /srv/hg/repos/* - -[diff] -git = 1 -showfunc = 1 - -[web] -allow_archive = gz, zip, bz2 -collapse = True -contact = - -descend = True -encoding = utf-8 -logourl = https://hg.python.org -pygments_style = default -style = hgpythonorg - -[extensions] -hgext.highlight = diff --git a/salt/hg/files/hg/bin/gcrepos b/salt/hg/files/hg/bin/gcrepos deleted file mode 100644 index e834cc6c..00000000 --- a/salt/hg/files/hg/bin/gcrepos +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python - -# enable importing on demand to reduce startup time -from mercurial import demandimport; demandimport.enable() -from mercurial import ui as uimod -from mercurial import hg, discovery -from mercurial.node import nullid, nullrev, hex -from mercurial.error import RepoError - -from optparse import OptionParser -import os -import sys -import time -import collections - -# Minimum age of stale repos -STALE_DAYS = 3 - - -def is_stale_repo(options, ui, path): - """ - Return True if the repository didn't get any changeset from its creation. - """ - repo = hg.repository(ui, path) - ui = repo.ui - tip = repo.changelog.tip() - origtip = ui.config('gcrepos', 'initialtip', hex(nullid)) - return hex(tip) == origtip - #for name, path in ui.configitems("paths"): - #if name == 'default': - #if not hg.islocal(path): - #return False - #try: - #orig_repo = hg.repository(ui, path) - #except RepoError, e: - ## Repo not found? - #if options.verbose: - #print "-- error:", e - #return tip == nullid - #return len(discovery.findoutgoing(repo, orig_repo)) == 0 - #else: - ## No parent - #return tip == nullid - - -if __name__ == "__main__": - usage = "usage: %prog [options] arg" - parser = OptionParser(usage) - parser.add_option("-v", "--verbose", default=False, - action="store_true", dest="verbose") - (options, args) = parser.parse_args() - if len(args) != 1: - parser.error("please specify base path for search") - - ui = uimod.ui() - ui.setconfig('ui', 'quiet', True) - stale_repos = {} - - for dirpath, dirnames, _ in os.walk(args[0]): - if '.hg' not in dirnames: - # Let os.walk recurse - continue - # We're in a repo, ignore subdirs (should we recurse for subrepos?) - dirnames[:] = [] - if options.verbose: - print "examining %s ... " % dirpath - if os.path.getmtime(dirpath) > time.time() - 24 * 3600 * STALE_DAYS: - continue - stale = is_stale_repo(options, ui, dirpath) - if options.verbose: - if stale: - print "%s is stale!" % dirpath - else: - if stale: - sys.stdout.write(dirpath + "\0") - diff --git a/salt/hg/files/hg/bin/genauth b/salt/hg/files/hg/bin/genauth deleted file mode 100644 index f0c2f949..00000000 --- a/salt/hg/files/hg/bin/genauth +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/python -import os, sys, datetime - -KEYTYPES = 'ssh-rsa', 'ssh-ed25519' -AUTHFILE = '/srv/hg/.ssh/authorized_keys' -KEYDIR = '/srv/hg/keys' -WEBDIR = '/srv/hg/web' -ADMIN = set() -# Explanation: -# - `cd /data/hg/repos` allows for short ssh URLs, e.g. ssh://hg@hg.python.org/test -# instead of ssh://hg@hg.python.org/repos/test -# - `HGPUSHER=%s` allows hooks/mail.py to know who pushed the changes, and display -# it in the notification e-mail. -COMMAND = 'command="cd /srv/hg/repos && HGPUSHER=%s /usr/bin/hg-ssh /srv/hg/repos/*"' -CMDOPTS = [ - 'no-port-forwarding', 'no-X11-forwarding', - 'no-agent-forwarding', 'no-pty', -] - -if sys.argv[1:2] == ['--update']: - os.chdir(KEYDIR) - if os.path.isdir('.hg'): - os.system('hg --config trusted.users=hg pull -u') - else: - os.system('svn up') - -keys = os.listdir(KEYDIR) -data = {} -users = [] -for u in keys: - added = False - fn = os.path.join(KEYDIR, u) - if os.path.isdir(fn): - continue - - f = open(fn) - lines = [i.strip() for i in f if i.strip()] - f.close() - - for ln in lines: - bits = ln.split(' ', 2) - if bits[0] not in KEYTYPES: - print 'unknown key type %s for user %s' % (bits[0], u) - continue - ukeys = data.setdefault(u, []) - ukeys.append((bits[0], bits[1])) - added = True - if added: - users.append(u) - -dt = datetime.datetime.utcnow() -# chmod'ing early avoids race conditions. the strict perms are needed -# otherwise sshd will ignore the file. -fd = os.open(AUTHFILE + '.tmp', os.O_CREAT | os.O_WRONLY, 0o640) -f = os.fdopen(fd, "w") - -notice = """ -# ATTENTION: this file was generated by genauth on %s -# don't modify it manually but edit bin/genauth instead, and run it again -""" % dt - -print >> f, notice -for u in ADMIN: - keys = data.pop(u) - for type, k in keys: - print >> f, '%s %s %s (admin)' % (type, k, u) - -for u, keys in sorted(data.iteritems()): - for type, k in keys: - cmd = COMMAND % u - opts = ','.join(CMDOPTS) - print >> f, '%s,%s %s %s %s' % (cmd, opts, type, k, u) -print >> f, notice - -f.close() -os.rename(AUTHFILE + '.tmp', AUTHFILE) - -f = open(os.path.join(WEBDIR, 'committers'), 'w') -f.write('\n'.join(sorted(users))) -f.close() diff --git a/salt/hg/files/hg/src/app.py b/salt/hg/files/hg/src/app.py new file mode 100644 index 00000000..2aa741f7 --- /dev/null +++ b/salt/hg/files/hg/src/app.py @@ -0,0 +1,72 @@ +import io +import os +import subprocess +import shlex +import json + +from flask import current_app, Flask, make_response, redirect + +app = Flask(__name__) + +HG_COMMITS = os.path.join(os.path.dirname(__file__), "hg_commits.json") +app.hg_commits = set() +with io.open(HG_COMMITS, "r", encoding="utf-8") as file: + hg_commits = json.load(file) + hg_commits = set(hg_commits) + hg_commits.update(commit[:12] for commit in list(hg_commits)) + app.hg_commits = frozenset(hg_commits) + + +@app.route("/lookup/") +def lookup(rev): + url = None + if rev.startswith("hg") or rev in current_app.hg_commits: + if rev.startswith("hg"): + rev = rev[len("hg") :] + url = "https://hg.python.org/cpython/rev/" + rev + elif rev.startswith("r"): + url = "http://svn.python.org/view?view=revision&revision=" + rev[1:] + else: + if rev.startswith("git"): + rev = rev[len("git") :] + url = "https://github.com/python/cpython/commit/" + rev + if url is None: + return make_response( + ( + "Usage: /lookup/GITHEXHASH or gitGITHEXHASH " + "(10, 11, or 40 hex characters)\n", + "/lookup/HGHEXNODE or hgHGHEXNODE (12 or 40 hex characters)\n", + "/lookup/rSVNREVISION\n", + ), + 404, + ) + else: + return redirect(url, code=303) + + +@app.route("//rev/") +def hgrev(repo, rev): + hg_repo = os.path.join("/srv/hg/repos", repo, ".hg") + if not os.path.exists(hg_repo): + return make_response(f"repo not found ({repo}) ({rev})", 404) + command = ["hg", "log", "-v", "-p", "-r", shlex.quote(rev)] + try: + result = subprocess.run( + command, + cwd=hg_repo, + capture_output=True, + text=False, + shell=False, + check=True, + ) + except Exception as e: + return make_response( + ( + f"{str(e)}\n" + "Usage: path/to/hg/repo/rev/HGHEXNODE " + "(12 or 40 hex characters)\n" + ), + 404, + ) + + return make_response(result.stdout, 200) diff --git a/salt/hg/files/hg/src/hglookup.py b/salt/hg/files/hg/src/hglookup.py deleted file mode 100644 index bc2aae36..00000000 --- a/salt/hg/files/hg/src/hglookup.py +++ /dev/null @@ -1,70 +0,0 @@ -# hglookup.py -# -# Lookup a revision hash in a bunch of different hgwebdir repos. -# Also includes special treatment for subversion revisions from -# the CPython repo. -# -# Written by Georg Brandl, 2010. -# Updated by Brett Cannon, 2017. - -from __future__ import print_function - -import io -import json -import os -from wsgiref.simple_server import make_server - - -class hglookup(object): - def __init__(self, hg_commits, verbose=False): - self.verbose = verbose - hg_commits = set(hg_commits) - hg_commits.update(commit[:12] for commit in list(hg_commits)) - self.hg_commits = frozenset(hg_commits) - - def successful_response(self, response, url): - content_type = 'text/plain' - headers = [("Content-Type", 'text/plain'), - ("Location", url)] - response("303 See Other", headers) - return [] - - def failed_response(self, response): - response("404 Not Found", [('Content-Type', 'text/plain')]) - return ['Usage: /lookup/GITHEXHASH or gitGITHEXHASH (10, 11, or 40 hex characters)\n', - '/lookup/HGHEXNODE or hgHGHEXNODE (12 or 40 hex characters)\n', - '/lookup/rSVNREVISION\n'] - - def __call__(self, env, response): - node = env.get('PATH_INFO', '').strip('/') - if not node: - return self.failed_response(response) - elif node.startswith('hg') or node in self.hg_commits: - if node.startswith('hg'): - node = node[len('hg'):] - url = 'https://hg.python.org/cpython/rev/' + node - return self.successful_response(response, url) - elif node.startswith('r'): - url = 'http://svn.python.org/view?view=revision&revision=' + node[1:] - return self.successful_response(response, url) - elif not node.startswith('git') and len(node) not in {10, 11, 40}: - return self.failed_response(response) - else: - if node.startswith('git'): - node = node[len('git'):] - url = 'https://github.com/python/cpython/commit/' + node - return self.successful_response(response, url) - - -if __name__ == '__main__': - HG_COMMITS = 'hg_commits.json' - print("Loading hg commits from the JSON file ...") - # Use `hg log --template "\"{node}\",\n` to help generate the JSON file. - with io.open(HG_COMMITS, 'r', encoding="utf-8") as file: - hg_commits = json.load(file) - application = hglookup(hg_commits, verbose=True) - - httpd = make_server('', 8123, application) - sa = httpd.socket.getsockname() - print("Serving HTTP on", sa[0], "port", sa[1], "...") - httpd.serve_forever() diff --git a/salt/hg/files/hg/src/hgrev.py b/salt/hg/files/hg/src/hgrev.py deleted file mode 100644 index a2ca8335..00000000 --- a/salt/hg/files/hg/src/hgrev.py +++ /dev/null @@ -1,65 +0,0 @@ -# hgrev.py -# -# Dump a revision of a given hg repo as a patch -# -# Written by Ee Durbin, 2025. - -import os -import shlex -import subprocess -from wsgiref.simple_server import make_server - - -class hgrev(object): - def __init__(self, verbose=False): - self.verbose = verbose - - def successful_response(self, response, contents): - headers = [("Content-Type", "text/plain")] - response("200 OK", headers) - return [contents] - - def failed_response(self, response, detail=""): - headers = [("Content-Type", "text/plain")] - response("404 Not Found", headers) - return [ - detail.encode(), - "\nUsage: path/to/hg/repo/rev/HGHEXNODE (12 or 40 hex characters)\n".encode(), - ] - - def __call__(self, env, response): - node = env.get("SCRIPT_NAME", "").strip("/") - repository = os.path.dirname(node).rstrip("/rev") - rev = os.path.basename(node) - - hg_repo = os.path.join("/srv/hg/repos", repository, ".hg") - if not os.path.exists(hg_repo): - return self.failed_response( - response, - detail=f"repo not found ({repository}) ({rev})", - ) - - command = ["hg", "log", "-v", "-p", "-r", shlex.quote(rev)] - - try: - result = subprocess.run( - command, - cwd=hg_repo, - capture_output=True, - text=False, - shell=False, - check=True - ) - except Exception as e: - return self.failed_response(response, detail=str(e)) - - return self.successful_response(response, result.stdout) - - -if __name__ == "__main__": - application = hgrev(verbose=True) - - httpd = make_server("", 8124, application) - sa = httpd.socket.getsockname() - print("Serving HTTP on", sa[0], "port", sa[1], "...") - httpd.serve_forever() diff --git a/salt/hg/files/hg/src/requirements.txt b/salt/hg/files/hg/src/requirements.txt new file mode 100644 index 00000000..cef5a165 --- /dev/null +++ b/salt/hg/files/hg/src/requirements.txt @@ -0,0 +1,2 @@ +Flask +gunicorn diff --git a/salt/hg/files/hg/templates/hgpythonorg/index.tmpl b/salt/hg/files/hg/templates/hgpythonorg/index.tmpl deleted file mode 100644 index 394deb1c..00000000 --- a/salt/hg/files/hg/templates/hgpythonorg/index.tmpl +++ /dev/null @@ -1,38 +0,0 @@ -{header} -Mercurial repositories index - - - -
- -
- - -
- - Note CPython is now hosted on GitHub. - -
-
- - - - - - - - - - - - - - {entries%indexentry} - -
NameDescriptionContactLast modified  
-
-
-{footer} diff --git a/salt/hg/files/hg/templates/hgpythonorg/map b/salt/hg/files/hg/templates/hgpythonorg/map deleted file mode 100644 index 66907a6c..00000000 --- a/salt/hg/files/hg/templates/hgpythonorg/map +++ /dev/null @@ -1,2 +0,0 @@ -__base__ = ../paper/map -index = index.tmpl diff --git a/salt/hg/files/hg/wsgi/lookup.wsgi b/salt/hg/files/hg/wsgi/lookup.wsgi deleted file mode 100644 index de2dcab2..00000000 --- a/salt/hg/files/hg/wsgi/lookup.wsgi +++ /dev/null @@ -1,13 +0,0 @@ -import os -import sys - -home = os.path.expanduser('~hg') - -sys.path.insert(0, os.path.join(home, 'src')) - -from hglookup import hglookup - -CONFIG = os.path.join(home, 'src', 'hg_commits.json') -import json -data = json.load(open(CONFIG)) -application = hglookup(data) diff --git a/salt/hg/files/hg/wsgi/python.wsgi b/salt/hg/files/hg/wsgi/python.wsgi deleted file mode 100644 index b1eb056d..00000000 --- a/salt/hg/files/hg/wsgi/python.wsgi +++ /dev/null @@ -1,6 +0,0 @@ -from mercurial.hgweb.hgwebdir_mod import hgwebdir -from mercurial import encoding - -CONFIG = '/srv/hg/repos.conf' -encoding.encoding = 'utf-8' -application = hgwebdir(CONFIG) diff --git a/salt/hg/files/hg/wsgi/python3.wsgi b/salt/hg/files/hg/wsgi/python3.wsgi deleted file mode 100644 index 5632caaf..00000000 --- a/salt/hg/files/hg/wsgi/python3.wsgi +++ /dev/null @@ -1,6 +0,0 @@ -from mercurial.hgweb.hgwebdir_mod import hgwebdir -from mercurial import encoding - -CONFIG = '/srv/hg/repos.conf' -encoding.encoding = 'utf-8' -application = hgwebdir(CONFIG.encode()) diff --git a/salt/hg/files/hg/wsgi/rev.wsgi b/salt/hg/files/hg/wsgi/rev.wsgi deleted file mode 100644 index 1217018f..00000000 --- a/salt/hg/files/hg/wsgi/rev.wsgi +++ /dev/null @@ -1,10 +0,0 @@ -import os -import sys - -home = os.path.expanduser('~hg') - -sys.path.insert(0, os.path.join(home, 'src')) - -from hgrev import hgrev - -application = hgrev() diff --git a/salt/hg/files/hgaccounts/bin/update-hook b/salt/hg/files/hgaccounts/bin/update-hook deleted file mode 100644 index 129146b9..00000000 --- a/salt/hg/files/hgaccounts/bin/update-hook +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -/srv/hgaccounts/bin/genauth-wrapper diff --git a/salt/hg/files/hgaccounts/src/genauth-wrapper.c b/salt/hg/files/hgaccounts/src/genauth-wrapper.c deleted file mode 100644 index d7b1086e..00000000 --- a/salt/hg/files/hgaccounts/src/genauth-wrapper.c +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include -#include -int main() -{ - setreuid(geteuid(),geteuid()); - printf("Regenerating authorized_keys file...\n"); - system("/srv/hg/bin/genauth --update"); -} diff --git a/salt/hg/init.sls b/salt/hg/init.sls index 7e395c7c..8103e610 100644 --- a/salt/hg/init.sls +++ b/salt/hg/init.sls @@ -2,9 +2,8 @@ hg-deps: pkg.installed: - pkgs: - mercurial - {% if grains["oscodename"] == ["noble"] %} - - python3-pygments - {% endif %} + - python3-dev + - python3-virtualenv svn-deps: pkg.installed: @@ -16,10 +15,6 @@ hg-user: - name: hg - home: /srv/hg - shell: /bin/bash - - groups: - - hgaccounts - - require: - - user: hgaccounts-user /srv/hg: file.directory: @@ -27,50 +22,12 @@ hg-user: - group: hg - mode: "0755" -/srv/hgaccounts: - file.directory: - - user: hgaccounts - - group: hgaccounts - - mode: "0755" - /srv/hg/repos: file.directory: - user: hg - group: hg - mode: "0755" -/srv/hg/bin: - file.recurse: - - source: salt://hg/files/hg/bin - - include_empty: True - - user: hg - - dir_mode: "0755" - - file_mode: "0755" - - require: - - user: hg-user - -/srv/hg/wsgi: - file.recurse: - - source: salt://hg/files/hg/wsgi - - include_empty: True - - user: hg - - dir_mode: "0755" - - file_mode: "0755" - - require: - - user: hg-user - -/srv/hg/wsgi/python.wsgi: - file.managed: - - user: hg - - mode: "0755" - - require: - - file: /srv/hg/wsgi - {% if grains["oscodename"] == "noble" %} - - source: salt://hg/files/hg/wsgi/python3.wsgi - {% else %} - - source: salt://hg/files/hg/wsgi/python.wsgi - {% endif %} - /srv/hg/src: file.recurse: - source: salt://hg/files/hg/src @@ -91,83 +48,35 @@ hg-user: - require: - user: hg-user -/srv/hg/repos.conf: - file.managed: - - source: salt://hg/config/repos.conf +hg-env: + virtualenv.managed: + - name: /srv/hg/env - user: hg - - group: hg - - require: - - user: hg-user - -hgaccounts-user: - user.present: - - name: hgaccounts - - home: /srv/hgaccounts - -/srv/hgaccounts/bin: - file.recurse: - - source: salt://hg/files/hgaccounts/bin - - include_empty: True - - user: hgaccounts - - dir_mode: "0755" - - file_mode: "0755" - - require: - - user: hgaccounts-user - -/srv/hgaccounts/src: - file.recurse: - - source: salt://hg/files/hgaccounts/src - - include_empty: True - - user: hgaccounts - - dir_mode: "0755" - - file_mode: "0644" - - require: - - user: hgaccounts-user - -compile-genauth-wrapper: - cmd.run: - - name: "gcc /srv/hgaccounts/src/genauth-wrapper.c -o /srv/hgaccounts/bin/genauth-wrapper" - - creates: /srv/hgaccounts/bin/genauth-wrapper + - cwd: /srv/hg/src + - pip_upgrade: True + - requirements: /srv/hg/src/requirements.txt - watch: - - file: /srv/hgaccounts/src - -genauth-wrapper-owner: - file.managed: - - name: /srv/hgaccounts/bin/genauth-wrapper - - user: hg - - group: hg - - require: - - user: hg-user - - user: hgaccounts-user - - file: /srv/hgaccounts/src - - cmd: compile-genauth-wrapper - -genauth-wrapper-setuid-setgid-workaround: - file.managed: - - name: /srv/hgaccounts/bin/genauth-wrapper - - mode: "6755" - - require: - - file: genauth-wrapper-owner + - file: /srv/hg/src -/srv/hgaccounts/.ssh/authorized_keys: +/etc/systemd/system/hgmin.service: file.managed: - - source: salt://hg/config/hg-account-admins - - user: hgaccounts - - group: hgaccounts - - mode: "0600" - - makedirs: true - - dir_mode: "0700" - - require: - - user: hgaccounts-user + - source: salt://hg/config/hgmin.service.jinja + - template: jinja + - user: root + - group: root + - mode: "0644" + cmd.run: + - name: systemctl daemon-reload + - onchanges: + - file: /etc/systemd/system/hgmin.service -/usr/share/mercurial/templates/hgpythonorg: - file.recurse: - - source: salt://hg/files/hg/templates/hgpythonorg - - include_empty: True - - dir_mode: "0755" - - file_mode: "0644" - - require: - - pkg: hg-deps +hgmin: + service.running: + - reload: True + - watch_any: + - file: /etc/systemd/system/hgmin.service + - virtualenv: hg-env + - file: /srv/hg/src apache2: pkg.installed: @@ -236,6 +145,18 @@ apache2: file.symlink: - target: /etc/apache2/mods-available/rewrite.load +/etc/apache2/mods-enabled/proxy.conf: + file.symlink: + - target: /etc/apache2/mods-available/proxy.conf + +/etc/apache2/mods-enabled/proxy.load: + file.symlink: + - target: /etc/apache2/mods-available/proxy.load + +/etc/apache2/mods-enabled/proxy_http.load: + file.symlink: + - target: /etc/apache2/mods-available/proxy_http.load + /etc/apache2/ports.conf: file.managed: @@ -398,16 +319,3 @@ apache2: - mode: "0644" - require: - pkg: consul-pkgs - -/etc/consul.d/service-hg-ssh.json: - file.managed: - - source: salt://consul/etc/service.jinja - - template: jinja - - context: - name: hg-ssh - port: 22 - - user: root - - group: root - - mode: "0644" - - require: - - pkg: consul-pkgs