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_set_keywords(pkgbase
, keywords
):
273 pkgbase_id
= pkgbase_from_name(pkgbase
)
275 die('{:s}: package base not found: {:s}'.format(action
, pkgbase
))
277 conn
= aurweb
.db
.Connection()
279 conn
.execute("DELETE FROM PackageKeywords WHERE PackageBaseID = ?",
281 for keyword
in keywords
:
282 conn
.execute("INSERT INTO PackageKeywords (PackageBaseID, Keyword) " +
283 "VALUES (?, ?)", [pkgbase_id
, keyword
])
289 def pkgbase_has_write_access(pkgbase
, user
):
290 conn
= aurweb
.db
.Connection()
292 cur
= conn
.execute("SELECT COUNT(*) FROM PackageBases " +
293 "LEFT JOIN PackageComaintainers " +
294 "ON PackageComaintainers.PackageBaseID = PackageBases.ID " +
295 "INNER JOIN Users " +
296 "ON Users.ID = PackageBases.MaintainerUID " +
297 "OR PackageBases.MaintainerUID IS NULL " +
298 "OR Users.ID = PackageComaintainers.UsersID " +
299 "WHERE Name = ? AND Username = ?", [pkgbase
, user
])
300 return cur
.fetchone()[0] > 0
303 def pkgbase_has_full_access(pkgbase
, user
):
304 conn
= aurweb
.db
.Connection()
306 cur
= conn
.execute("SELECT COUNT(*) FROM PackageBases " +
307 "INNER JOIN Users " +
308 "ON Users.ID = PackageBases.MaintainerUID " +
309 "WHERE Name = ? AND Username = ?", [pkgbase
, user
])
310 return cur
.fetchone()[0] > 0
314 sys
.stderr
.write("{:s}\n".format(msg
))
318 def die_with_help(msg
):
319 die(msg
+ "\nTry `{:s} help` for a list of commands.".format(ssh_cmdline
))
323 sys
.stderr
.write("warning: {:s}\n".format(msg
))
327 sys
.stderr
.write("Commands:\n")
328 colwidth
= max([len(cmd
) for cmd
in cmds
.keys()]) + 4
329 for key
in sorted(cmds
):
330 sys
.stderr
.write(" " + key
.ljust(colwidth
) + cmds
[key
] + "\n")
335 user
= os
.environ
.get('AUR_USER')
336 privileged
= (os
.environ
.get('AUR_PRIVILEGED', '0') == '1')
337 ssh_cmd
= os
.environ
.get('SSH_ORIGINAL_COMMAND')
338 ssh_client
= os
.environ
.get('SSH_CLIENT')
341 die_with_help("Interactive shell is disabled.")
342 cmdargv
= shlex
.split(ssh_cmd
)
344 remote_addr
= ssh_client
.split(' ')[0] if ssh_client
else None
346 if enable_maintenance
:
347 if remote_addr
not in maintenance_exc
:
348 die("The AUR is down due to maintenance. We will be back soon.")
350 if action
== 'git' and cmdargv
[1] in ('upload-pack', 'receive-pack'):
351 action
= action
+ '-' + cmdargv
[1]
354 if action
== 'git-upload-pack' or action
== 'git-receive-pack':
356 die_with_help("{:s}: missing path".format(action
))
358 path
= cmdargv
[1].rstrip('/')
359 if not path
.startswith('/'):
361 if not path
.endswith('.git'):
364 if not re
.match(repo_regex
, pkgbase
):
365 die('{:s}: invalid repository name: {:s}'.format(action
, pkgbase
))
367 if action
== 'git-receive-pack' and pkgbase_exists(pkgbase
):
368 if not privileged
and not pkgbase_has_write_access(pkgbase
, user
):
369 die('{:s}: permission denied: {:s}'.format(action
, user
))
371 os
.environ
["AUR_USER"] = user
372 os
.environ
["AUR_PKGBASE"] = pkgbase
373 os
.environ
["GIT_NAMESPACE"] = pkgbase
374 cmd
= action
+ " '" + repo_path
+ "'"
375 os
.execl(git_shell_cmd
, git_shell_cmd
, '-c', cmd
)
376 elif action
== 'set-keywords':
378 die_with_help("{:s}: missing repository name".format(action
))
379 pkgbase_set_keywords(cmdargv
[1], cmdargv
[2:])
380 elif action
== 'list-repos':
382 die_with_help("{:s}: too many arguments".format(action
))
384 elif action
== 'setup-repo':
386 die_with_help("{:s}: missing repository name".format(action
))
388 die_with_help("{:s}: too many arguments".format(action
))
389 warn('{:s} is deprecated. '
390 'Use `git push` to create new repositories.'.format(action
))
391 create_pkgbase(cmdargv
[1], user
)
392 elif action
== 'restore':
394 die_with_help("{:s}: missing repository name".format(action
))
396 die_with_help("{:s}: too many arguments".format(action
))
399 if not re
.match(repo_regex
, pkgbase
):
400 die('{:s}: invalid repository name: {:s}'.format(action
, pkgbase
))
402 if pkgbase_exists(pkgbase
):
403 die('{:s}: package base exists: {:s}'.format(action
, pkgbase
))
404 create_pkgbase(pkgbase
, user
)
406 os
.environ
["AUR_USER"] = user
407 os
.environ
["AUR_PKGBASE"] = pkgbase
408 os
.execl(git_update_cmd
, git_update_cmd
, 'restore')
409 elif action
== 'adopt':
411 die_with_help("{:s}: missing repository name".format(action
))
413 die_with_help("{:s}: too many arguments".format(action
))
416 pkgbase_adopt(pkgbase
, user
, privileged
)
417 elif action
== 'disown':
419 die_with_help("{:s}: missing repository name".format(action
))
421 die_with_help("{:s}: too many arguments".format(action
))
424 pkgbase_disown(pkgbase
, user
, privileged
)
425 elif action
== 'set-comaintainers':
427 die_with_help("{:s}: missing repository name".format(action
))
430 userlist
= cmdargv
[2:]
431 pkgbase_set_comaintainers(pkgbase
, userlist
, user
, privileged
)
432 elif action
== 'help':
434 "adopt <name>": "Adopt a package base.",
435 "disown <name>": "Disown a package base.",
436 "help": "Show this help message and exit.",
437 "list-repos": "List all your repositories.",
438 "restore <name>": "Restore a deleted package base.",
439 "set-comaintainers <name> [...]": "Set package base co-maintainers.",
440 "set-keywords <name> [...]": "Change package base keywords.",
441 "setup-repo <name>": "Create a repository (deprecated).",
442 "git-receive-pack": "Internal command used with Git.",
443 "git-upload-pack": "Internal command used with Git.",
447 die_with_help("invalid command: {:s}".format(action
))
450 if __name__
== '__main__':