Projects
Kolab:16:Enterprise
pykolab
Log In
Username
Password
We truncated the diff of some files because they were too big. If you want to see the full diff for every file,
click here
.
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 9
View file
pykolab.spec
Changed
@@ -28,18 +28,15 @@ Summary: Kolab Groupware Solution Name: pykolab -Version: 0.8.0 -Release: 1%{?dist} +Version: 0.8.1 +Release: 2%{?dist} License: GPLv3+ Group: Applications/System URL: http://kolab.org/ -Source0: pykolab-0.8.0.tar.gz +Source0: pykolab-0.8.1.tar.gz Source1: pykolab.logrotate -Patch0001: 0001-Fix-mistake-in-manticore-configuration-template.patch -Patch0002: 0002-Fix-default-template-for-Guam.patch - BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root BuildArch: noarch %if 0%{?suse_version} @@ -54,7 +51,14 @@ BuildRequires: gettext BuildRequires: glib2-devel BuildRequires: intltool +%if 0%{?suse_version} +BuildRequires: python-mysql +%else +BuildRequires: MySQL-python +%endif BuildRequires: python +BuildRequires: python-augeas +BuildRequires: python-gnupg BuildRequires: python-icalendar BuildRequires: python-kolab BuildRequires: python-kolabformat @@ -205,6 +209,7 @@ %else Requires: MySQL-python %endif +Requires: python-gnupg Requires: python-icalendar >= 3.0 Requires: %{name}-xml = %{version}-%{release} @@ -214,9 +219,6 @@ %prep %setup -q -%patch0001 -p1 -%patch0002 -p1 - %build autoreconf -v || automake --add-missing && autoreconf -v %configure @@ -542,6 +544,9 @@ %attr(0700,%{kolab_user},%{kolab_group}) %dir %{_var}/spool/pykolab/wallace %changelog +* Wed Mar 09 2016 Timotheus Pokorra <tp@tbits.net> - 0.8.1-2 +- wallace requires python-gnupg to be installed. avoid ImportError: No module named gnupg + * Wed Oct 14 2015 Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> - 0.7.16-1 - New upstream release 0.7.16
View file
0001-Fix-mistake-in-manticore-configuration-template.patch
Deleted
@@ -1,25 +0,0 @@ -From 6769c10da417e65698f71e0f34d0b15a597a3641 Mon Sep 17 00:00:00 2001 -From: "Jeroen van Meeuwen (Kolab Systems)" <vanmeeuwen@kolabsys.com> -Date: Tue, 19 Jan 2016 12:55:44 +0100 -Subject: PATCH 1/2 Fix mistake in manticore configuration template - ---- - share/templates/manticore.js.tpl | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/share/templates/manticore.js.tpl b/share/templates/manticore.js.tpl -index 90a75ec..c3a31bd 100644 ---- a/share/templates/manticore.js.tpl -+++ b/share/templates/manticore.js.tpl -@@ -17,7 +17,7 @@ module.exports = { - CHWALA_SERVER: 'http://$fqdn/chwala/api/document', - ROUNDCUBE_SERVER: 'http://$fqdn/roundcubemail', - -- AUTH_ENCRYPTION_KEY: '$auth_secret', -+ AUTH_ENCRYPTION_KEY: '$auth_key', - - LDAP_SERVER: 'ldap://$server_host:389', - LDAP_BASE: '$user_base_dn', --- -2.4.3 -
View file
0002-Fix-default-template-for-Guam.patch
Deleted
@@ -1,25 +0,0 @@ -From 19912492bdadb1f3c90d0ee06ba5119193ced726 Mon Sep 17 00:00:00 2001 -From: "Jeroen van Meeuwen (Kolab Systems)" <vanmeeuwen@kolabsys.com> -Date: Mon, 25 Jan 2016 13:58:17 +0100 -Subject: PATCH 2/2 Fix default template for Guam - ---- - share/templates/guam.sys.config.tpl | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/share/templates/guam.sys.config.tpl b/share/templates/guam.sys.config.tpl -index 2ad9456..955e17b 100644 ---- a/share/templates/guam.sys.config.tpl -+++ b/share/templates/guam.sys.config.tpl -@@ -18,7 +18,7 @@ - { - imap, - { port, 143 }, -- { imap_server, imap }, -+ { imap_server, imaps }, - { - rules, - { filter_groupware, } --- -2.4.3 -
View file
debian.changelog
Changed
@@ -1,3 +1,9 @@ +pykolab (0.8.1-0~kolab1) unstable; urgency=low + + * Fix setup + + -- Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Tue, 23 Feb 2016 01:49:00 +0100 + pykolab (0.8.0-0~kolab2) unstable; urgency=low * Fix guam template
View file
debian.control
Changed
@@ -23,7 +23,6 @@ python-icalendar, python-minimal, python-nose, - python-support, univention-config-dev | bash Standards-Version: 3.9.3 Homepage: http://www.kolab.org
View file
debian.series
Changed
@@ -1,3 +1,1 @@ cyrus-imapd.conf-cert-paths.patch -p1 -0001-Fix-mistake-in-manticore-configuration-template.patch -p1 -0002-Fix-default-template-for-Guam.patch -p1
View file
pykolab-0.8.0.tar.gz/kolabd/__init__.py -> pykolab-0.8.1.tar.gz/kolabd/__init__.py
Changed
@@ -218,7 +218,7 @@ exitcode = 1 traceback.print_exc() print >> sys.stderr, _("Traceback occurred, please report a " + \ - "bug at http://bugzilla.kolabsys.com") + "bug at https://issues.kolab.org") except TypeError, errmsg: exitcode = 1 @@ -229,7 +229,7 @@ exitcode = 2 traceback.print_exc() print >> sys.stderr, _("Traceback occurred, please report a " + \ - "bug at http://bugzilla.kolabsys.com") + "bug at https://issues.kolab.org") sys.exit(exitcode)
View file
pykolab-0.8.0.tar.gz/pykolab/auth/ldap/__init__.py -> pykolab-0.8.1.tar.gz/pykolab/auth/ldap/__init__.py
Changed
@@ -1195,7 +1195,7 @@ try: self.ldap.simple_bind_s(bind_dn, bind_pw) self.bind = True - except ldap.SERVER_DOWN: + except ldap.SERVER_DOWN, errmsg: log.error(_("LDAP server unavailable: %r") % (errmsg)) log.error(_("%s") % (traceback.format_exc())) except ldap.INVALID_CREDENTIALS:
View file
pykolab-0.8.0.tar.gz/pykolab/imap/__init__.py -> pykolab-0.8.1.tar.gz/pykolab/imap/__init__.py
Changed
@@ -522,6 +522,9 @@ if not hasattr(self, 'domain'): self.domain == None + if self.domain == None and len(mailbox_base_name.split('@')) > 1: + self.domain = mailbox_base_name.split('@')1 + if not self.domain == None: if conf.has_option(self.domain, "autocreate_folders"): _additional_folders = conf.get_raw( @@ -529,6 +532,25 @@ "autocreate_folders" ) + else: + from pykolab.auth import Auth + auth = Auth() + auth.connect() + + domains = auth.list_domains(self.domain) + + auth.disconnect() + + if len(domains.keys()) > 0: + if domains.has_key(self.domain): + primary = domainsself.domain + + if conf.has_option(primary, "autocreate_folders"): + _additional_folders = conf.get_raw( + primary, + "autocreate_folders" + ) + if _additional_folders == None: if conf.has_option('kolab', "autocreate_folders"): _additional_folders = conf.get_raw(
View file
pykolab-0.8.0.tar.gz/pykolab/imap/cyrus.py -> pykolab-0.8.1.tar.gz/pykolab/imap/cyrus.py
Changed
@@ -25,12 +25,14 @@ import pykolab +from pykolab.constants import * from pykolab.imap import IMAP from pykolab.translate import _ log = pykolab.getLogger('pykolab.imap') conf = pykolab.getConf() + class Cyrus(cyruslib.CYRUS): """ Abstraction class for some common actions to do exclusively in Cyrus. @@ -70,14 +72,19 @@ self.server = hostname - self.uri = "%s://%s:%s" % (scheme,hostname,port) + self.uri = "%s://%s:%s" % (scheme, hostname, port) while 1: try: cyruslib.CYRUS.__init__(self, self.uri) break except cyruslib.CYRUSError: - log.warning(_("Could not connect to Cyrus IMAP server %r") % (self.uri)) + log.warning( + _("Could not connect to Cyrus IMAP server %r") % ( + self.uri + ) + ) + time.sleep(10) if conf.debuglevel > 8: @@ -134,17 +141,32 @@ """ cyruslib.CYRUS.login(self, *args, **kw) self.separator = self.SEP + try: + self._id() + except Exception, errmsg: + pass + + log.debug( + _("Continuing with separator: %r") % (self.separator), + level=8 + ) - log.debug(_("Continuing with separator: %r") % (self.separator), level=8) self.murder = False for capability in self.m.capabilities: if capability.startswith("MUPDATE="): - log.debug(_("Detected we are running in a Murder topology"), level=8) + log.debug( + _("Detected we are running in a Murder topology"), + level=8 + ) + self.murder = True if not self.murder: - log.debug(_("This system is not part of a murder topology"), level=8) + log.debug( + _("This system is not part of a murder topology"), + level=8 + ) def find_mailfolder_server(self, mailfolder): annotations = {} @@ -153,8 +175,13 @@ prefix = _mailfolder'path_parts'.pop(0) mbox = _mailfolder'path_parts'.pop(0) - if not _mailfolder'domain' == None: - mailfolder = "%s%s%s@%s" % (prefix,self.separator,mbox,_mailfolder'domain') + if _mailfolder'domain' is not None: + mailfolder = "%s%s%s@%s" % ( + prefix, + self.separator, + mbox, + _mailfolder'domain' + ) # TODO: Workaround for undelete if len(self.lm(mailfolder)) < 1: @@ -165,14 +192,20 @@ if not self.murder: return self.server - log.debug(_("Checking actual backend server for folder %s through annotations") % (mailfolder), level=8) + log.debug( + _("Checking actual backend server for folder %s " + + "through annotations") % ( + mailfolder + ), + level=8 + ) if self.mbox.has_key(mailfolder): log.debug( _( - "Possibly reproducing the find " + \ - "mailfolder server answer from " + \ - "previously detected and stored " + \ + "Possibly reproducing the find " + + "mailfolder server answer from " + + "previously detected and stored " + "annotation value: %r" ) % ( self.mboxmailfolder @@ -185,26 +218,53 @@ max_tries = 20 num_try = 0 + + annotation_path = "/shared/vendor/cmu/cyrus-imapd/server" + while 1: num_try += 1 - annotations = self._getannotation(mailfolder, "/vendor/cmu/cyrus-imapd/server") + annotations = self._getannotation( + mailfolder, + annotation_path + ) if annotations.has_key(mailfolder): break if max_tries <= num_try: - log.error(_("Could not get the annotations after %s tries.") % (num_try)) - annotations = { mailfolder: { '/shared/vendor/cmu/cyrus-imapd/server': self.server }} + log.error( + _("Could not get the annotations after %s tries.") % ( + num_try + ) + ) + + annotations = { + mailfolder: { + annotation_path: self.server + } + } + break - log.warning(_("No annotations for %s: %r") % (mailfolder,annotations)) + log.warning( + _("No annotations for %s: %r") % ( + mailfolder, + annotations + ) + ) time.sleep(1) - server = annotationsmailfolder'/shared/vendor/cmu/cyrus-imapd/server' + server = annotationsmailfolderannotation_path self.mboxmailfolder = server - log.debug(_("Server for INBOX folder %s is %s") % (mailfolder,server), level=8) + log.debug( + _("Server for INBOX folder %s is %s") % ( + mailfolder, + server + ), + level=8 + ) return server @@ -216,33 +276,68 @@ from pykolab import imap_utf7 return imap_utf7.decode(folder) + def _id(self, identity=None): + if identity is None: + identity = '("name" "Python/Kolab" "version" "%s")' % (__version__) + + typ, dat = self.m._simple_command('ID', identity) + res, dat = self.m._untagged_response(typ, dat, 'ID') + def _setquota(self, mailfolder, quota): """ Login to the actual backend server. """ server = self.find_mailfolder_server(mailfolder) - #print "server:", server - self.connect(self.uri.replace(self.server,server)) + self.connect(self.uri.replace(self.server, server)) + + log.debug( + _("Setting quota for folder %s to %s") % ( + mailfolder, + quota + ), + level=8
View file
pykolab-0.8.0.tar.gz/pykolab/setup/__init__.py -> pykolab-0.8.1.tar.gz/pykolab/setup/__init__.py
Changed
@@ -21,6 +21,7 @@ import sys import pykolab +from pykolab.translate import _ log = pykolab.getLogger('pykolab.setup') conf = pykolab.getConf() @@ -40,6 +41,36 @@ to_execute.append(sys.argvarg_num.replace('-','_')) def run(self): + if os.path.isfile('/sys/fs/selinux/enforce'): + if os.access('/sys/fs/selinux/enforce', os.R_OK): + # Set a gentle default because strictly speaking, + # setup won't fail (run-time does) + enforce = "0" + + with open('/sys/fs/selinux/enforce', 'r') as f: + enforce = f.read() + + if enforce.strip() == "1": + log.fatal( + _("SELinux currently enforcing. Read " + \ + "https://git.kolab.org/u/1") + ) + + sys.exit(1) + + if os.path.isfile('/etc/selinux/config'): + if os.access('/etc/selinux/config', os.R_OK): + with open('/etc/selinux/config', 'r') as f: + for line in f: + if line.strip() == "SELINUX=enforcing": + log.fatal( + _("SELinux configured to enforce a " + \ + "policy on startup. Read " + \ + "https://git.kolab.org/u/1") + ) + + sys.exit(1) + components.execute('_'.join(to_execute)) if os.path.exists('/tmp/kolab-setup-my.cnf'):
View file
pykolab-0.8.0.tar.gz/pykolab/setup/setup_mta.py -> pykolab-0.8.1.tar.gz/pykolab/setup/setup_mta.py
Changed
@@ -442,6 +442,20 @@ myaugeas.save() myaugeas.close() + if os.path.isfile('/usr/lib/systemd/system/clamd@.service'): + from ConfigParser import SafeConfigParser + unitfile = SafeConfigParser() + unitfile.optionxform = str + unitfile.read('/usr/lib/systemd/system/clamd@.service') + if not unitfile.has_section('Install'): + unitfile.add_section('Install') + + if not unitfile.has_option('Install', 'WantedBy'): + unitfile.set('Install', 'WantedBy', 'multi-user.target') + + with open('/etc/systemd/system/clamd@.service', 'wb') as f: + unitfile.write(f) + amavisservice = 'amavisd.service' clamavservice = 'clamd@amavisd.service' if os.path.isfile('/usr/lib/systemd/system/amavis.service'):
View file
pykolab-0.8.0.tar.gz/pykolab/setup/setup_roundcube.py -> pykolab-0.8.1.tar.gz/pykolab/setup/setup_roundcube.py
Changed
@@ -176,7 +176,8 @@ if len(schema_files) > 0: break - break + if len(schema_files) > 0: + break for root, directories, filenames in os.walk(rcpath + 'plugins/calendar/drivers/kolab/'): for filename in filenames: @@ -192,6 +193,22 @@ if not schema_filepath in schema_files: schema_files.append(schema_filepath) + for root, directories, filenames in os.walk('/usr/share/doc/'): + directories.sort() + for directory in directories: + if directory.startswith("chwala"): + for nested_root, nested_directories, nested_filenames in os.walk(os.path.join(root, directory)): + for filename in nested_filenames: + if filename.startswith('mysql.initial') and filename.endswith('.sql'): + schema_filepath = os.path.join(nested_root,filename) + if not schema_filepath in schema_files: + schema_files.append(schema_filepath) + + if len(schema_files) > 0: + break + if len(schema_files) > 0: + break + if not os.path.isfile('/tmp/kolab-setup-my.cnf'): utils.multiline_message( """Please supply the MySQL root password"""
View file
pykolab-0.8.0.tar.gz/saslauthd/__init__.py -> pykolab-0.8.1.tar.gz/saslauthd/__init__.py
Changed
@@ -166,7 +166,7 @@ except AttributeError, e: exitcode = 1 traceback.print_exc() - print >> sys.stderr, _("Traceback occurred, please report a bug at http://bugzilla.kolabsys.com") + print >> sys.stderr, _("Traceback occurred, please report a bug at https://issues.kolab.org") except TypeError, e: exitcode = 1 traceback.print_exc() @@ -174,7 +174,7 @@ except: exitcode = 2 traceback.print_exc() - print >> sys.stderr, _("Traceback occurred, please report a bug at http://bugzilla.kolabsys.com") + print >> sys.stderr, _("Traceback occurred, please report a bug at https://issues.kolab.org") sys.exit(exitcode)
View file
pykolab-0.8.0.tar.gz/setup-kolab.py -> pykolab-0.8.1.tar.gz/setup-kolab.py
Changed
@@ -29,6 +29,7 @@ import pykolab from pykolab.setup import Setup +from pykolab.translate import _ try: from pykolab.constants import *
View file
pykolab-0.8.0.tar.gz/share/templates/guam.sys.config.tpl -> pykolab-0.8.1.tar.gz/share/templates/guam.sys.config.tpl
Changed
@@ -18,7 +18,7 @@ { imap, { port, 143 }, - { imap_server, imap }, + { imap_server, imaps }, { rules, { filter_groupware, }
View file
pykolab-0.8.0.tar.gz/share/templates/manticore.js.tpl -> pykolab-0.8.1.tar.gz/share/templates/manticore.js.tpl
Changed
@@ -17,7 +17,7 @@ CHWALA_SERVER: 'http://$fqdn/chwala/api/document', ROUNDCUBE_SERVER: 'http://$fqdn/roundcubemail', - AUTH_ENCRYPTION_KEY: '$auth_secret', + AUTH_ENCRYPTION_KEY: '$auth_key', LDAP_SERVER: 'ldap://$server_host:389', LDAP_BASE: '$user_base_dn',
View file
pykolab-0.8.0.tar.gz/share/templates/roundcubemail/kolab_delegation.inc.php.tpl -> pykolab-0.8.1.tar.gz/share/templates/roundcubemail/kolab_delegation.inc.php.tpl
Changed
@@ -8,18 +8,6 @@ // Note: LDAP addressbook defined for kolab_auth plugin is used \$config'kolab_delegation_delegate_field' = 'kolabDelegate'; - // User authentication ID field (from fieldmap configuration) - // See kolab_auth plugin config - \$config'kolab_delegation_login_field' = 'email'; - - // Use this fields (from fieldmap configuration) for identities - // If the value array contains more than one field, first non-empty will be used - // Note: These are not LDAP attributes, but field names in config - // Note: If there are more than one email address, as many identities will be created - // See kolab_auth plugin config - \$config'kolab_delegation_name_field' = array('name', 'cn'); - \$config'kolab_delegation_email_field' = array('email'); - // Remove all user identities which do not match the users primary or alias // addresses and delegators addresses \$config'kolab_delegation_purge_identities' = false;
View file
pykolab-0.8.0.tar.gz/wallace/__init__.py -> pykolab-0.8.1.tar.gz/wallace/__init__.py
Changed
@@ -464,7 +464,7 @@ except AttributeError, e: exitcode = 1 traceback.print_exc() - print >> sys.stderr, _("Traceback occurred, please report a bug at http://bugzilla.kolabsys.com") + print >> sys.stderr, _("Traceback occurred, please report a bug at https://issues.kolab.org") except TypeError, e: exitcode = 1 @@ -473,7 +473,7 @@ except: exitcode = 2 traceback.print_exc() - print >> sys.stderr, _("Traceback occurred, please report a bug at http://bugzilla.kolabsys.com") + print >> sys.stderr, _("Traceback occurred, please report a bug at https://issues.kolab.org") sys.exit(exitcode)
View file
pykolab-0.8.0.tar.gz/wallace/module_gpgencrypt.py -> pykolab-0.8.1.tar.gz/wallace/module_gpgencrypt.py
Changed
@@ -28,6 +28,12 @@ from email.utils import formataddr from email.utils import getaddresses +import email.mime.application +import email.mime.multipart +import email.mime.text +import email.encoders +import gnupg + import modules import pykolab @@ -45,6 +51,40 @@ def description(): return """Encrypt messages to the recipient(s).""" +def pgp_mime(msg, recepients): + gpg = gnupg.GPG(gnupghome='/var/lib/kolab/.gnupg', verbose=conf.debuglevel > 8) + gpg.encoding = 'utf-8' + + msg = msg + + msg_boundary = str(msg.get_boundary()) + msg_content_type = str(msg.get_content_type()) + payload = msg.get_payload() + + content = "Content-Type: " + msg_content_type + ";" + "\n boundary=\"" + msg_boundary + "\"\n\n" + payload + encrypted_content = gpg.encrypt(content, recepients, always_trust=True) + msg.set_type("multipart/encrypted") + msg.set_param("protocol","application/pgp-encrypted") + + msg_boundary_gpg = "--boundary-gpg-encryption-42" + + msg_preamble = "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\n\ +" + msg_boundary_gpg + "\n\ +Content-Type: application/pgp-encrypted\n\ +Content-Description: PGP/MIME version identification\n\ +\n\ +Version: 1\n\ +\n\ +" + msg_boundary_gpg + "\n\ +Content-Type: application/octet-stream; name=\"encrypted.asc\"\n\ +Content-Description: OpenPGP encrypted message\n\ +Content-Disposition: inline; filename=\"encrypted.asc\"\n\n" + + msg.set_boundary(msg_boundary_gpg) + msg.set_payload(msg_preamble + str(encrypted_content) + "\n" + msg_boundary_gpg) + + return msg + def execute(*args, **kw): if not os.path.isdir(mybasepath): os.makedirs(mybasepath) @@ -175,7 +215,6 @@ nocrypt_rcpts = - import gnupg gpg = gnupg.GPG(gnupghome='/var/lib/kolab/.gnupg', verbose=conf.debuglevel > 8) gpg.encoding = 'utf-8' @@ -211,16 +250,29 @@ encrypt_rcpts.append(key_local) payload = message.get_payload() - print "payload:", payload + #print "payload:", payload if len(encrypt_rcpts) < 1: return filepath - encrypted_data = gpg.encrypt(payload, encrypt_rcpts, always_trust=True) - encrypted_string = str(encrypted_data) + if "multipart" in message.get_content_type(): + + log.debug(_("Mime Message - we need to build multipart/encrypted structure"), level=8) + + msg = message + enc_mime_message = pgp_mime(msg, encrypt_rcpts) + + message = enc_mime_message + + else: + + log.debug(_("No Mime Message - encypt plain"), level=8) + + encrypted_data = gpg.encrypt(payload, encrypt_rcpts, always_trust=True) + encrypted_string = str(encrypted_data) - print "encrypted string:", encrypted_string + message.set_payload(encrypted_string) - message.set_payload(encrypted_string) + message.add_header('X-wallace-gpg-encrypted', 'true') (fp, new_filepath) = tempfile.mkstemp(dir="/var/spool/pykolab/wallace/gpgencrypt/ACCEPT") os.write(fp, message.as_string())
View file
pykolab.dsc
Changed
@@ -2,7 +2,7 @@ Source: pykolab Binary: pykolab, kolab-cli, kolab-conf, kolab-saslauthd, kolab-server, kolab-telemetry, kolab-xml, wallace Architecture: all -Version: 0.8.0-0~kolab2 +Version: 0.8.1-0~kolab1 Maintainer: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Uploaders: Paul Klos <kolab@klos2day.nl> Homepage: http://www.kolab.org @@ -28,7 +28,6 @@ python-icalendar, python-minimal, python-nose, - python-support, univention-config-dev | bash Package-List: kolab-cli deb python optional @@ -40,5 +39,5 @@ pykolab deb python optional wallace deb python optional Files: - 00000000000000000000000000000000 0 pykolab-0.8.0.tar.gz + 00000000000000000000000000000000 0 pykolab-0.8.1.tar.gz 00000000000000000000000000000000 0 debian.tar.gz
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.