13 notify_cmd
= aurweb
.config
.get('notifications', 'notify-cmd')
15 repo_path
= aurweb
.config
.get('serve', 'repo-path')
16 repo_regex
= aurweb
.config
.get('serve', 'repo-regex')
17 git_shell_cmd
= aurweb
.config
.get('serve', 'git-shell-cmd')
18 git_update_cmd
= aurweb
.config
.get('serve', 'git-update-cmd')
19 ssh_cmdline
= aurweb
.config
.get('serve', 'ssh-cmdline')
21 enable_maintenance
= aurweb
.config
.getboolean('options', 'enable-maintenance')
22 maintenance_exc
= aurweb
.config
.get('options', 'maintenance-exceptions').split()
25 def pkgbase_from_name(pkgbase
):
26 conn
= aurweb
.db
.Connection()
27 cur
= conn
.execute("SELECT ID FROM PackageBases WHERE Name = ?", [pkgbase
])
30 return row
[0] if row
else None
33 def pkgbase_exists(pkgbase
):
34 return pkgbase_from_name(pkgbase
) is not None
38 conn
= aurweb
.db
.Connection()
40 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?", [user
])
41 userid
= cur
.fetchone()[0]
43 die('{:s}: unknown user: {:s}'.format(action
, user
))
45 cur
= conn
.execute("SELECT Name, PackagerUID FROM PackageBases " +
46 "WHERE MaintainerUID = ?", [userid
])
48 print((' ' if row
[1] else '*') + row
[0])
52 def create_pkgbase(pkgbase
, user
):
53 if not re
.match(repo_regex
, pkgbase
):
54 die('{:s}: invalid repository name: {:s}'.format(action
, pkgbase
))
55 if pkgbase_exists(pkgbase
):
56 die('{:s}: package base already exists: {:s}'.format(action
, pkgbase
))
58 conn
= aurweb
.db
.Connection()
60 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?", [user
])
61 userid
= cur
.fetchone()[0]
63 die('{:s}: unknown user: {:s}'.format(action
, user
))
65 now
= int(time
.time())
66 cur
= conn
.execute("INSERT INTO PackageBases (Name, SubmittedTS, " +
67 "ModifiedTS, SubmitterUID, MaintainerUID) VALUES " +
68 "(?, ?, ?, ?, ?)", [pkgbase
, now
, now
, userid
, userid
])
69 pkgbase_id
= cur
.lastrowid
71 cur
= conn
.execute("INSERT INTO PackageNotifications " +
72 "(PackageBaseID, UserID) VALUES (?, ?)",
79 def pkgbase_adopt(pkgbase
, user
, privileged
):
80 pkgbase_id
= pkgbase_from_name(pkgbase
)
82 die('{:s}: package base not found: {:s}'.format(action
, pkgbase
))
84 conn
= aurweb
.db
.Connection()
86 cur
= conn
.execute("SELECT ID FROM PackageBases WHERE ID = ? AND " +
87 "MaintainerUID IS NULL", [pkgbase_id
])
88 if not privileged
and not cur
.fetchone():
89 die('{:s}: permission denied: {:s}'.format(action
, user
))
91 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?", [user
])
92 userid
= cur
.fetchone()[0]
94 die('{:s}: unknown user: {:s}'.format(action
, user
))
96 cur
= conn
.execute("UPDATE PackageBases SET MaintainerUID = ? " +
97 "WHERE ID = ?", [userid
, pkgbase_id
])
99 cur
= conn
.execute("SELECT COUNT(*) FROM PackageNotifications WHERE " +
100 "PackageBaseID = ? AND UserID = ?",
101 [pkgbase_id
, userid
])
102 if cur
.fetchone()[0] == 0:
103 cur
= conn
.execute("INSERT INTO PackageNotifications " +
104 "(PackageBaseID, UserID) VALUES (?, ?)",
105 [pkgbase_id
, userid
])
108 subprocess
.Popen((notify_cmd
, 'adopt', str(pkgbase_id
), str(userid
)))
113 def pkgbase_get_comaintainers(pkgbase
):
114 conn
= aurweb
.db
.Connection()
116 cur
= conn
.execute("SELECT UserName FROM PackageComaintainers " +
117 "INNER JOIN Users " +
118 "ON Users.ID = PackageComaintainers.UsersID " +
119 "INNER JOIN PackageBases " +
120 "ON PackageBases.ID = PackageComaintainers.PackageBaseID " +
121 "WHERE PackageBases.Name = ? " +
122 "ORDER BY Priority ASC", [pkgbase
])
124 return [row
[0] for row
in cur
.fetchall()]
127 def pkgbase_set_comaintainers(pkgbase
, userlist
, user
, privileged
):
128 pkgbase_id
= pkgbase_from_name(pkgbase
)
130 die('{:s}: package base not found: {:s}'.format(action
, pkgbase
))
132 if not privileged
and not pkgbase_has_full_access(pkgbase
, user
):
133 die('{:s}: permission denied: {:s}'.format(action
, user
))
135 conn
= aurweb
.db
.Connection()
137 userlist_old
= set(pkgbase_get_comaintainers(pkgbase
))
140 for olduser
in userlist_old
:
141 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?",
143 userid
= cur
.fetchone()[0]
145 die('{:s}: unknown user: {:s}'.format(action
, user
))
149 for newuser
in userlist
:
150 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?",
152 userid
= cur
.fetchone()[0]
154 die('{:s}: unknown user: {:s}'.format(action
, user
))
157 uids_add
= uids_new
- uids_old
158 uids_rem
= uids_old
- uids_new
161 for userid
in uids_new
:
162 if userid
in uids_add
:
163 cur
= conn
.execute("INSERT INTO PackageComaintainers " +
164 "(PackageBaseID, UsersID, Priority) " +
165 "VALUES (?, ?, ?)", [pkgbase_id
, userid
, i
])
166 subprocess
.Popen((notify_cmd
, 'comaintainer-add', str(pkgbase_id
),
169 cur
= conn
.execute("UPDATE PackageComaintainers " +
170 "SET Priority = ? " +
171 "WHERE PackageBaseID = ? AND UsersID = ?",
172 [i
, pkgbase_id
, userid
])
175 for userid
in uids_rem
:
176 cur
= conn
.execute("DELETE FROM PackageComaintainers " +
177 "WHERE PackageBaseID = ? AND UsersID = ?",
178 [pkgbase_id
, userid
])
179 subprocess
.Popen((notify_cmd
, 'comaintainer-remove',
180 str(pkgbase_id
), str(userid
)))
186 def pkgbase_disown(pkgbase
, user
, privileged
):
187 pkgbase_id
= pkgbase_from_name(pkgbase
)
189 die('{:s}: package base not found: {:s}'.format(action
, pkgbase
))
191 initialized_by_owner
= pkgbase_has_full_access(pkgbase
, user
)
192 if not privileged
and not initialized_by_owner
:
193 die('{:s}: permission denied: {:s}'.format(action
, user
))
195 # TODO: Support disowning package bases via package request.
196 # TODO: Scan through pending orphan requests and close them.
199 new_maintainer_userid
= None
201 conn
= aurweb
.db
.Connection()
203 # Make the first co-maintainer the new maintainer, unless the action was
204 # enforced by a Trusted User.
205 if initialized_by_owner
:
206 comaintainers
= pkgbase_get_comaintainers(pkgbase
)
207 if len(comaintainers
) > 0:
208 new_maintainer
= comaintainers
[0]
209 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?",
211 new_maintainer_userid
= cur
.fetchone()[0]
212 comaintainers
.remove(new_maintainer
)
214 pkgbase_set_comaintainers(pkgbase
, comaintainers
, user
, privileged
)
215 cur
= conn
.execute("UPDATE PackageBases SET MaintainerUID = ? " +
216 "WHERE ID = ?", [new_maintainer_userid
, pkgbase_id
])
220 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?", [user
])
221 userid
= cur
.fetchone()[0]
223 die('{:s}: unknown user: {:s}'.format(action
, user
))
225 subprocess
.Popen((notify_cmd
, 'disown', str(pkgbase_id
), str(userid
)))
230 def pkgbase_set_keywords(pkgbase
, keywords
):
231 pkgbase_id
= pkgbase_from_name(pkgbase
)
233 die('{:s}: package base not found: {:s}'.format(action
, pkgbase
))
235 conn
= aurweb
.db
.Connection()
237 conn
.execute("DELETE FROM PackageKeywords WHERE PackageBaseID = ?",
239 for keyword
in keywords
:
240 conn
.execute("INSERT INTO PackageKeywords (PackageBaseID, Keyword) " +
241 "VALUES (?, ?)", [pkgbase_id
, keyword
])
247 def pkgbase_has_write_access(pkgbase
, user
):
248 conn
= aurweb
.db
.Connection()
250 cur
= conn
.execute("SELECT COUNT(*) FROM PackageBases " +
251 "LEFT JOIN PackageComaintainers " +
252 "ON PackageComaintainers.PackageBaseID = PackageBases.ID " +
253 "INNER JOIN Users " +
254 "ON Users.ID = PackageBases.MaintainerUID " +
255 "OR PackageBases.MaintainerUID IS NULL " +
256 "OR Users.ID = PackageComaintainers.UsersID " +
257 "WHERE Name = ? AND Username = ?", [pkgbase
, user
])
258 return cur
.fetchone()[0] > 0
261 def pkgbase_has_full_access(pkgbase
, user
):
262 conn
= aurweb
.db
.Connection()
264 cur
= conn
.execute("SELECT COUNT(*) FROM PackageBases " +
265 "INNER JOIN Users " +
266 "ON Users.ID = PackageBases.MaintainerUID " +
267 "WHERE Name = ? AND Username = ?", [pkgbase
, user
])
268 return cur
.fetchone()[0] > 0
272 sys
.stderr
.write("{:s}\n".format(msg
))
276 def die_with_help(msg
):
277 die(msg
+ "\nTry `{:s} help` for a list of commands.".format(ssh_cmdline
))
281 sys
.stderr
.write("warning: {:s}\n".format(msg
))
285 sys
.stderr
.write("Commands:\n")
286 colwidth
= max([len(cmd
) for cmd
in cmds
.keys()]) + 4
287 for key
in sorted(cmds
):
288 sys
.stderr
.write(" " + key
.ljust(colwidth
) + cmds
[key
] + "\n")
293 user
= os
.environ
.get('AUR_USER')
294 privileged
= (os
.environ
.get('AUR_PRIVILEGED', '0') == '1')
295 ssh_cmd
= os
.environ
.get('SSH_ORIGINAL_COMMAND')
296 ssh_client
= os
.environ
.get('SSH_CLIENT')
299 die_with_help("Interactive shell is disabled.")
300 cmdargv
= shlex
.split(ssh_cmd
)
302 remote_addr
= ssh_client
.split(' ')[0] if ssh_client
else None
304 if enable_maintenance
:
305 if remote_addr
not in maintenance_exc
:
306 die("The AUR is down due to maintenance. We will be back soon.")
308 if action
== 'git-upload-pack' or action
== 'git-receive-pack':
310 die_with_help("{:s}: missing path".format(action
))
312 path
= cmdargv
[1].rstrip('/')
313 if not path
.startswith('/'):
315 if not path
.endswith('.git'):
318 if not re
.match(repo_regex
, pkgbase
):
319 die('{:s}: invalid repository name: {:s}'.format(action
, pkgbase
))
321 if action
== 'git-receive-pack' and pkgbase_exists(pkgbase
):
322 if not privileged
and not pkgbase_has_write_access(pkgbase
, user
):
323 die('{:s}: permission denied: {:s}'.format(action
, user
))
325 os
.environ
["AUR_USER"] = user
326 os
.environ
["AUR_PKGBASE"] = pkgbase
327 os
.environ
["GIT_NAMESPACE"] = pkgbase
328 cmd
= action
+ " '" + repo_path
+ "'"
329 os
.execl(git_shell_cmd
, git_shell_cmd
, '-c', cmd
)
330 elif action
== 'set-keywords':
332 die_with_help("{:s}: missing repository name".format(action
))
333 pkgbase_set_keywords(cmdargv
[1], cmdargv
[2:])
334 elif action
== 'list-repos':
336 die_with_help("{:s}: too many arguments".format(action
))
338 elif action
== 'setup-repo':
340 die_with_help("{:s}: missing repository name".format(action
))
342 die_with_help("{:s}: too many arguments".format(action
))
343 warn('{:s} is deprecated. '
344 'Use `git push` to create new repositories.'.format(action
))
345 create_pkgbase(cmdargv
[1], user
)
346 elif action
== 'restore':
348 die_with_help("{:s}: missing repository name".format(action
))
350 die_with_help("{:s}: too many arguments".format(action
))
353 if not re
.match(repo_regex
, pkgbase
):
354 die('{:s}: invalid repository name: {:s}'.format(action
, pkgbase
))
356 if pkgbase_exists(pkgbase
):
357 die('{:s}: package base exists: {:s}'.format(action
, pkgbase
))
358 create_pkgbase(pkgbase
, user
)
360 os
.environ
["AUR_USER"] = user
361 os
.environ
["AUR_PKGBASE"] = pkgbase
362 os
.execl(git_update_cmd
, git_update_cmd
, 'restore')
363 elif action
== 'adopt':
365 die_with_help("{:s}: missing repository name".format(action
))
367 die_with_help("{:s}: too many arguments".format(action
))
370 pkgbase_adopt(pkgbase
, user
, privileged
)
371 elif action
== 'disown':
373 die_with_help("{:s}: missing repository name".format(action
))
375 die_with_help("{:s}: too many arguments".format(action
))
378 pkgbase_disown(pkgbase
, user
, privileged
)
379 elif action
== 'set-comaintainers':
381 die_with_help("{:s}: missing repository name".format(action
))
384 userlist
= cmdargv
[2:]
385 pkgbase_set_comaintainers(pkgbase
, userlist
, user
, privileged
)
386 elif action
== 'help':
388 "adopt <name>": "Adopt a package base.",
389 "disown <name>": "Disown a package base.",
390 "help": "Show this help message and exit.",
391 "list-repos": "List all your repositories.",
392 "restore <name>": "Restore a deleted package base.",
393 "set-comaintainers <name> [...]": "Set package base co-maintainers.",
394 "set-keywords <name> [...]": "Change package base keywords.",
395 "setup-repo <name>": "Create a repository (deprecated).",
396 "git-receive-pack": "Internal command used with Git.",
397 "git-upload-pack": "Internal command used with Git.",
401 die_with_help("invalid command: {:s}".format(action
))
404 if __name__
== '__main__':