diff options
author | mcrute <devnull@localhost> | 2011-02-24 00:11:42 -0500 |
---|---|---|
committer | mcrute <devnull@localhost> | 2011-02-24 00:11:42 -0500 |
commit | 82aaff86a5aefbadc0e520949976ec11e312d2b6 (patch) | |
tree | 660a6a2ef9dabc1bb25959b048cde435a8f175c8 | |
download | hg_hosting-82aaff86a5aefbadc0e520949976ec11e312d2b6.tar.bz2 hg_hosting-82aaff86a5aefbadc0e520949976ec11e312d2b6.tar.xz hg_hosting-82aaff86a5aefbadc0e520949976ec11e312d2b6.zip |
Initial import
-rw-r--r-- | .hgignore | 5 | ||||
-rwxr-xr-x | lock-repo.py | 37 | ||||
-rw-r--r-- | repolib.py | 275 | ||||
-rw-r--r-- | scripts.log | 119 | ||||
-rwxr-xr-x | server.fcgi | 11 | ||||
-rwxr-xr-x | sync-repo-config.py | 55 | ||||
-rwxr-xr-x | validate-login.py | 63 |
7 files changed, 565 insertions, 0 deletions
diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..88f8e1d --- /dev/null +++ b/.hgignore | |||
@@ -0,0 +1,5 @@ | |||
1 | ^\.ssh/ | ||
2 | ^repos/ | ||
3 | \.pyc$ | ||
4 | ^\.viminfo$ | ||
5 | ^\.bash_history$ | ||
diff --git a/lock-repo.py b/lock-repo.py new file mode 100755 index 0000000..f651d3a --- /dev/null +++ b/lock-repo.py | |||
@@ -0,0 +1,37 @@ | |||
1 | #!/usr/bin/env python | ||
2 | # vim: set filencoding=utf8 | ||
3 | """ | ||
4 | Mercurial Shared SSH Repo Lock Script | ||
5 | |||
6 | @author: Mike Crute (mcrute@gmail.com) | ||
7 | @organization: SoftGroup Hosting | ||
8 | @date: February 23, 2011 | ||
9 | """ | ||
10 | |||
11 | import os | ||
12 | import repolib | ||
13 | |||
14 | def main(argv): | ||
15 | log = repolib.get_logger('validate-login') | ||
16 | |||
17 | if ('SSH_HG_REPO' not in os.environ or | ||
18 | 'SSH_HG_USER' not in os.environ): | ||
19 | log.error("Failed to execute pre-lock checks") | ||
20 | return 1 | ||
21 | |||
22 | try: | ||
23 | repo = repolib.Repository(os.environ['SSH_HG_REPO']) | ||
24 | repo.load_from_hgrc() | ||
25 | except IOError: | ||
26 | log.error("Could not load repository config") | ||
27 | return 1 | ||
28 | |||
29 | if not repo.can_be_written_by(os.environ['SSH_HG_USER']): | ||
30 | log.error("You can not write to this repository") | ||
31 | return 1 | ||
32 | |||
33 | return 0 | ||
34 | |||
35 | if __name__ == "__main__": | ||
36 | import sys | ||
37 | sys.exit(main(sys.argv[1:])) | ||
diff --git a/repolib.py b/repolib.py new file mode 100644 index 0000000..1ba4814 --- /dev/null +++ b/repolib.py | |||
@@ -0,0 +1,275 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Mercurial Repository Management Library | ||
4 | |||
5 | @author: Mike Crute (mcrute@gmail.com) | ||
6 | @organization: SoftGroup Hosting | ||
7 | @date: February 23, 2011 | ||
8 | """ | ||
9 | |||
10 | import os.path | ||
11 | import logging | ||
12 | from StringIO import StringIO | ||
13 | from ConfigParser import SafeConfigParser | ||
14 | |||
15 | |||
16 | class Adornments(object): | ||
17 | |||
18 | BASE_CSS = ("padding: 0px 4px;" "font-size: 10px;" | ||
19 | "font-weight: normal;" "border: 1px solid;" | ||
20 | "background-color: #fafafa;" | ||
21 | "border-color: #ffffff #f0f0f0 #f0f0f0 #ffffff;") | ||
22 | |||
23 | CSS_YELLOW = ("background-color: #ffe500;" "color: #222222;" | ||
24 | "border-color: #fff066 #a89e43 #a89e43 #fff066;") | ||
25 | |||
26 | CSS_BROWN = ("background-color: #815555;" "color: #ffffff;" | ||
27 | "border-color: #ff3347 #541118 #541118 #ff3347;") | ||
28 | |||
29 | CSS_GREEN = ("background-color: #11a800;" "color: #ffffff;" | ||
30 | "border-color: #5eff4c #3ea832 #3ea832 #5eff4c;") | ||
31 | |||
32 | CSS_BLUE = ("background-color: #0099ff;" "color: #ffffff;" | ||
33 | "border-color: #4cb8ff #3279a8 #3279a8 #4cb8ff;") | ||
34 | |||
35 | CSS_RED = ("background-color: #ff0000;" "color: #ffffff;" | ||
36 | "border-color: #ff6666 #000000 #000000 #ff6666;") | ||
37 | |||
38 | BASE_HTML = '<span style="{0}{{css}}">{{tag}}</span> '.format(BASE_CSS) | ||
39 | LINK_CSS_DARK = 'color: #ffffff; text-decoration: none;' | ||
40 | LINK_CSS_LIGHT = 'color: #000000; text-decoration: none;' | ||
41 | |||
42 | def __init__(self, repo): | ||
43 | self.repo = repo | ||
44 | |||
45 | def __str__(self): | ||
46 | if self.repo.moved_to: | ||
47 | tag = '<a style="{link_css}" href="{url}">MOVED</a>' | ||
48 | tag = tag.format(link_css=self.LINK_CSS_DARK, | ||
49 | url=self.repo.moved_to) | ||
50 | return self.BASE_HTML.format(css=self.CSS_BLUE, tag=tag) | ||
51 | |||
52 | if self.repo.defunct: | ||
53 | return self.BASE_HTML.format(css=self.CSS_BROWN, tag="DEFUNCT") | ||
54 | |||
55 | if self.repo.upstream: | ||
56 | tag = '<a style="{link_css}" href="{url}">FORK</a>' | ||
57 | tag = tag.format(link_css=self.LINK_CSS_LIGHT, | ||
58 | url=self.repo.upstream) | ||
59 | return self.BASE_HTML.format(css=self.CSS_YELLOW, tag=tag) | ||
60 | |||
61 | return "" | ||
62 | |||
63 | |||
64 | class Repository(object): | ||
65 | |||
66 | def __init__(self, path, description=None, contact=None): | ||
67 | self.path = path | ||
68 | self.description = description | ||
69 | self.contact = contact | ||
70 | self.readers = [] | ||
71 | self.writers = [] | ||
72 | self.defunct = False | ||
73 | self.moved_to = None | ||
74 | self.repo_path = None | ||
75 | self.upstream = None | ||
76 | self.lock_script = None | ||
77 | |||
78 | def can_be_read_by(self, user): | ||
79 | return user in self.readers | ||
80 | |||
81 | def can_be_written_by(self, user): | ||
82 | return user in self.writers | ||
83 | |||
84 | @property | ||
85 | def exists(self): | ||
86 | return os.path.exists(self.full_path) | ||
87 | |||
88 | @property | ||
89 | def full_path(self): | ||
90 | return os.path.join(self.repo_path, self.path) | ||
91 | |||
92 | @property | ||
93 | def hgrc_path(self): | ||
94 | return os.path.join(self.full_path, '.hg', 'hgrc') | ||
95 | |||
96 | @classmethod | ||
97 | def from_config(cls, config, repo_path, lock_script=None): | ||
98 | self = cls(config['path'], config.get('description', None), | ||
99 | config.get('contact', None)) | ||
100 | |||
101 | self.readers = ConfigLoader.as_list(config.get('read', '')) | ||
102 | self.writers = ConfigLoader.as_list(config.get('write', '')) | ||
103 | |||
104 | self.defunct = ConfigLoader.as_bool(config.get('defunct', 'no')) | ||
105 | self.moved_to = config.get('moved_to', None) | ||
106 | self.upstream = config.get('upstream', None) | ||
107 | self.upstream = config.get('upstream', None) | ||
108 | |||
109 | self.repo_path = repo_path | ||
110 | self.lock_script = lock_script | ||
111 | |||
112 | return self | ||
113 | |||
114 | def build_hgrc(self, users): | ||
115 | buf = StringIO() | ||
116 | |||
117 | buf.write("# This file is generated by sync-repo-config\n") | ||
118 | buf.write("# Changes are over-written on each run\n\n") | ||
119 | |||
120 | if self.contact or self.description or self.writers: | ||
121 | buf.write("[web]\n") | ||
122 | |||
123 | if self.contact: | ||
124 | buf.write("contact = {0}\n".format(users[self.contact])) | ||
125 | |||
126 | if self.description: | ||
127 | buf.write("description = {adornments}" | ||
128 | " {self.description}\n".format( | ||
129 | adornments=Adornments(self), self=self)) | ||
130 | |||
131 | if self.writers: | ||
132 | buf.write("allow_push = {0}\n".format(",".join(self.writers))) | ||
133 | |||
134 | buf.write("\n") | ||
135 | |||
136 | if self.lock_script: | ||
137 | buf.write("[hooks]\n") | ||
138 | buf.write("pretxnchangegroup.deny.lock = {0}\n".format( | ||
139 | self.lock_script)) | ||
140 | buf.write("\n") | ||
141 | |||
142 | buf.write("[ssh]\n") | ||
143 | buf.write("readers = {0}\n".format(",".join(self.readers))) | ||
144 | buf.write("writers = {0}\n".format(",".join(self.writers))) | ||
145 | |||
146 | return buf.getvalue() | ||
147 | |||
148 | def write_hgrc(self, users): | ||
149 | if not self.exists: | ||
150 | raise IOError("Repository {0!r} does not exist".format( | ||
151 | self.full_path)) | ||
152 | |||
153 | with open(self.hgrc_path, 'w') as hgrc: | ||
154 | hgrc.write(self.build_hgrc(users)) | ||
155 | |||
156 | def load_hgrc(self): | ||
157 | hgrc = SafeConfigParser() | ||
158 | with open(self.hgrc_path, 'r') as fp: | ||
159 | hgrc.readfp(fp) | ||
160 | |||
161 | return hgrc | ||
162 | |||
163 | def load_from_hgrc(self): | ||
164 | hgrc = self.load_hgrc() | ||
165 | |||
166 | self.description = hgrc.get("web", "description") | ||
167 | self.contact = hgrc.get("web", "contact") | ||
168 | self.readers = ConfigLoader.as_list(hgrc.get("ssh", "readers")) | ||
169 | self.writers = ConfigLoader.as_list(hgrc.get("ssh", "writers")) | ||
170 | |||
171 | |||
172 | class User(object): | ||
173 | |||
174 | def __init__(self, username, name, email): | ||
175 | self.username = username | ||
176 | self.name = name | ||
177 | self.email = email | ||
178 | self.can_create = False | ||
179 | self.ssh_key = None | ||
180 | self.login_script = None | ||
181 | |||
182 | def __str__(self): | ||
183 | return "{self.name} <{self.email}>".format(self=self) | ||
184 | |||
185 | @classmethod | ||
186 | def from_config(cls, config, login_script): | ||
187 | self = cls(config['username'], config['name'], config['email']) | ||
188 | |||
189 | self.can_create = ConfigLoader.as_bool(config['can_create']) | ||
190 | self.ssh_key = config['ssh_key'] | ||
191 | self.login_script = login_script | ||
192 | |||
193 | return self | ||
194 | |||
195 | @property | ||
196 | def ssh_line(self): | ||
197 | return ('command="{self.login_script} {self.username}",' | ||
198 | 'no-port-forwarding,no-X11-forwarding,no-agent-forwarding' | ||
199 | ' {self.ssh_key} {self.email}\n').format(self=self) | ||
200 | |||
201 | |||
202 | class ConfigLoader(object): | ||
203 | |||
204 | def __init__(self, cfg_file): | ||
205 | self.config = SafeConfigParser() | ||
206 | with open(cfg_file, 'r') as config_file: | ||
207 | self.config.readfp(config_file) | ||
208 | |||
209 | self.login_script = self.config.get('system', 'login_script') | ||
210 | self.repo_path = self.config.get('system', 'repo_path') | ||
211 | self.repo_user = self.config.get('system', 'repo_user') | ||
212 | self.lock_script = self.config.get('system', 'lock_script') | ||
213 | |||
214 | def _filtered_sections(self, section_name): | ||
215 | for section in self.config.sections(): | ||
216 | if section.startswith(section_name + ":"): | ||
217 | yield section | ||
218 | |||
219 | @staticmethod | ||
220 | def as_bool(value): | ||
221 | return value.lower() in ('yes', 'true', 'on') | ||
222 | |||
223 | @staticmethod | ||
224 | def as_list(value): | ||
225 | return [item.strip() for item in value.split(",") | ||
226 | if value] | ||
227 | |||
228 | @staticmethod | ||
229 | def clean_section_name(name): | ||
230 | return name.split(':')[1] | ||
231 | |||
232 | @property | ||
233 | def repos(self): | ||
234 | for section in self._filtered_sections('repo'): | ||
235 | values = dict(self.config.items(section)) | ||
236 | values['path'] = self.clean_section_name(section) | ||
237 | |||
238 | yield Repository.from_config(values, self.repo_path, | ||
239 | self.lock_script) | ||
240 | |||
241 | @property | ||
242 | def users(self): | ||
243 | for section in self._filtered_sections('user'): | ||
244 | values = dict(self.config.items(section)) | ||
245 | values['username'] = self.clean_section_name(section) | ||
246 | |||
247 | yield User.from_config(values, self.login_script) | ||
248 | |||
249 | @property | ||
250 | def user_dict(self): | ||
251 | return dict((user.username, user) for user in self.users) | ||
252 | |||
253 | @property | ||
254 | def repo_user_authorized_keys(self): | ||
255 | home_dir = os.path.expanduser('~' + self.repo_user) | ||
256 | |||
257 | if '~' in home_dir: | ||
258 | raise ValueError("User {0!r} doesn't exist".format(self.repo_user)) | ||
259 | |||
260 | return os.path.join(home_dir, '.ssh', 'authorized_keys') | ||
261 | |||
262 | |||
263 | def get_logger(name): | ||
264 | format = "%(levelname)s: %(message)s" | ||
265 | logging.basicConfig(level=logging.DEBUG, format=format, | ||
266 | filename="scripts.log") | ||
267 | |||
268 | stream = logging.StreamHandler() | ||
269 | stream.setLevel(logging.WARNING) | ||
270 | stream.setFormatter(logging.Formatter(format)) | ||
271 | |||
272 | logger = logging.getLogger(name) | ||
273 | logger.addHandler(stream) | ||
274 | |||
275 | return logger | ||
diff --git a/scripts.log b/scripts.log new file mode 100644 index 0000000..4684049 --- /dev/null +++ b/scripts.log | |||
@@ -0,0 +1,119 @@ | |||
1 | ERROR: Repo /srv/hg/repos/private/vim-site does not exist | ||
2 | INFO: Writing hgrc for 'clients/cameo_tickets_php' | ||
3 | INFO: Writing hgrc for 'forks/hg' | ||
4 | INFO: Writing hgrc for 'private/slate_machine' | ||
5 | INFO: Writing hgrc for 'clients/maxwellness-rewards' | ||
6 | INFO: Writing hgrc for 'mongo_madness' | ||
7 | INFO: Writing hgrc for 'greenbox' | ||
8 | INFO: Writing hgrc for 'codemash_taste_of_cocoa' | ||
9 | INFO: Writing hgrc for 'build_scripts' | ||
10 | INFO: Writing hgrc for 'private/vim-site' | ||
11 | INFO: Writing hgrc for 'clients/finelli_site' | ||
12 | INFO: Writing hgrc for 'private/mcrute_blog_drafts' | ||
13 | INFO: Writing hgrc for 'clients/cameo_tickets_py' | ||
14 | INFO: Writing hgrc for 'milkman' | ||
15 | INFO: Writing hgrc for 'hudson_sc' | ||
16 | INFO: Writing hgrc for 'code_manager' | ||
17 | INFO: Writing hgrc for 'dev_urandom' | ||
18 | INFO: Writing hgrc for 'designer_site' | ||
19 | INFO: Writing hgrc for 'pyrobots' | ||
20 | INFO: Writing hgrc for 'svn_repolist' | ||
21 | INFO: Writing hgrc for 'code_golf' | ||
22 | INFO: Writing hgrc for 'firewall' | ||
23 | INFO: Writing hgrc for 'screen_hooks' | ||
24 | INFO: Writing hgrc for 'avolites_show_cleanup' | ||
25 | INFO: Writing hgrc for 'clients/chapman_site' | ||
26 | INFO: Writing hgrc for 'python_koans' | ||
27 | INFO: Writing hgrc for 'pydepgraph' | ||
28 | INFO: Writing hgrc for 'ventriloquist' | ||
29 | INFO: Writing hgrc for 'myturl_clients' | ||
30 | INFO: Writing hgrc for 'zine-patches' | ||
31 | INFO: Writing hgrc for 'kronos' | ||
32 | INFO: Writing hgrc for 'css_compressor' | ||
33 | INFO: Writing hgrc for 'clients/affordable_site' | ||
34 | INFO: Writing hgrc for 'mcrute_dotfiles' | ||
35 | INFO: Writing hgrc for 'codemash_iphone' | ||
36 | INFO: Writing hgrc for 'clients/brewbuster' | ||
37 | INFO: Writing hgrc for 'clients/cris_site' | ||
38 | INFO: Writing hgrc for 'cardigan' | ||
39 | INFO: Writing hgrc for 'ventriloquy' | ||
40 | INFO: Writing hgrc for 'conductor' | ||
41 | INFO: Writing hgrc for 'pyapache' | ||
42 | INFO: Writing hgrc for 'private/personal_wiki' | ||
43 | INFO: Writing hgrc for 'clients/cameo_website' | ||
44 | INFO: Writing hgrc for 'calendar_proxy' | ||
45 | INFO: Writing hgrc for 'pompom_configs' | ||
46 | INFO: Writing hgrc for 'opendmx_experiments' | ||
47 | INFO: Writing hgrc for 'wordpress_plugins' | ||
48 | INFO: Writing hgrc for 'nose_machineout' | ||
49 | INFO: Writing hgrc for 'awesomebar' | ||
50 | INFO: Writing hgrc for 'hg_sshsign' | ||
51 | INFO: Writing hgrc for 'code_mining' | ||
52 | INFO: Writing hgrc for 'snakeplan' | ||
53 | INFO: Writing hgrc for 'clepy_no_frameworks' | ||
54 | INFO: Writing hgrc for 'full_armor_site' | ||
55 | INFO: Writing hgrc for 'clients/sweet_dreams' | ||
56 | INFO: Writing hgrc for 'mike_crute_org' | ||
57 | INFO: Writing '/srv/hg/.ssh/authorized_keys' | ||
58 | INFO: Writing user 'Mike Crute <mcrute@gmail.com>' | ||
59 | INFO: Writing user 'Marc Weber <marco-oweber@gmx.de>' | ||
60 | INFO: Writing user 'Max Cantor <max@maxcantor.net>' | ||
61 | INFO: Writing hgrc for 'clients/cameo_tickets_php' | ||
62 | INFO: Writing hgrc for 'forks/hg' | ||
63 | INFO: Writing hgrc for 'private/slate_machine' | ||
64 | INFO: Writing hgrc for 'clients/maxwellness-rewards' | ||
65 | INFO: Writing hgrc for 'mongo_madness' | ||
66 | INFO: Writing hgrc for 'greenbox' | ||
67 | INFO: Writing hgrc for 'codemash_taste_of_cocoa' | ||
68 | INFO: Writing hgrc for 'build_scripts' | ||
69 | INFO: Writing hgrc for 'private/vim-site' | ||
70 | INFO: Writing hgrc for 'clients/finelli_site' | ||
71 | INFO: Writing hgrc for 'private/mcrute_blog_drafts' | ||
72 | INFO: Writing hgrc for 'clients/cameo_tickets_py' | ||
73 | INFO: Writing hgrc for 'milkman' | ||
74 | INFO: Writing hgrc for 'hudson_sc' | ||
75 | INFO: Writing hgrc for 'code_manager' | ||
76 | INFO: Writing hgrc for 'dev_urandom' | ||
77 | INFO: Writing hgrc for 'designer_site' | ||
78 | INFO: Writing hgrc for 'pyrobots' | ||
79 | INFO: Writing hgrc for 'svn_repolist' | ||
80 | INFO: Writing hgrc for 'code_golf' | ||
81 | INFO: Writing hgrc for 'firewall' | ||
82 | INFO: Writing hgrc for 'screen_hooks' | ||
83 | INFO: Writing hgrc for 'avolites_show_cleanup' | ||
84 | INFO: Writing hgrc for 'clients/chapman_site' | ||
85 | INFO: Writing hgrc for 'python_koans' | ||
86 | INFO: Writing hgrc for 'pydepgraph' | ||
87 | INFO: Writing hgrc for 'ventriloquist' | ||
88 | INFO: Writing hgrc for 'myturl_clients' | ||
89 | INFO: Writing hgrc for 'zine-patches' | ||
90 | INFO: Writing hgrc for 'kronos' | ||
91 | INFO: Writing hgrc for 'css_compressor' | ||
92 | INFO: Writing hgrc for 'clients/affordable_site' | ||
93 | INFO: Writing hgrc for 'mcrute_dotfiles' | ||
94 | INFO: Writing hgrc for 'codemash_iphone' | ||
95 | INFO: Writing hgrc for 'clients/brewbuster' | ||
96 | INFO: Writing hgrc for 'clients/cris_site' | ||
97 | INFO: Writing hgrc for 'cardigan' | ||
98 | INFO: Writing hgrc for 'ventriloquy' | ||
99 | INFO: Writing hgrc for 'conductor' | ||
100 | INFO: Writing hgrc for 'pyapache' | ||
101 | INFO: Writing hgrc for 'private/personal_wiki' | ||
102 | INFO: Writing hgrc for 'clients/cameo_website' | ||
103 | INFO: Writing hgrc for 'calendar_proxy' | ||
104 | INFO: Writing hgrc for 'pompom_configs' | ||
105 | INFO: Writing hgrc for 'opendmx_experiments' | ||
106 | INFO: Writing hgrc for 'wordpress_plugins' | ||
107 | INFO: Writing hgrc for 'nose_machineout' | ||
108 | INFO: Writing hgrc for 'awesomebar' | ||
109 | INFO: Writing hgrc for 'hg_sshsign' | ||
110 | INFO: Writing hgrc for 'code_mining' | ||
111 | INFO: Writing hgrc for 'snakeplan' | ||
112 | INFO: Writing hgrc for 'clepy_no_frameworks' | ||
113 | INFO: Writing hgrc for 'full_armor_site' | ||
114 | INFO: Writing hgrc for 'clients/sweet_dreams' | ||
115 | INFO: Writing hgrc for 'mike_crute_org' | ||
116 | INFO: Writing '/srv/hg/.ssh/authorized_keys' | ||
117 | INFO: Writing user 'Mike Crute <mcrute@gmail.com>' | ||
118 | INFO: Writing user 'Marc Weber <marco-oweber@gmx.de>' | ||
119 | INFO: Writing user 'Max Cantor <max@maxcantor.net>' | ||
diff --git a/server.fcgi b/server.fcgi new file mode 100755 index 0000000..d8fc53a --- /dev/null +++ b/server.fcgi | |||
@@ -0,0 +1,11 @@ | |||
1 | #!/usr/bin/env python | ||
2 | |||
3 | from flup.server.fcgi_fork import WSGIServer | ||
4 | from mercurial import demandimport; demandimport.enable() | ||
5 | from mercurial.hgweb.hgwebdir_mod import hgwebdir | ||
6 | |||
7 | |||
8 | WSGIServer(hgwebdir('/etc/hgweb.cfg'), | ||
9 | bindAddress='/tmp/hgserver.sock', | ||
10 | maxRequests=10, minSpare=1, maxSpare=3, | ||
11 | maxChildren=3).run() | ||
diff --git a/sync-repo-config.py b/sync-repo-config.py new file mode 100755 index 0000000..b706e02 --- /dev/null +++ b/sync-repo-config.py | |||
@@ -0,0 +1,55 @@ | |||
1 | #!/usr/bin/env python | ||
2 | # vim: set filencoding=utf8 | ||
3 | """ | ||
4 | Mercurial Shared SSH Repository Metadat Sync | ||
5 | |||
6 | @author: Mike Crute (mcrute@gmail.com) | ||
7 | @organization: SoftGroup Hosting | ||
8 | @date: February 23, 2011 | ||
9 | """ | ||
10 | |||
11 | import repolib | ||
12 | |||
13 | |||
14 | def sync_repository_config(repos, users, log): | ||
15 | for repo in repos: | ||
16 | if repo.exists: | ||
17 | log.info("Writing hgrc for %r", repo.path) | ||
18 | repo.write_hgrc(users) | ||
19 | else: | ||
20 | log.warn("Non-existent repo %r", repo.path) | ||
21 | |||
22 | |||
23 | def sync_ssh_config(auth_keys_filename, users, log): | ||
24 | with open(auth_keys_filename, 'w') as auth_keys: | ||
25 | log.info("Writing %r", auth_keys_filename) | ||
26 | |||
27 | for user in users: | ||
28 | log.info("Writing user '%s'", user) | ||
29 | auth_keys.write(user.ssh_line) | ||
30 | |||
31 | |||
32 | def main(argv): | ||
33 | log = repolib.get_logger('sync-repo-config') | ||
34 | |||
35 | try: | ||
36 | cfg_file = argv[-1] if argv else "/etc/hgssh.cfg" | ||
37 | cfg = repolib.ConfigLoader(cfg_file) | ||
38 | except IOError: | ||
39 | log.error("Config file %r doesn't exist", cfg_file) | ||
40 | return 1 | ||
41 | |||
42 | sync_repository_config(cfg.repos, cfg.user_dict, log) | ||
43 | |||
44 | try: | ||
45 | sync_ssh_config(cfg.repo_user_authorized_keys, cfg.users, log) | ||
46 | except ValueError, exc: | ||
47 | log.error("%s", exc) | ||
48 | return 1 | ||
49 | |||
50 | return 0 | ||
51 | |||
52 | |||
53 | if __name__ == "__main__": | ||
54 | import sys | ||
55 | sys.exit(main(sys.argv[1:])) | ||
diff --git a/validate-login.py b/validate-login.py new file mode 100755 index 0000000..bd87fff --- /dev/null +++ b/validate-login.py | |||
@@ -0,0 +1,63 @@ | |||
1 | #!/usr/bin/env python | ||
2 | # vim: set filencoding=utf8 | ||
3 | """ | ||
4 | Mercurial Shared SSH Login Validator | ||
5 | |||
6 | @author: Mike Crute (mcrute@gmail.com) | ||
7 | @organization: SoftGroup Hosting | ||
8 | @date: February 23, 2011 | ||
9 | """ | ||
10 | |||
11 | import re | ||
12 | import os | ||
13 | import repolib | ||
14 | |||
15 | from mercurial import demandimport; demandimport.enable() | ||
16 | from mercurial import dispatch | ||
17 | |||
18 | |||
19 | def parse_path(): | ||
20 | cmd = os.environ.get('SSH_ORIGINAL_COMMAND', '') | ||
21 | path = re.match("hg -R (\S+) serve --stdio", cmd) | ||
22 | |||
23 | if path: | ||
24 | return path.groups()[0] | ||
25 | |||
26 | return None | ||
27 | |||
28 | |||
29 | def main(argv): | ||
30 | log = repolib.get_logger('validate-login') | ||
31 | |||
32 | path = parse_path() | ||
33 | if path: | ||
34 | repo = repolib.Repository(path) | ||
35 | repo.repo_path = os.getcwd() | ||
36 | else: | ||
37 | log.error("Invalid command") | ||
38 | return 1 | ||
39 | |||
40 | if not repo.exists: | ||
41 | log.error("Repo %s does not exist", repo.full_path) | ||
42 | return 1 | ||
43 | |||
44 | try: | ||
45 | repo.load_from_hgrc() | ||
46 | except IOError: | ||
47 | log.error("Could not read repo config") | ||
48 | return 1 | ||
49 | |||
50 | user = argv[-1] | ||
51 | if not repo.can_be_read_by(user): | ||
52 | log.error("You can not read this repository") | ||
53 | return 1 | ||
54 | |||
55 | os.environ['SSH_HG_USER'] = user | ||
56 | os.environ['SSH_HG_REPO'] = repo.full_path | ||
57 | |||
58 | dispatch.dispatch(['-R', path, 'serve', '--stdio']) | ||
59 | |||
60 | |||
61 | if __name__ == "__main__": | ||
62 | import sys | ||
63 | sys.exit(main(sys.argv[1:])) | ||