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 pkgreq_by_pkgbase(pkgbase_id
, reqtype
):
187 conn
= aurweb
.db
.Connection()
189 cur
= conn
.execute("SELECT PackageRequests.ID FROM PackageRequests " +
190 "INNER JOIN RequestTypes ON " +
191 "RequestTypes.ID = PackageRequests.ReqTypeID " +
192 "WHERE PackageRequests.Status = 0 " +
193 "AND PackageRequests.PackageBaseID = ?" +
194 "AND RequestTypes.Name = ?", [pkgbase_id
, reqtype
])
196 return [row
[0] for row
in cur
.fetchall()]
199 def pkgreq_close(reqid
, reason
, comments
, autoclose
=False):
200 statusmap
= {'accepted': 2, 'rejected': 3}
201 if reason
not in statusmap
:
202 die('{:s}: invalid reason: {:s}'.format(action
, reason
))
203 status
= statusmap
[reason
]
205 conn
= aurweb
.db
.Connection()
210 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?", [user
])
211 userid
= cur
.fetchone()[0]
213 die('{:s}: unknown user: {:s}'.format(action
, user
))
215 conn
.execute("UPDATE PackageRequests SET Status = ?, ClosureComment = ? " +
216 "WHERE ID = ?", [status
, comments
, reqid
])
220 subprocess
.Popen((notify_cmd
, 'request-close', str(userid
), str(reqid
),
224 def pkgbase_disown(pkgbase
, user
, privileged
):
225 pkgbase_id
= pkgbase_from_name(pkgbase
)
227 die('{:s}: package base not found: {:s}'.format(action
, pkgbase
))
229 initialized_by_owner
= pkgbase_has_full_access(pkgbase
, user
)
230 if not privileged
and not initialized_by_owner
:
231 die('{:s}: permission denied: {:s}'.format(action
, user
))
233 # TODO: Support disowning package bases via package request.
235 # Scan through pending orphan requests and close them.
236 comment
= 'The user {:s} disowned the package.'.format(user
)
237 for reqid
in pkgreq_by_pkgbase(pkgbase_id
, 'orphan'):
238 pkgreq_close(reqid
, 'accepted', comment
, True)
241 new_maintainer_userid
= None
243 conn
= aurweb
.db
.Connection()
245 # Make the first co-maintainer the new maintainer, unless the action was
246 # enforced by a Trusted User.
247 if initialized_by_owner
:
248 comaintainers
= pkgbase_get_comaintainers(pkgbase
)
249 if len(comaintainers
) > 0:
250 new_maintainer
= comaintainers
[0]
251 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?",
253 new_maintainer_userid
= cur
.fetchone()[0]
254 comaintainers
.remove(new_maintainer
)
256 pkgbase_set_comaintainers(pkgbase
, comaintainers
, user
, privileged
)
257 cur
= conn
.execute("UPDATE PackageBases SET MaintainerUID = ? " +
258 "WHERE ID = ?", [new_maintainer_userid
, pkgbase_id
])
262 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?", [user
])
263 userid
= cur
.fetchone()[0]
265 die('{:s}: unknown user: {:s}'.format(action
, user
))
267 subprocess
.Popen((notify_cmd
, 'disown', str(pkgbase_id
), str(userid
)))
272 def pkgbase_flag(pkgbase
, user
, comment
):
273 pkgbase_id
= pkgbase_from_name(pkgbase
)
275 die('{:s}: package base not found: {:s}'.format(action
, pkgbase
))
277 conn
= aurweb
.db
.Connection()
279 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?", [user
])
280 userid
= cur
.fetchone()[0]
282 die('{:s}: unknown user: {:s}'.format(action
, user
))
284 now
= int(time
.time())
285 conn
.execute("UPDATE PackageBases SET " +
286 "OutOfDateTS = ?, FlaggerUID = ?, FlaggerComment = ? " +
287 "WHERE ID = ? AND OutOfDateTS IS NULL",
288 [now
, userid
, comment
, pkgbase_id
])
292 subprocess
.Popen((notify_cmd
, 'flag', str(userid
), str(pkgbase_id
)))
295 def pkgbase_unflag(pkgbase
, user
):
296 pkgbase_id
= pkgbase_from_name(pkgbase
)
298 die('{:s}: package base not found: {:s}'.format(action
, pkgbase
))
300 conn
= aurweb
.db
.Connection()
302 cur
= conn
.execute("SELECT ID FROM Users WHERE Username = ?", [user
])
303 userid
= cur
.fetchone()[0]
305 die('{:s}: unknown user: {:s}'.format(action
, user
))
307 if user
in pkgbase_get_comaintainers(pkgbase
):
308 conn
.execute("UPDATE PackageBases SET OutOfDateTS = NULL " +
309 "WHERE ID = ?", [pkgbase_id
])
311 conn
.execute("UPDATE PackageBases SET OutOfDateTS = NULL " +
312 "WHERE ID = ? AND (MaintainerUID = ? OR FlaggerUID = ?)",
313 [pkgbase_id
, userid
, userid
])
318 def pkgbase_set_keywords(pkgbase
, keywords
):
319 pkgbase_id
= pkgbase_from_name(pkgbase
)
321 die('{:s}: package base not found: {:s}'.format(action
, pkgbase
))
323 conn
= aurweb
.db
.Connection()
325 conn
.execute("DELETE FROM PackageKeywords WHERE PackageBaseID = ?",
327 for keyword
in keywords
:
328 conn
.execute("INSERT INTO PackageKeywords (PackageBaseID, Keyword) " +
329 "VALUES (?, ?)", [pkgbase_id
, keyword
])
335 def pkgbase_has_write_access(pkgbase
, user
):
336 conn
= aurweb
.db
.Connection()
338 cur
= conn
.execute("SELECT COUNT(*) FROM PackageBases " +
339 "LEFT JOIN PackageComaintainers " +
340 "ON PackageComaintainers.PackageBaseID = PackageBases.ID " +
341 "INNER JOIN Users " +
342 "ON Users.ID = PackageBases.MaintainerUID " +
343 "OR PackageBases.MaintainerUID IS NULL " +
344 "OR Users.ID = PackageComaintainers.UsersID " +
345 "WHERE Name = ? AND Username = ?", [pkgbase
, user
])
346 return cur
.fetchone()[0] > 0
349 def pkgbase_has_full_access(pkgbase
, user
):
350 conn
= aurweb
.db
.Connection()
352 cur
= conn
.execute("SELECT COUNT(*) FROM PackageBases " +
353 "INNER JOIN Users " +
354 "ON Users.ID = PackageBases.MaintainerUID " +
355 "WHERE Name = ? AND Username = ?", [pkgbase
, user
])
356 return cur
.fetchone()[0] > 0
360 sys
.stderr
.write("{:s}\n".format(msg
))
364 def die_with_help(msg
):
365 die(msg
+ "\nTry `{:s} help` for a list of commands.".format(ssh_cmdline
))
369 sys
.stderr
.write("warning: {:s}\n".format(msg
))
373 sys
.stderr
.write("Commands:\n")
374 colwidth
= max([len(cmd
) for cmd
in cmds
.keys()]) + 4
375 for key
in sorted(cmds
):
376 sys
.stderr
.write(" " + key
.ljust(colwidth
) + cmds
[key
] + "\n")
381 user
= os
.environ
.get('AUR_USER')
382 privileged
= (os
.environ
.get('AUR_PRIVILEGED', '0') == '1')
383 ssh_cmd
= os
.environ
.get('SSH_ORIGINAL_COMMAND')
384 ssh_client
= os
.environ
.get('SSH_CLIENT')
387 die_with_help("Interactive shell is disabled.")
388 cmdargv
= shlex
.split(ssh_cmd
)
390 remote_addr
= ssh_client
.split(' ')[0] if ssh_client
else None
392 if enable_maintenance
:
393 if remote_addr
not in maintenance_exc
:
394 die("The AUR is down due to maintenance. We will be back soon.")
396 if action
== 'git' and cmdargv
[1] in ('upload-pack', 'receive-pack'):
397 action
= action
+ '-' + cmdargv
[1]
400 if action
== 'git-upload-pack' or action
== 'git-receive-pack':
402 die_with_help("{:s}: missing path".format(action
))
404 path
= cmdargv
[1].rstrip('/')
405 if not path
.startswith('/'):
407 if not path
.endswith('.git'):
410 if not re
.match(repo_regex
, pkgbase
):
411 die('{:s}: invalid repository name: {:s}'.format(action
, pkgbase
))
413 if action
== 'git-receive-pack' and pkgbase_exists(pkgbase
):
414 if not privileged
and not pkgbase_has_write_access(pkgbase
, user
):
415 die('{:s}: permission denied: {:s}'.format(action
, user
))
417 os
.environ
["AUR_USER"] = user
418 os
.environ
["AUR_PKGBASE"] = pkgbase
419 os
.environ
["GIT_NAMESPACE"] = pkgbase
420 cmd
= action
+ " '" + repo_path
+ "'"
421 os
.execl(git_shell_cmd
, git_shell_cmd
, '-c', cmd
)
422 elif action
== 'set-keywords':
424 die_with_help("{:s}: missing repository name".format(action
))
425 pkgbase_set_keywords(cmdargv
[1], cmdargv
[2:])
426 elif action
== 'list-repos':
428 die_with_help("{:s}: too many arguments".format(action
))
430 elif action
== 'setup-repo':
432 die_with_help("{:s}: missing repository name".format(action
))
434 die_with_help("{:s}: too many arguments".format(action
))
435 warn('{:s} is deprecated. '
436 'Use `git push` to create new repositories.'.format(action
))
437 create_pkgbase(cmdargv
[1], user
)
438 elif action
== 'restore':
440 die_with_help("{:s}: missing repository name".format(action
))
442 die_with_help("{:s}: too many arguments".format(action
))
445 if not re
.match(repo_regex
, pkgbase
):
446 die('{:s}: invalid repository name: {:s}'.format(action
, pkgbase
))
448 if pkgbase_exists(pkgbase
):
449 die('{:s}: package base exists: {:s}'.format(action
, pkgbase
))
450 create_pkgbase(pkgbase
, user
)
452 os
.environ
["AUR_USER"] = user
453 os
.environ
["AUR_PKGBASE"] = pkgbase
454 os
.execl(git_update_cmd
, git_update_cmd
, 'restore')
455 elif action
== 'adopt':
457 die_with_help("{:s}: missing repository name".format(action
))
459 die_with_help("{:s}: too many arguments".format(action
))
462 pkgbase_adopt(pkgbase
, user
, privileged
)
463 elif action
== 'disown':
465 die_with_help("{:s}: missing repository name".format(action
))
467 die_with_help("{:s}: too many arguments".format(action
))
470 pkgbase_disown(pkgbase
, user
, privileged
)
471 elif action
== 'flag':
473 die_with_help("{:s}: missing repository name".format(action
))
475 die_with_help("{:s}: missing comment".format(action
))
477 die_with_help("{:s}: too many arguments".format(action
))
482 die_with_help("{:s}: comment is too short".format(action
))
484 pkgbase_flag(pkgbase
, user
, comment
)
485 elif action
== 'unflag':
487 die_with_help("{:s}: missing repository name".format(action
))
489 die_with_help("{:s}: too many arguments".format(action
))
492 pkgbase_unflag(pkgbase
, user
)
493 elif action
== 'set-comaintainers':
495 die_with_help("{:s}: missing repository name".format(action
))
498 userlist
= cmdargv
[2:]
499 pkgbase_set_comaintainers(pkgbase
, userlist
, user
, privileged
)
500 elif action
== 'help':
502 "adopt <name>": "Adopt a package base.",
503 "disown <name>": "Disown a package base.",
504 "flag <name> <comment>": "Flag a package base out-of-date.",
505 "help": "Show this help message and exit.",
506 "list-repos": "List all your repositories.",
507 "restore <name>": "Restore a deleted package base.",
508 "set-comaintainers <name> [...]": "Set package base co-maintainers.",
509 "set-keywords <name> [...]": "Change package base keywords.",
510 "setup-repo <name>": "Create a repository (deprecated).",
511 "unflag <name>": "Remove out-of-date flag from a package base.",
512 "git-receive-pack": "Internal command used with Git.",
513 "git-upload-pack": "Internal command used with Git.",
517 die_with_help("invalid command: {:s}".format(action
))
520 if __name__
== '__main__':