├── .github └── workflows │ ├── check.yml │ ├── codeql.yml │ └── coverage.yml ├── .gitignore ├── .packit.yml ├── .tito ├── custom │ └── abrt │ │ └── tagger │ │ └── __init__.py ├── packages │ ├── .readme │ └── retrace-server ├── releasers.conf └── tito.props ├── CONTRIBUTING.md ├── COPYING ├── DEPLOYING.md ├── README.md ├── asciidoc.conf ├── codecov.yml ├── container ├── Containerfile ├── Containerfile_local ├── Makefile └── files │ ├── plugins │ └── fedora.py │ └── usr │ ├── bin │ ├── retrace-server-entrypoint │ └── run_retrace_server │ └── libexec │ └── fix-permissions ├── doc ├── meson.build └── retrace-server.texi ├── lgtm.yml ├── man ├── asciidoc.conf ├── meson.build ├── retrace-server-cleanup.txt ├── retrace-server-interact.txt ├── retrace-server-reposync.txt ├── retrace-server-task.txt └── retrace-server-worker.txt ├── meson.build ├── meson_options.txt ├── mypy.ini ├── po ├── LINGUAS ├── POTFILES.in ├── af.po ├── ar.po ├── as.po ├── ast.po ├── bg.po ├── bn.po ├── bn_IN.po ├── bs.po ├── ca.po ├── cs.po ├── da.po ├── de.po ├── el.po ├── en_GB.po ├── eo.po ├── es.po ├── et.po ├── eu.po ├── fa.po ├── fi.po ├── fr.po ├── fur.po ├── gl.po ├── gu.po ├── he.po ├── hi.po ├── hu.po ├── ia.po ├── id.po ├── it.po ├── ja.po ├── ka.po ├── kk.po ├── km.po ├── kn.po ├── ko.po ├── lt.po ├── lv.po ├── meson.build ├── ml.po ├── mr.po ├── nb.po ├── nds.po ├── nl.po ├── nn.po ├── or.po ├── pa.po ├── pl.po ├── pt.po ├── pt_BR.po ├── retrace-server.pot ├── ru.po ├── si.po ├── sk.po ├── sq.po ├── sr.po ├── sr@latin.po ├── sv.po ├── ta.po ├── te.po ├── tg.po ├── th.po ├── tr.po ├── uk.po ├── ur.po ├── vi.po ├── zh_CN.po ├── zh_HK.po └── zh_TW.po ├── pylintrc ├── retrace-server.spec ├── src ├── backtrace.wsgi ├── checkpackage.wsgi ├── config │ ├── hooks │ │ ├── debuginfo.conf │ │ ├── environment.conf │ │ ├── fail.conf │ │ ├── retrace.conf │ │ ├── start.conf │ │ ├── success.conf │ │ └── task.conf │ ├── retrace-server │ ├── retrace-server-hooks.conf │ ├── retrace-server-httpd.conf │ └── retrace-server.conf ├── coredump2packages ├── create.wsgi ├── delete.wsgi ├── exploitable.wsgi ├── ftp.wsgi ├── index.wsgi ├── index.xhtml ├── log.wsgi ├── manager.wsgi ├── manager.xhtml ├── manager_usrcore_task_form.xhtml ├── manager_vmcore_task_form.xhtml ├── managertask.xhtml ├── meson.build ├── metrics.wsgi ├── plugins │ ├── __init__.py │ ├── centos.py │ ├── fedora.py │ ├── meson.build │ └── rhel.py ├── retrace-server-bugzilla-query ├── retrace-server-bugzilla-refresh ├── retrace-server-cleanup ├── retrace-server-cleanup.txt ├── retrace-server-interact ├── retrace-server-interact.txt ├── retrace-server-plugin-checker ├── retrace-server-reposync ├── retrace-server-reposync-faf ├── retrace-server-reposync.txt ├── retrace-server-task ├── retrace-server-task.txt ├── retrace-server-worker ├── retrace-server-worker.txt ├── retrace │ ├── __init__.py │ ├── architecture.py │ ├── archive.py │ ├── argparser.py │ ├── backends │ │ ├── __init__.py │ │ ├── meson.build │ │ └── podman.py │ ├── config.py.in │ ├── hooks │ │ ├── __init__.py │ │ ├── config.py │ │ ├── hooks.py │ │ └── meson.build │ ├── logging.py │ ├── meson.build │ ├── plugins.py │ ├── retrace.py │ ├── retrace_worker.py │ ├── stats.py │ └── util.py ├── settings.wsgi ├── start.wsgi ├── stats.wsgi ├── stats.xhtml └── status.wsgi └── test ├── meson.build ├── test_architecture.py ├── test_backends.py └── test_util.py /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Static analysis 2 | on: 3 | pull_request: 4 | branches: [master] 5 | push: 6 | branches: [master] 7 | jobs: 8 | lint_and_typing: 9 | container: 10 | image: fedora:latest 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out sources 14 | uses: actions/checkout@v3 15 | 16 | - name: Install build environment 17 | run: dnf --assumeyes install dnf-plugins-core python3-pylint python3-mypy 18 | 19 | - name: Install build dependencies 20 | run: | 21 | dnf --assumeyes builddep --spec retrace-server.spec 22 | # For the pyfaf imports. 23 | dnf --assumeyes copr enable @abrt/faf-el8-devel 24 | dnf --assumeyes install faf python3-createrepo_c 25 | 26 | - name: Build 27 | run: | 28 | meson build 29 | ninja -C build 30 | 31 | - name: Run Mypy 32 | run: | 33 | cd build 34 | stubgen --output mypy_stubs --package createrepo_c --package dnf --package hawkey \ 35 | --package pyfaf 36 | MYPYPATH=$(pwd)/mypy_stubs mypy --config-file=../mypy.ini src/ 37 | MYPYPATH=$(pwd)/mypy_stubs:$(pwd)/src/ mypy \ 38 | --config-file=../mypy.ini \ 39 | --scripts-are-modules \ 40 | src/coredump2packages \ 41 | src/retrace-server-bugzilla-query \ 42 | src/retrace-server-bugzilla-refresh \ 43 | src/retrace-server-cleanup \ 44 | src/retrace-server-interact \ 45 | src/retrace-server-plugin-checker \ 46 | src/retrace-server-reposync \ 47 | src/retrace-server-reposync-faf \ 48 | src/retrace-server-task \ 49 | src/retrace-server-worker 50 | 51 | - name: Run Pylint 52 | if: always() 53 | run: | 54 | cd build 55 | python3 -m pylint --rcfile=pylintrc --output-format=colorized \ 56 | src/plugins \ 57 | src/retrace \ 58 | src/coredump2packages \ 59 | src/retrace-server-bugzilla-query \ 60 | src/retrace-server-bugzilla-refresh \ 61 | src/retrace-server-cleanup \ 62 | src/retrace-server-interact \ 63 | src/retrace-server-plugin-checker \ 64 | src/retrace-server-reposync \ 65 | src/retrace-server-reposync-faf \ 66 | src/retrace-server-task \ 67 | src/retrace-server-worker \ 68 | test 69 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | schedule: 8 | - cron: "0 11 * * 3" 9 | jobs: 10 | analyze: 11 | name: Analyze 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | language: [ python ] 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | 25 | - name: Initialize CodeQL 26 | uses: github/codeql-action/init@v2 27 | with: 28 | languages: ${{ matrix.language }} 29 | queries: +security-and-quality 30 | 31 | - name: Autobuild 32 | uses: github/codeql-action/autobuild@v2 33 | 34 | - name: Perform CodeQL Analysis 35 | uses: github/codeql-action/analyze@v2 36 | with: 37 | category: "/language:${{ matrix.language }}" 38 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage analysis 2 | on: 3 | pull_request: 4 | branches: [master] 5 | push: 6 | branches: [master] 7 | jobs: 8 | coverage: 9 | container: 10 | image: fedora:latest 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out sources 14 | uses: actions/checkout@v3 15 | 16 | - name: Install build environment 17 | run: dnf --assumeyes install dnf-plugins-core 18 | 19 | - name: Install build and test dependencies 20 | run: | 21 | dnf --assumeyes builddep --spec retrace-server.spec 22 | # For the pyfaf imports. 23 | dnf --assumeyes copr enable @abrt/faf-el8-devel 24 | dnf --assumeyes install faf python3-pytest-cov 25 | 26 | - name: Build the project 27 | run: | 28 | meson setup build -Dcoverage=true 29 | ninja -C build -v 30 | 31 | - name: Run tests and generate coverage report 32 | run: | 33 | meson test -C build -v 34 | 35 | - name: Upload coverage report 36 | uses: codecov/codecov-action@v3 37 | with: 38 | directory: ./build 39 | fail_ci_if_error: true 40 | flags: unittests 41 | root_dir: . 42 | verbose: true 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | 3 | #root 4 | ABOUT-NLS 5 | aclocal.m4 6 | autom4te.cache 7 | config.guess 8 | config.h 9 | config.h.in 10 | config.log 11 | config.rpath 12 | config.status 13 | config.sub 14 | configure 15 | depcomp 16 | install-sh 17 | libtool 18 | ltmain.sh 19 | Makefile 20 | Makefile.in 21 | missing 22 | m4 23 | py-compile 24 | retrace-server.spec 25 | retrace-server-[0-9]* 26 | stamp-h1 27 | retrace-server-version 28 | 29 | #doc 30 | doc/mdate-sh 31 | doc/retrace-server.info 32 | doc/stamp-vti 33 | doc/texinfo.tex 34 | doc/version.texi 35 | 36 | #po 37 | po/Makefile.in.in 38 | po/Makevars.template 39 | po/POTFILES 40 | po/Rules-quot 41 | po/boldquot.sed 42 | po/en@boldquot.header 43 | po/en@quot.header 44 | po/insert-header.sin 45 | po/quot.sed 46 | po/remove-potcdate.sin 47 | po/stamp-it 48 | 49 | #src 50 | src/.deps 51 | 52 | #builds 53 | po/*.gmo 54 | src/worker.o 55 | src/abrt-retrace-worker 56 | 57 | #rpms 58 | i386 59 | i686 60 | x86_64 61 | 62 | #bytecode 63 | *.pyc 64 | *.pyo 65 | 66 | #typing 67 | .mypy_cache/ 68 | mypy_stubs 69 | -------------------------------------------------------------------------------- /.packit.yml: -------------------------------------------------------------------------------- 1 | specfile_path: retrace-server.spec 2 | synced_files: 3 | - .packit.yml 4 | - retrace-server.spec 5 | upstream_package_name: retrace-server 6 | upstream_project_url: https://github.com/abrt/retrace-server 7 | downstream_package_name: retrace-server 8 | 9 | # No extra dependencies are necessary to build the SRPM. 10 | srpm_build_deps: [] 11 | 12 | jobs: 13 | # Automatically start a Copr build for each pull request. 14 | - job: copr_build 15 | trigger: pull_request 16 | targets: 17 | - epel-8 18 | - fedora-all 19 | # Automatically propose changes in downstream Dist-Git for active Fedora 20 | # branches and for EPEL 8. 21 | - job: propose_downstream 22 | trigger: release 23 | dist_git_branches: 24 | - epel8 25 | - fedora-all 26 | # Automatically submit Koji builds for commits that change the spec file. 27 | - job: koji_build 28 | trigger: commit 29 | dist_git_branches: 30 | - epel8 31 | - fedora-all 32 | -------------------------------------------------------------------------------- /.tito/custom/abrt/tagger/__init__.py: -------------------------------------------------------------------------------- 1 | from tito.common import run_command 2 | from tito.tagger import VersionTagger 3 | 4 | class MesonVersionTagger(VersionTagger): 5 | def _set_meson_project_version(self, version): 6 | version = version.split('-', maxsplit=1)[0] 7 | 8 | run_command('meson rewrite kwargs set project / version %s' % (version)) 9 | run_command('git add -- meson.build') 10 | 11 | def _tag_release(self): 12 | self._make_changelog() 13 | new_version = self._bump_version() 14 | self._check_tag_does_not_exist(self._get_new_tag(new_version)) 15 | self._set_meson_project_version(new_version) 16 | self._update_changelog(new_version) 17 | self._update_package_metadata(new_version) 18 | -------------------------------------------------------------------------------- /.tito/packages/.readme: -------------------------------------------------------------------------------- 1 | the .tito/packages directory contains metadata files 2 | named after their packages. Each file has the latest tagged 3 | version and the project's relative directory. 4 | -------------------------------------------------------------------------------- /.tito/packages/retrace-server: -------------------------------------------------------------------------------- 1 | 1.24.2-1 ./ 2 | -------------------------------------------------------------------------------- /.tito/releasers.conf: -------------------------------------------------------------------------------- 1 | [all] 2 | releaser = tito.release.FedoraGitReleaser 3 | branches = master f33 f32 epel8 4 | 5 | [epel] 6 | releaser = tito.release.FedoraGitReleaser 7 | branches = epel8 8 | -------------------------------------------------------------------------------- /.tito/tito.props: -------------------------------------------------------------------------------- 1 | [buildconfig] 2 | builder = tito.builder.Builder 3 | tagger = abrt.tagger.MesonVersionTagger 4 | lib_dir = .tito/custom 5 | changelog_do_not_remove_cherrypick = 0 6 | changelog_format = %s (%ae) 7 | tag_commit_message_format = Release version %(version)s 8 | tag_format = {version} 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Retrace Server 2 | 3 | Adopted from http://www.contribution-guide.org/ 4 | 5 | BSD, Copyright (c) 2015 Jeff Forcier 6 | 7 | ## Submitting bugs 8 | 9 | ### Due diligence 10 | 11 | Before submitting a bug, please do the following: 12 | 13 | * Perform **basic troubleshooting** steps: 14 | 15 | * **Make sure you're on the latest version.** If you're not on the most 16 | recent version, your problem may have been solved already! Upgrading is 17 | always the best first step. 18 | * **Try older versions.** If you're already *on* the latest release, try 19 | rolling back a few minor versions (e.g. if on 1.7, try 1.5 or 1.6) and 20 | see if the problem goes away. This will help the devs narrow down when 21 | the problem first arose in the commit log. 22 | * **Try switching up dependency versions.** If the software in question has 23 | dependencies (other libraries, etc) try upgrading/downgrading those as 24 | well. 25 | 26 | * **Search the project's bug/issue tracker** to make sure it's not a known issue. 27 | * If you don't find a pre-existing issue, consider **checking with the mailing 28 | list and/or IRC channel** in case the problem is non-bug-related. 29 | * Consult [README.md](README.md) for links to bugtracker, mailinglist or IRC. 30 | 31 | ### What to put in your bug report 32 | 33 | Make sure your report gets the attention it deserves: bug reports with missing 34 | information may be ignored or punted back to you, delaying a fix. The below 35 | constitutes a bare minimum; more info is almost always better: 36 | 37 | * **What version of the core programming language interpreter/compiler are you 38 | using?** For example, if it's a Python project, are you using Python 2.7.3? 39 | Python 3.3.1? PyPy 2.0? 40 | * **What operating system are you on?** Make sure to include release and distribution. 41 | * **Which version or versions of the software are you using?** Ideally, you 42 | followed the advice above and have ruled out (or verified that the problem 43 | exists in) a few different versions. 44 | * **How can the developers recreate the bug on their end?** If possible, 45 | include a copy of your code, the command you used to invoke it, and the full 46 | output of your run (if applicable.) 47 | 48 | * A common tactic is to pare down your code until a simple (but still 49 | bug-causing) "base case" remains. Not only can this help you identify 50 | problems which aren't real bugs, but it means the developer can get to 51 | fixing the bug faster. 52 | 53 | 54 | ## Contributing changes 55 | 56 | It would be the best if you could discuss your plans with us on #abrt or on our 57 | mailinig list crash-catcher@lists.fedorahosted.org before you spent too much 58 | energy and time. 59 | 60 | Before contributing, please, make yourself familiar with git. You can [try git 61 | online](https://try.github.io/). Things would be easier for all of us if you do 62 | your changes on a branch. Use a single commit for every logical reviewable 63 | change, without unrelated modifications (that will help us if need to revert a 64 | particular commit). Please avoid adding commits fixing your previous 65 | commits, do amend or rebase instead. 66 | 67 | Every commit must have either comprehensive commit message saying what is being 68 | changed and why or a link (an issue number on Github) to a bug report where 69 | this information is available. It is also useful to include notes about 70 | negative decisions - i.e. why you decided to not do particular things. Please 71 | bare in mind that other developers might not understand what the original 72 | problem was. 73 | 74 | ### Full example 75 | 76 | Here's an example workflow for a project `abrt` hosted on Github 77 | Your username is `yourname` and you're submitting a basic bugfix or feature. 78 | 79 | * Hit 'fork' on Github, creating e.g. `yourname/retrace-server`. 80 | * `git clone git@github.com:yourname/retrace-server` 81 | * `cd retrace-server` 82 | * `git checkout -b foo_the_bars` to create new local branch named foo_the_bars 83 | * Hack, hack, hack 84 | * Run `sudo make check` and [deploy](DEPLOYING.md) your version for better testing 85 | * `git status` 86 | * `git add` 87 | * `git commit -s -m "Foo the bars"` 88 | * `git push -u origin HEAD` to create foo_the_bars branch in your fork 89 | * Visit your fork at Github and click handy "Pull request" button. 90 | * In the description field, write down issue number (if submitting code fixing 91 | an existing issue) or describe the issue + your fix (if submitting a wholly 92 | new bugfix). 93 | * Hit 'submit'! And please be patient - the maintainers will get to you when 94 | they can. 95 | -------------------------------------------------------------------------------- /asciidoc.conf: -------------------------------------------------------------------------------- 1 | man/asciidoc.conf -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | # Only comment on a pull request if code coverage changes. 3 | require_changes: true 4 | -------------------------------------------------------------------------------- /container/Containerfile: -------------------------------------------------------------------------------- 1 | FROM fedora:35 2 | USER root 3 | RUN dnf --assumeyes install git-core tito && \ 4 | git clone --quiet https://github.com/abrt/retrace-server.git /src && \ 5 | dnf --assumeyes builddep --spec /src/retrace-server.spec && \ 6 | useradd --no-create-home builder && \ 7 | chown --recursive --quiet builder:builder /src 8 | USER builder 9 | WORKDIR /src 10 | RUN tito build --output=rpm --rpm --test 11 | 12 | FROM fedora:35 as build 13 | USER root 14 | COPY --from=0 /src/rpm/noarch/retrace-server-*.rpm rpm/ 15 | RUN \ 16 | dnf --assumeyes install mod_ssl uwsgi rpm/retrace-server-*.rpm && \ 17 | dnf clean all 18 | 19 | FROM build 20 | 21 | ENV NAME="Retrace Server" \ 22 | SUMMARY="Application for remote coredump analysis " \ 23 | DESCRIPTION=" Remote service for generating backtraces from coredumps of crashes. \ 24 | Supports user space coredumps as well as a kernel coredumps. \ 25 | All communication with server can be over a simple HTTP API or via Web UI." 26 | 27 | LABEL summary="$SUMMARY" \ 28 | description="$DESCRIPTION" \ 29 | io.openshift.tags="retrace-server,crash,abrt" \ 30 | io.k8s.description="$DESCRIPTION" \ 31 | io.k8s.display-name="Retrace Server" \ 32 | io.openshift.expose-services="8181:TCP" \ 33 | name="$NAME" \ 34 | usage="docker run -d --name retrace-server" \ 35 | maintainer="ABRT devel team " 36 | 37 | # Copy main run script 38 | COPY container/files/usr/bin /usr/bin 39 | COPY container/files/usr/libexec /usr/libexec 40 | 41 | RUN rm -rf /run/httpd && mkdir /run/httpd && chmod -R a+rwx /run/httpd && \ 42 | sed -i -e"s/Listen\s*80/Listen 8181/i" /etc/httpd/conf/httpd.conf && \ 43 | sed -i -e"s/ErrorLog\s*\"logs\/error_log\"/ErrorLog \"\/var\/log\/retrace-server\/httpd_error_log\"/i" /etc/httpd/conf/httpd.conf && \ 44 | sed -i -e"s/CustomLog\s*\"logs\/access_log\"/CustomLog \"\/var\/log\/retrace-server\/httpd_access_log\"/i" /etc/httpd/conf/httpd.conf && \ 45 | sed -i -e"s/RequireHTTPS\s*=\s*1/RequireHTTPS = 0/i" /etc/retrace-server/retrace-server.conf && \ 46 | echo "cron = 0 -5 -1 -1 -1 /usr/bin/retrace-server-reposync fedora 28 x86_64" >> /etc/uwsgi.ini && \ 47 | chmod g=u /etc/passwd && \ 48 | mkdir -p /run/uwsgi && \ 49 | /usr/libexec/httpd-ssl-gencerts && \ 50 | chmod 644 /etc/pki/tls/private/localhost.key && \ 51 | /usr/libexec/fix-permissions /run/uwsgi && \ 52 | /usr/libexec/fix-permissions /var/log/retrace-server && \ 53 | /usr/libexec/fix-permissions /var/spool/retrace-server 54 | 55 | VOLUME /var/spool/retrace-server 56 | 57 | EXPOSE 8181 58 | RUN mkdir -p /usr/share/retrace-server/.mock && \ 59 | echo "config_opts['use_nspawn'] = False" > /usr/share/retrace-server/.mock/user.cfg 60 | 61 | ENTRYPOINT ["retrace-server-entrypoint"] 62 | CMD ["run_retrace_server"] 63 | -------------------------------------------------------------------------------- /container/Containerfile_local: -------------------------------------------------------------------------------- 1 | FROM abrt/retrace-server-image 2 | MAINTAINER abrt-devel-list@redhat.com 3 | ARG fedoraversion=35 4 | 5 | USER root 6 | 7 | # Copy sources to the docker image 8 | COPY . /retrace-server 9 | 10 | # From not on work from retrace-server directory 11 | WORKDIR /retrace-server 12 | 13 | # Change owner of /retrace-server, clean git and install dependences 14 | RUN chown -R --silent retrace /retrace-server && \ 15 | chmod -R --silent g=u /retrace-server && \ 16 | dnf -y install git meson rpm-build sudo tito vim && \ 17 | git clean -dfx && \ 18 | dnf --assumeyes builddep --spec /retrace-server/retrace-server.spec 19 | 20 | # Build as non root 21 | USER retrace 22 | 23 | ENV HOME /retrace-server 24 | 25 | # Build retrace-server 26 | RUN tito build --output=rpm --rpm --test 27 | 28 | #And continue as root 29 | USER 0 30 | 31 | # Update FAF 32 | RUN rpm -Uvh /retrace-server/rpm/noarch/retrace-server-*.rpm && \ 33 | /usr/libexec/fix-permissions /retrace-server && \ 34 | /usr/libexec/fix-permissions /var/log/retrace-server && \ 35 | /usr/libexec/fix-permissions /var/spool/retrace-server && \ 36 | sed -i -e"s/AllowTaskManager\s*=\s*0/AllowTaskManager = 1/i" /etc/retrace-server/retrace-server.conf && \ 37 | sed -i -e"s/RequireGPGCheck\s*=\s*1/RequireGPGCheck = 0/i" /etc/retrace-server/retrace-server.conf && \ 38 | mkdir /var/tmp/local_repo && \ 39 | dnf --releasever=$fedoraversion --enablerepo=\*debuginfo\* -y --installroot=/var/tmp/local_repo/ \ 40 | download --resolve --destdir /var/tmp/local_repo/ abrt-addon-ccpp shadow-utils \ 41 | gdb rpm will-crash 42 | 43 | COPY container/files/plugins /usr/share/retrace-server/plugins 44 | 45 | RUN sudo -u retrace retrace-server-reposync fedora $fedoraversion x86_64 46 | 47 | #Switch workdir back to / 48 | WORKDIR '/' 49 | -------------------------------------------------------------------------------- /container/Makefile: -------------------------------------------------------------------------------- 1 | MANAGER=docker 2 | 3 | build: 4 | $(MANAGER) build -t retrace-server-image -f Containerfile ../ 5 | $(MANAGER) tag retrace-server-image abrt/retrace-server-image 6 | 7 | build_local: 8 | $(MANAGER) build -t retrace-server-image-local -f Containerfile_local ../ 9 | 10 | run: 11 | $(MANAGER) run --privileged --name retrace-server -dit -p 8181:8181 abrt/retrace-server-image 12 | 13 | run_local: 14 | $(MANAGER) run --privileged --name retrace-server -dit -p 8181:8181 retrace-server-image-local 15 | 16 | sh: 17 | $(MANAGER) exec -it retrace-server bash 18 | 19 | del: 20 | $(MANAGER) rm -f retrace-server 21 | -------------------------------------------------------------------------------- /container/files/plugins/fedora.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | distribution = "fedora" 4 | abrtparser = re.compile("^Fedora release ([0-9]+) \(([^\)]+)\)$") 5 | guessparser = re.compile("\.fc([0-9]+)") 6 | displayrelease = "Fedora release" 7 | gdb_package = "gdb" 8 | gdb_executable = "/usr/bin/gdb" 9 | versionlist = [ 10 | "fc1", 11 | "fc2", 12 | "fc3", 13 | "fc4", 14 | "fc5", 15 | "fc6", 16 | "fc7", 17 | "fc8", 18 | "fc9", 19 | "fc10", 20 | "fc11", 21 | "fc12", 22 | "fc13", 23 | "fc14", 24 | "fc15", 25 | "fc16", 26 | "fc17", 27 | "fc18", 28 | "fc19", 29 | "fc20", 30 | "fc21", 31 | "fc22", 32 | "fc23", 33 | "fc24", 34 | ] 35 | 36 | # Find more details about Fedora Mirroring at: 37 | # https://fedoraproject.org/wiki/Infrastructure/Mirroring 38 | # 39 | # fedora-enchilada is /pub/fedora on http://dl.fedoraproject.org 40 | repos = [ 41 | [ 42 | "/var/tmp/local_repo" 43 | ] 44 | ] 45 | -------------------------------------------------------------------------------- /container/files/usr/bin/retrace-server-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec "$@" 4 | -------------------------------------------------------------------------------- /container/files/usr/bin/run_retrace_server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | USER_ID=$(id -u) 4 | GROUP_ID=$(id -g) 5 | 6 | # Openshift runs containers with user that has assigned UID >= 1000000000 7 | # Because there is no record of this user in /etc/passwd, it causes problems with some 8 | # applications. We edit the /etc/passwd so the user is assigned as retrace-server. 9 | if [ x"$USER_ID" != x"0" -a x"$USER_ID" != x"997" ];then 10 | echo "retrace-server:x:${USER_ID}:${GROUP_ID}::/etc/retrace-server:/sbin/nologin" >> /etc/passwd 11 | fi 12 | 13 | /usr/sbin/uwsgi --ini /etc/uwsgi.ini --logto /var/log/retrace-server/uwsgi_logs & 14 | 15 | /usr/sbin/httpd -DFOREGROUND 16 | -------------------------------------------------------------------------------- /container/files/usr/libexec/fix-permissions: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Fix permissions on the given directory to allow group read/write of 3 | # regular files and execute of directories. 4 | 5 | find ${1} -exec chown retrace {} \; 6 | find ${1} -exec chgrp 0 {} \; 7 | find ${1} -exec chmod g+rw {} \; 8 | find ${1} -type d -exec chmod g+x {} \; 9 | -------------------------------------------------------------------------------- /doc/meson.build: -------------------------------------------------------------------------------- 1 | custom_target('retrace-server.info', 2 | build_by_default: true, 3 | command: [ 4 | makeinfo, 5 | # Can be removed when Autotools support is nuked: it’s the inclusion of version.texi. 6 | '--force', 7 | '--output=@OUTPUT@', 8 | '-D', 'VERSION @0@'.format(meson.project_version()), 9 | '-D', 'UPDATED @0@"'.format(run_command(date, '--iso-8601').stdout()), 10 | '@INPUT@', 11 | ], 12 | input: 'retrace-server.texi', 13 | install: true, 14 | install_dir: get_option('infodir'), 15 | output: 'retrace-server.info', 16 | ) 17 | -------------------------------------------------------------------------------- /lgtm.yml: -------------------------------------------------------------------------------- 1 | extraction: 2 | python: 3 | python_setup: 4 | requirements_files: false 5 | setup_py: false 6 | version: 3 7 | -------------------------------------------------------------------------------- /man/asciidoc.conf: -------------------------------------------------------------------------------- 1 | ifdef::doctype-manpage[] 2 | ifdef::backend-docbook[] 3 | [header] 4 | template::[header-declarations] 5 | 6 | 7 | {mantitle} 8 | {manvolnum} 9 | retrace-server 10 | {rs_version} 11 | Retrace Server Manual 12 | 13 | 14 | {manname} 15 | {manpurpose} 16 | 17 | endif::backend-docbook[] 18 | endif::doctype-manpage[] 19 | -------------------------------------------------------------------------------- /man/meson.build: -------------------------------------------------------------------------------- 1 | templates = [ 2 | 'retrace-server-cleanup.txt', 3 | 'retrace-server-interact.txt', 4 | 'retrace-server-reposync.txt', 5 | 'retrace-server-worker.txt', 6 | 'retrace-server-task.txt', 7 | ] 8 | 9 | foreach template : templates 10 | base_name = template.split('.')[0] 11 | xml_output = base_name + '.xml' 12 | docbook = custom_target(xml_output, 13 | command: [ 14 | asciidoc, 15 | '--backend=docbook', 16 | '--doctype=manpage', 17 | '--conf-file=@0@'.format(join_paths(meson.current_source_dir(), 'asciidoc.conf')), 18 | '--attribute=rs_version=' + meson.project_version(), 19 | '--out-file=@OUTPUT@', 20 | '@INPUT@', 21 | ], 22 | input: template, 23 | output: xml_output, 24 | ) 25 | custom_target(base_name + '.1', 26 | build_by_default: true, 27 | command: [ 28 | xmlto, 29 | '-o', '@OUTDIR@', 30 | 'man', 31 | '@INPUT@' 32 | ], 33 | depends: docbook, 34 | input: docbook, 35 | install: true, 36 | install_dir: join_paths(get_option('mandir'), 'man1'), 37 | output: base_name + '.1', 38 | ) 39 | endforeach 40 | -------------------------------------------------------------------------------- /man/retrace-server-cleanup.txt: -------------------------------------------------------------------------------- 1 | retrace-server-cleanup(1) 2 | ========================= 3 | 4 | NAME 5 | ---- 6 | retrace-server-cleanup - Collects garbage from Retrace server. 7 | 8 | SYNOPSIS 9 | -------- 10 | 'retrace-server-cleanup' 11 | 12 | DESCRIPTION 13 | ----------- 14 | The tool collects different kinds of garbage created by Retrace server: 15 | 16 | * Deletes old tasks based on mtime. Will remove the tasks 17 | with a failure status with an mtime defined by the DeleteFailedTaskAfter 18 | variable set in the configuration file. Successful tasks will be deleted 19 | with an mtime defined by the DeleteTaskAfter variable set in the configuration file. 20 | 21 | * Kills tasks running for a long time (> 1 hour). 22 | 23 | * Cleans up fakeroots and task directories from jobs finished 24 | in an unexpected way. 25 | 26 | Should be set in root\'s crontab to run every hour. 27 | 28 | AUTHORS 29 | ------- 30 | * Michal Toman <_mtoman@redhat.com_> 31 | -------------------------------------------------------------------------------- /man/retrace-server-interact.txt: -------------------------------------------------------------------------------- 1 | retrace-server-interact(1) 2 | ========================== 3 | 4 | NAME 5 | ---- 6 | retrace-server-interact - use Retrace server's chroot 7 | for interactive debugging. 8 | 9 | SYNOPSIS 10 | -------- 11 | 'retrace-server-interact' [-h] [--priv] task_id action 12 | 13 | DESCRIPTION 14 | ----------- 15 | The tool makes Retrace server\'s chroot available for interactive 16 | debugging. It enables user to jump into the chroot and execute 17 | the correct tool (gdb/crash), automatically loading all 18 | relevant files. 19 | 20 | ACTIONS 21 | ------- 22 | shell:: 23 | Jump to a shell. Not available if mock chroot is not used. This is 24 | the case of vmcores from the same architecture as host system. 25 | 26 | gdb:: 27 | Run GDB, load executable and coredump and jump to prompt. Only available 28 | for binary crashes (coredumps). 29 | 30 | crash:: 31 | Run Crash, load vmcore and vmlinux and jump to prompt. Only available 32 | for kernel crashes (vmcores). 33 | 34 | printdir:: 35 | Print the directory of the task. 36 | 37 | delete:: 38 | Delete the task. Use with caution (there is no undo). 39 | 40 | set-success:: 41 | Force task status to 'success'. This may be useful if a task has been 42 | marked as "failed" by retrace-server but a user has other knowledge it 43 | is still useful. This will avoid a possible early deletion of the task 44 | by retrace-server-cleanup which uses the DeleteFailedTaskAfter 45 | configuration setting. 46 | 47 | set-fail:: 48 | Force task status to 'fail'. This may be useful if a task has been 49 | marked as "success" by retrace-server but a user has other knowledge it 50 | is not useful. This will allow a possible early deletion of the task 51 | by retrace-server-cleanup which uses DeleteFailedTaskAfter 52 | configuration setting. 53 | 54 | 55 | OPTIONS 56 | ------- 57 | -h:: 58 | Display help and exit. 59 | 60 | --priv:: 61 | Use the chroot as privileged user. 62 | 63 | AUTHORS 64 | ------- 65 | * Michal Toman <_mtoman@redhat.com_> 66 | -------------------------------------------------------------------------------- /man/retrace-server-reposync.txt: -------------------------------------------------------------------------------- 1 | retrace-server-reposync(1) 2 | ======================== 3 | 4 | NAME 5 | ---- 6 | retrace-server-reposync - Maintains local repository cache for Retrace server. 7 | 8 | SYNOPSIS 9 | -------- 10 | 'retrace-server-reposync' distribution version architecture 11 | 12 | DESCRIPTION 13 | ----------- 14 | The tool downloads new packages from public repositories to a local repository 15 | cache. A proper plugin for every distribution needs to be installed in plugins 16 | directory (by default '/usr/share/retrace-server/plugins'). 17 | Should be set up in root\'s or retrace\'s crontab to run every day. 18 | 19 | AUTHORS 20 | ------- 21 | * Michal Toman <_mtoman@redhat.com_> 22 | -------------------------------------------------------------------------------- /man/retrace-server-worker.txt: -------------------------------------------------------------------------------- 1 | retrace-server-worker(1) 2 | ======================== 3 | 4 | NAME 5 | ---- 6 | retrace-server-worker - Executes the retrace job. 7 | 8 | SYNOPSIS 9 | -------- 10 | 'retrace-server-worker' [-h] [-v] [--restart] [--foreground] 11 | [--kernelver KERNELVER] [--arch ARCH] 12 | task_id 13 | 14 | DESCRIPTION 15 | ----------- 16 | The tool creates a worker process which does the bulk of the processing 17 | of a retrace-server task. The worker process handles processing slightly 18 | differently depending on the value of CONFIG["RetraceEnvironment"], and 19 | may use mock, podman, or the native environment to to prepare an environment 20 | for the processing of the coredump or vmcore. The process then runs the 21 | debugger (gdb in the case of a coredump, or crash in the case of a vmcore), 22 | saves the backtrace, also calling hooks at various points in the processing, 23 | assuming hooks is configured (see /etc/retrace-server-hooks.conf). Once all 24 | processing is complete, the worker process then cleans up environment. Note 25 | that the task_id argument is a number derived from the name of the directory 26 | where crash data is saved, CONFIG["SaveDir"]/task_id. 27 | 28 | COMMON OPTIONS 29 | ------------- 30 | -h, --help:: 31 | Display help and exit. 32 | 33 | -v, --verbose:: 34 | Be verbose. Note this also turns on debug logging for create, batch or 35 | restart operations. 36 | 37 | --restart:: 38 | Restart an existing task. Useful for vmcore tasks where kernelver may 39 | not have been detected and thus the task failed. In such cases, using 40 | this option along with --kernelver may allow a task to succeed. 41 | 42 | --foreground:: 43 | Run the worker task in the foreground rather than forking to the background. 44 | 45 | --kernelver:: 46 | For vmcore tasks, specify the version of the kernel rather than attempting 47 | to detect it by scanning the vmcore file. 48 | 49 | --arch:: 50 | Architecture of the vmcore file. This option should be used with the 51 | kernelver option. 52 | 53 | AUTHORS 54 | ------- 55 | * Michal Toman <_mtoman@redhat.com_> 56 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('retrace-server', license : 'GPL2+', meson_version : '>= 0.49.0', version : '1.24.2') 2 | 3 | bindir = get_option('bindir') 4 | datadir = get_option('datadir') 5 | mandir = get_option('mandir') 6 | sysconfdir = get_option('sysconfdir') 7 | 8 | spec_name = '@0@.spec'.format(meson.project_name()) 9 | 10 | i18n = import('i18n') 11 | python = import('python') 12 | 13 | python_installation = python.find_installation('python3') 14 | 15 | asciidoc = find_program('asciidoc', 16 | required: get_option('docs'), 17 | disabler: true, 18 | ) 19 | createrepo = find_program('createrepo') 20 | date = find_program('date', 21 | required: get_option('docs'), 22 | disabler: true, 23 | ) 24 | df = find_program('df') 25 | gzip = find_program('gzip') 26 | lsof = find_program('lsof') 27 | makeinfo = find_program('makeinfo', 28 | required: get_option('docs'), 29 | disabler: true, 30 | ) 31 | podman = find_program('podman', 32 | required: false, 33 | disabler: true, 34 | ) 35 | pytest = find_program('pytest-3') 36 | ps = find_program('ps') 37 | tar = find_program('tar') 38 | tito = find_program('tito', 39 | required: false, 40 | disabler: true, 41 | ) 42 | unar = find_program('unar', 43 | required: false, 44 | disabler: true, 45 | ) 46 | xmlto = find_program('xmlto', 47 | required: get_option('docs'), 48 | disabler: true, 49 | ) 50 | xz = find_program('xz') 51 | 52 | subdir('doc') 53 | subdir('man') 54 | subdir('po') 55 | subdir('src') 56 | subdir('test') 57 | 58 | # Copy the spec file to the build directory so that Tito can run inside it to build 59 | # the rpm and srpm targets (below). 60 | configure_file( 61 | copy: true, 62 | input: spec_name, 63 | output: spec_name, 64 | ) 65 | 66 | configure_file( 67 | copy: true, 68 | input: 'pylintrc', 69 | output: 'pylintrc', 70 | ) 71 | 72 | # This will, naturally, fail if the build directory is outside the git repo, 73 | # since Tito does not provide a way to specify the working directory or the spec 74 | # file using the CLI. 75 | run_target('rpm', 76 | command: [ 77 | tito, 78 | 'build', 79 | '--offline', 80 | '--output=@0@/rpm'.format(meson.current_build_dir()), 81 | '--rpm', 82 | '--test' 83 | ], 84 | ) 85 | run_target('srpm', 86 | command: [ 87 | tito, 88 | 'build', 89 | '--offline', 90 | '--output=@0@/rpm'.format(meson.current_build_dir()), 91 | '--srpm', 92 | '--test' 93 | ], 94 | ) 95 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('coverage', 2 | type: 'boolean', 3 | value: false, 4 | description: 'Generate code coverage report when running tests') 5 | option('docs', 6 | type: 'feature', 7 | value: 'enabled', 8 | description: 'Build documentation') 9 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | disallow_untyped_calls = True 3 | pretty = True 4 | show_column_numbers = True 5 | show_error_codes = True 6 | show_error_context = True 7 | strict_equality = True 8 | warn_unreachable = True 9 | 10 | [mypy-bugzilla] 11 | ignore_missing_imports = True 12 | 13 | [mypy-dnf.*] 14 | ignore_missing_imports = True 15 | 16 | [mypy-magic] 17 | ignore_missing_imports = True 18 | 19 | [mypy-requests_gssapi] 20 | ignore_missing_imports = True 21 | 22 | [mypy-rpm] 23 | ignore_missing_imports = True 24 | 25 | [mypy-sqlalchemy.*] 26 | ignore_missing_imports = True 27 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | ar 2 | as 3 | ast 4 | bn_IN 5 | ca 6 | cs 7 | da 8 | de 9 | el 10 | en_GB 11 | es 12 | fa 13 | fi 14 | fr 15 | gu 16 | he 17 | hi 18 | hu 19 | id 20 | it 21 | ja 22 | kn 23 | ko 24 | ml 25 | mr 26 | nb 27 | nl 28 | or 29 | pa 30 | pl 31 | pt 32 | pt_BR 33 | ru 34 | sk 35 | sr 36 | sr@latin 37 | sv 38 | ta 39 | te 40 | tr 41 | uk 42 | zh_CN 43 | zh_TW 44 | lv 45 | vi 46 | bs 47 | gl 48 | bn 49 | zh_HK 50 | et 51 | th 52 | ur 53 | sq 54 | km 55 | tg 56 | kk 57 | ia 58 | fur 59 | ka 60 | bg 61 | lt 62 | eo 63 | eu 64 | nds 65 | nn 66 | si 67 | af 68 | -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | # [encoding: UTF-8] 2 | # List of source files containing translatable strings. 3 | # Please keep this file sorted alphabetically. 4 | src/backtrace.wsgi 5 | src/create.wsgi 6 | src/index.wsgi 7 | src/log.wsgi 8 | src/stats.wsgi 9 | src/status.wsgi 10 | -------------------------------------------------------------------------------- /po/bn.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # Weblate , 2020. 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2020-05-27 21:44+0200\n" 10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 | "Last-Translator: Automatically generated\n" 12 | "Language-Team: none\n" 13 | "Language: bn\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | 18 | #: ../src/backtrace.wsgi:18 ../src/create.wsgi:55 ../src/log.wsgi:19 19 | #: ../src/status.wsgi:19 20 | msgid "You must use HTTPS" 21 | msgstr "" 22 | 23 | #: ../src/backtrace.wsgi:23 ../src/log.wsgi:24 ../src/status.wsgi:24 24 | msgid "Invalid URL" 25 | msgstr "" 26 | 27 | #: ../src/backtrace.wsgi:29 ../src/log.wsgi:29 ../src/status.wsgi:30 28 | msgid "There is no such task" 29 | msgstr "" 30 | 31 | #: ../src/backtrace.wsgi:34 ../src/log.wsgi:34 ../src/status.wsgi:35 32 | msgid "Invalid password" 33 | msgstr "" 34 | 35 | #: ../src/backtrace.wsgi:38 36 | msgid "There is no backtrace for the specified task" 37 | msgstr "" 38 | 39 | #: ../src/create.wsgi:60 ../src/create.wsgi:116 40 | msgid "Retrace server is fully loaded at the moment" 41 | msgstr "" 42 | 43 | #: ../src/create.wsgi:64 44 | msgid "You must use POST method" 45 | msgstr "" 46 | 47 | #: ../src/create.wsgi:68 48 | msgid "Specified archive format is not supported" 49 | msgstr "" 50 | 51 | #: ../src/create.wsgi:72 52 | msgid "You need to set Content-Length header properly" 53 | msgstr "" 54 | 55 | #: ../src/create.wsgi:76 56 | msgid "Specified archive is too large" 57 | msgstr "" 58 | 59 | #: ../src/create.wsgi:81 60 | msgid "X-CoreFileDirectory header has been disabled by server administrator" 61 | msgstr "" 62 | 63 | #: ../src/create.wsgi:91 64 | msgid "Unable to create working directory" 65 | msgstr "" 66 | 67 | #: ../src/create.wsgi:97 68 | msgid "Unable to obtain disk free space" 69 | msgstr "" 70 | 71 | #: ../src/create.wsgi:101 ../src/create.wsgi:172 72 | msgid "There is not enough storage space on the server" 73 | msgstr "" 74 | 75 | #: ../src/create.wsgi:109 76 | msgid "Unable to create new task" 77 | msgstr "" 78 | 79 | #: ../src/create.wsgi:121 80 | msgid "The directory specified in 'X-CoreFileDirectory' does not exist" 81 | msgstr "" 82 | 83 | #: ../src/create.wsgi:127 84 | #, c-format 85 | msgid "" 86 | "There are %d files in the '%s' directory. Only a single archive is supported " 87 | "at the moment" 88 | msgstr "" 89 | 90 | #: ../src/create.wsgi:136 91 | #, c-format 92 | msgid "You header specifies '%s' type, but the file type does not match" 93 | msgstr "" 94 | 95 | #: ../src/create.wsgi:154 96 | msgid "Unable to save archive" 97 | msgstr "" 98 | 99 | #: ../src/create.wsgi:162 100 | msgid "Unable to obtain unpacked size" 101 | msgstr "" 102 | 103 | #: ../src/create.wsgi:167 104 | msgid "Specified archive's content is too large" 105 | msgstr "" 106 | 107 | #: ../src/create.wsgi:184 108 | msgid "Unable to unpack archive" 109 | msgstr "" 110 | 111 | #: ../src/create.wsgi:197 112 | msgid "Symlinks are not allowed to be in the archive" 113 | msgstr "" 114 | 115 | #: ../src/create.wsgi:204 116 | #, c-format 117 | msgid "The '%s' file is larger than expected" 118 | msgstr "" 119 | 120 | #: ../src/create.wsgi:208 121 | #, c-format 122 | msgid "File '%s' is not allowed to be in the archive" 123 | msgstr "" 124 | 125 | #: ../src/create.wsgi:223 126 | msgid "Interactive tasks were disabled by server administrator" 127 | msgstr "" 128 | 129 | #: ../src/create.wsgi:232 130 | #, c-format 131 | msgid "Required file '%s' is missing" 132 | msgstr "" 133 | 134 | #: ../src/index.wsgi:20 135 | msgid "Retrace Server" 136 | msgstr "" 137 | 138 | #: ../src/index.wsgi:21 139 | msgid "Welcome to Retrace Server" 140 | msgstr "" 141 | 142 | #: ../src/index.wsgi:23 143 | msgid "" 144 | "Retrace Server is a service that provides the possibility to analyze " 145 | "coredump and generate backtrace over network. You can find further " 146 | "information at Retrace Server's github:" 147 | msgstr "" 148 | 149 | #: ../src/index.wsgi:29 150 | msgid "" 151 | "Only the secure HTTPS connection is now allowed by the server. HTTP requests " 152 | "will be denied." 153 | msgstr "" 154 | 155 | #: ../src/index.wsgi:31 156 | msgid "" 157 | "Both HTTP and HTTPS are allowed. Using HTTPS is strictly recommended because " 158 | "of security reasons." 159 | msgstr "" 160 | 161 | #: ../src/index.wsgi:32 162 | #, c-format 163 | msgid "The following releases are supported: %s" 164 | msgstr "" 165 | 166 | #. CONFIG["MaxParallelTasks"], active, CONFIG["MaxParallelTasks"])) 167 | #: ../src/index.wsgi:36 168 | #, c-format 169 | msgid "" 170 | "Your coredump is only kept on the server while the retrace job is running. " 171 | "Once the job is finished, the server keeps retrace log and backtrace. All " 172 | "the other data (including coredump) are deleted. The retrace log and " 173 | "backtrace are only accessible via unique task ID and password, thus no one " 174 | "(except the author) is allowed to view it. All the crash information " 175 | "(including backtrace) is deleted after %d hours of inactivity. No possibly " 176 | "private data are kept on the server any longer." 177 | msgstr "" 178 | 179 | #: ../src/index.wsgi:43 180 | msgid "" 181 | "Your coredump is only used for retrace purposes. Server administrators are " 182 | "not trying to get your private data from coredumps or backtraces. Using a " 183 | "secure communication channel (HTTPS) is strictly recommended. Server " 184 | "administrators are not responsible for the problems related to the usage of " 185 | "an insecure channel (such as HTTP)." 186 | msgstr "" 187 | 188 | #: ../src/index.wsgi:34 189 | #, c-format 190 | msgid "" 191 | "At the moment the server is loaded for %d%% (running %d out of %d jobs)." 192 | msgstr "" 193 | 194 | #: ../src/log.wsgi:38 195 | msgid "There is no log for the specified task" 196 | msgstr "" 197 | 198 | #: ../src/stats.wsgi:36 199 | msgid "Architecture" 200 | msgstr "" 201 | 202 | #: ../src/stats.wsgi:37 203 | msgid "Architectures" 204 | msgstr "" 205 | 206 | #: ../src/stats.wsgi:38 207 | msgid "Build-id" 208 | msgstr "" 209 | 210 | #: ../src/stats.wsgi:39 211 | msgid "Count" 212 | msgstr "" 213 | 214 | #: ../src/stats.wsgi:40 215 | msgid "Denied jobs" 216 | msgstr "" 217 | 218 | #: ../src/stats.wsgi:41 219 | msgid "Failed" 220 | msgstr "" 221 | 222 | #: ../src/stats.wsgi:42 223 | msgid "First retrace" 224 | msgstr "" 225 | 226 | #: ../src/stats.wsgi:43 227 | msgid "Global statistics" 228 | msgstr "" 229 | 230 | #: ../src/stats.wsgi:44 231 | msgid "Missing build-ids" 232 | msgstr "" 233 | 234 | #: ../src/stats.wsgi:45 235 | msgid "Name" 236 | msgstr "" 237 | 238 | #: ../src/stats.wsgi:46 239 | msgid "Release" 240 | msgstr "" 241 | 242 | #: ../src/stats.wsgi:47 243 | msgid "Releases" 244 | msgstr "" 245 | 246 | #: ../src/stats.wsgi:48 247 | msgid "Required packages" 248 | msgstr "" 249 | 250 | #: ../src/stats.wsgi:49 251 | msgid "Retraced packages" 252 | msgstr "" 253 | 254 | #: ../src/stats.wsgi:50 255 | msgid "Retrace Server statistics" 256 | msgstr "" 257 | 258 | #: ../src/stats.wsgi:51 259 | msgid "Shared object name" 260 | msgstr "" 261 | 262 | #: ../src/stats.wsgi:52 263 | msgid "Successful" 264 | msgstr "" 265 | 266 | #: ../src/stats.wsgi:53 267 | msgid "Total" 268 | msgstr "" 269 | 270 | #: ../src/stats.wsgi:54 271 | msgid "Versions" 272 | msgstr "" 273 | -------------------------------------------------------------------------------- /po/eo.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # Weblate , 2020. 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2020-05-27 21:44+0200\n" 10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 | "Last-Translator: Automatically generated\n" 12 | "Language-Team: none\n" 13 | "Language: eo\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | 18 | #: ../src/backtrace.wsgi:18 ../src/create.wsgi:55 ../src/log.wsgi:19 19 | #: ../src/status.wsgi:19 20 | msgid "You must use HTTPS" 21 | msgstr "" 22 | 23 | #: ../src/backtrace.wsgi:23 ../src/log.wsgi:24 ../src/status.wsgi:24 24 | msgid "Invalid URL" 25 | msgstr "" 26 | 27 | #: ../src/backtrace.wsgi:29 ../src/log.wsgi:29 ../src/status.wsgi:30 28 | msgid "There is no such task" 29 | msgstr "" 30 | 31 | #: ../src/backtrace.wsgi:34 ../src/log.wsgi:34 ../src/status.wsgi:35 32 | msgid "Invalid password" 33 | msgstr "" 34 | 35 | #: ../src/backtrace.wsgi:38 36 | msgid "There is no backtrace for the specified task" 37 | msgstr "" 38 | 39 | #: ../src/create.wsgi:60 ../src/create.wsgi:116 40 | msgid "Retrace server is fully loaded at the moment" 41 | msgstr "" 42 | 43 | #: ../src/create.wsgi:64 44 | msgid "You must use POST method" 45 | msgstr "" 46 | 47 | #: ../src/create.wsgi:68 48 | msgid "Specified archive format is not supported" 49 | msgstr "" 50 | 51 | #: ../src/create.wsgi:72 52 | msgid "You need to set Content-Length header properly" 53 | msgstr "" 54 | 55 | #: ../src/create.wsgi:76 56 | msgid "Specified archive is too large" 57 | msgstr "" 58 | 59 | #: ../src/create.wsgi:81 60 | msgid "X-CoreFileDirectory header has been disabled by server administrator" 61 | msgstr "" 62 | 63 | #: ../src/create.wsgi:91 64 | msgid "Unable to create working directory" 65 | msgstr "" 66 | 67 | #: ../src/create.wsgi:97 68 | msgid "Unable to obtain disk free space" 69 | msgstr "" 70 | 71 | #: ../src/create.wsgi:101 ../src/create.wsgi:172 72 | msgid "There is not enough storage space on the server" 73 | msgstr "" 74 | 75 | #: ../src/create.wsgi:109 76 | msgid "Unable to create new task" 77 | msgstr "" 78 | 79 | #: ../src/create.wsgi:121 80 | msgid "The directory specified in 'X-CoreFileDirectory' does not exist" 81 | msgstr "" 82 | 83 | #: ../src/create.wsgi:127 84 | #, c-format 85 | msgid "" 86 | "There are %d files in the '%s' directory. Only a single archive is supported " 87 | "at the moment" 88 | msgstr "" 89 | 90 | #: ../src/create.wsgi:136 91 | #, c-format 92 | msgid "You header specifies '%s' type, but the file type does not match" 93 | msgstr "" 94 | 95 | #: ../src/create.wsgi:154 96 | msgid "Unable to save archive" 97 | msgstr "" 98 | 99 | #: ../src/create.wsgi:162 100 | msgid "Unable to obtain unpacked size" 101 | msgstr "" 102 | 103 | #: ../src/create.wsgi:167 104 | msgid "Specified archive's content is too large" 105 | msgstr "" 106 | 107 | #: ../src/create.wsgi:184 108 | msgid "Unable to unpack archive" 109 | msgstr "" 110 | 111 | #: ../src/create.wsgi:197 112 | msgid "Symlinks are not allowed to be in the archive" 113 | msgstr "" 114 | 115 | #: ../src/create.wsgi:204 116 | #, c-format 117 | msgid "The '%s' file is larger than expected" 118 | msgstr "" 119 | 120 | #: ../src/create.wsgi:208 121 | #, c-format 122 | msgid "File '%s' is not allowed to be in the archive" 123 | msgstr "" 124 | 125 | #: ../src/create.wsgi:223 126 | msgid "Interactive tasks were disabled by server administrator" 127 | msgstr "" 128 | 129 | #: ../src/create.wsgi:232 130 | #, c-format 131 | msgid "Required file '%s' is missing" 132 | msgstr "" 133 | 134 | #: ../src/index.wsgi:20 135 | msgid "Retrace Server" 136 | msgstr "" 137 | 138 | #: ../src/index.wsgi:21 139 | msgid "Welcome to Retrace Server" 140 | msgstr "" 141 | 142 | #: ../src/index.wsgi:23 143 | msgid "" 144 | "Retrace Server is a service that provides the possibility to analyze " 145 | "coredump and generate backtrace over network. You can find further " 146 | "information at Retrace Server's github:" 147 | msgstr "" 148 | 149 | #: ../src/index.wsgi:29 150 | msgid "" 151 | "Only the secure HTTPS connection is now allowed by the server. HTTP requests " 152 | "will be denied." 153 | msgstr "" 154 | 155 | #: ../src/index.wsgi:31 156 | msgid "" 157 | "Both HTTP and HTTPS are allowed. Using HTTPS is strictly recommended because " 158 | "of security reasons." 159 | msgstr "" 160 | 161 | #: ../src/index.wsgi:32 162 | #, c-format 163 | msgid "The following releases are supported: %s" 164 | msgstr "" 165 | 166 | #. CONFIG["MaxParallelTasks"], active, CONFIG["MaxParallelTasks"])) 167 | #: ../src/index.wsgi:36 168 | #, c-format 169 | msgid "" 170 | "Your coredump is only kept on the server while the retrace job is running. " 171 | "Once the job is finished, the server keeps retrace log and backtrace. All " 172 | "the other data (including coredump) are deleted. The retrace log and " 173 | "backtrace are only accessible via unique task ID and password, thus no one " 174 | "(except the author) is allowed to view it. All the crash information " 175 | "(including backtrace) is deleted after %d hours of inactivity. No possibly " 176 | "private data are kept on the server any longer." 177 | msgstr "" 178 | 179 | #: ../src/index.wsgi:43 180 | msgid "" 181 | "Your coredump is only used for retrace purposes. Server administrators are " 182 | "not trying to get your private data from coredumps or backtraces. Using a " 183 | "secure communication channel (HTTPS) is strictly recommended. Server " 184 | "administrators are not responsible for the problems related to the usage of " 185 | "an insecure channel (such as HTTP)." 186 | msgstr "" 187 | 188 | #: ../src/index.wsgi:34 189 | #, c-format 190 | msgid "" 191 | "At the moment the server is loaded for %d%% (running %d out of %d jobs)." 192 | msgstr "" 193 | 194 | #: ../src/log.wsgi:38 195 | msgid "There is no log for the specified task" 196 | msgstr "" 197 | 198 | #: ../src/stats.wsgi:36 199 | msgid "Architecture" 200 | msgstr "" 201 | 202 | #: ../src/stats.wsgi:37 203 | msgid "Architectures" 204 | msgstr "" 205 | 206 | #: ../src/stats.wsgi:38 207 | msgid "Build-id" 208 | msgstr "" 209 | 210 | #: ../src/stats.wsgi:39 211 | msgid "Count" 212 | msgstr "" 213 | 214 | #: ../src/stats.wsgi:40 215 | msgid "Denied jobs" 216 | msgstr "" 217 | 218 | #: ../src/stats.wsgi:41 219 | msgid "Failed" 220 | msgstr "" 221 | 222 | #: ../src/stats.wsgi:42 223 | msgid "First retrace" 224 | msgstr "" 225 | 226 | #: ../src/stats.wsgi:43 227 | msgid "Global statistics" 228 | msgstr "" 229 | 230 | #: ../src/stats.wsgi:44 231 | msgid "Missing build-ids" 232 | msgstr "" 233 | 234 | #: ../src/stats.wsgi:45 235 | msgid "Name" 236 | msgstr "" 237 | 238 | #: ../src/stats.wsgi:46 239 | msgid "Release" 240 | msgstr "" 241 | 242 | #: ../src/stats.wsgi:47 243 | msgid "Releases" 244 | msgstr "" 245 | 246 | #: ../src/stats.wsgi:48 247 | msgid "Required packages" 248 | msgstr "" 249 | 250 | #: ../src/stats.wsgi:49 251 | msgid "Retraced packages" 252 | msgstr "" 253 | 254 | #: ../src/stats.wsgi:50 255 | msgid "Retrace Server statistics" 256 | msgstr "" 257 | 258 | #: ../src/stats.wsgi:51 259 | msgid "Shared object name" 260 | msgstr "" 261 | 262 | #: ../src/stats.wsgi:52 263 | msgid "Successful" 264 | msgstr "" 265 | 266 | #: ../src/stats.wsgi:53 267 | msgid "Total" 268 | msgstr "" 269 | 270 | #: ../src/stats.wsgi:54 271 | msgid "Versions" 272 | msgstr "" 273 | -------------------------------------------------------------------------------- /po/et.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # Weblate , 2020. 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2020-05-27 21:44+0200\n" 10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 | "Last-Translator: Automatically generated\n" 12 | "Language-Team: none\n" 13 | "Language: et\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | 18 | #: ../src/backtrace.wsgi:18 ../src/create.wsgi:55 ../src/log.wsgi:19 19 | #: ../src/status.wsgi:19 20 | msgid "You must use HTTPS" 21 | msgstr "" 22 | 23 | #: ../src/backtrace.wsgi:23 ../src/log.wsgi:24 ../src/status.wsgi:24 24 | msgid "Invalid URL" 25 | msgstr "" 26 | 27 | #: ../src/backtrace.wsgi:29 ../src/log.wsgi:29 ../src/status.wsgi:30 28 | msgid "There is no such task" 29 | msgstr "" 30 | 31 | #: ../src/backtrace.wsgi:34 ../src/log.wsgi:34 ../src/status.wsgi:35 32 | msgid "Invalid password" 33 | msgstr "" 34 | 35 | #: ../src/backtrace.wsgi:38 36 | msgid "There is no backtrace for the specified task" 37 | msgstr "" 38 | 39 | #: ../src/create.wsgi:60 ../src/create.wsgi:116 40 | msgid "Retrace server is fully loaded at the moment" 41 | msgstr "" 42 | 43 | #: ../src/create.wsgi:64 44 | msgid "You must use POST method" 45 | msgstr "" 46 | 47 | #: ../src/create.wsgi:68 48 | msgid "Specified archive format is not supported" 49 | msgstr "" 50 | 51 | #: ../src/create.wsgi:72 52 | msgid "You need to set Content-Length header properly" 53 | msgstr "" 54 | 55 | #: ../src/create.wsgi:76 56 | msgid "Specified archive is too large" 57 | msgstr "" 58 | 59 | #: ../src/create.wsgi:81 60 | msgid "X-CoreFileDirectory header has been disabled by server administrator" 61 | msgstr "" 62 | 63 | #: ../src/create.wsgi:91 64 | msgid "Unable to create working directory" 65 | msgstr "" 66 | 67 | #: ../src/create.wsgi:97 68 | msgid "Unable to obtain disk free space" 69 | msgstr "" 70 | 71 | #: ../src/create.wsgi:101 ../src/create.wsgi:172 72 | msgid "There is not enough storage space on the server" 73 | msgstr "" 74 | 75 | #: ../src/create.wsgi:109 76 | msgid "Unable to create new task" 77 | msgstr "" 78 | 79 | #: ../src/create.wsgi:121 80 | msgid "The directory specified in 'X-CoreFileDirectory' does not exist" 81 | msgstr "" 82 | 83 | #: ../src/create.wsgi:127 84 | #, c-format 85 | msgid "" 86 | "There are %d files in the '%s' directory. Only a single archive is supported " 87 | "at the moment" 88 | msgstr "" 89 | 90 | #: ../src/create.wsgi:136 91 | #, c-format 92 | msgid "You header specifies '%s' type, but the file type does not match" 93 | msgstr "" 94 | 95 | #: ../src/create.wsgi:154 96 | msgid "Unable to save archive" 97 | msgstr "" 98 | 99 | #: ../src/create.wsgi:162 100 | msgid "Unable to obtain unpacked size" 101 | msgstr "" 102 | 103 | #: ../src/create.wsgi:167 104 | msgid "Specified archive's content is too large" 105 | msgstr "" 106 | 107 | #: ../src/create.wsgi:184 108 | msgid "Unable to unpack archive" 109 | msgstr "" 110 | 111 | #: ../src/create.wsgi:197 112 | msgid "Symlinks are not allowed to be in the archive" 113 | msgstr "" 114 | 115 | #: ../src/create.wsgi:204 116 | #, c-format 117 | msgid "The '%s' file is larger than expected" 118 | msgstr "" 119 | 120 | #: ../src/create.wsgi:208 121 | #, c-format 122 | msgid "File '%s' is not allowed to be in the archive" 123 | msgstr "" 124 | 125 | #: ../src/create.wsgi:223 126 | msgid "Interactive tasks were disabled by server administrator" 127 | msgstr "" 128 | 129 | #: ../src/create.wsgi:232 130 | #, c-format 131 | msgid "Required file '%s' is missing" 132 | msgstr "" 133 | 134 | #: ../src/index.wsgi:20 135 | msgid "Retrace Server" 136 | msgstr "" 137 | 138 | #: ../src/index.wsgi:21 139 | msgid "Welcome to Retrace Server" 140 | msgstr "" 141 | 142 | #: ../src/index.wsgi:23 143 | msgid "" 144 | "Retrace Server is a service that provides the possibility to analyze " 145 | "coredump and generate backtrace over network. You can find further " 146 | "information at Retrace Server's github:" 147 | msgstr "" 148 | 149 | #: ../src/index.wsgi:29 150 | msgid "" 151 | "Only the secure HTTPS connection is now allowed by the server. HTTP requests " 152 | "will be denied." 153 | msgstr "" 154 | 155 | #: ../src/index.wsgi:31 156 | msgid "" 157 | "Both HTTP and HTTPS are allowed. Using HTTPS is strictly recommended because " 158 | "of security reasons." 159 | msgstr "" 160 | 161 | #: ../src/index.wsgi:32 162 | #, c-format 163 | msgid "The following releases are supported: %s" 164 | msgstr "" 165 | 166 | #. CONFIG["MaxParallelTasks"], active, CONFIG["MaxParallelTasks"])) 167 | #: ../src/index.wsgi:36 168 | #, c-format 169 | msgid "" 170 | "Your coredump is only kept on the server while the retrace job is running. " 171 | "Once the job is finished, the server keeps retrace log and backtrace. All " 172 | "the other data (including coredump) are deleted. The retrace log and " 173 | "backtrace are only accessible via unique task ID and password, thus no one " 174 | "(except the author) is allowed to view it. All the crash information " 175 | "(including backtrace) is deleted after %d hours of inactivity. No possibly " 176 | "private data are kept on the server any longer." 177 | msgstr "" 178 | 179 | #: ../src/index.wsgi:43 180 | msgid "" 181 | "Your coredump is only used for retrace purposes. Server administrators are " 182 | "not trying to get your private data from coredumps or backtraces. Using a " 183 | "secure communication channel (HTTPS) is strictly recommended. Server " 184 | "administrators are not responsible for the problems related to the usage of " 185 | "an insecure channel (such as HTTP)." 186 | msgstr "" 187 | 188 | #: ../src/index.wsgi:34 189 | #, c-format 190 | msgid "" 191 | "At the moment the server is loaded for %d%% (running %d out of %d jobs)." 192 | msgstr "" 193 | 194 | #: ../src/log.wsgi:38 195 | msgid "There is no log for the specified task" 196 | msgstr "" 197 | 198 | #: ../src/stats.wsgi:36 199 | msgid "Architecture" 200 | msgstr "" 201 | 202 | #: ../src/stats.wsgi:37 203 | msgid "Architectures" 204 | msgstr "" 205 | 206 | #: ../src/stats.wsgi:38 207 | msgid "Build-id" 208 | msgstr "" 209 | 210 | #: ../src/stats.wsgi:39 211 | msgid "Count" 212 | msgstr "" 213 | 214 | #: ../src/stats.wsgi:40 215 | msgid "Denied jobs" 216 | msgstr "" 217 | 218 | #: ../src/stats.wsgi:41 219 | msgid "Failed" 220 | msgstr "" 221 | 222 | #: ../src/stats.wsgi:42 223 | msgid "First retrace" 224 | msgstr "" 225 | 226 | #: ../src/stats.wsgi:43 227 | msgid "Global statistics" 228 | msgstr "" 229 | 230 | #: ../src/stats.wsgi:44 231 | msgid "Missing build-ids" 232 | msgstr "" 233 | 234 | #: ../src/stats.wsgi:45 235 | msgid "Name" 236 | msgstr "" 237 | 238 | #: ../src/stats.wsgi:46 239 | msgid "Release" 240 | msgstr "" 241 | 242 | #: ../src/stats.wsgi:47 243 | msgid "Releases" 244 | msgstr "" 245 | 246 | #: ../src/stats.wsgi:48 247 | msgid "Required packages" 248 | msgstr "" 249 | 250 | #: ../src/stats.wsgi:49 251 | msgid "Retraced packages" 252 | msgstr "" 253 | 254 | #: ../src/stats.wsgi:50 255 | msgid "Retrace Server statistics" 256 | msgstr "" 257 | 258 | #: ../src/stats.wsgi:51 259 | msgid "Shared object name" 260 | msgstr "" 261 | 262 | #: ../src/stats.wsgi:52 263 | msgid "Successful" 264 | msgstr "" 265 | 266 | #: ../src/stats.wsgi:53 267 | msgid "Total" 268 | msgstr "" 269 | 270 | #: ../src/stats.wsgi:54 271 | msgid "Versions" 272 | msgstr "" 273 | -------------------------------------------------------------------------------- /po/eu.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # Weblate , 2020. 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2020-05-27 21:44+0200\n" 10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 | "Last-Translator: Automatically generated\n" 12 | "Language-Team: none\n" 13 | "Language: eu\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | 18 | #: ../src/backtrace.wsgi:18 ../src/create.wsgi:55 ../src/log.wsgi:19 19 | #: ../src/status.wsgi:19 20 | msgid "You must use HTTPS" 21 | msgstr "" 22 | 23 | #: ../src/backtrace.wsgi:23 ../src/log.wsgi:24 ../src/status.wsgi:24 24 | msgid "Invalid URL" 25 | msgstr "" 26 | 27 | #: ../src/backtrace.wsgi:29 ../src/log.wsgi:29 ../src/status.wsgi:30 28 | msgid "There is no such task" 29 | msgstr "" 30 | 31 | #: ../src/backtrace.wsgi:34 ../src/log.wsgi:34 ../src/status.wsgi:35 32 | msgid "Invalid password" 33 | msgstr "" 34 | 35 | #: ../src/backtrace.wsgi:38 36 | msgid "There is no backtrace for the specified task" 37 | msgstr "" 38 | 39 | #: ../src/create.wsgi:60 ../src/create.wsgi:116 40 | msgid "Retrace server is fully loaded at the moment" 41 | msgstr "" 42 | 43 | #: ../src/create.wsgi:64 44 | msgid "You must use POST method" 45 | msgstr "" 46 | 47 | #: ../src/create.wsgi:68 48 | msgid "Specified archive format is not supported" 49 | msgstr "" 50 | 51 | #: ../src/create.wsgi:72 52 | msgid "You need to set Content-Length header properly" 53 | msgstr "" 54 | 55 | #: ../src/create.wsgi:76 56 | msgid "Specified archive is too large" 57 | msgstr "" 58 | 59 | #: ../src/create.wsgi:81 60 | msgid "X-CoreFileDirectory header has been disabled by server administrator" 61 | msgstr "" 62 | 63 | #: ../src/create.wsgi:91 64 | msgid "Unable to create working directory" 65 | msgstr "" 66 | 67 | #: ../src/create.wsgi:97 68 | msgid "Unable to obtain disk free space" 69 | msgstr "" 70 | 71 | #: ../src/create.wsgi:101 ../src/create.wsgi:172 72 | msgid "There is not enough storage space on the server" 73 | msgstr "" 74 | 75 | #: ../src/create.wsgi:109 76 | msgid "Unable to create new task" 77 | msgstr "" 78 | 79 | #: ../src/create.wsgi:121 80 | msgid "The directory specified in 'X-CoreFileDirectory' does not exist" 81 | msgstr "" 82 | 83 | #: ../src/create.wsgi:127 84 | #, c-format 85 | msgid "" 86 | "There are %d files in the '%s' directory. Only a single archive is supported " 87 | "at the moment" 88 | msgstr "" 89 | 90 | #: ../src/create.wsgi:136 91 | #, c-format 92 | msgid "You header specifies '%s' type, but the file type does not match" 93 | msgstr "" 94 | 95 | #: ../src/create.wsgi:154 96 | msgid "Unable to save archive" 97 | msgstr "" 98 | 99 | #: ../src/create.wsgi:162 100 | msgid "Unable to obtain unpacked size" 101 | msgstr "" 102 | 103 | #: ../src/create.wsgi:167 104 | msgid "Specified archive's content is too large" 105 | msgstr "" 106 | 107 | #: ../src/create.wsgi:184 108 | msgid "Unable to unpack archive" 109 | msgstr "" 110 | 111 | #: ../src/create.wsgi:197 112 | msgid "Symlinks are not allowed to be in the archive" 113 | msgstr "" 114 | 115 | #: ../src/create.wsgi:204 116 | #, c-format 117 | msgid "The '%s' file is larger than expected" 118 | msgstr "" 119 | 120 | #: ../src/create.wsgi:208 121 | #, c-format 122 | msgid "File '%s' is not allowed to be in the archive" 123 | msgstr "" 124 | 125 | #: ../src/create.wsgi:223 126 | msgid "Interactive tasks were disabled by server administrator" 127 | msgstr "" 128 | 129 | #: ../src/create.wsgi:232 130 | #, c-format 131 | msgid "Required file '%s' is missing" 132 | msgstr "" 133 | 134 | #: ../src/index.wsgi:20 135 | msgid "Retrace Server" 136 | msgstr "" 137 | 138 | #: ../src/index.wsgi:21 139 | msgid "Welcome to Retrace Server" 140 | msgstr "" 141 | 142 | #: ../src/index.wsgi:23 143 | msgid "" 144 | "Retrace Server is a service that provides the possibility to analyze " 145 | "coredump and generate backtrace over network. You can find further " 146 | "information at Retrace Server's github:" 147 | msgstr "" 148 | 149 | #: ../src/index.wsgi:29 150 | msgid "" 151 | "Only the secure HTTPS connection is now allowed by the server. HTTP requests " 152 | "will be denied." 153 | msgstr "" 154 | 155 | #: ../src/index.wsgi:31 156 | msgid "" 157 | "Both HTTP and HTTPS are allowed. Using HTTPS is strictly recommended because " 158 | "of security reasons." 159 | msgstr "" 160 | 161 | #: ../src/index.wsgi:32 162 | #, c-format 163 | msgid "The following releases are supported: %s" 164 | msgstr "" 165 | 166 | #. CONFIG["MaxParallelTasks"], active, CONFIG["MaxParallelTasks"])) 167 | #: ../src/index.wsgi:36 168 | #, c-format 169 | msgid "" 170 | "Your coredump is only kept on the server while the retrace job is running. " 171 | "Once the job is finished, the server keeps retrace log and backtrace. All " 172 | "the other data (including coredump) are deleted. The retrace log and " 173 | "backtrace are only accessible via unique task ID and password, thus no one " 174 | "(except the author) is allowed to view it. All the crash information " 175 | "(including backtrace) is deleted after %d hours of inactivity. No possibly " 176 | "private data are kept on the server any longer." 177 | msgstr "" 178 | 179 | #: ../src/index.wsgi:43 180 | msgid "" 181 | "Your coredump is only used for retrace purposes. Server administrators are " 182 | "not trying to get your private data from coredumps or backtraces. Using a " 183 | "secure communication channel (HTTPS) is strictly recommended. Server " 184 | "administrators are not responsible for the problems related to the usage of " 185 | "an insecure channel (such as HTTP)." 186 | msgstr "" 187 | 188 | #: ../src/index.wsgi:34 189 | #, c-format 190 | msgid "" 191 | "At the moment the server is loaded for %d%% (running %d out of %d jobs)." 192 | msgstr "" 193 | 194 | #: ../src/log.wsgi:38 195 | msgid "There is no log for the specified task" 196 | msgstr "" 197 | 198 | #: ../src/stats.wsgi:36 199 | msgid "Architecture" 200 | msgstr "" 201 | 202 | #: ../src/stats.wsgi:37 203 | msgid "Architectures" 204 | msgstr "" 205 | 206 | #: ../src/stats.wsgi:38 207 | msgid "Build-id" 208 | msgstr "" 209 | 210 | #: ../src/stats.wsgi:39 211 | msgid "Count" 212 | msgstr "" 213 | 214 | #: ../src/stats.wsgi:40 215 | msgid "Denied jobs" 216 | msgstr "" 217 | 218 | #: ../src/stats.wsgi:41 219 | msgid "Failed" 220 | msgstr "" 221 | 222 | #: ../src/stats.wsgi:42 223 | msgid "First retrace" 224 | msgstr "" 225 | 226 | #: ../src/stats.wsgi:43 227 | msgid "Global statistics" 228 | msgstr "" 229 | 230 | #: ../src/stats.wsgi:44 231 | msgid "Missing build-ids" 232 | msgstr "" 233 | 234 | #: ../src/stats.wsgi:45 235 | msgid "Name" 236 | msgstr "" 237 | 238 | #: ../src/stats.wsgi:46 239 | msgid "Release" 240 | msgstr "" 241 | 242 | #: ../src/stats.wsgi:47 243 | msgid "Releases" 244 | msgstr "" 245 | 246 | #: ../src/stats.wsgi:48 247 | msgid "Required packages" 248 | msgstr "" 249 | 250 | #: ../src/stats.wsgi:49 251 | msgid "Retraced packages" 252 | msgstr "" 253 | 254 | #: ../src/stats.wsgi:50 255 | msgid "Retrace Server statistics" 256 | msgstr "" 257 | 258 | #: ../src/stats.wsgi:51 259 | msgid "Shared object name" 260 | msgstr "" 261 | 262 | #: ../src/stats.wsgi:52 263 | msgid "Successful" 264 | msgstr "" 265 | 266 | #: ../src/stats.wsgi:53 267 | msgid "Total" 268 | msgstr "" 269 | 270 | #: ../src/stats.wsgi:54 271 | msgid "Versions" 272 | msgstr "" 273 | -------------------------------------------------------------------------------- /po/gl.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # Weblate , 2020. 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2020-05-27 21:44+0200\n" 10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 | "Last-Translator: Automatically generated\n" 12 | "Language-Team: none\n" 13 | "Language: gl\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | 18 | #: ../src/backtrace.wsgi:18 ../src/create.wsgi:55 ../src/log.wsgi:19 19 | #: ../src/status.wsgi:19 20 | msgid "You must use HTTPS" 21 | msgstr "" 22 | 23 | #: ../src/backtrace.wsgi:23 ../src/log.wsgi:24 ../src/status.wsgi:24 24 | msgid "Invalid URL" 25 | msgstr "" 26 | 27 | #: ../src/backtrace.wsgi:29 ../src/log.wsgi:29 ../src/status.wsgi:30 28 | msgid "There is no such task" 29 | msgstr "" 30 | 31 | #: ../src/backtrace.wsgi:34 ../src/log.wsgi:34 ../src/status.wsgi:35 32 | msgid "Invalid password" 33 | msgstr "" 34 | 35 | #: ../src/backtrace.wsgi:38 36 | msgid "There is no backtrace for the specified task" 37 | msgstr "" 38 | 39 | #: ../src/create.wsgi:60 ../src/create.wsgi:116 40 | msgid "Retrace server is fully loaded at the moment" 41 | msgstr "" 42 | 43 | #: ../src/create.wsgi:64 44 | msgid "You must use POST method" 45 | msgstr "" 46 | 47 | #: ../src/create.wsgi:68 48 | msgid "Specified archive format is not supported" 49 | msgstr "" 50 | 51 | #: ../src/create.wsgi:72 52 | msgid "You need to set Content-Length header properly" 53 | msgstr "" 54 | 55 | #: ../src/create.wsgi:76 56 | msgid "Specified archive is too large" 57 | msgstr "" 58 | 59 | #: ../src/create.wsgi:81 60 | msgid "X-CoreFileDirectory header has been disabled by server administrator" 61 | msgstr "" 62 | 63 | #: ../src/create.wsgi:91 64 | msgid "Unable to create working directory" 65 | msgstr "" 66 | 67 | #: ../src/create.wsgi:97 68 | msgid "Unable to obtain disk free space" 69 | msgstr "" 70 | 71 | #: ../src/create.wsgi:101 ../src/create.wsgi:172 72 | msgid "There is not enough storage space on the server" 73 | msgstr "" 74 | 75 | #: ../src/create.wsgi:109 76 | msgid "Unable to create new task" 77 | msgstr "" 78 | 79 | #: ../src/create.wsgi:121 80 | msgid "The directory specified in 'X-CoreFileDirectory' does not exist" 81 | msgstr "" 82 | 83 | #: ../src/create.wsgi:127 84 | #, c-format 85 | msgid "" 86 | "There are %d files in the '%s' directory. Only a single archive is supported " 87 | "at the moment" 88 | msgstr "" 89 | 90 | #: ../src/create.wsgi:136 91 | #, c-format 92 | msgid "You header specifies '%s' type, but the file type does not match" 93 | msgstr "" 94 | 95 | #: ../src/create.wsgi:154 96 | msgid "Unable to save archive" 97 | msgstr "" 98 | 99 | #: ../src/create.wsgi:162 100 | msgid "Unable to obtain unpacked size" 101 | msgstr "" 102 | 103 | #: ../src/create.wsgi:167 104 | msgid "Specified archive's content is too large" 105 | msgstr "" 106 | 107 | #: ../src/create.wsgi:184 108 | msgid "Unable to unpack archive" 109 | msgstr "" 110 | 111 | #: ../src/create.wsgi:197 112 | msgid "Symlinks are not allowed to be in the archive" 113 | msgstr "" 114 | 115 | #: ../src/create.wsgi:204 116 | #, c-format 117 | msgid "The '%s' file is larger than expected" 118 | msgstr "" 119 | 120 | #: ../src/create.wsgi:208 121 | #, c-format 122 | msgid "File '%s' is not allowed to be in the archive" 123 | msgstr "" 124 | 125 | #: ../src/create.wsgi:223 126 | msgid "Interactive tasks were disabled by server administrator" 127 | msgstr "" 128 | 129 | #: ../src/create.wsgi:232 130 | #, c-format 131 | msgid "Required file '%s' is missing" 132 | msgstr "" 133 | 134 | #: ../src/index.wsgi:20 135 | msgid "Retrace Server" 136 | msgstr "" 137 | 138 | #: ../src/index.wsgi:21 139 | msgid "Welcome to Retrace Server" 140 | msgstr "" 141 | 142 | #: ../src/index.wsgi:23 143 | msgid "" 144 | "Retrace Server is a service that provides the possibility to analyze " 145 | "coredump and generate backtrace over network. You can find further " 146 | "information at Retrace Server's github:" 147 | msgstr "" 148 | 149 | #: ../src/index.wsgi:29 150 | msgid "" 151 | "Only the secure HTTPS connection is now allowed by the server. HTTP requests " 152 | "will be denied." 153 | msgstr "" 154 | 155 | #: ../src/index.wsgi:31 156 | msgid "" 157 | "Both HTTP and HTTPS are allowed. Using HTTPS is strictly recommended because " 158 | "of security reasons." 159 | msgstr "" 160 | 161 | #: ../src/index.wsgi:32 162 | #, c-format 163 | msgid "The following releases are supported: %s" 164 | msgstr "" 165 | 166 | #. CONFIG["MaxParallelTasks"], active, CONFIG["MaxParallelTasks"])) 167 | #: ../src/index.wsgi:36 168 | #, c-format 169 | msgid "" 170 | "Your coredump is only kept on the server while the retrace job is running. " 171 | "Once the job is finished, the server keeps retrace log and backtrace. All " 172 | "the other data (including coredump) are deleted. The retrace log and " 173 | "backtrace are only accessible via unique task ID and password, thus no one " 174 | "(except the author) is allowed to view it. All the crash information " 175 | "(including backtrace) is deleted after %d hours of inactivity. No possibly " 176 | "private data are kept on the server any longer." 177 | msgstr "" 178 | 179 | #: ../src/index.wsgi:43 180 | msgid "" 181 | "Your coredump is only used for retrace purposes. Server administrators are " 182 | "not trying to get your private data from coredumps or backtraces. Using a " 183 | "secure communication channel (HTTPS) is strictly recommended. Server " 184 | "administrators are not responsible for the problems related to the usage of " 185 | "an insecure channel (such as HTTP)." 186 | msgstr "" 187 | 188 | #: ../src/index.wsgi:34 189 | #, c-format 190 | msgid "" 191 | "At the moment the server is loaded for %d%% (running %d out of %d jobs)." 192 | msgstr "" 193 | 194 | #: ../src/log.wsgi:38 195 | msgid "There is no log for the specified task" 196 | msgstr "" 197 | 198 | #: ../src/stats.wsgi:36 199 | msgid "Architecture" 200 | msgstr "" 201 | 202 | #: ../src/stats.wsgi:37 203 | msgid "Architectures" 204 | msgstr "" 205 | 206 | #: ../src/stats.wsgi:38 207 | msgid "Build-id" 208 | msgstr "" 209 | 210 | #: ../src/stats.wsgi:39 211 | msgid "Count" 212 | msgstr "" 213 | 214 | #: ../src/stats.wsgi:40 215 | msgid "Denied jobs" 216 | msgstr "" 217 | 218 | #: ../src/stats.wsgi:41 219 | msgid "Failed" 220 | msgstr "" 221 | 222 | #: ../src/stats.wsgi:42 223 | msgid "First retrace" 224 | msgstr "" 225 | 226 | #: ../src/stats.wsgi:43 227 | msgid "Global statistics" 228 | msgstr "" 229 | 230 | #: ../src/stats.wsgi:44 231 | msgid "Missing build-ids" 232 | msgstr "" 233 | 234 | #: ../src/stats.wsgi:45 235 | msgid "Name" 236 | msgstr "" 237 | 238 | #: ../src/stats.wsgi:46 239 | msgid "Release" 240 | msgstr "" 241 | 242 | #: ../src/stats.wsgi:47 243 | msgid "Releases" 244 | msgstr "" 245 | 246 | #: ../src/stats.wsgi:48 247 | msgid "Required packages" 248 | msgstr "" 249 | 250 | #: ../src/stats.wsgi:49 251 | msgid "Retraced packages" 252 | msgstr "" 253 | 254 | #: ../src/stats.wsgi:50 255 | msgid "Retrace Server statistics" 256 | msgstr "" 257 | 258 | #: ../src/stats.wsgi:51 259 | msgid "Shared object name" 260 | msgstr "" 261 | 262 | #: ../src/stats.wsgi:52 263 | msgid "Successful" 264 | msgstr "" 265 | 266 | #: ../src/stats.wsgi:53 267 | msgid "Total" 268 | msgstr "" 269 | 270 | #: ../src/stats.wsgi:54 271 | msgid "Versions" 272 | msgstr "" 273 | -------------------------------------------------------------------------------- /po/ia.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # Weblate , 2020. 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2020-05-27 21:44+0200\n" 10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 | "Last-Translator: Automatically generated\n" 12 | "Language-Team: none\n" 13 | "Language: ia\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | 18 | #: ../src/backtrace.wsgi:18 ../src/create.wsgi:55 ../src/log.wsgi:19 19 | #: ../src/status.wsgi:19 20 | msgid "You must use HTTPS" 21 | msgstr "" 22 | 23 | #: ../src/backtrace.wsgi:23 ../src/log.wsgi:24 ../src/status.wsgi:24 24 | msgid "Invalid URL" 25 | msgstr "" 26 | 27 | #: ../src/backtrace.wsgi:29 ../src/log.wsgi:29 ../src/status.wsgi:30 28 | msgid "There is no such task" 29 | msgstr "" 30 | 31 | #: ../src/backtrace.wsgi:34 ../src/log.wsgi:34 ../src/status.wsgi:35 32 | msgid "Invalid password" 33 | msgstr "" 34 | 35 | #: ../src/backtrace.wsgi:38 36 | msgid "There is no backtrace for the specified task" 37 | msgstr "" 38 | 39 | #: ../src/create.wsgi:60 ../src/create.wsgi:116 40 | msgid "Retrace server is fully loaded at the moment" 41 | msgstr "" 42 | 43 | #: ../src/create.wsgi:64 44 | msgid "You must use POST method" 45 | msgstr "" 46 | 47 | #: ../src/create.wsgi:68 48 | msgid "Specified archive format is not supported" 49 | msgstr "" 50 | 51 | #: ../src/create.wsgi:72 52 | msgid "You need to set Content-Length header properly" 53 | msgstr "" 54 | 55 | #: ../src/create.wsgi:76 56 | msgid "Specified archive is too large" 57 | msgstr "" 58 | 59 | #: ../src/create.wsgi:81 60 | msgid "X-CoreFileDirectory header has been disabled by server administrator" 61 | msgstr "" 62 | 63 | #: ../src/create.wsgi:91 64 | msgid "Unable to create working directory" 65 | msgstr "" 66 | 67 | #: ../src/create.wsgi:97 68 | msgid "Unable to obtain disk free space" 69 | msgstr "" 70 | 71 | #: ../src/create.wsgi:101 ../src/create.wsgi:172 72 | msgid "There is not enough storage space on the server" 73 | msgstr "" 74 | 75 | #: ../src/create.wsgi:109 76 | msgid "Unable to create new task" 77 | msgstr "" 78 | 79 | #: ../src/create.wsgi:121 80 | msgid "The directory specified in 'X-CoreFileDirectory' does not exist" 81 | msgstr "" 82 | 83 | #: ../src/create.wsgi:127 84 | #, c-format 85 | msgid "" 86 | "There are %d files in the '%s' directory. Only a single archive is supported " 87 | "at the moment" 88 | msgstr "" 89 | 90 | #: ../src/create.wsgi:136 91 | #, c-format 92 | msgid "You header specifies '%s' type, but the file type does not match" 93 | msgstr "" 94 | 95 | #: ../src/create.wsgi:154 96 | msgid "Unable to save archive" 97 | msgstr "" 98 | 99 | #: ../src/create.wsgi:162 100 | msgid "Unable to obtain unpacked size" 101 | msgstr "" 102 | 103 | #: ../src/create.wsgi:167 104 | msgid "Specified archive's content is too large" 105 | msgstr "" 106 | 107 | #: ../src/create.wsgi:184 108 | msgid "Unable to unpack archive" 109 | msgstr "" 110 | 111 | #: ../src/create.wsgi:197 112 | msgid "Symlinks are not allowed to be in the archive" 113 | msgstr "" 114 | 115 | #: ../src/create.wsgi:204 116 | #, c-format 117 | msgid "The '%s' file is larger than expected" 118 | msgstr "" 119 | 120 | #: ../src/create.wsgi:208 121 | #, c-format 122 | msgid "File '%s' is not allowed to be in the archive" 123 | msgstr "" 124 | 125 | #: ../src/create.wsgi:223 126 | msgid "Interactive tasks were disabled by server administrator" 127 | msgstr "" 128 | 129 | #: ../src/create.wsgi:232 130 | #, c-format 131 | msgid "Required file '%s' is missing" 132 | msgstr "" 133 | 134 | #: ../src/index.wsgi:20 135 | msgid "Retrace Server" 136 | msgstr "" 137 | 138 | #: ../src/index.wsgi:21 139 | msgid "Welcome to Retrace Server" 140 | msgstr "" 141 | 142 | #: ../src/index.wsgi:23 143 | msgid "" 144 | "Retrace Server is a service that provides the possibility to analyze " 145 | "coredump and generate backtrace over network. You can find further " 146 | "information at Retrace Server's github:" 147 | msgstr "" 148 | 149 | #: ../src/index.wsgi:29 150 | msgid "" 151 | "Only the secure HTTPS connection is now allowed by the server. HTTP requests " 152 | "will be denied." 153 | msgstr "" 154 | 155 | #: ../src/index.wsgi:31 156 | msgid "" 157 | "Both HTTP and HTTPS are allowed. Using HTTPS is strictly recommended because " 158 | "of security reasons." 159 | msgstr "" 160 | 161 | #: ../src/index.wsgi:32 162 | #, c-format 163 | msgid "The following releases are supported: %s" 164 | msgstr "" 165 | 166 | #. CONFIG["MaxParallelTasks"], active, CONFIG["MaxParallelTasks"])) 167 | #: ../src/index.wsgi:36 168 | #, c-format 169 | msgid "" 170 | "Your coredump is only kept on the server while the retrace job is running. " 171 | "Once the job is finished, the server keeps retrace log and backtrace. All " 172 | "the other data (including coredump) are deleted. The retrace log and " 173 | "backtrace are only accessible via unique task ID and password, thus no one " 174 | "(except the author) is allowed to view it. All the crash information " 175 | "(including backtrace) is deleted after %d hours of inactivity. No possibly " 176 | "private data are kept on the server any longer." 177 | msgstr "" 178 | 179 | #: ../src/index.wsgi:43 180 | msgid "" 181 | "Your coredump is only used for retrace purposes. Server administrators are " 182 | "not trying to get your private data from coredumps or backtraces. Using a " 183 | "secure communication channel (HTTPS) is strictly recommended. Server " 184 | "administrators are not responsible for the problems related to the usage of " 185 | "an insecure channel (such as HTTP)." 186 | msgstr "" 187 | 188 | #: ../src/index.wsgi:34 189 | #, c-format 190 | msgid "" 191 | "At the moment the server is loaded for %d%% (running %d out of %d jobs)." 192 | msgstr "" 193 | 194 | #: ../src/log.wsgi:38 195 | msgid "There is no log for the specified task" 196 | msgstr "" 197 | 198 | #: ../src/stats.wsgi:36 199 | msgid "Architecture" 200 | msgstr "" 201 | 202 | #: ../src/stats.wsgi:37 203 | msgid "Architectures" 204 | msgstr "" 205 | 206 | #: ../src/stats.wsgi:38 207 | msgid "Build-id" 208 | msgstr "" 209 | 210 | #: ../src/stats.wsgi:39 211 | msgid "Count" 212 | msgstr "" 213 | 214 | #: ../src/stats.wsgi:40 215 | msgid "Denied jobs" 216 | msgstr "" 217 | 218 | #: ../src/stats.wsgi:41 219 | msgid "Failed" 220 | msgstr "" 221 | 222 | #: ../src/stats.wsgi:42 223 | msgid "First retrace" 224 | msgstr "" 225 | 226 | #: ../src/stats.wsgi:43 227 | msgid "Global statistics" 228 | msgstr "" 229 | 230 | #: ../src/stats.wsgi:44 231 | msgid "Missing build-ids" 232 | msgstr "" 233 | 234 | #: ../src/stats.wsgi:45 235 | msgid "Name" 236 | msgstr "" 237 | 238 | #: ../src/stats.wsgi:46 239 | msgid "Release" 240 | msgstr "" 241 | 242 | #: ../src/stats.wsgi:47 243 | msgid "Releases" 244 | msgstr "" 245 | 246 | #: ../src/stats.wsgi:48 247 | msgid "Required packages" 248 | msgstr "" 249 | 250 | #: ../src/stats.wsgi:49 251 | msgid "Retraced packages" 252 | msgstr "" 253 | 254 | #: ../src/stats.wsgi:50 255 | msgid "Retrace Server statistics" 256 | msgstr "" 257 | 258 | #: ../src/stats.wsgi:51 259 | msgid "Shared object name" 260 | msgstr "" 261 | 262 | #: ../src/stats.wsgi:52 263 | msgid "Successful" 264 | msgstr "" 265 | 266 | #: ../src/stats.wsgi:53 267 | msgid "Total" 268 | msgstr "" 269 | 270 | #: ../src/stats.wsgi:54 271 | msgid "Versions" 272 | msgstr "" 273 | -------------------------------------------------------------------------------- /po/kk.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # Weblate , 2020. 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2020-05-27 21:44+0200\n" 10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 | "Last-Translator: Automatically generated\n" 12 | "Language-Team: none\n" 13 | "Language: kk\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | 18 | #: ../src/backtrace.wsgi:18 ../src/create.wsgi:55 ../src/log.wsgi:19 19 | #: ../src/status.wsgi:19 20 | msgid "You must use HTTPS" 21 | msgstr "" 22 | 23 | #: ../src/backtrace.wsgi:23 ../src/log.wsgi:24 ../src/status.wsgi:24 24 | msgid "Invalid URL" 25 | msgstr "" 26 | 27 | #: ../src/backtrace.wsgi:29 ../src/log.wsgi:29 ../src/status.wsgi:30 28 | msgid "There is no such task" 29 | msgstr "" 30 | 31 | #: ../src/backtrace.wsgi:34 ../src/log.wsgi:34 ../src/status.wsgi:35 32 | msgid "Invalid password" 33 | msgstr "" 34 | 35 | #: ../src/backtrace.wsgi:38 36 | msgid "There is no backtrace for the specified task" 37 | msgstr "" 38 | 39 | #: ../src/create.wsgi:60 ../src/create.wsgi:116 40 | msgid "Retrace server is fully loaded at the moment" 41 | msgstr "" 42 | 43 | #: ../src/create.wsgi:64 44 | msgid "You must use POST method" 45 | msgstr "" 46 | 47 | #: ../src/create.wsgi:68 48 | msgid "Specified archive format is not supported" 49 | msgstr "" 50 | 51 | #: ../src/create.wsgi:72 52 | msgid "You need to set Content-Length header properly" 53 | msgstr "" 54 | 55 | #: ../src/create.wsgi:76 56 | msgid "Specified archive is too large" 57 | msgstr "" 58 | 59 | #: ../src/create.wsgi:81 60 | msgid "X-CoreFileDirectory header has been disabled by server administrator" 61 | msgstr "" 62 | 63 | #: ../src/create.wsgi:91 64 | msgid "Unable to create working directory" 65 | msgstr "" 66 | 67 | #: ../src/create.wsgi:97 68 | msgid "Unable to obtain disk free space" 69 | msgstr "" 70 | 71 | #: ../src/create.wsgi:101 ../src/create.wsgi:172 72 | msgid "There is not enough storage space on the server" 73 | msgstr "" 74 | 75 | #: ../src/create.wsgi:109 76 | msgid "Unable to create new task" 77 | msgstr "" 78 | 79 | #: ../src/create.wsgi:121 80 | msgid "The directory specified in 'X-CoreFileDirectory' does not exist" 81 | msgstr "" 82 | 83 | #: ../src/create.wsgi:127 84 | #, c-format 85 | msgid "" 86 | "There are %d files in the '%s' directory. Only a single archive is supported " 87 | "at the moment" 88 | msgstr "" 89 | 90 | #: ../src/create.wsgi:136 91 | #, c-format 92 | msgid "You header specifies '%s' type, but the file type does not match" 93 | msgstr "" 94 | 95 | #: ../src/create.wsgi:154 96 | msgid "Unable to save archive" 97 | msgstr "" 98 | 99 | #: ../src/create.wsgi:162 100 | msgid "Unable to obtain unpacked size" 101 | msgstr "" 102 | 103 | #: ../src/create.wsgi:167 104 | msgid "Specified archive's content is too large" 105 | msgstr "" 106 | 107 | #: ../src/create.wsgi:184 108 | msgid "Unable to unpack archive" 109 | msgstr "" 110 | 111 | #: ../src/create.wsgi:197 112 | msgid "Symlinks are not allowed to be in the archive" 113 | msgstr "" 114 | 115 | #: ../src/create.wsgi:204 116 | #, c-format 117 | msgid "The '%s' file is larger than expected" 118 | msgstr "" 119 | 120 | #: ../src/create.wsgi:208 121 | #, c-format 122 | msgid "File '%s' is not allowed to be in the archive" 123 | msgstr "" 124 | 125 | #: ../src/create.wsgi:223 126 | msgid "Interactive tasks were disabled by server administrator" 127 | msgstr "" 128 | 129 | #: ../src/create.wsgi:232 130 | #, c-format 131 | msgid "Required file '%s' is missing" 132 | msgstr "" 133 | 134 | #: ../src/index.wsgi:20 135 | msgid "Retrace Server" 136 | msgstr "" 137 | 138 | #: ../src/index.wsgi:21 139 | msgid "Welcome to Retrace Server" 140 | msgstr "" 141 | 142 | #: ../src/index.wsgi:23 143 | msgid "" 144 | "Retrace Server is a service that provides the possibility to analyze " 145 | "coredump and generate backtrace over network. You can find further " 146 | "information at Retrace Server's github:" 147 | msgstr "" 148 | 149 | #: ../src/index.wsgi:29 150 | msgid "" 151 | "Only the secure HTTPS connection is now allowed by the server. HTTP requests " 152 | "will be denied." 153 | msgstr "" 154 | 155 | #: ../src/index.wsgi:31 156 | msgid "" 157 | "Both HTTP and HTTPS are allowed. Using HTTPS is strictly recommended because " 158 | "of security reasons." 159 | msgstr "" 160 | 161 | #: ../src/index.wsgi:32 162 | #, c-format 163 | msgid "The following releases are supported: %s" 164 | msgstr "" 165 | 166 | #. CONFIG["MaxParallelTasks"], active, CONFIG["MaxParallelTasks"])) 167 | #: ../src/index.wsgi:36 168 | #, c-format 169 | msgid "" 170 | "Your coredump is only kept on the server while the retrace job is running. " 171 | "Once the job is finished, the server keeps retrace log and backtrace. All " 172 | "the other data (including coredump) are deleted. The retrace log and " 173 | "backtrace are only accessible via unique task ID and password, thus no one " 174 | "(except the author) is allowed to view it. All the crash information " 175 | "(including backtrace) is deleted after %d hours of inactivity. No possibly " 176 | "private data are kept on the server any longer." 177 | msgstr "" 178 | 179 | #: ../src/index.wsgi:43 180 | msgid "" 181 | "Your coredump is only used for retrace purposes. Server administrators are " 182 | "not trying to get your private data from coredumps or backtraces. Using a " 183 | "secure communication channel (HTTPS) is strictly recommended. Server " 184 | "administrators are not responsible for the problems related to the usage of " 185 | "an insecure channel (such as HTTP)." 186 | msgstr "" 187 | 188 | #: ../src/index.wsgi:34 189 | #, c-format 190 | msgid "" 191 | "At the moment the server is loaded for %d%% (running %d out of %d jobs)." 192 | msgstr "" 193 | 194 | #: ../src/log.wsgi:38 195 | msgid "There is no log for the specified task" 196 | msgstr "" 197 | 198 | #: ../src/stats.wsgi:36 199 | msgid "Architecture" 200 | msgstr "" 201 | 202 | #: ../src/stats.wsgi:37 203 | msgid "Architectures" 204 | msgstr "" 205 | 206 | #: ../src/stats.wsgi:38 207 | msgid "Build-id" 208 | msgstr "" 209 | 210 | #: ../src/stats.wsgi:39 211 | msgid "Count" 212 | msgstr "" 213 | 214 | #: ../src/stats.wsgi:40 215 | msgid "Denied jobs" 216 | msgstr "" 217 | 218 | #: ../src/stats.wsgi:41 219 | msgid "Failed" 220 | msgstr "" 221 | 222 | #: ../src/stats.wsgi:42 223 | msgid "First retrace" 224 | msgstr "" 225 | 226 | #: ../src/stats.wsgi:43 227 | msgid "Global statistics" 228 | msgstr "" 229 | 230 | #: ../src/stats.wsgi:44 231 | msgid "Missing build-ids" 232 | msgstr "" 233 | 234 | #: ../src/stats.wsgi:45 235 | msgid "Name" 236 | msgstr "" 237 | 238 | #: ../src/stats.wsgi:46 239 | msgid "Release" 240 | msgstr "" 241 | 242 | #: ../src/stats.wsgi:47 243 | msgid "Releases" 244 | msgstr "" 245 | 246 | #: ../src/stats.wsgi:48 247 | msgid "Required packages" 248 | msgstr "" 249 | 250 | #: ../src/stats.wsgi:49 251 | msgid "Retraced packages" 252 | msgstr "" 253 | 254 | #: ../src/stats.wsgi:50 255 | msgid "Retrace Server statistics" 256 | msgstr "" 257 | 258 | #: ../src/stats.wsgi:51 259 | msgid "Shared object name" 260 | msgstr "" 261 | 262 | #: ../src/stats.wsgi:52 263 | msgid "Successful" 264 | msgstr "" 265 | 266 | #: ../src/stats.wsgi:53 267 | msgid "Total" 268 | msgstr "" 269 | 270 | #: ../src/stats.wsgi:54 271 | msgid "Versions" 272 | msgstr "" 273 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n.gettext(meson.project_name(), 2 | preset: 'glib', 3 | ) 4 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | # Add the source directory to PYTHONPATH so that we don't get invalid 3 | # "Unable to import" errors. 4 | # Snippet by alex_koval: https://stackoverflow.com/a/39207275 5 | init-hook="from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.join(os.path.dirname(find_pylintrc()), 'src'))" 6 | 7 | # Parallelize automatically over available CPUs. 8 | jobs=0 9 | 10 | [MESSAGES CONTROL] 11 | disable=missing-class-docstring, 12 | missing-function-docstring, 13 | missing-module-docstring, 14 | similarities, 15 | 16 | [REPORTS] 17 | reports=yes 18 | score=no 19 | 20 | [FORMAT] 21 | # Maximum number of characters on a single line. 22 | max-line-length=100 23 | # Maximum number of lines in a module. 24 | max-module-lines=1000 25 | # Disallow body of class and if statements to be on one line. 26 | single-line-class-stmt=no 27 | single-line-if-stmt=no 28 | 29 | [TYPECHECK] 30 | ignored-classes=retrace.config.Config 31 | 32 | [VARIABLES] 33 | # Do not check for unused imports in __init__ files. 34 | init-import=no 35 | # A regular expression matching the beginning of the name of dummy/unused variables. 36 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$) 37 | 38 | [BASIC] 39 | # Regular expression which for function and class names that do not require 40 | # a docstring. 41 | no-docstring-rgx=^_ 42 | # Good variable names which should always be accepted. 43 | good-names=a,db,e,ex,fd,i,j,k,n,r,to,v 44 | 45 | [CLASSES] 46 | # List of method names used to declare instance attributes. 47 | defining-attr-methods=__init__,__new__,setUp 48 | # List of members excluded from the protected access warnings. 49 | exclude-protected=_arch 50 | 51 | [DESIGN] 52 | min-public-methods=1 53 | 54 | [STRING] 55 | check-quote-consistency=yes 56 | 57 | [MISCELLANEOUS] 58 | notes=FIXME,XXX,TODO 59 | -------------------------------------------------------------------------------- /src/backtrace.wsgi: -------------------------------------------------------------------------------- 1 | from webob import Request 2 | 3 | from retrace.retrace import RetraceTask 4 | from retrace.config import Config 5 | from retrace.util import URL_PARSER, parse_http_gettext, response 6 | 7 | CONFIG = Config() 8 | 9 | def application(environ, start_response): 10 | request = Request(environ) 11 | 12 | _ = parse_http_gettext("%s" % request.accept_language, 13 | "%s" % request.accept_charset) 14 | 15 | if CONFIG["RequireHTTPS"] and request.scheme != "https": 16 | return response(start_response, "403 Forbidden", 17 | _("You must use HTTPS")) 18 | 19 | match = URL_PARSER.match(request.script_name) 20 | if not match: 21 | return response(start_response, "404 Not Found", 22 | _("Invalid URL")) 23 | 24 | try: 25 | task = RetraceTask(int(match.group(1))) 26 | except Exception: 27 | return response(start_response, "404 Not Found", 28 | _("There is no such task")) 29 | 30 | if "X-Task-Password" not in request.headers or \ 31 | not task.verify_password(request.headers["X-Task-Password"]): 32 | return response(start_response, "403 Forbidden", 33 | _("Invalid password")) 34 | 35 | if not task.has_backtrace(): 36 | return response(start_response, "404 Not Found", 37 | _("There is no backtrace for the specified task")) 38 | 39 | bt = task.get_backtrace() 40 | 41 | return response(start_response, "200 OK", bt) 42 | -------------------------------------------------------------------------------- /src/checkpackage.wsgi: -------------------------------------------------------------------------------- 1 | from webob import Request 2 | 3 | from retrace.retrace import is_package_known 4 | from retrace.util import (INPUT_ARCH_PARSER, 5 | INPUT_PACKAGE_PARSER, 6 | INPUT_RELEASEID_PARSER, 7 | parse_http_gettext, 8 | response) 9 | 10 | 11 | def application(environ, start_response): 12 | request = Request(environ) 13 | 14 | _ = parse_http_gettext("%s" % request.accept_language, 15 | "%s" % request.accept_charset) 16 | 17 | if "X-Package-NVR" not in request.headers: 18 | return response(start_response, "403 Forbidden", 19 | _("Required header 'X-Package-NVR' not found")) 20 | 21 | if not INPUT_PACKAGE_PARSER.match(request.headers["X-Package-NVR"]): 22 | return response(start_response, "403 Forbidden", 23 | _("Package NVR contains illegal characters")) 24 | 25 | if "X-Package-Arch" not in request.headers: 26 | return response(start_response, "403 Forbidden", 27 | _("Required header 'X-Package-Arch' not found")) 28 | 29 | if not INPUT_ARCH_PARSER.match(request.headers["X-Package-Arch"]): 30 | return response(start_response, "403 Forbidden", 31 | _("Architecture contains illegal characters")) 32 | 33 | if "X-OS-Release" not in request.headers: 34 | request.headers["X-OS-Release"] = None 35 | elif not INPUT_RELEASEID_PARSER.match(request.headers["X-OS-Release"]): 36 | return response(start_response, "403 Forbidden", 37 | _("OS release contains illegal characters")) 38 | 39 | if is_package_known(request.headers["X-Package-NVR"], 40 | request.headers["X-Package-Arch"], 41 | request.headers["X-OS-Release"]): 42 | return response(start_response, "302 Found") 43 | 44 | return response(start_response, "404 Not Found") 45 | -------------------------------------------------------------------------------- /src/config/hooks/debuginfo.conf: -------------------------------------------------------------------------------- 1 | # Parameters are replaced using python's format. 2 | # Available parameters for cmdline: hook_name, taskid, task_results_dir 3 | # Example: 4 | # cmdline = {hook_name} {taskid} {task_results_dir} 5 | # 6 | # Before the preparation of debuginfo packages 7 | # [pre_prepare_debuginfo] 8 | 9 | # After the preparation of debuginfo packages 10 | # [post_prepare_debuginfo] 11 | -------------------------------------------------------------------------------- /src/config/hooks/environment.conf: -------------------------------------------------------------------------------- 1 | # Parameters are replaced using python's format. 2 | # Available parameters for cmdline: hook_name, taskid, task_results_dir 3 | # Example: 4 | # cmdline = {hook_name} {taskid} {task_results_dir} 5 | # 6 | # Before the preparation of retrace environment 7 | # [pre_prepare_environment] 8 | 9 | # After the preparation of retrace environment 10 | # [post_prepare_environment] 11 | -------------------------------------------------------------------------------- /src/config/hooks/fail.conf: -------------------------------------------------------------------------------- 1 | # Parameters are replaced using python's format. 2 | # Available parameters for cmdline: hook_name, taskid, task_results_dir 3 | # Example: 4 | # cmdline = {hook_name} {taskid} {task_results_dir} 5 | # 6 | # After retracing fails 7 | # [fail] 8 | -------------------------------------------------------------------------------- /src/config/hooks/retrace.conf: -------------------------------------------------------------------------------- 1 | # Parameters are replaced using python's format. 2 | # Available parameters for cmdline: hook_name, taskid, task_results_dir 3 | # Example: 4 | # cmdline = {hook_name} {taskid} {task_results_dir} 5 | # 6 | # Before starting of the retracing itself 7 | # [pre_retrace] 8 | 9 | # After retracing is done 10 | # [post_retrace] 11 | -------------------------------------------------------------------------------- /src/config/hooks/start.conf: -------------------------------------------------------------------------------- 1 | # Parameters are replaced using python's format. 2 | # Available parameters for cmdline: hook_name, taskid, task_results_dir 3 | # Example: 4 | # cmdline = {hook_name} {taskid} {task_results_dir} 5 | # 6 | # When worker.start() is called 7 | # [pre_start] 8 | 9 | # When task type is determined and the main task starts 10 | # [start] 11 | -------------------------------------------------------------------------------- /src/config/hooks/success.conf: -------------------------------------------------------------------------------- 1 | # Parameters are replaced using python's format. 2 | # Available parameters for cmdline: hook_name, taskid, task_results_dir 3 | # Example: 4 | # cmdline = {hook_name} {taskid} {task_results_dir} 5 | # 6 | # After retracing success 7 | # [success] 8 | -------------------------------------------------------------------------------- /src/config/hooks/task.conf: -------------------------------------------------------------------------------- 1 | # Parameters are replaced using python's format. 2 | # Available parameters for cmdline: hook_name, taskid, task_results_dir 3 | # Example: 4 | # cmdline = {hook_name} {taskid} {task_results_dir} 5 | # 6 | # Before removing task 7 | # [pre_remove_task] 8 | 9 | # After removing task 10 | # [post_remove_task] 11 | 12 | # Before cleaning task 13 | # [pre_clean_task] 14 | 15 | # After cleaning task 16 | # [post_clean_task] 17 | -------------------------------------------------------------------------------- /src/config/retrace-server: -------------------------------------------------------------------------------- 1 | /var/log/retrace-server/*.log { 2 | missingok 3 | notifempty 4 | weekly 5 | } 6 | -------------------------------------------------------------------------------- /src/config/retrace-server-hooks.conf: -------------------------------------------------------------------------------- 1 | [main] 2 | # Path to the executable hook scripts 3 | # HookDir = /usr/libexec/retrace-server/hooks/ 4 | # 5 | # Global time limit for hook scripts (in seconds) 6 | # Timeout = 300 7 | -------------------------------------------------------------------------------- /src/config/retrace-server-httpd.conf: -------------------------------------------------------------------------------- 1 | WSGISocketPrefix /var/run/retrace 2 | WSGIDaemonProcess retrace user=retrace group=retrace processes=5 threads=3 lang=en_US.UTF-8 locale=en_US.UTF-8 3 | 4 | WSGIScriptAliasMatch ^/manager(/.*)?$ /usr/share/retrace-server/manager.wsgi 5 | WSGIScriptAliasMatch ^/ftp(/.*)?$ /usr/share/retrace-server/ftp.wsgi 6 | WSGIScriptAliasMatch ^/settings$ /usr/share/retrace-server/settings.wsgi 7 | WSGIScriptAliasMatch ^/create$ /usr/share/retrace-server/create.wsgi 8 | WSGIScriptAliasMatch ^/metrics$ /usr/share/retrace-server/metrics.wsgi 9 | WSGIScriptAliasMatch ^/stats$ /usr/share/retrace-server/stats.wsgi 10 | WSGIScriptAliasMatch ^/checkpackage$ /usr/share/retrace-server/checkpackage.wsgi 11 | WSGIScriptAliasMatch ^/[0-9]+/?$ /usr/share/retrace-server/status.wsgi 12 | WSGIScriptAliasMatch ^/[0-9]+/delete$ /usr/share/retrace-server/delete.wsgi 13 | WSGIScriptAliasMatch ^/[0-9]+/log$ /usr/share/retrace-server/log.wsgi 14 | WSGIScriptAliasMatch ^/[0-9]+/backtrace$ /usr/share/retrace-server/backtrace.wsgi 15 | WSGIScriptAliasMatch ^/[0-9]+/exploitable$ /usr/share/retrace-server/exploitable.wsgi 16 | WSGIScriptAliasMatch ^/[0-9]+/start$ /usr/share/retrace-server/start.wsgi 17 | WSGIScriptAliasMatch ^/$ /usr/share/retrace-server/index.wsgi 18 | 19 | 20 | Options Indexes FollowSymLinks 21 | AllowOverride None 22 | 23 | # Apache 2.4 24 | Require all granted 25 | 26 | 27 | # Apache 2.2 28 | Order allow,deny 29 | Allow from all 30 | 31 | 32 | 33 | 34 | WSGIProcessGroup retrace 35 | WSGIApplicationGroup %{GLOBAL} 36 | Options -Indexes -FollowSymLinks 37 | 38 | # Apache 2.4 39 | Require all granted 40 | 41 | 42 | # Apache 2.2 43 | Order allow,deny 44 | Allow from all 45 | 46 | 47 | 48 | Alias /repos /var/cache/retrace-server 49 | -------------------------------------------------------------------------------- /src/config/retrace-server.conf: -------------------------------------------------------------------------------- 1 | [retrace] 2 | # Which group is used for authentication 3 | # Do not change AuthGroup if you really don't need to! 4 | # When using non-default group "foo", you also need to 5 | # 1) Set group=foo in WSGIDaemonProcess in /etc/httpd/conf.d/retrace-server-httpd.conf 6 | # 2) Make LogDir, SaveDir and RepoDir readable and writable for foo 7 | # 3) Execute all retrace-server-* scripts (including cron jobs!) with foo membership 8 | AuthGroup = retrace 9 | 10 | # Force to use HTTPS - only disable on trusted network 11 | RequireHTTPS = 1 12 | 13 | # Allow to delete task data via HTTP API (https://server//delete) 14 | AllowAPIDelete = 0 15 | 16 | # Allow interactive tasks (security risk, do not use on public systems) 17 | AllowInteractive = 0 18 | 19 | # Allow X-CoreFileDirectory header 20 | AllowExternalDir = 0 21 | 22 | # Expose metrics for monitoring via Prometheus 23 | AllowMetrics = 0 24 | 25 | # Allow to create tasks owned by task manager (security risk) 26 | AllowTaskManager = 0 27 | 28 | # Allow to create VMCore tasks in the task manager 29 | AllowVMCoreTask = 1 30 | 31 | # Allow to create Userspace core tasks in the task manager 32 | AllowUsrCoreTask = 1 33 | 34 | # If white list is disabled, anyone can delete tasks 35 | TaskManagerAuthDelete = 0 36 | 37 | # Whitespace-separated list of users allowed to delete tasks 38 | TaskManagerDeleteUsers = 39 | 40 | # If set to non-empty string, makes the case number clickable in task manager 41 | # The string is expanded by python, with the case number passed 42 | # as the only argument, do not forget %d 43 | CaseNumberURL = 44 | 45 | # Verify GPG signatures of installed packages 46 | RequireGPGCheck = 1 47 | 48 | # Maximum tasks running at one moment 49 | MaxParallelTasks = 5 50 | 51 | # Maximum size of archive uploaded by user (MB) 52 | MaxPackedSize = 50 53 | 54 | # Maximum size of archive contents (MB) 55 | MaxUnpackedSize = 1024 56 | 57 | # Minimal storage left on WorkDir FS after unpacking archive (MB) 58 | MinStorageLeft = 1024 59 | 60 | # Delete old tasks after (hours); <= 0 means never 61 | # This is mutually exclusive with ArchiveTasksAfter (see below) 62 | # The one that occurs first removes the task from the system 63 | # In case DeleteTaskAfter = ArchiveTaskAfter, archiving executes first 64 | DeleteTaskAfter = 0 65 | 66 | # Delete old failed tasks after (hours); <= 0 means never 67 | # This is useful for cleanup of failed tasks before the standard 68 | # mechanisms do (DeleteTaskAfter or ArchiveTaskAfter) 69 | # In case DeleteFailedTaskAfter > DeleteTaskAfter 70 | # or DeleteFailedTaskAfter > ArchiveTaskAfter, this option does nothing 71 | DeleteFailedTaskAfter = 0 72 | 73 | # Archive old task after (hours); <= 0 means never 74 | # This is mutually exclusive with DeleteTasksAfter (see above) 75 | # The one that occurs first removes the task from the system 76 | # In case DeleteTaskAfter = ArchiveTaskAfter, archiving executes first 77 | ArchiveTaskAfter = 0 78 | 79 | # SQLite statistics DB filename 80 | DBFile = stats.db 81 | 82 | # Log directory 83 | LogDir = /var/log/retrace-server 84 | 85 | # Local repos directory 86 | # if changed, you also need to update httpd config 87 | RepoDir = /var/cache/retrace-server 88 | 89 | # Directory where the crashes and results are saved 90 | SaveDir = /var/spool/retrace-server 91 | 92 | # Directory where old tasks are moved 93 | DropDir = /srv/retrace/archive 94 | 95 | # Whether to use createrepo's --update option (faster, but requires a lot of memory) 96 | UseCreaterepoUpdate = False 97 | 98 | # How many latest packages to keep for rawhide 99 | KeepRawhideLatest = 3 100 | 101 | # Repo used to install chroot for vmcores 102 | KernelChrootRepo = http://dl.fedoraproject.org/pub/fedora/linux/releases/16/Everything/$ARCH/os/ 103 | 104 | # Koji directory structure can be used to search for kernel debuginfo 105 | KojiRoot = /mnt/koji 106 | 107 | # Whether task manager should look to an external FTP for task data 108 | UseFTPTasks = 0 109 | 110 | # FTP connection parameters 111 | FTPSSL = 0 112 | FTPHost = ftp.example.com 113 | FTPUser = user 114 | FTPPass = password 115 | FTPDir = / 116 | 117 | # Size of buffer for downloading from FTP (MB) 118 | FTPBufferSize = 16 119 | 120 | # Whether to use wget as a fallback to finding kernel debuginfos 121 | WgetKernelDebuginfos = 0 122 | 123 | # Where to download kernel debuginfos from 124 | # $VERSION $RELEASE and $ARCH are replaced by the appropriate value 125 | # kernel-debuginfo-VRA.rpm is appended to the end 126 | KernelDebuginfoURL = http://kojipkgs.fedoraproject.org/packages/$BASENAME/$VERSION/$RELEASE/$ARCH/ 127 | 128 | # Run makedumpfile with specified dumplevel; <= 0 or >= 32 means disabled 129 | VmcoreDumpLevel = 0 130 | 131 | # Percentage of space a stripped vmcore has to save for us to replace 132 | # the old vmcore with the new vmcore created by makedumpfile 133 | VmcoreDumpSavePercent = 10 134 | 135 | # EXPERIMENTAL! Use ABRT Server's storage to map build-ids 136 | # into debuginfo packages and resolve dependencies 137 | # Requires support from ABRT Server 138 | UseFafPackages = 0 139 | 140 | # Spool directory for FAF packages 141 | FafLinkDir = /var/spool/faf 142 | 143 | # Run the retrace in a Mock chroot (default), a Podman container, 144 | # or on the native machine. 145 | # (mock|podman|native) 146 | RetraceEnvironment = mock 147 | 148 | # Whether to enable e-mail notifications 149 | EmailNotify = 0 150 | 151 | # Who sends the e-mail notifications 152 | EmailNotifyFrom = retrace@localhost 153 | 154 | # Calculate md5sum for remote resources - changeable on manager page 155 | CalculateMd5 = 0 156 | 157 | # URL of Bugzilla 158 | BugzillaURL = https://bugzilla.redhat.com 159 | # Custom path to the file with Bugzilla credentials, stored in format: 160 | # [bugzilla.yoursite.com] 161 | # user = 162 | # password = 163 | # If not set checks for credentials in: 164 | # ~/.config/python-bugzilla/bugzillarc, ~/.bugzillarc, /etc/bugzillarc 165 | BugzillaCredentials = 166 | # Clean up tasks with assigned bugzilla bugs in following states 167 | # NEW, ASSIGNED, ON_DEV, POST, MODIFIED, ON_QA, VERIFIED, RELEASE_PENDING, CLOSED 168 | BugzillaStatus = VERIFIED, RELEASE_PENDING, CLOSED 169 | # Search query options for bugzilla bugs 170 | BugzillaProduct = Red Hat Enterprise Linux 7 171 | BugzillaComponent = kernel 172 | # Number and order of values in TriggerWords and RegExes should be identical 173 | # Trigger words to look for in the text of bugzilla bugs 174 | BugzillaTriggerWords = retrace-server-interact, retrace/tasks 175 | # Regular expressions used to get task numbers from the text of bugzilla bugs 176 | BugzillaRegExes = retrace-server-interact\\s+([0-9]{9}), /var/spool/retrace-server/([0-9]{9})/crash/vmcore 177 | # Used to limit the bugzilla query to bugs changed to a number of hours ago 178 | # Should be set to a number of hours that is greater than or equal to 179 | # the frequency of the cronjob that runs retrace-server-bugzilla-query. 180 | # Default: 168 hours (7 days) 181 | BugzillaQueryLastChangeDelta = 168 182 | 183 | # Timeout (in seconds) for communication with any process 184 | # Default: 12 hours 185 | ProcessCommunicateTimeout = 43200 186 | 187 | # Path to the kernel (vmcore) debugger 188 | KernelDebuggerPath = /usr/bin/crash 189 | 190 | [archhosts] 191 | i386 = 192 | x86_64 = 193 | ppc64 = 194 | armhfp = 195 | s390x = 196 | -------------------------------------------------------------------------------- /src/delete.wsgi: -------------------------------------------------------------------------------- 1 | from webob import Request 2 | 3 | from retrace.retrace import RetraceTask 4 | from retrace.config import Config 5 | from retrace.util import URL_PARSER, parse_http_gettext, response 6 | 7 | CONFIG = Config() 8 | 9 | def application(environ, start_response): 10 | request = Request(environ) 11 | 12 | _ = parse_http_gettext("%s" % request.accept_language, 13 | "%s" % request.accept_charset) 14 | 15 | if CONFIG["RequireHTTPS"] and request.scheme != "https": 16 | return response(start_response, "403 Forbidden", 17 | _("You must use HTTPS")) 18 | 19 | match = URL_PARSER.match(request.script_name) 20 | if not match: 21 | return response(start_response, "404 Not Found", 22 | _("Invalid URL")) 23 | 24 | if not CONFIG["AllowAPIDelete"]: 25 | return response(start_response, "403 Forbidden", 26 | _("Manual deleting is disabled")) 27 | 28 | try: 29 | task = RetraceTask(int(match.group(1))) 30 | except Exception: 31 | return response(start_response, "404 Not Found", 32 | _("There is no such task")) 33 | 34 | if "X-Task-Password" not in request.headers or \ 35 | not task.verify_password(request.headers["X-Task-Password"]): 36 | return response(start_response, "403 Forbidden", 37 | _("Invalid password")) 38 | 39 | try: 40 | task.remove() 41 | except Exception: 42 | return response(start_response, "500 Internal Server Error", 43 | _("An error occurred while deleting task data")) 44 | 45 | return response(start_response, "200 OK", 46 | _("All task data were deleted successfully")) 47 | -------------------------------------------------------------------------------- /src/exploitable.wsgi: -------------------------------------------------------------------------------- 1 | from webob import Request 2 | 3 | from retrace.retrace import RetraceTask 4 | from retrace.config import Config 5 | from retrace.util import URL_PARSER, parse_http_gettext, response 6 | 7 | CONFIG = Config() 8 | 9 | def application(environ, start_response): 10 | request = Request(environ) 11 | 12 | _ = parse_http_gettext("%s" % request.accept_language, 13 | "%s" % request.accept_charset) 14 | 15 | if CONFIG["RequireHTTPS"] and request.scheme != "https": 16 | return response(start_response, "403 Forbidden", 17 | _("You must use HTTPS")) 18 | 19 | match = URL_PARSER.match(request.script_name) 20 | if not match: 21 | return response(start_response, "404 Not Found", 22 | _("Invalid URL")) 23 | try: 24 | task = RetraceTask(int(match.group(1))) 25 | except Exception: 26 | return response(start_response, "404 Not Found", 27 | _("There is no such task")) 28 | 29 | if "X-Task-Password" not in request.headers or \ 30 | not task.verify_password(request.headers["X-Task-Password"]): 31 | return response(start_response, "403 Forbidden", 32 | _("Invalid password")) 33 | 34 | if not task.has_results("exploitable"): 35 | return response(start_response, "404 Not Found", 36 | _("There is no exploitability data for the specified task")) 37 | 38 | result = task.get_results("exploitable") 39 | 40 | return response(start_response, "200 OK", result) 41 | -------------------------------------------------------------------------------- /src/ftp.wsgi: -------------------------------------------------------------------------------- 1 | import fnmatch 2 | import re 3 | import urllib 4 | from webob import Request 5 | 6 | from retrace.config import Config 7 | from retrace.util import ftp_list_dir, parse_http_gettext, response 8 | 9 | CONFIG = Config() 10 | 11 | MANAGER_URL_PARSER = re.compile(r"^(.*/manager)(/(([^/]+)(/(__custom__|start|backtrace|savenotes|" 12 | r"caseno|notify|delete(/(sure/?)?)?|results/([^/]+)/?)?)?)?)?$") 13 | tableheader = """ 14 | 15 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | """ 36 | tablefooter = """ 37 |
FTP files
38 | 39 | """ 40 | 41 | def async_ftp_list_dir(filterexp): 42 | available = [] 43 | rawtasklist = ftp_list_dir(CONFIG["FTPDir"]) 44 | 45 | if filterexp: 46 | tasklist = sorted(fnmatch.filter(rawtasklist, filterexp)) 47 | else: 48 | tasklist = sorted(rawtasklist) 49 | 50 | for fname in tasklist: 51 | available.append("%s" \ 52 | % (urllib.parse.quote(fname), fname)) 53 | 54 | available.append(tablefooter) 55 | return "\n ".join(available) 56 | 57 | def application(environ, start_response): 58 | request = Request(environ) 59 | 60 | _ = parse_http_gettext("%s" % request.accept_language, 61 | "%s" % request.accept_charset) 62 | 63 | get = urllib.parse.parse_qs(request.query_string) 64 | filterexp = None 65 | if "filterexp" in get: 66 | filterexp = get["filterexp"][0] 67 | 68 | output = "" 69 | output += "

%s

" % "Back to task manager" 70 | output += tableheader 71 | output += async_ftp_list_dir(filterexp) 72 | output += tablefooter 73 | 74 | return response(start_response, "200 OK", output, [("Content-Type", "text/html")]) 75 | -------------------------------------------------------------------------------- /src/index.wsgi: -------------------------------------------------------------------------------- 1 | from webob import Request 2 | 3 | from retrace.retrace import (get_active_tasks, 4 | get_supported_releases) 5 | from retrace.config import Config 6 | from retrace.util import parse_http_gettext, response 7 | 8 | CONFIG = Config() 9 | 10 | def application(environ, start_response): 11 | request = Request(environ) 12 | 13 | _ = parse_http_gettext("%s" % request.accept_language, 14 | "%s" % request.accept_charset) 15 | 16 | with open("/usr/share/retrace-server/index.xhtml") as file: 17 | # Read 1 MiB 18 | output = file.read(1 << 20) 19 | 20 | title = _("Retrace Server") 21 | welcome = _("Welcome to Retrace Server") 22 | host = environ["HTTP_HOST"] 23 | about = "%s %s" % (_("Retrace Server is a service that provides the possibility to analyze " 24 | "coredump and generate backtrace over network. " 25 | "You can find further information at Retrace Server's github:"), 26 | "" 27 | "https://github.com/abrt/retrace-server") 28 | if CONFIG["RequireHTTPS"]: 29 | https = _("Only the secure HTTPS connection is now allowed by the server. HTTP requests will be denied.") 30 | else: 31 | https = _("Both HTTP and HTTPS are allowed. Using HTTPS is strictly recommended because of security reasons.") 32 | releases = _("The following releases are supported: %s" % ", ".join(sorted(get_supported_releases()))) 33 | active = len(get_active_tasks()) 34 | running = _("At the moment the server is loaded for %d%% (running %d out of %d jobs)." 35 | % (100 * active // CONFIG["MaxParallelTasks"], active, CONFIG["MaxParallelTasks"])) 36 | disclaimer1 = _("Your coredump is only kept on the server while the retrace job is running. " 37 | "Once the job is finished, the server keeps retrace log and backtrace. " 38 | "All the other data (including coredump) are deleted. " 39 | "The retrace log and backtrace are only accessible via unique task ID and password, thus no one " 40 | "(except the author) is allowed to view it. " 41 | "All the crash information (including backtrace) is deleted after %d hours of inactivity. " 42 | "No possibly private data are kept on the server any longer." % CONFIG["DeleteTaskAfter"]) 43 | disclaimer2 = _("Your coredump is only used for retrace purposes. " 44 | "Server administrators are not trying to get your private data from coredumps or backtraces. " 45 | "Using a secure communication channel (HTTPS) is strictly recommended. " 46 | "Server administrators are not responsible for the problems related to the usage of " 47 | "an insecure channel (such as HTTP).") 48 | 49 | output = output.replace("{title}", title) 50 | output = output.replace("{welcome}", welcome) 51 | output = output.replace("{host}", host) 52 | output = output.replace("{about}", about) 53 | output = output.replace("{https}", https) 54 | output = output.replace("{releases}", releases) 55 | output = output.replace("{running}", running) 56 | output = output.replace("{disclaimer1}", disclaimer1) 57 | output = output.replace("{disclaimer2}", disclaimer2) 58 | 59 | return response(start_response, "200 OK", output, [("Content-Type", "text/html")]) 60 | -------------------------------------------------------------------------------- /src/index.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {title} 7 | 39 | 40 | 41 |

{welcome}

42 |

{host}

43 |
44 |

{about}

45 |

{https}

46 |

{running}

47 |

{releases}

48 |
49 |
50 |

{disclaimer1}

51 |

{disclaimer2}

52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /src/log.wsgi: -------------------------------------------------------------------------------- 1 | from webob import Request 2 | from retrace.retrace import RetraceTask 3 | from retrace.config import Config 4 | from retrace.util import URL_PARSER, parse_http_gettext, response 5 | 6 | CONFIG = Config() 7 | 8 | 9 | def application(environ, start_response): 10 | request = Request(environ) 11 | 12 | _ = parse_http_gettext("%s" % request.accept_language, 13 | "%s" % request.accept_charset) 14 | 15 | if CONFIG["RequireHTTPS"] and request.scheme != "https": 16 | return response(start_response, "403 Forbidden", 17 | _("You must use HTTPS")) 18 | 19 | match = URL_PARSER.match(request.script_name) 20 | if not match: 21 | return response(start_response, "404 Not Found", 22 | _("Invalid URL")) 23 | try: 24 | task = RetraceTask(int(match.group(1))) 25 | except Exception: 26 | return response(start_response, "404 Not Found", 27 | _("There is no such task")) 28 | 29 | if "X-Task-Password" not in request.headers or \ 30 | not task.verify_password(request.headers["X-Task-Password"]): 31 | return response(start_response, "403 Forbidden", 32 | _("Invalid password")) 33 | 34 | if not task.has_log(): 35 | return response(start_response, "404 Not Found", 36 | _("There is no log for the specified task")) 37 | 38 | log = task.get_log() 39 | 40 | return response(start_response, "200 OK", log) 41 | -------------------------------------------------------------------------------- /src/manager.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {title} 7 | 214 | 215 | 216 | 217 | {ftp_box} 218 | {vmcore_task_form} 219 | {usrcore_task_form} 220 |
221 |
222 | {available_table} 223 | {running_table} 224 | {finished_table} 225 |
226 |
227 | 228 | 229 | -------------------------------------------------------------------------------- /src/manager_usrcore_task_form.xhtml: -------------------------------------------------------------------------------- 1 |
2 |

Create coredump task (userspace memory dump)

3 |
4 |
5 | Package: 6 | 7 | (e.g. coreutils-8.22-11.el7) 8 |
9 | 10 |
11 | Executable: 12 | 13 | (e.g. /usr/bin/sleep) 14 |
15 | 16 |
17 | OS Release: 18 | 19 | (e.g. Red Hat Enterprise Linux Workstation release 7.0 (Maipo)) 20 |
21 | 22 |

All of these three fields override contents of package, executable and os_release files contained in the coredump archive.
23 | If you're uploading you have just the bare (compressed) coredump, you must specify these.

24 |
25 | Be more verbose in case of error
26 |
27 |
28 | Calculate md5 checksum for all downloaded resources 29 |
30 |
31 | Custom core location: 32 | (Any URL that wget can download or a local path, e.g. file:///foo/bar or just /foo/bar) 33 | 34 | 35 |
36 | 37 | 38 |
39 |
40 | -------------------------------------------------------------------------------- /src/manager_vmcore_task_form.xhtml: -------------------------------------------------------------------------------- 1 |
2 |

Create vmcore task (kernel memory dump)

3 |
4 | Kernel version: 5 | 6 | (e.g. 2.6.32-287.el6.x86_64, empty to autodetect) 7 | 8 |
9 | 10 | Include memory file for vmcore snapshot 11 |
12 | 13 |
14 | 15 | Be more verbose in case of error 16 |
17 | 18 |
19 | 20 | Calculate md5 checksum for all downloaded resources 21 |
22 | 23 |
24 | Custom core location: 25 | (Any URL that wget can download or a local path, e.g. file:///foo/bar or just /foo/bar) 26 | 27 | 28 |
29 | 30 |
31 | Custom memory location: 32 | (Any URL that wget can download or a local path, e.g. file:///foo/bar or just /foo/bar) 33 | 34 |
35 | 36 |
37 | 38 | 43 |
44 | -------------------------------------------------------------------------------- /src/managertask.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {title} 7 | 93 | 94 | 95 |

{taskno}

96 | {notes} 97 | 98 | {back} 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | {starttime} 108 | {finishtime} 109 | {downloaded} 110 | {md5sum} 111 | {results} 112 | {notify} 113 | {caseno} 114 | {bugzillano} 115 | {unknownext} 116 | {interactive} 117 | {start} 118 | {backtrace} 119 | {delete} 120 | {delete_yesno} 121 | {restart} 122 |
{str_type}{type}
{str_status}{status}
123 | {backtracewindow} 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | subdir('plugins') 2 | subdir('retrace') 3 | 4 | scripts = [ 5 | 'coredump2packages', 6 | 'retrace-server-cleanup', 7 | 'retrace-server-reposync', 8 | 'retrace-server-reposync-faf', 9 | 'retrace-server-worker', 10 | 'retrace-server-interact', 11 | 'retrace-server-plugin-checker', 12 | 'retrace-server-task', 13 | 'retrace-server-bugzilla-refresh', 14 | 'retrace-server-bugzilla-query', 15 | ] 16 | 17 | foreach file: scripts 18 | configure_file( 19 | copy: true, 20 | input: file, 21 | output: file, 22 | ) 23 | endforeach 24 | install_data(scripts, 25 | install_dir: bindir, 26 | ) 27 | 28 | install_data([ 29 | 'backtrace.wsgi', 30 | 'create.wsgi', 31 | 'checkpackage.wsgi', 32 | 'delete.wsgi', 33 | 'exploitable.wsgi', 34 | 'index.wsgi', 35 | 'log.wsgi', 36 | 'manager.wsgi', 37 | 'metrics.wsgi', 38 | 'ftp.wsgi', 39 | 'settings.wsgi', 40 | 'start.wsgi', 41 | 'stats.wsgi', 42 | 'status.wsgi', 43 | 'index.xhtml', 44 | 'manager.xhtml', 45 | 'manager_vmcore_task_form.xhtml', 46 | 'manager_usrcore_task_form.xhtml', 47 | 'managertask.xhtml', 48 | 'stats.xhtml', 49 | ], 50 | install_dir: join_paths(datadir, meson.project_name()) 51 | ) 52 | 53 | install_data([ 54 | 'config/retrace-server.conf', 55 | 'config/retrace-server-hooks.conf', 56 | ], 57 | install_dir: join_paths(sysconfdir, meson.project_name()) 58 | ) 59 | install_data([ 60 | 'config/hooks/debuginfo.conf', 61 | 'config/hooks/fail.conf', 62 | 'config/hooks/environment.conf', 63 | 'config/hooks/retrace.conf', 64 | 'config/hooks/start.conf', 65 | 'config/hooks/success.conf', 66 | 'config/hooks/task.conf', 67 | ], 68 | install_dir: join_paths(sysconfdir, meson.project_name(), 'hooks') 69 | ) 70 | install_data('config/retrace-server-httpd.conf', 71 | install_dir: join_paths(sysconfdir, 'httpd', 'conf.d') 72 | ) 73 | install_data('config/retrace-server', 74 | install_dir: join_paths(sysconfdir, 'logrotate.d') 75 | ) 76 | -------------------------------------------------------------------------------- /src/metrics.wsgi: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from typing import Dict 3 | 4 | from webob import Request 5 | 6 | from retrace.config import Config 7 | from retrace.retrace import get_running_tasks, STATUS_SUCCESS, STATUS_FAIL 8 | from retrace.stats import init_crashstats_db 9 | from retrace.util import free_space, parse_http_gettext, response 10 | 11 | CONFIG = Config() 12 | 13 | StatsDict = Dict[str, int] 14 | 15 | RESPONSE_TEMPLATE = """ 16 | # HELP retrace_savedir_free_bytes Free disk space on volume with tasks 17 | # TYPE retrace_savedir_free_bytes gauge 18 | retrace_savedir_free_bytes {savedir_free_bytes} 19 | # HELP retrace_tasks_running Number of retrace workers currently running 20 | # TYPE retrace_tasks_running gauge 21 | retrace_tasks_running {tasks_running} 22 | # HELP retrace_tasks_overload Number of retrace jobs denied because of exceeded capacity 23 | # TYPE retrace_tasks_overload counter 24 | retrace_tasks_overload {tasks_overload} 25 | # HELP Total number of retrace tasks finished 26 | # TYPE retrace_tasks_finished counter 27 | retrace_tasks_finished{{result="fail"}} {tasks_failed} 28 | retrace_tasks_finished{{result="success"}} {tasks_successful} 29 | """ 30 | 31 | 32 | def get_num_tasks_failed(db: sqlite3.Connection) -> int: 33 | cursor = db.cursor() 34 | 35 | result = cursor.execute("SELECT COUNT(*) FROM tasks WHERE status = ?", 36 | (STATUS_FAIL,)).fetchone() 37 | 38 | return result[0] 39 | 40 | 41 | def get_num_tasks_successful(db: sqlite3.Connection) -> int: 42 | cursor = db.cursor() 43 | 44 | result = cursor.execute("SELECT COUNT(*) FROM tasks WHERE status = ?", 45 | (STATUS_SUCCESS,)).fetchone() 46 | 47 | return result[0] 48 | 49 | 50 | def get_num_tasks_overload(db: sqlite3.Connection) -> int: 51 | cursor = db.cursor() 52 | 53 | result = cursor.execute("SELECT COUNT(*) FROM reportfull").fetchone() 54 | 55 | return result[0] 56 | 57 | 58 | def get_stats(db: sqlite3.Connection) -> StatsDict: 59 | cursor = db.cursor() 60 | 61 | # Calculate free space left on volume where crashes and tasks are stored. 62 | savedir_free_bytes = free_space(CONFIG["SaveDir"]) 63 | 64 | # Number of tasks (worker processes) currently running. 65 | tasks_running = len(get_running_tasks()) 66 | 67 | tasks_successful = get_num_tasks_successful(db) 68 | tasks_failed = get_num_tasks_failed(db) 69 | 70 | # Number of tasks denied because of server capacity overload, also called 71 | # denied tasks. See also 'MaxParallelTasks' option in retrace-server.conf. 72 | tasks_overload = get_num_tasks_overload(db) 73 | 74 | stats = { 75 | "savedir_free_bytes": savedir_free_bytes, 76 | "tasks_failed": tasks_failed, 77 | "tasks_overload": tasks_overload, 78 | "tasks_running": tasks_running, 79 | "tasks_successful": tasks_successful, 80 | } 81 | 82 | return stats 83 | 84 | 85 | def application(environ, start_response): 86 | request = Request(environ) 87 | 88 | _ = parse_http_gettext("%s" % request.accept_language, 89 | "%s" % request.accept_charset) 90 | 91 | if CONFIG["RequireHTTPS"] and request.scheme != "https": 92 | return response(start_response, "403 Forbidden", 93 | _("You must use HTTPS")) 94 | 95 | if not CONFIG["AllowMetrics"]: 96 | return response(start_response, "403 Forbidden", 97 | _("Metrics are not enabled for this server")) 98 | 99 | # Pull together all the required data. 100 | db = init_crashstats_db() 101 | stats = get_stats(db) 102 | db.close() 103 | 104 | # Format the data into format readable by Prometheus. 105 | body = RESPONSE_TEMPLATE.strip().format(**stats) 106 | 107 | return response(start_response, "200 OK", body) 108 | -------------------------------------------------------------------------------- /src/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abrt/retrace-server/63d538c82b77fe25e633ad21b0471d3a5fc10ae8/src/plugins/__init__.py -------------------------------------------------------------------------------- /src/plugins/centos.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import List 3 | 4 | distribution = "centos" 5 | abrtparser = re.compile(r"^CentOS Linux release (\d+)(?:\.(\d+)\.(\d+))? \(([^\)]+)\)$") 6 | guessparser = re.compile(r"\.el(\d+)") 7 | displayrelease = "CentOS release" 8 | gdb_package = "gdb" 9 | gdb_executable = "/usr/bin/gdb" 10 | gpg_keys = [ 11 | "/usr/share/distribution-gpg-keys/epel/RPM-GPG-KEY-EPEL-{release}", 12 | "/usr/share/distribution-gpg-keys/centos/RPM-GPG-KEY-CentOS-{release}", 13 | ] 14 | versionlist = [ 15 | "el6", 16 | "el7", 17 | "el8", 18 | "el9", 19 | ] 20 | repos: List[List[str]] = [[]] 21 | -------------------------------------------------------------------------------- /src/plugins/fedora.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | distribution = "fedora" 4 | abrtparser = re.compile(r"^Fedora release ([0-9]+) \(([^\)]+)\)$") 5 | guessparser = re.compile(r"\.fc([0-9]+)") 6 | displayrelease = "Fedora release" 7 | gdb_package = "gdb" 8 | gdb_executable = "/usr/bin/gdb" 9 | gpg_keys = [ 10 | "/usr/share/distribution-gpg-keys/fedora/RPM-GPG-KEY-fedora-{release}-primary", 11 | ] 12 | versionlist = [ 13 | "fc31", 14 | "fc32", 15 | "fc33", 16 | "fc34", 17 | ] 18 | 19 | # Find more details about Fedora Mirroring at: 20 | # https://fedoraproject.org/wiki/Infrastructure/Mirroring 21 | # 22 | # fedora-enchilada is /pub/fedora on http://dl.fedoraproject.org 23 | # pylint: disable=line-too-long 24 | repos = [ 25 | [ 26 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/releases/$VER/Everything/$ARCH/os/Packages/*/*.rpm", 27 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/development/$VER/Everything/$ARCH/os/Packages/*/*.rpm", 28 | ], 29 | [ 30 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/releases/$VER/Everything/$ARCH/debug/*/*.rpm", 31 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/development/$VER/Everything/$ARCH/debug/tree/Packages/*/*.rpm", 32 | ], 33 | [ 34 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/updates/$VER/$ARCH/*.rpm", 35 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/updates/$VER/$ARCH/*/*.rpm", 36 | ], 37 | [ 38 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/updates/$VER/$ARCH/debug/*.rpm", 39 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/updates/$VER/$ARCH/debug/*/*.rpm", 40 | ], 41 | [ 42 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/updates/testing/$VER/$ARCH/*.rpm", 43 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/updates/testing/$VER/$ARCH/*/*.rpm", 44 | ], 45 | [ 46 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/updates/testing/$VER/$ARCH/debug/*.rpm", 47 | "rsync://dl.fedoraproject.org/fedora-enchilada/linux/updates/testing/$VER/$ARCH/debug/*/*.rpm", 48 | ], 49 | ] 50 | -------------------------------------------------------------------------------- /src/plugins/meson.build: -------------------------------------------------------------------------------- 1 | sources = [ 2 | '__init__.py', 3 | 'centos.py', 4 | 'fedora.py', 5 | 'rhel.py', 6 | ] 7 | 8 | foreach file: sources 9 | configure_file( 10 | copy: true, 11 | input: file, 12 | output: file, 13 | ) 14 | endforeach 15 | 16 | install_data(sources, 17 | install_dir: join_paths(datadir, meson.project_name(), 'plugins'), 18 | ) 19 | -------------------------------------------------------------------------------- /src/plugins/rhel.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import List 3 | 4 | distribution = "rhel" 5 | abrtparser = re.compile(r"^Red Hat Enterprise Linux(?:\s\w+)? release (\d+)(?:\.(\d+))?(?:\s\w+)?" 6 | r" \(([^\)]+)\)$") 7 | guessparser = re.compile(r"\.el(\d+)") 8 | displayrelease = "Red Hat Enterprise Linux release" 9 | gpg_keys = [ 10 | "/usr/share/distribution-gpg-keys/epel/RPM-GPG-KEY-EPEL-{release}", 11 | "/usr/share/distribution-gpg-keys/redhat/RPM-GPG-KEY-redhat{release}-release", 12 | ] 13 | gdb_package = "devtoolset-8-gdb" 14 | gdb_executable = "/opt/rh/devtoolset-8/root/usr/bin/gdb" 15 | versionlist = [ 16 | "el1", 17 | "el2", 18 | "el3", 19 | "el4", 20 | "el5", 21 | "el6", 22 | "el7", 23 | "el8", 24 | "el9", 25 | ] 26 | repos: List[List[str]] = [[]] 27 | -------------------------------------------------------------------------------- /src/retrace-server-bugzilla-query: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """ 3 | Query bugzilla for component bugs and search through comments for 4 | trigger words. Output results to a log and for tasks found in the 5 | comments set bugzillano field with bugzilla ids. 6 | """ 7 | 8 | import sys 9 | import re 10 | import time 11 | from datetime import datetime, timedelta 12 | 13 | from typing import Dict, List, Set 14 | from pathlib import Path 15 | 16 | import bugzilla 17 | 18 | from retrace.retrace import BUGZILLA_STATUS, RetraceTask 19 | from retrace.config import Config 20 | 21 | CONFIG = Config() 22 | BUGZILLA_QUERY_LIMIT = 500 23 | 24 | if __name__ == "__main__": 25 | 26 | logfile = Path(CONFIG["LogDir"], "bugzilla-query.log") 27 | 28 | with logfile.open("a") as log: 29 | log.write(time.strftime("[%Y-%m-%d %H:%M:%S] Running bugzilla query\n")) 30 | 31 | # connect to bugzilla 32 | bz_url = CONFIG["BugzillaURL"] 33 | bz_creds = CONFIG["BugzillaCredentials"] 34 | 35 | try: 36 | bzapi = bugzilla.Bugzilla(url=bz_url, cookiefile=None, 37 | tokenfile=None) 38 | except (bugzilla.BugzillaError, ValueError) as e: 39 | log.write("Exception: {0}".format(e)) 40 | sys.exit(1) 41 | 42 | if not bzapi.logged_in and bz_creds: 43 | bzapi.readconfig(bz_creds) 44 | try: 45 | bzapi.connect() 46 | except (bugzilla.BugzillaError, ValueError) as e: 47 | log.write("Exception: {0}".format(e)) 48 | 49 | if bzapi.logged_in: 50 | log.write("Successfuly logged in as {0}.\n".format(bzapi.user)) 51 | else: 52 | log.write("Not logged in. Continue as anonymous user.\n") 53 | 54 | found_tasks: Dict[str, List[str]] = {} 55 | product_list = CONFIG.get_list("BugzillaProduct") 56 | component_list = CONFIG.get_list("BugzillaComponent") 57 | config_status = CONFIG.get_list("BugzillaStatus") 58 | trigger_words = CONFIG.get_list("BugzillaTriggerWords") 59 | regexes = CONFIG.get_list("BugzillaRegExes") 60 | delta = CONFIG["BugzillaQueryLastChangeDelta"] 61 | 62 | query = bzapi.build_query(product=product_list, 63 | component=component_list, 64 | include_fields=["id", "comments"]) 65 | query["status"] = list(set(BUGZILLA_STATUS).difference(config_status)) 66 | query["last_change_time"] = datetime.today() - timedelta(hours = delta) 67 | bugzilla_offset = 0 68 | bugzilla_bug_count = 0 69 | while True: 70 | bugzilla_offset += bugzilla_bug_count 71 | query["offset"] = bugzilla_offset 72 | query["limit"] = BUGZILLA_QUERY_LIMIT 73 | bugs = bzapi.query(query) 74 | 75 | bugzilla_bug_count = len(bugs) 76 | log.write("Query returned {0} bugs at offset {1}" 77 | "\n".format(bugzilla_bug_count, bugzilla_offset)) 78 | # Query until we get no bugs back, which indicates the end 79 | if bugzilla_bug_count == 0: 80 | break 81 | 82 | for bug in bugs: 83 | found_ids: Set[str] = set() 84 | for comment in bug.comments: 85 | for i, trigger_word in enumerate(trigger_words): 86 | # Use slower regex only if trigger word is in comment 87 | if trigger_word in comment["text"]: 88 | m = re.findall(regexes[i], comment["text"]) 89 | found_ids.update(m) 90 | for f in found_ids: 91 | if f in found_tasks: 92 | found_tasks[f].append(str(bug.bug_id)) 93 | else: 94 | found_tasks[f] = [str(bug.bug_id)] 95 | 96 | try: 97 | files = sorted(Path(CONFIG["SaveDir"]).iterdir()) 98 | except OSError as ex: 99 | files = [] 100 | log.write("Error listing task directory: %s\n" % ex) 101 | 102 | # Find all tasks 103 | existing_tasks = [] 104 | for taskdir in files: 105 | if not taskdir.is_dir(): 106 | continue 107 | 108 | existing_tasks.append(taskdir.name) 109 | 110 | log.write("------------" 111 | "Existing tasks with found bugzillas" 112 | "------------\n") 113 | for taskid in found_tasks: 114 | if taskid in existing_tasks: 115 | log.write("Task {0} has following bugzilla(s): {1}." 116 | "\n".format(taskid, ", ".join(found_tasks[taskid]))) 117 | try: 118 | task = RetraceTask(taskid) 119 | except Exception: 120 | continue 121 | 122 | if task.has_bugzillano(): 123 | current = task.get_bugzillano() 124 | assert isinstance(current, list) 125 | found = found_tasks[taskid] 126 | # Only set bugzillano if some aren't in current list 127 | if not set(found).issubset(current): 128 | task.set_bugzillano(current + found) 129 | else: 130 | task.set_bugzillano(found_tasks[taskid]) 131 | 132 | log.write("\n\n" 133 | "------------" 134 | "Found tasks in bugzillas that do not exist on local system" 135 | "------------\n") 136 | for taskid in found_tasks: 137 | if taskid not in existing_tasks: 138 | log.write("Unknown task {0} has following bugzilla(s): {1}.\n" 139 | .format(taskid, ", ".join(found_tasks[taskid]))) 140 | 141 | log.write("\n\n" 142 | "------------" 143 | "Existing tasks that no bugzilla was found " 144 | "------------\n") 145 | no_bz_tasks = [taskid for taskid in existing_tasks 146 | if taskid not in found_tasks.keys()] 147 | log.write("{0}\n\n".format(", ".join(no_bz_tasks))) 148 | -------------------------------------------------------------------------------- /src/retrace-server-bugzilla-refresh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """ 3 | Refresh tasks that have bugzilla bugs in an open state to prevent them from getting deleted by 4 | retrace-server-cleanup tool. 5 | """ 6 | 7 | import sys 8 | import time 9 | 10 | from pathlib import Path 11 | 12 | import bugzilla 13 | 14 | from retrace.retrace import RetraceTask 15 | from retrace.config import Config 16 | 17 | CONFIG = Config() 18 | 19 | if __name__ == "__main__": 20 | 21 | logfile = Path(CONFIG["LogDir"], "bugzilla-refresh.log") 22 | 23 | with logfile.open("a") as log: 24 | log.write(time.strftime("[%Y-%m-%d %H:%M:%S] Running bugzilla refresh\n")) 25 | 26 | # connect to bugzilla 27 | bz_url = CONFIG["BugzillaURL"] 28 | bz_creds = CONFIG["BugzillaCredentials"] 29 | bz_status_list = CONFIG.get_list("BugzillaStatus") 30 | 31 | try: 32 | bzapi = bugzilla.Bugzilla(url=bz_url, cookiefile=None, tokenfile=None) 33 | except (bugzilla.BugzillaError, ValueError) as e: 34 | log.write("Exception: {0}".format(e)) 35 | sys.exit(1) 36 | 37 | if not bzapi.logged_in and bz_creds: 38 | bzapi.readconfig(bz_creds) 39 | try: 40 | bzapi.connect() 41 | except (bugzilla.BugzillaError, ValueError) as e: 42 | log.write("Exception: {0}".format(e)) 43 | 44 | if bzapi.logged_in: 45 | log.write("Successfuly logged in as {0}.\n".format(bzapi.user)) 46 | else: 47 | log.write("Not logged in. Continue as anonymous user.\n") 48 | 49 | try: 50 | files = list(Path(CONFIG["SaveDir"]).iterdir()) 51 | except OSError as ex: 52 | files = [] 53 | log.write("Error listing task directory: %s\n" % ex) 54 | 55 | for filepath in files: 56 | try: 57 | task = RetraceTask(filepath.name) 58 | except Exception: 59 | continue 60 | 61 | if task.has_bugzillano(): 62 | bz_id_list = task.get_bugzillano() 63 | if not bz_id_list: 64 | continue 65 | 66 | bz_list = bzapi.getbugs(bz_id_list) 67 | 68 | for bgz in bz_list: 69 | if bgz and bgz.status not in bz_status_list: 70 | log.write("Modifying time of the task %s\n" % filepath.name) 71 | task.reset_age() 72 | break 73 | -------------------------------------------------------------------------------- /src/retrace-server-cleanup.txt: -------------------------------------------------------------------------------- 1 | ../man/retrace-server-cleanup.txt -------------------------------------------------------------------------------- /src/retrace-server-interact.txt: -------------------------------------------------------------------------------- 1 | ../man/retrace-server-interact.txt -------------------------------------------------------------------------------- /src/retrace-server-plugin-checker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import argparse 4 | import os.path 5 | from subprocess import run, DEVNULL 6 | import sys 7 | import urllib.request 8 | 9 | 10 | def main() -> None: 11 | argparser = argparse.ArgumentParser(description="Retrace Server plugin checker") 12 | argparser.add_argument("PLUGIN", type=str, help="Plugin name") 13 | argparser.add_argument("VERSION", type=str, help="Release version") 14 | argparser.add_argument("ARCHITECTURE", type=str, help="CPU architecture") 15 | argparser.add_argument("--plugin-dir", default="/usr/share/retrace-server/plugins", 16 | help="Path to plugins.") 17 | argparser.add_argument("--only-valid", action="store_true", 18 | help="Print only valid mirrors") 19 | argparser.add_argument("--only-invalid", action="store_true", 20 | help="Print only invalid mirrors") 21 | argparser.add_argument("--first-valid", action="store_true", 22 | help="Print first valid mirror per repository.") 23 | 24 | args = argparser.parse_args() 25 | 26 | # Prepend path and import plugin 27 | sys.path.insert(0, args.plugin_dir) 28 | 29 | try: 30 | plugin = __import__(args.PLUGIN) 31 | except Exception as ex: 32 | print("Plugin could not be loaded. Use --plugin-dir if in another location.") 33 | print(f"Encountered error: {ex}") 34 | sys.exit(1) 35 | except KeyboardInterrupt: 36 | print("Interrupted") 37 | sys.exit(0) 38 | 39 | 40 | verbose = not(args.first_valid or args.only_valid or args.only_invalid) 41 | 42 | for i, r in enumerate(plugin.repos): 43 | if verbose: 44 | print("Repository {0} - testing".format(i)) 45 | 46 | repo_ok = False 47 | for p in r: 48 | path_ok = False 49 | mirror_path = p.replace("$VER", args.VERSION).replace("$ARCH", args.ARCHITECTURE) 50 | if p.startswith("http:") or p.startswith("https:") or p.startswith("ftp:"): 51 | try: 52 | with urllib.request.urlopen(mirror_path) as _response: 53 | # Don't do anything. We only want to know whether the URL 54 | # works. 55 | pass 56 | path_ok = True 57 | except Exception: 58 | pass 59 | elif p.startswith("rsync:"): 60 | rsync = run(["rsync", "--list-only", mirror_path], stdout=DEVNULL, stderr=DEVNULL, check=False) 61 | path_ok = not rsync.returncode 62 | else: 63 | path_ok = os.path.exists(mirror_path) 64 | 65 | repo_ok = repo_ok or path_ok 66 | if verbose: 67 | print("\t[ {0} ] {1}".format(" OK " if path_ok else "FAIL", mirror_path)) 68 | else: 69 | if args.first_valid and path_ok: 70 | print(mirror_path) 71 | break 72 | if args.only_valid and path_ok: 73 | print(mirror_path) 74 | elif args.only_invalid and not path_ok: 75 | print(mirror_path) 76 | 77 | if verbose: 78 | print("Repository {1} - {0}".format("OK" if repo_ok else "FAIL", i)) 79 | 80 | if __name__ == "__main__": 81 | main() 82 | -------------------------------------------------------------------------------- /src/retrace-server-reposync-faf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from argparse import ArgumentParser 4 | import logging 5 | import os 6 | from os.path import abspath, join, relpath 7 | import shutil 8 | import sys 9 | from typing import Generator 10 | 11 | import createrepo_c as cr 12 | from pyfaf.queries import get_packages_by_osrelease 13 | from pyfaf.storage import Database, getDatabase 14 | 15 | faf_names = {"rhel": "Red Hat Enterprise Linux", 16 | "fedora": "Fedora", 17 | "centos": "CentOS"} 18 | 19 | logging.basicConfig(format="[%(asctime)s] %(levelname)s: %(message)s", 20 | datefmt="%F %T", level=logging.INFO) 21 | logger = logging.getLogger("r-s-reposync-faf") 22 | 23 | 24 | def get_pkglist(db: Database, opsys: str, release: str, arch: str) -> \ 25 | Generator[str, None, None]: 26 | if opsys in faf_names.keys(): 27 | opsys = faf_names[opsys] 28 | 29 | for pkg in get_packages_by_osrelease(db, opsys, release, arch): 30 | if pkg.has_lob("package"): 31 | logger.info("Adding package: %s", pkg.nevra()) 32 | yield abspath(pkg.get_lob_path("package")) 33 | 34 | 35 | def generate_repo(db: Database, outputdir: str, opsys: str, release: str, 36 | arch: str) -> None: 37 | repodata_path = join(outputdir, "repodata") 38 | 39 | if os.path.exists(repodata_path): 40 | shutil.rmtree(repodata_path) 41 | 42 | os.makedirs(repodata_path) 43 | 44 | # Prepare metadata files 45 | repomd_path = join(repodata_path, "repomd.xml") 46 | pri_xml_path = join(repodata_path, "primary.xml.gz") 47 | fil_xml_path = join(repodata_path, "filelists.xml.gz") 48 | oth_xml_path = join(repodata_path, "other.xml.gz") 49 | pri_db_path = join(repodata_path, "primary.sqlite") 50 | fil_db_path = join(repodata_path, "filelists.sqlite") 51 | oth_db_path = join(repodata_path, "other.sqlite") 52 | 53 | pri_xml = cr.PrimaryXmlFile(pri_xml_path) 54 | fil_xml = cr.FilelistsXmlFile(fil_xml_path) 55 | oth_xml = cr.OtherXmlFile(oth_xml_path) 56 | pri_db = cr.PrimarySqlite(pri_db_path) 57 | fil_db = cr.FilelistsSqlite(fil_db_path) 58 | oth_db = cr.OtherSqlite(oth_db_path) 59 | 60 | # Prepare list of packages to process 61 | pkg_list = list(get_pkglist(db, opsys, release, arch)) 62 | 63 | pkg_list_len = len(pkg_list) 64 | pri_xml.set_num_of_pkgs(pkg_list_len) 65 | fil_xml.set_num_of_pkgs(pkg_list_len) 66 | oth_xml.set_num_of_pkgs(pkg_list_len) 67 | 68 | # Process all packages 69 | for filename in pkg_list: 70 | pkg = cr.package_from_rpm(filename) 71 | pkg.location_href = relpath(filename, outputdir) 72 | pri_xml.add_pkg(pkg) 73 | fil_xml.add_pkg(pkg) 74 | oth_xml.add_pkg(pkg) 75 | pri_db.add_pkg(pkg) 76 | fil_db.add_pkg(pkg) 77 | oth_db.add_pkg(pkg) 78 | 79 | pri_xml.close() 80 | fil_xml.close() 81 | oth_xml.close() 82 | 83 | # Prepare repomd.xml 84 | repomd = cr.Repomd() 85 | 86 | # Add records into the repomd.xml 87 | repomd_records = (("primary", pri_xml_path, pri_db), 88 | ("filelists", fil_xml_path, fil_db), 89 | ("other", oth_xml_path, oth_db), 90 | ("primary_db", pri_db_path, None), 91 | ("filelists_db", fil_db_path, None), 92 | ("other_db", oth_db_path, None)) 93 | 94 | for name, path, db_to_update in repomd_records: 95 | logger.info("Postprocessing database ‘%s’", name) 96 | 97 | # Compress sqlite files with bzip2 98 | if path.endswith('.sqlite'): 99 | new_path = '%s.bz2' % path 100 | logger.info("Compressing %s...", path) 101 | cr.compress_file(path, new_path, cr.BZ2) 102 | os.remove(path) 103 | path = new_path 104 | logger.info("Done") 105 | 106 | record = cr.RepomdRecord(name, path) 107 | record.fill(cr.SHA256) 108 | record.rename_file() 109 | if db_to_update: 110 | logger.info("Updating related database") 111 | db_to_update.dbinfo_update(record.checksum) 112 | db_to_update.close() 113 | repomd.set_record(record) 114 | 115 | logger.info("Postprocessing ‘%s’ finished", name) 116 | 117 | # Write repomd.xml 118 | with open(repomd_path, "w") as repomd_file: 119 | repomd_file.write(repomd.xml_dump()) 120 | 121 | logger.info("Repository metadata written to %s", repomd_path) 122 | 123 | 124 | def main() -> None: 125 | parser = ArgumentParser(description="Generate a DNF repository from FAF package " 126 | "database") 127 | parser.add_argument("OPSYS") 128 | parser.add_argument("RELEASE") 129 | parser.add_argument("ARCHITECTURE") 130 | parser.add_argument("--outputdir", default=os.getcwd()) 131 | args = parser.parse_args() 132 | 133 | logger.info("Creating DNF repository in ‘%s’", args.outputdir) 134 | 135 | try: 136 | generate_repo(getDatabase(), args.outputdir, args.OPSYS, args.RELEASE, 137 | args.ARCHITECTURE) 138 | except Exception as ex: 139 | logger.error("Could not create repository: %s", ex) 140 | sys.exit(1) 141 | 142 | logger.info("Repository created successfully") 143 | 144 | 145 | if __name__ == "__main__": 146 | main() 147 | -------------------------------------------------------------------------------- /src/retrace-server-reposync.txt: -------------------------------------------------------------------------------- 1 | ../man/retrace-server-reposync.txt -------------------------------------------------------------------------------- /src/retrace-server-task.txt: -------------------------------------------------------------------------------- 1 | ../man/retrace-server-task.txt -------------------------------------------------------------------------------- /src/retrace-server-worker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import pwd 4 | import sys 5 | from typing import Optional 6 | 7 | from retrace.argparser import ArgumentParser 8 | from retrace.retrace import (log_debug, 9 | log_error, 10 | log_warn, 11 | KernelVer, 12 | RetraceTask, 13 | RetraceWorkerError) 14 | from retrace.config import Config 15 | 16 | CONFIG = Config() 17 | 18 | if __name__ == "__main__": 19 | cmdline_parser = ArgumentParser(description="Execute a retrace job") 20 | cmdline_parser.add_argument("task_id", type=int, help="Task ID (%s/) must exist" % CONFIG["SaveDir"]) 21 | cmdline_parser.add_argument("--restart", action="store_true", default=False, 22 | help="Restart the task if it has already been processed") 23 | cmdline_parser.add_argument("--foreground", action="store_true", default=False, help="Do not fork to background") 24 | cmdline_parser.add_argument("--kernelver", default=None, 25 | help="Kernel version (e.g. 2.6.32-287.el6), also needs --arch") 26 | cmdline_parser.add_argument("--arch", help="Architecture") 27 | cmdline = cmdline_parser.parse_args() 28 | 29 | log = cmdline._log 30 | 31 | if not os.environ.get("RETRACE_SERVER_TESTING") and \ 32 | pwd.getpwnam("retrace").pw_uid != os.getuid(): 33 | sys.stderr.write("Please use 'retrace-server-task' to restart or create a new task\n") 34 | sys.exit(1) 35 | 36 | # do not use logging yet - we need the task 37 | if cmdline.kernelver and not cmdline.arch: 38 | sys.stderr.write("You also need to specify architecture when overriding kernel version\n") 39 | sys.exit(1) 40 | 41 | try: 42 | task = RetraceTask(cmdline.task_id) 43 | except Exception: 44 | sys.stderr.write("Task '%d' does not exist\n" % cmdline.task_id) 45 | sys.exit(1) 46 | 47 | if task.has_status(): 48 | if not cmdline.restart: 49 | sys.stderr.write("%s has already been executed for task %d\n" % (sys.argv[0], cmdline.task_id)) 50 | sys.stdout.write("You can use --restart option if you really want to restart the task\n") 51 | sys.exit(1) 52 | 53 | task.reset() 54 | 55 | worker = task.create_worker() 56 | worker.begin_logging() 57 | 58 | if not cmdline.foreground: 59 | try: 60 | pid = os.fork() 61 | except OSError: 62 | log_error("Unable to fork") 63 | worker._fail() 64 | 65 | # parent - kill 66 | if pid != 0: 67 | sys.exit(0) 68 | 69 | try: 70 | os.setpgrp() 71 | except Exception as ex: 72 | log_warn("Failed to detach from process group: %s" % str(ex)) 73 | 74 | kernelver: Optional[KernelVer] = None 75 | if cmdline.kernelver is not None: 76 | try: 77 | kernelver = KernelVer(cmdline.kernelver) 78 | if cmdline.arch: 79 | kernelver.arch = cmdline.arch 80 | log_debug("Using kernel version from command line: %s" % kernelver) 81 | except Exception as ex: 82 | log_warn(str(ex)) 83 | 84 | try: 85 | worker.start(kernelver=kernelver, arch=cmdline.arch) 86 | except RetraceWorkerError as ex: 87 | sys.exit(ex.errorcode) 88 | -------------------------------------------------------------------------------- /src/retrace-server-worker.txt: -------------------------------------------------------------------------------- 1 | ../man/retrace-server-worker.txt -------------------------------------------------------------------------------- /src/retrace/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "architecture", 3 | "archive", 4 | "argparser", 5 | "config", 6 | "plugins", 7 | "retrace", 8 | "retrace_worker", 9 | "util" 10 | ] 11 | 12 | from . import architecture 13 | from . import archive 14 | from . import argparser 15 | from . import config 16 | from . import plugins 17 | from . import retrace 18 | from . import retrace_worker 19 | from . import util 20 | -------------------------------------------------------------------------------- /src/retrace/architecture.py: -------------------------------------------------------------------------------- 1 | import re 2 | from pathlib import Path 3 | from subprocess import PIPE, STDOUT, run 4 | from typing import Dict, Optional, Set 5 | 6 | ARCHITECTURES: Set[str] = { 7 | "src", "noarch", "i386", "i486", "i586", "i686", "x86_64", 8 | "s390", "s390x", "ppc", "ppc64", "ppc64le", "ppc64iseries", 9 | "armel", "armhfp", "armv5tel", "armv7l", "armv7hl", 10 | "armv7hnl", "aarch64", "sparc", "sparc64", "mips4kec", 11 | "ia64" 12 | } 13 | 14 | # armhfp is not correct, but there is no way to distinguish armv5/armv6/armv7 coredumps 15 | # as armhfp (RPM armv7hl) is the only supported now, let's approximate arm = armhfp 16 | 17 | # "arm" has been intentionally removed - when guessing architecture, it matches 18 | # "alarm" or "hdparm" and thus leads to wrong results. 19 | # As soon as plain "arm" needs to be supported, this needs to be solved properly. 20 | ARCH_MAP: Dict[str, Set[str]] = { 21 | "i386": {"i386", "i486", "i586", "i686"}, 22 | "armhfp": {"armhfp", "armel", "armv5tel", "armv7l", "armv7hl", "armv7hnl"}, 23 | "x86_64": {"x86_64"}, 24 | "s390x": {"s390x"}, 25 | "ppc64": {"ppc64"}, 26 | "ppc64le": {"ppc64le"}, 27 | "aarch64": {"aarch64"}, 28 | } 29 | 30 | CORE_ARCH_PARSER = re.compile(r"core file,? .*(x86-64|80386|ARM|aarch64|IBM S/390|64-bit PowerPC)") 31 | 32 | 33 | def get_canon_arch(arch: str) -> str: 34 | for canon_arch, derived_archs in ARCH_MAP.items(): 35 | if arch in derived_archs: 36 | return canon_arch 37 | 38 | return arch 39 | 40 | 41 | def guess_arch(coredump_path: Path) -> Optional[str]: 42 | output = run(["file", str(coredump_path)], stdout=PIPE, encoding="utf-8", 43 | check=False).stdout 44 | match = CORE_ARCH_PARSER.search(output) 45 | if match: 46 | if match.group(1) == "80386": 47 | return "i386" 48 | if match.group(1) == "x86-64": 49 | return "x86_64" 50 | if match.group(1) == "ARM": 51 | # hack - There is no reliable way to determine which ARM 52 | # version the coredump is. At the moment we only support 53 | # armv7hl / armhfp - let's approximate arm = armhfp 54 | return "armhfp" 55 | if match.group(1) == "aarch64": 56 | return "aarch64" 57 | if match.group(1) == "IBM S/390": 58 | return "s390x" 59 | if match.group(1) == "64-bit PowerPC": 60 | if "LSB" in output: 61 | return "ppc64le" 62 | 63 | return "ppc64" 64 | 65 | result: Optional[str] = None 66 | lines = run(["strings", str(coredump_path)], 67 | stdout=PIPE, stderr=STDOUT, encoding="utf-8", 68 | check=False).stdout.splitlines() 69 | for line in lines: 70 | for canon_arch, derived_archs in ARCH_MAP.items(): 71 | if any(arch in line for arch in derived_archs): 72 | result = canon_arch 73 | break 74 | 75 | if result is not None: 76 | break 77 | 78 | # "ppc64le" matches both ppc64 and ppc64le 79 | # if file magic says little endian, fix it 80 | if result == "ppc64" and "LSB" in output: 81 | result = "ppc64le" 82 | 83 | return result 84 | -------------------------------------------------------------------------------- /src/retrace/argparser.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012 Red Hat, Inc. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | import argparse 16 | import logging 17 | import sys 18 | 19 | from io import StringIO 20 | 21 | 22 | class ArgumentParser(argparse.ArgumentParser): 23 | def __init__(self, description=None, prog=sys.argv[0], usage=None, 24 | add_help=True, argument_default=None, prefix_chars="-"): 25 | super().__init__(epilog="See 'man %(prog)s' for more information.", 26 | description=description, 27 | prog=prog, 28 | usage=usage, 29 | add_help=add_help, 30 | argument_default=argument_default, 31 | prefix_chars=prefix_chars) 32 | self.add_argument("-v", "--verbose", action="store_true", default=False, dest="verbose") 33 | 34 | def parse_args(self, args=None, namespace=None): 35 | args = super().parse_args(args=args, namespace=namespace) 36 | if args.verbose: 37 | level = logging.DEBUG 38 | else: 39 | level = logging.INFO 40 | 41 | # pylint: disable=protected-access 42 | if args.foreground: 43 | args._log = None 44 | logging.basicConfig(level=level) 45 | else: 46 | args._log = StringIO() 47 | logging.basicConfig(level=level, stream=args._log) 48 | 49 | return args 50 | -------------------------------------------------------------------------------- /src/retrace/backends/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abrt/retrace-server/63d538c82b77fe25e633ad21b0471d3a5fc10ae8/src/retrace/backends/__init__.py -------------------------------------------------------------------------------- /src/retrace/backends/meson.build: -------------------------------------------------------------------------------- 1 | sources = [ 2 | '__init__.py', 3 | 'podman.py', 4 | ] 5 | 6 | foreach file: sources 7 | configure_file( 8 | copy: true, 9 | input: file, 10 | output: file, 11 | ) 12 | endforeach 13 | 14 | python_installation.install_sources(sources, 15 | subdir: join_paths('retrace', 'backends'), 16 | pure: true, 17 | ) 18 | -------------------------------------------------------------------------------- /src/retrace/backends/podman.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from subprocess import CompletedProcess, DEVNULL, PIPE, run, STDOUT 3 | from typing import List, Optional, Union 4 | 5 | from retrace.retrace import (log_debug, 6 | log_info, 7 | RETRACE_GPG_KEYS, 8 | RetraceError) 9 | from retrace.config import Config, PODMAN_BIN 10 | 11 | 12 | class PodmanContainer: 13 | def __init__(self, container_id: str) -> None: 14 | self.id = container_id 15 | 16 | def copy_to(self, src: Union[str, Path], dst: Union[str, Path]) -> None: 17 | proc = run([PODMAN_BIN, "cp", str(src), f"{self.id}:{dst}"], 18 | stdout=DEVNULL, stderr=PIPE, encoding="utf-8", check=False) 19 | 20 | if proc.returncode: 21 | raise RetraceError( 22 | f"Could not copy file ‘{src}’ to container: {proc.stderr}") 23 | 24 | def exec(self, cmd: List[str], user: Optional[str] = None) \ 25 | -> CompletedProcess: 26 | args = [PODMAN_BIN, "exec"] 27 | 28 | if user is not None: 29 | args.append(f"--user={user}") 30 | 31 | args.append(self.id) 32 | args.extend(cmd) 33 | 34 | return run(args, stderr=STDOUT, stdout=PIPE, encoding="utf-8", check=False) 35 | 36 | @property 37 | def short_id(self) -> str: 38 | return self.id[:7] 39 | 40 | def stop_and_remove(self) -> None: 41 | proc = run([PODMAN_BIN, "rm", "--force", self.id], 42 | stderr=PIPE, stdout=DEVNULL, encoding="utf-8", check=False) 43 | 44 | if proc.returncode: 45 | raise RetraceError(f"Could not stop container {self.short_id}: {proc.stderr}") 46 | 47 | log_info(f"Container {self.short_id} stopped and removed") 48 | 49 | def __enter__(self) -> "PodmanContainer": 50 | return self 51 | 52 | def __exit__(self, exc_type, exc_value, exc_traceback) -> None: 53 | self.stop_and_remove() 54 | 55 | 56 | class LocalPodmanBackend: 57 | def __init__(self, retrace_config: Config): 58 | self.config = retrace_config 59 | 60 | def start_container(self, image_tag: str, taskid: int, repopath: str) \ 61 | -> PodmanContainer: 62 | run_call = [PODMAN_BIN, "run", 63 | "--quiet", 64 | "--detach", 65 | "--interactive", 66 | "--tty", 67 | "--sdnotify=ignore", 68 | f"--name=retrace-{taskid}", 69 | f"--volume={repopath}:{repopath}:ro"] 70 | 71 | if self.config["RequireGPGCheck"]: 72 | run_call.append("--volume={0}:{0}:ro".format(RETRACE_GPG_KEYS)) 73 | 74 | if self.config["UseFafPackages"]: 75 | log_debug("Using FAF repository") 76 | run_call.append("--volume={0}:{0}:ro".format(self.config["FafLinkDir"])) 77 | 78 | run_call.append(image_tag) 79 | 80 | child = run(run_call, stderr=PIPE, stdout=PIPE, encoding="utf-8", 81 | check=False) 82 | 83 | if child.returncode: 84 | raise RetraceError(f"Could not start container: {child.stderr}") 85 | 86 | container_id = child.stdout.strip() 87 | container = PodmanContainer(container_id) 88 | log_info(f"Container {container.short_id} started") 89 | 90 | return container 91 | -------------------------------------------------------------------------------- /src/retrace/config.py.in: -------------------------------------------------------------------------------- 1 | # Singleton class for saving path to the configuration file 2 | 3 | # Heavily inspired by 4 | # http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Singleton.html 5 | # All modules that need reading from configuration file must create instance of 6 | # Config. When different path than default is needed, before first 7 | # reading from any GLOBAL item, a load(/path/to/the/config/file) 8 | # must be called. When this function was not called, is automatically called 9 | # before first reading from GLOBAL with default path. 10 | # Note:all modules share one instance, therefore only one load is needed. 11 | import os 12 | from typing import Any, Callable, Dict, List, Optional, Union 13 | 14 | import configparser 15 | 16 | CREATEREPO_BIN = "@CREATEREPO_BIN@" 17 | DF_BIN = "@DF_BIN@" 18 | GZIP_BIN = "@GZIP_BIN@" 19 | LSOF_BIN = "@LSOF_BIN@" 20 | PODMAN_BIN = "@PODMAN_BIN@" 21 | PS_BIN = "@PS_BIN@" 22 | TAR_BIN = "@TAR_BIN@" 23 | UNAR_BIN = "@UNAR_BIN@" 24 | XZ_BIN = "@XZ_BIN@" 25 | 26 | Getter = Union[Callable[[str, str], int], 27 | Callable[[str, str], bool], 28 | Callable[[str, str], float], 29 | Callable[[str, str], List[str]], 30 | Callable[[str, str], str]] 31 | 32 | 33 | class Config: 34 | class _Config: 35 | _conf_file_read = False 36 | 37 | ARCH_HOSTS: Dict[str, str] = {} 38 | 39 | # pylint: disable=line-too-long 40 | GLOBAL: Dict[str, Any] = { 41 | "TaskIdLength": 9, 42 | "TaskPassLength": 32, 43 | "MaxParallelTasks": 10, 44 | "MaxPackedSize": 30, 45 | "MaxUnpackedSize": 600, 46 | "MinStorageLeft": 10240, 47 | "DeleteTaskAfter": 120, 48 | "DeleteFailedTaskAfter": 24, 49 | "ArchiveTaskAfter": 0, 50 | "KeepRawhideLatest": 3, 51 | "KojiRoot": "/mnt/koji", 52 | "DropDir": "/srv/retrace/archive", 53 | "LogDir": "/var/log/retrace-server", 54 | "RepoDir": "/var/cache/retrace-server", 55 | "SaveDir": "/var/spool/retrace-server", 56 | "RequireHTTPS": True, 57 | "AllowAPIDelete": False, 58 | "AllowExternalDir": False, 59 | "AllowInteractive": False, 60 | "AllowMetrics": False, 61 | "AllowTaskManager": False, 62 | "AllowVMCoreTask": False, 63 | "AllowUsrCoreTask": False, 64 | "TaskManagerAuthDelete": False, 65 | "TaskManagerDeleteUsers": [], 66 | "UseFTPTasks": False, 67 | "FTPSSL": False, 68 | "FTPHost": "", 69 | "FTPUser": "", 70 | "FTPPass": "", 71 | "FTPDir": "/", 72 | "FTPBufferSize": 16, 73 | "WgetKernelDebuginfos": False, 74 | "KernelDebuginfoURL": "http://kojipkgs.fedoraproject.org/packages/kernel/$VERSION/$RELEASE/$ARCH/", 75 | "VmcoreDumpLevel": 0, 76 | "VmcoreDumpSavePercent": 10, 77 | "RequireGPGCheck": True, 78 | "UseCreaterepoUpdate": False, 79 | "DBFile": "stats.db", 80 | "KernelChrootRepo": "http://dl.fedoraproject.org/pub/fedora/linux/releases/16/Everything/$ARCH/os/", 81 | "UseFafPackages": False, 82 | "RetraceEnvironment": "mock", 83 | "FafLinkDir": "/var/spool/faf", 84 | "AuthGroup": "retrace", 85 | "EmailNotify": False, 86 | "EmailNotifyFrom": "retrace@localhost", 87 | "CalculateMd5": True, 88 | "CaseNumberURL": "", 89 | "BugzillaURL": "https://bugzilla.redhat.com", 90 | "BugzillaStatus": "VERIFIED, RELEASE_PENDING, CLOSED", 91 | "BugzillaCredentials": "", 92 | "BugzillaProduct": "Red Hat Enterprise Linux 7", 93 | "BugzillaComponent": "kernel", 94 | "BugzillaTriggerWords": "", 95 | "BugzillaRegExes": "", 96 | "BugzillaQueryLastChangeDelta": 168, 97 | "ProcessCommunicateTimeout": 43200, 98 | "KernelDebuggerPath": "/usr/bin/crash", 99 | } 100 | 101 | def __getitem__(self, key: str) -> Any: 102 | if not self._conf_file_read: 103 | self.load() 104 | 105 | return self.GLOBAL[key] 106 | 107 | def load(self, filepath: str = "/etc/retrace-server/retrace-server.conf") \ 108 | -> None: 109 | self._conf_file_read = True 110 | 111 | # Prefer environment variable over argument if set. 112 | env_config_path = os.environ.get("RETRACE_SERVER_CONFIG_PATH") 113 | if env_config_path: 114 | filepath = env_config_path 115 | 116 | parser = configparser.ConfigParser() 117 | parser.read(filepath) 118 | 119 | for key, value in self.GLOBAL.items(): 120 | vartype = type(value) 121 | get: Getter = parser.get 122 | 123 | if vartype is int: 124 | get = parser.getint 125 | elif vartype is bool: 126 | get = parser.getboolean 127 | elif vartype is float: 128 | get = parser.getfloat 129 | elif vartype is list: 130 | # This is not _really_ a redefinition of an existing function. 131 | # It is better though of as an assignment just like the ones above. 132 | # pylint: disable=function-redefined 133 | def get(section, key): 134 | return parser.get(section, key).split() 135 | 136 | assert get is not None 137 | 138 | try: 139 | self.GLOBAL[key] = get("retrace", key) 140 | except configparser.NoOptionError: 141 | pass 142 | 143 | if "archhosts" in parser.sections(): 144 | for arch, host in parser.items("archhosts"): 145 | host = host.strip() 146 | if host: 147 | self.ARCH_HOSTS[arch] = host 148 | 149 | def get_arch_hosts(self) -> Dict[str, str]: 150 | return self.ARCH_HOSTS 151 | 152 | def get_list(self, key: str, sep: str = ",") -> List[str]: 153 | values = self.GLOBAL.get(key, "").split(sep) 154 | return [val.strip() for val in values if val.strip()] 155 | 156 | _instance: Optional[_Config] = None 157 | 158 | def __new__(cls): 159 | if Config._instance is None: 160 | Config._instance = Config._Config() 161 | 162 | return Config._instance 163 | 164 | def __getattr__(self, name: str) -> Any: 165 | return getattr(self._instance, name) 166 | 167 | def __setattr__(self, name: str, value: Any) -> None: 168 | pass 169 | -------------------------------------------------------------------------------- /src/retrace/hooks/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["config", "hooks"] 2 | 3 | from . import hooks 4 | from . import config 5 | -------------------------------------------------------------------------------- /src/retrace/hooks/config.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | from pathlib import Path 3 | from typing import Dict, List 4 | 5 | from retrace.retrace import log_warn 6 | 7 | MAIN_CONFIG_PATH = Path("/etc/retrace-server/") 8 | MAIN_HOOK_CONFIG_FILE = Path("retrace-server-hooks.conf") 9 | MAIN_HOOK_CONFIGS_PATH = Path("/etc/retrace-server/hooks") 10 | USER_CONFIG_PATH = Path.home() / ".config/retrace-server/" 11 | USER_HOOK_CONFIGS_PATH = Path.home() / ".config/retrace-server/hooks" 12 | HOOK_PATH = Path("/usr/libexec/retrace-server/hooks/") 13 | HOOK_TIMEOUT = 300 14 | 15 | 16 | def get_config_files(directory: Path) -> List[Path]: 17 | if not directory.is_dir(): 18 | log_warn(f"Configuration directory {directory} does not exist") 19 | return [] 20 | 21 | return [fname for fname in directory.iterdir() 22 | if fname.suffix == ".conf"] 23 | 24 | 25 | def load_config_files(config_files: List[Path]) -> Dict[str, str]: 26 | result = {} 27 | cfg_parser = configparser.ConfigParser() 28 | cfg_parser.read(config_files) 29 | 30 | for section in cfg_parser.sections(): 31 | for option in cfg_parser.options(section): 32 | key = f"{section.lower()}.{option.lower()}" 33 | result[key] = cfg_parser.get(section, option) 34 | 35 | return result 36 | 37 | 38 | def load_hook_config() -> Dict[str, str]: 39 | hook_configs: List[Path] = [] 40 | hook_configs += get_config_files(MAIN_HOOK_CONFIGS_PATH) 41 | 42 | main_hook_config_file = Path(MAIN_CONFIG_PATH, MAIN_HOOK_CONFIG_FILE) 43 | hook_configs.append(main_hook_config_file) 44 | 45 | try: 46 | if USER_HOOK_CONFIGS_PATH.exists(): 47 | hook_configs += get_config_files(USER_HOOK_CONFIGS_PATH) 48 | except Exception as ex: 49 | log_warn(f"USER_HOOK_CONFIGS_PATH {USER_HOOK_CONFIGS_PATH} does not exist: {ex}") 50 | 51 | try: 52 | if USER_CONFIG_PATH.exists(): 53 | hook_configs.append(USER_CONFIG_PATH) 54 | except Exception as ex: 55 | log_warn(f"USER_CONFIG_PATH {USER_CONFIG_PATH} does not exist: {ex}") 56 | 57 | cfgs = load_config_files(hook_configs) 58 | 59 | return cfgs 60 | 61 | 62 | hooks_config = load_hook_config() 63 | -------------------------------------------------------------------------------- /src/retrace/hooks/hooks.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import multiprocessing as mp 3 | import os 4 | import shlex 5 | from typing import Iterable, Optional 6 | 7 | from pathlib import Path 8 | from subprocess import PIPE, CalledProcessError, run, TimeoutExpired 9 | 10 | from retrace.retrace import log_info, log_error, log_debug, RetraceTask 11 | from .config import HOOK_PATH, HOOK_TIMEOUT, hooks_config 12 | 13 | # Hooks description: 14 | # pre_start -- When self.start() is called 15 | # start -- When task type is determined and the main task starts 16 | # pre_prepare_debuginfo -- Before the preparation of debuginfo packages 17 | # post_prepare_debuginfo -- After the preparation of debuginfo packages 18 | # pre_prepare_environment -- Before the preparation of retrace environment 19 | # post_prepare_environment -- After the preparation of retrace environment 20 | # pre_retrace -- Before starting of the retracing itself 21 | # post_retrace -- After retracing is done 22 | # success -- After retracing success 23 | # fail -- After retracing fails 24 | # pre_remove_task -- Before removing task 25 | # post_remove_task -- After removing task 26 | # pre_clean_task -- Before cleaning task 27 | # post_clean_task -- After cleaning task 28 | 29 | 30 | def get_executables(directory: Path) -> Iterable[Path]: 31 | """ 32 | Scan `directory` and return list of found executable scripts. 33 | """ 34 | if not directory.is_dir(): 35 | return [] 36 | 37 | return ( 38 | entry for entry in sorted(directory.iterdir()) 39 | if entry.is_file() and os.access(entry, os.X_OK) 40 | ) 41 | 42 | 43 | class RetraceHook: 44 | taskid: int 45 | task_results_dir: Path 46 | 47 | def __init__(self, task: RetraceTask) -> None: 48 | self.taskid = task.get_taskid() 49 | self.task_results_dir = task.get_results_dir() 50 | 51 | def _get_cmdline(self, hook: str, exc: Optional[str] = None) -> Optional[str]: 52 | if exc: 53 | cmdline = hooks_config.get(f"{hook}.{exc}.cmdline", None) 54 | 55 | if not cmdline: 56 | cmdline = hooks_config.get(f"{hook}.cmdline", None) 57 | 58 | if cmdline: 59 | cmdline = cmdline.format(hook_name=hook, 60 | taskid=self.taskid, 61 | task_results_dir=self.task_results_dir) 62 | 63 | return cmdline 64 | 65 | @staticmethod 66 | def _get_hookdir() -> Path: 67 | hooks_path = hooks_config.get("main.hookdir", HOOK_PATH) 68 | 69 | return Path(hooks_path) 70 | 71 | @staticmethod 72 | def _get_timeout(hook: str, exc: Optional[str] = None) -> int: 73 | timeout = hooks_config.get("main.timeout", HOOK_TIMEOUT) 74 | 75 | if f"{hook}.timeout" in hooks_config: 76 | timeout = hooks_config.get(f"{hook}.timeout", timeout) 77 | 78 | if exc and f"{hook}.{exc}.timeout" in hooks_config: 79 | timeout = hooks_config.get(f"{hook}.{exc}.timeout", timeout) 80 | 81 | return int(timeout) 82 | 83 | def _process_script(self, hook: str, hook_path: Path, exc_path: str) -> None: 84 | exc = Path(exc_path).name 85 | script = exc_path 86 | 87 | log_debug(f"Running '{hook}' hook - script '{exc}'") 88 | hook_cmdline = self._get_cmdline(hook, exc) 89 | hook_timeout = self._get_timeout(hook, exc) 90 | 91 | if hook_cmdline: 92 | script = shlex.quote(f"{script} {hook_cmdline}") 93 | 94 | cmd = shlex.split(script) 95 | 96 | try: 97 | child = run(cmd, shell=True, timeout=hook_timeout, cwd=hook_path, 98 | stdout=PIPE, stderr=PIPE, encoding="utf-8", check=True) 99 | except TimeoutExpired as ex: 100 | if ex.stdout: 101 | log_info(ex.stdout) 102 | if ex.stderr: 103 | log_error(ex.stderr) 104 | log_error(f"Hook script '{exc}' timed out in {ex.timeout} seconds.") 105 | except CalledProcessError as ex: 106 | if ex.stdout: 107 | log_info(ex.stdout) 108 | if ex.stderr: 109 | log_error(ex.stderr) 110 | log_error(f"Hook script '{exc}' failed with exit status {ex.returncode}.") 111 | else: 112 | if child.stdout: 113 | log_info(child.stdout) 114 | if child.stderr: 115 | log_error(child.stderr) 116 | 117 | def run(self, hook: str) -> None: 118 | """Called by the default hook implementations""" 119 | hook_path = Path(self._get_hookdir(), hook) 120 | executables = get_executables(hook_path) 121 | 122 | params = itertools.product([hook], [hook_path], executables) 123 | 124 | with mp.Pool() as hook_pool: 125 | hook_pool.starmap(self._process_script, params) 126 | -------------------------------------------------------------------------------- /src/retrace/hooks/meson.build: -------------------------------------------------------------------------------- 1 | sources = [ 2 | '__init__.py', 3 | 'config.py', 4 | 'hooks.py', 5 | ] 6 | 7 | foreach file: sources 8 | configure_file( 9 | copy: true, 10 | input: file, 11 | output: file, 12 | ) 13 | endforeach 14 | 15 | python_installation.install_sources(sources, 16 | subdir: join_paths('retrace', 'hooks'), 17 | pure: true, 18 | ) 19 | -------------------------------------------------------------------------------- /src/retrace/logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | def log_debug(msg: str): 8 | logger.debug(msg) 9 | 10 | 11 | def log_error(msg: str): 12 | logger.error(msg) 13 | 14 | 15 | def log_exception(msg: str): 16 | logger.debug(msg, exc_info=True) 17 | 18 | 19 | def log_info(msg: str): 20 | logger.info(msg) 21 | 22 | 23 | def log_warn(msg: str): 24 | logger.warning(msg) 25 | -------------------------------------------------------------------------------- /src/retrace/meson.build: -------------------------------------------------------------------------------- 1 | subdir('backends') 2 | subdir('hooks') 3 | 4 | configuration = configuration_data() 5 | 6 | configuration.set('CREATEREPO_BIN', createrepo.path()) 7 | configuration.set('DF_BIN', df.path()) 8 | configuration.set('GZIP_BIN', gzip.path()) 9 | configuration.set('LSOF_BIN', lsof.path()) 10 | configuration.set('PODMAN_BIN', podman.path()) 11 | configuration.set('PS_BIN', ps.path()) 12 | configuration.set('TAR_BIN', tar.path()) 13 | configuration.set('UNAR_BIN', unar.path()) 14 | configuration.set('XZ_BIN', xz.path()) 15 | 16 | sources = [ 17 | '__init__.py', 18 | 'architecture.py', 19 | 'archive.py', 20 | 'argparser.py', 21 | 'logging.py', 22 | 'plugins.py', 23 | 'retrace.py', 24 | 'retrace_worker.py', 25 | 'stats.py', 26 | 'util.py', 27 | ] 28 | 29 | foreach file: sources 30 | configure_file( 31 | copy: true, 32 | input: file, 33 | output: file, 34 | ) 35 | endforeach 36 | 37 | python_installation.install_sources([ 38 | configure_file( 39 | configuration: configuration, 40 | input: 'config.py.in', 41 | output: 'config.py', 42 | ), 43 | sources, 44 | ], 45 | subdir: 'retrace', 46 | pure: true, 47 | ) 48 | -------------------------------------------------------------------------------- /src/retrace/plugins.py: -------------------------------------------------------------------------------- 1 | # Singleton class for providing plugins 2 | 3 | # heavily inspired by 4 | # http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Singleton.html 5 | # Each module that needs working with plugins, needs reference on Plugins 6 | # instance. To obtain all existing plugins, calling method all() should be 7 | # sufficient enough. List of imported modules is returned. 8 | # If different path to plugins then default is needed, there are 2 options: 9 | # 1) Calling method load() with parameter before any calling of method all() 10 | # 2) Setting environment variable "RETRACE_SERVER_PLUGIN_DIR" 11 | # Note: No.2 has bigger priority the No.1 12 | # Note: Loading, if not forced by calling load(), is done in first calling of 13 | # all(). Another calls of all() return the same list. To change this list 14 | # method load() must be called explicitly. 15 | 16 | import os 17 | import sys 18 | from importlib import import_module 19 | from pathlib import Path 20 | from types import ModuleType 21 | from typing import Any, List, Optional 22 | 23 | 24 | class Plugins: 25 | _instance: Optional[ModuleType] = None 26 | 27 | class _Plugins: 28 | plugins_read: bool 29 | plugin_list: List[ModuleType] 30 | 31 | def __init__(self) -> None: 32 | self.plugin_list = [] 33 | self.plugins_read = False 34 | 35 | def load(self, plugin_dir: Path = Path("/usr/share/retrace-server/plugins")) -> None: 36 | self.plugin_list = [] 37 | self.plugins_read = True 38 | # if environment variable set, use rather that 39 | env_plugin_dir = os.environ.get("RETRACE_SERVER_PLUGIN_DIR") 40 | if env_plugin_dir: 41 | plugin_dir = Path(env_plugin_dir) 42 | sys.path.insert(0, str(plugin_dir)) 43 | 44 | try: 45 | files = list(plugin_dir.iterdir()) 46 | except Exception as ex: 47 | print("Unable to list directory '%s': %s" % (plugin_dir, ex)) 48 | raise ImportError(ex) from ex 49 | 50 | for filepath in files: 51 | if not filepath.name.startswith("_") and filepath.suffix == ".py": 52 | pluginname = filepath.stem 53 | try: 54 | this = import_module(pluginname) 55 | except Exception: # pylint: disable=broad-except 56 | continue 57 | if "distribution" in this.__dict__ and "repos" in this.__dict__: 58 | self.plugin_list.append(this) 59 | 60 | def all(self) -> List[ModuleType]: 61 | if not self.plugins_read: 62 | self.load() 63 | return self.plugin_list 64 | 65 | def __new__(cls,): 66 | if not Plugins._instance: 67 | Plugins._instance = Plugins._Plugins() 68 | return Plugins._instance 69 | 70 | def __getattr__(self, name: str) -> Any: 71 | return getattr(self._instance, name) 72 | 73 | def __setattr__(self, name: str, value: Any) -> None: 74 | setattr(self._instance, name, value) 75 | -------------------------------------------------------------------------------- /src/retrace/stats.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sqlite3 3 | import time 4 | from typing import Any, Dict, List, Optional, Tuple 5 | 6 | from .retrace import CONFIG 7 | from .util import parse_rpm_name 8 | 9 | 10 | def init_crashstats_db() -> sqlite3.Connection: 11 | # create the database group-writable and world-readable 12 | old_umask = os.umask(0o113) 13 | con = sqlite3.connect(os.path.join(CONFIG["SaveDir"], CONFIG["DBFile"])) 14 | os.umask(old_umask) 15 | 16 | query = con.cursor() 17 | query.execute("PRAGMA foreign_keys = ON") 18 | query.execute(""" 19 | CREATE TABLE IF NOT EXISTS 20 | tasks(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid, package, version, 21 | arch, starttime NOT NULL, duration NOT NULL, coresize, status NOT NULL) 22 | """) 23 | query.execute(""" 24 | CREATE TABLE IF NOT EXISTS 25 | success(taskid REFERENCES tasks(id), pre NOT NULL, post NOT NULL, 26 | rootsize NOT NULL) 27 | """) 28 | query.execute(""" 29 | CREATE TABLE IF NOT EXISTS 30 | packages(id INTEGER PRIMARY KEY AUTOINCREMENT, 31 | name NOT NULL, version NOT NULL) 32 | """) 33 | query.execute(""" 34 | CREATE TABLE IF NOT EXISTS 35 | packages_tasks(pkgid REFERENCES packages(id), 36 | taskid REFERENCES tasks(id)) 37 | """) 38 | query.execute(""" 39 | CREATE TABLE IF NOT EXISTS 40 | buildids(taskid REFERENCES tasks(id), soname, buildid NOT NULL) 41 | """) 42 | query.execute(""" 43 | CREATE TABLE IF NOT EXISTS 44 | reportfull(requesttime NOT NULL, ip NOT NULL) 45 | """) 46 | con.commit() 47 | 48 | return con 49 | 50 | 51 | def save_crashstats(stats: Dict[str, Any], con: Optional[sqlite3.Connection] = None) -> int: 52 | close = False 53 | if con is None: 54 | con = init_crashstats_db() 55 | close = True 56 | 57 | query = con.cursor() 58 | query.execute(""" 59 | INSERT INTO tasks (taskid, package, version, arch, 60 | starttime, duration, coresize, status) 61 | VALUES (?, ?, ?, ?, ?, ?, ?, ?) 62 | """, 63 | (stats["taskid"], stats["package"], stats["version"], 64 | stats["arch"], stats["starttime"], stats["duration"], 65 | stats["coresize"], stats["status"])) 66 | 67 | con.commit() 68 | if close: 69 | con.close() 70 | 71 | return query.lastrowid 72 | 73 | 74 | def save_crashstats_success(statsid: int, pre: int, post: int, rootsize: int, 75 | con: Optional[sqlite3.Connection] = None) -> None: 76 | close = False 77 | if con is None: 78 | con = init_crashstats_db() 79 | close = True 80 | 81 | query = con.cursor() 82 | query.execute(""" 83 | INSERT INTO success (taskid, pre, post, rootsize) 84 | VALUES (?, ?, ?, ?) 85 | """, 86 | (statsid, pre, post, rootsize)) 87 | 88 | con.commit() 89 | if close: 90 | con.close() 91 | 92 | 93 | def save_crashstats_packages(statsid: int, 94 | packages: List[str], 95 | con: Optional[sqlite3.Connection] = None) -> None: 96 | close = False 97 | if con is None: 98 | con = init_crashstats_db() 99 | close = True 100 | 101 | query = con.cursor() 102 | for package in packages: 103 | pkgdata = parse_rpm_name(package) 104 | if pkgdata["name"] is None: 105 | continue 106 | 107 | ver = "%s-%s" % (pkgdata["version"], pkgdata["release"]) 108 | query.execute("SELECT id FROM packages WHERE name = ? AND version = ?", 109 | (pkgdata["name"], ver)) 110 | row = query.fetchone() 111 | if row: 112 | pkgid = row[0] 113 | else: 114 | query.execute("INSERT INTO packages (name, version) VALUES (?, ?)", 115 | (pkgdata["name"], ver)) 116 | pkgid = query.lastrowid 117 | 118 | query.execute(""" 119 | INSERT INTO packages_tasks (taskid, pkgid) VALUES (?, ?) 120 | """, (statsid, pkgid)) 121 | 122 | con.commit() 123 | if close: 124 | con.close() 125 | 126 | 127 | def save_crashstats_build_ids(statsid: int, buildids: List[Tuple[str, str]], 128 | con: Optional[sqlite3.Connection] = None) -> None: 129 | close = False 130 | if con is None: 131 | con = init_crashstats_db() 132 | close = True 133 | 134 | query = con.cursor() 135 | for soname, buildid in buildids: 136 | query.execute(""" 137 | INSERT INTO buildids (taskid, soname, buildid) 138 | VALUES (?, ?, ?) 139 | """, 140 | (statsid, soname, buildid)) 141 | 142 | con.commit() 143 | if close: 144 | con.close() 145 | 146 | 147 | def save_crashstats_reportfull(ip_addr: str, 148 | con: Optional[sqlite3.Connection] = None) -> None: 149 | close = False 150 | if con is None: 151 | con = init_crashstats_db() 152 | close = True 153 | 154 | query = con.cursor() 155 | query.execute(""" 156 | INSERT INTO reportfull (requesttime, ip) 157 | VALUES (?, ?) 158 | """, 159 | (int(time.time()), ip_addr)) 160 | 161 | con.commit() 162 | if close: 163 | con.close() 164 | -------------------------------------------------------------------------------- /src/retrace/util.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import ftplib 3 | import gettext 4 | import os 5 | import re 6 | import smtplib 7 | from pathlib import Path 8 | from subprocess import run, PIPE 9 | from typing import Any, Callable, Dict, List, Optional, SupportsFloat, Tuple, Union 10 | 11 | from dnf.subject import Subject 12 | from hawkey import FORM_NEVRA 13 | 14 | from .config import Config, DF_BIN 15 | 16 | GETTEXT_DOMAIN = "retrace-server" 17 | 18 | DF_OUTPUT_PARSER = re.compile( 19 | r"^([^ ^\t]*)[ \t]+([0-9]+)[ \t]+([0-9]+)[ \t]+([0-9]+)[ \t]+([0-9]+%)[ \t]+(.*)$" 20 | ) 21 | 22 | # architecture (i386, x86_64, armv7hl, mips4kec) 23 | INPUT_ARCH_PARSER = re.compile(r"^\w+$", re.ASCII) 24 | # characters, numbers, dash (utf-8, iso-8859-2 etc.) 25 | INPUT_CHARSET_PARSER = re.compile(r"^([a-zA-Z0-9-]+)(,.*)?$") 26 | # en_GB, sk-SK, cs, fr etc. 27 | INPUT_LANG_PARSER = re.compile(r"^([a-z]{2}([_\-][A-Z]{2})?)(,.*)?$") 28 | # characters allowed by Fedora Naming Guidelines 29 | INPUT_PACKAGE_PARSER = re.compile(r"^[\w.+-]+([1-9][0-9]*:)?[a-zA-Z0-9.+~-]+$", re.ASCII) 30 | # name-version-arch (fedora-16-x86_64, rhel-6.2-i386, opensuse-12.1-x86_64) 31 | INPUT_RELEASEID_PARSER = re.compile(r"^[a-zA-Z0-9]+-[a-zA-Z0-9.]+-\w+$", re.ASCII) 32 | 33 | UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB"] 34 | URL_PARSER = re.compile(r"^/([0-9]+)/?") 35 | 36 | def lock(lockfile: Path) -> bool: 37 | try: 38 | fd = os.open(lockfile, os.O_CREAT | os.O_EXCL, 0o600) 39 | except OSError as ex: 40 | if ex.errno == errno.EEXIST: 41 | return False 42 | raise ex 43 | 44 | os.close(fd) 45 | return True 46 | 47 | 48 | def unlock(lockfile: Path) -> bool: 49 | try: 50 | if lockfile.stat().st_size == 0: 51 | lockfile.unlink() 52 | except OSError: 53 | return False 54 | 55 | return True 56 | 57 | 58 | def free_space(path: str) -> Optional[int]: 59 | lines = run([DF_BIN, "-B", "1", path], 60 | stdout=PIPE, encoding="utf-8", check=False).stdout.split("\n") 61 | for line in lines: 62 | match = DF_OUTPUT_PARSER.match(line) 63 | if match: 64 | return int(match.group(4)) 65 | 66 | return None 67 | 68 | 69 | def ftp_init() -> ftplib.FTP: 70 | config = Config() 71 | if config["FTPSSL"]: 72 | ftp = ftplib.FTP_TLS(config["FTPHost"]) 73 | ftp.prot_p() 74 | else: 75 | ftp = ftplib.FTP(config["FTPHost"]) 76 | 77 | ftp.login(config["FTPUser"], config["FTPPass"]) 78 | ftp.cwd(config["FTPDir"]) 79 | 80 | return ftp 81 | 82 | 83 | def ftp_close(ftp: ftplib.FTP) -> None: 84 | try: 85 | ftp.quit() 86 | except ftplib.all_errors: 87 | ftp.close() 88 | 89 | 90 | def ftp_list_dir(ftpdir: str = "/", ftp: Optional[ftplib.FTP] = None) -> List[str]: 91 | close = False 92 | if ftp is None: 93 | ftp = ftp_init() 94 | close = True 95 | 96 | result = [f.lstrip("/") for f in ftp.nlst(ftpdir)] 97 | 98 | if close: 99 | ftp_close(ftp) 100 | 101 | return result 102 | 103 | 104 | def human_readable_size(bytesize: SupportsFloat) -> str: 105 | size = float(bytesize) 106 | unit = 0 107 | while size >= 1024.0 and unit < len(UNITS) - 1: 108 | unit += 1 109 | size /= 1024.0 110 | return "%.2f %s" % (size, UNITS[unit]) 111 | 112 | 113 | def parse_http_gettext(lang: str, charset: str) -> Callable[[str], str]: 114 | result = lambda x: x 115 | lang_match = INPUT_LANG_PARSER.match(lang) 116 | charset_match = INPUT_CHARSET_PARSER.match(charset) 117 | if lang_match and charset_match: 118 | try: 119 | result = gettext.translation( 120 | GETTEXT_DOMAIN, 121 | languages=[lang_match.group(1)] 122 | ).gettext 123 | except OSError: 124 | pass 125 | 126 | return result 127 | 128 | 129 | def parse_rpm_name(name: str) -> Dict[str, Any]: 130 | result = { 131 | "epoch": 0, 132 | "name": None, 133 | "version": "", 134 | "release": "", 135 | "arch": "", 136 | } 137 | (result["name"], 138 | result["version"], 139 | result["release"], 140 | result["epoch"], 141 | result["arch"]) = split_filename(name + ".mockarch.rpm") 142 | 143 | return result 144 | 145 | 146 | def response(start_response: Callable[[str, List[Tuple[str, str]]], None], 147 | status: str, 148 | body: Union[bytes, str] = "", 149 | extra_headers: Optional[List[Tuple[str, str]]] = None) -> List[bytes]: 150 | if isinstance(body, str): 151 | body = body.encode("utf-8") 152 | 153 | headers = [("Content-Type", "text/plain"), 154 | ("Content-Length", "%d" % len(body))] 155 | if extra_headers is not None: 156 | headers.extend(extra_headers) 157 | 158 | start_response(status, headers) 159 | return [body] 160 | 161 | 162 | def send_email(frm: str, to: Union[str, List[str]], subject: str, body: str) -> None: 163 | if isinstance(to, list): 164 | to = ",".join(to) 165 | 166 | msg = (f"From: {frm}\n" 167 | f"To: {to}\n" 168 | f"Subject: {subject}\n" 169 | f"\n" 170 | f"{body}") 171 | 172 | smtp = smtplib.SMTP("localhost") 173 | smtp.sendmail(frm, to, msg) 174 | smtp.close() 175 | 176 | 177 | def split_filename(filename: str) -> Union[Tuple[None, None, None, None, None], 178 | Tuple[str, str, str, str, str]]: 179 | """ 180 | Pass in a standard style rpm fullname 181 | 182 | Return a name, version, release, epoch, arch, e.g.:: 183 | foo-1.0-1.i386.rpm returns foo, 1.0, 1, i386 184 | """ 185 | 186 | if filename.endswith(".rpm"): 187 | filename = filename[:-4] 188 | 189 | subject = Subject(filename) 190 | possible_nevra = list(subject.get_nevra_possibilities(forms=FORM_NEVRA)) 191 | if possible_nevra: 192 | nevra = possible_nevra[0] 193 | else: 194 | return None, None, None, None, None 195 | 196 | return nevra.name, nevra.version, nevra.release, nevra.epoch, nevra.arch 197 | -------------------------------------------------------------------------------- /src/settings.wsgi: -------------------------------------------------------------------------------- 1 | from retrace.archive import get_supported_mime_types 2 | from retrace.config import Config 3 | from retrace.retrace import get_active_tasks, get_supported_releases 4 | from retrace.stats import save_crashstats_reportfull 5 | from retrace.util import response 6 | 7 | CONFIG = Config() 8 | 9 | def application(environ, start_response): 10 | activetasks = len(get_active_tasks()) 11 | if activetasks >= CONFIG["MaxParallelTasks"]: 12 | save_crashstats_reportfull(environ["REMOTE_ADDR"]) 13 | 14 | output = [ 15 | "running_tasks %d" % activetasks, 16 | "max_running_tasks %d" % CONFIG["MaxParallelTasks"], 17 | "max_packed_size %d" % CONFIG["MaxPackedSize"], 18 | "max_unpacked_size %d" % CONFIG["MaxUnpackedSize"], 19 | "supported_formats %s" % " ".join(get_supported_mime_types()), 20 | "supported_releases %s" % " ".join(get_supported_releases()), 21 | ] 22 | 23 | return response(start_response, "200 OK", "\n".join(output)) 24 | -------------------------------------------------------------------------------- /src/start.wsgi: -------------------------------------------------------------------------------- 1 | import urllib 2 | from webob import Request 3 | 4 | from retrace.retrace import RetraceTask 5 | from retrace.config import Config 6 | from retrace.util import URL_PARSER, parse_http_gettext, response 7 | 8 | CONFIG = Config() 9 | 10 | 11 | def application(environ, start_response): 12 | request = Request(environ) 13 | 14 | _ = parse_http_gettext("%s" % request.accept_language, 15 | "%s" % request.accept_charset) 16 | 17 | if CONFIG["RequireHTTPS"] and request.scheme != "https": 18 | return response(start_response, "403 Forbidden", 19 | _("You must use HTTPS")) 20 | 21 | match = URL_PARSER.match(request.script_name) 22 | if not match: 23 | return response(start_response, "404 Not Found", 24 | _("Invalid URL")) 25 | 26 | try: 27 | task = RetraceTask(int(match.group(1))) 28 | except Exception: 29 | return response(start_response, "404 Not Found", 30 | _("There is no such task")) 31 | 32 | qs = urllib.parse.parse_qs(request.query_string, keep_blank_values=True) 33 | 34 | debug = "debug" in qs 35 | 36 | kernelver = None 37 | if "kernelver" in qs: 38 | kernelver = qs["kernelver"][0] 39 | 40 | arch = None 41 | if "arch" in qs: 42 | arch = qs["arch"][0] 43 | 44 | task.start(debug=debug, kernelver=kernelver, arch=arch) 45 | 46 | return response(start_response, "201 Created") 47 | -------------------------------------------------------------------------------- /src/stats.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {_Retrace_Server_statistics} 7 | 75 | 76 | 77 |
78 |

{_Retrace_Server_statistics}

79 |

{host}

80 |
81 |
82 |

{_Releases}

83 | 84 | 85 | 86 | 87 | 88 | {release_rows} 89 |
{_Release}{_Count}
90 |

{_Retraced_packages}

91 | 92 | 93 | 94 | 95 | 96 | {retraced_rows} 97 |
{_Name}{_Count}
98 |
99 |
100 |

{_Global_statistics}

101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
{_Total}{total}
{_Successful}{success}
{_Failed}{fail}
{_First_retrace}{first}
{_Denied_jobs}{denied}
123 |

{_Architectures}

124 | 125 | 126 | 127 | 128 | 129 | {arch_rows} 130 |
{_Architecture}{_Count}
131 |

{_Required_packages}

132 | 133 | 134 | 135 | 136 | 137 | 138 | {required_rows} 139 |
{_Name}{_Versions}{_Count}
140 |
141 |
142 |
143 |

{_Missing_build-ids}

144 | 145 | 146 | 147 | 148 | 149 | 150 | {buildids_rows} 151 |
{_Build-id}{_Shared_object_name}{_Count}
152 |
153 |
154 | 155 | 156 | -------------------------------------------------------------------------------- /src/status.wsgi: -------------------------------------------------------------------------------- 1 | from webob import Request 2 | 3 | from retrace.retrace import STATUS, RetraceTask 4 | from retrace.config import Config 5 | from retrace.util import URL_PARSER, parse_http_gettext, response 6 | 7 | CONFIG = Config() 8 | 9 | 10 | def application(environ, start_response): 11 | request = Request(environ) 12 | 13 | _ = parse_http_gettext("%s" % request.accept_language, 14 | "%s" % request.accept_charset) 15 | 16 | if CONFIG["RequireHTTPS"] and request.scheme != "https": 17 | return response(start_response, "403 Forbidden", 18 | _("You must use HTTPS")) 19 | 20 | match = URL_PARSER.match(request.script_name) 21 | if not match: 22 | return response(start_response, "404 Not Found", 23 | _("Invalid URL")) 24 | 25 | try: 26 | task = RetraceTask(int(match.group(1))) 27 | except Exception: 28 | return response(start_response, "404 Not Found", 29 | _("There is no such task")) 30 | 31 | if "X-Task-Password" not in request.headers or \ 32 | not task.verify_password(request.headers["X-Task-Password"]): 33 | return response(start_response, "403 Forbidden", 34 | _("Invalid password")) 35 | 36 | status = "PENDING" 37 | if task.has_finished_time(): 38 | if task.has_backtrace(): 39 | status = "FINISHED_SUCCESS" 40 | else: 41 | status = "FINISHED_FAILURE" 42 | 43 | statusmsg = status 44 | try: 45 | statusmsg = _(STATUS[task.get_status()]) 46 | except Exception: 47 | pass 48 | 49 | return response(start_response, "200 OK", 50 | statusmsg, [("X-Task-Status", status)]) 51 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | # TODO: build_root() is deprecated since Meson 0.56.0. Port to 2 | # project_build_root() once the version lands in all supported 3 | # releases. 4 | build_dir = meson.build_root() 5 | # TODO: source_root() is deprecated since Meson 0.56.0. Port to 6 | # project_source_root() once the version lands in all supported 7 | # releases. 8 | source_dir = meson.source_root() 9 | 10 | test_env = environment() 11 | test_env.set('PYTHONPATH', 12 | join_paths(build_dir, 'src') 13 | ) 14 | test_env.prepend('PATH', 15 | join_paths(build_dir, 'src') 16 | ) 17 | test_env.set('RETRACE_SERVER_CONFIG_PATH', 18 | join_paths(source_dir, 'src', 'config', 'retrace-server.conf') 19 | ) 20 | test_env.set('RETRACE_SERVER_PLUGIN_DIR', 21 | join_paths(build_dir, 'src', 'plugins') 22 | ) 23 | test_env.set('RETRACE_SERVER_TESTING', '1') 24 | 25 | test_files = [ 26 | 'test_architecture.py', 27 | 'test_backends.py', 28 | 'test_util.py', 29 | ] 30 | 31 | foreach file: test_files 32 | configure_file( 33 | copy: true, 34 | input: file, 35 | output: file, 36 | ) 37 | endforeach 38 | 39 | pytest_args = [] 40 | if get_option('coverage') 41 | pytest_args += ['--cov=./', '--cov-report=xml'] 42 | endif 43 | 44 | test('Python unit tests', 45 | pytest, 46 | args: pytest_args, 47 | env: test_env, 48 | ) 49 | -------------------------------------------------------------------------------- /test/test_architecture.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from retrace.architecture import get_canon_arch 4 | 5 | 6 | class TestGetCanonArch(TestCase): 7 | def test_i386(self): 8 | self.assertEqual(get_canon_arch("i386"), "i386") 9 | self.assertEqual(get_canon_arch("i686"), "i386") 10 | 11 | def test_armhfp(self): 12 | self.assertEqual(get_canon_arch("armhfp"), "armhfp") 13 | self.assertEqual(get_canon_arch("armv7hl"), "armhfp") 14 | 15 | def test_x86_64(self): 16 | self.assertEqual(get_canon_arch("x86_64"), "x86_64") 17 | 18 | def test_ppc64(self): 19 | self.assertEqual(get_canon_arch("ppc64"), "ppc64") 20 | 21 | def test_aarch64(self): 22 | self.assertEqual(get_canon_arch("aarch64"), "aarch64") 23 | 24 | def test_unknown(self): 25 | self.assertEqual(get_canon_arch("unknown"), "unknown") 26 | -------------------------------------------------------------------------------- /test/test_backends.py: -------------------------------------------------------------------------------- 1 | from unittest import mock, TestCase 2 | 3 | # The first import is required to make mock work correctly. 4 | import retrace.backends.podman # pylint: disable=unused-import 5 | from retrace.backends.podman import LocalPodmanBackend, PodmanContainer 6 | from retrace.config import PODMAN_BIN 7 | from retrace.retrace import RetraceError 8 | 9 | 10 | class TestPodmanContainer(TestCase): 11 | CONTAINER_ID = "cefe728950018e14f00cc874d45259742e2925c6ab40e0044a4aedaf1514b660" 12 | 13 | def setUp(self): 14 | self.container = PodmanContainer(self.CONTAINER_ID) 15 | 16 | def test_short_id(self): 17 | self.assertEqual(self.container.short_id, "cefe728") 18 | 19 | @mock.patch("retrace.backends.podman.run") 20 | def test_copy_to(self, mock_run): 21 | mock_run.return_value.returncode = 0 22 | 23 | self.container.copy_to("/tmp/oldname", "/tmp/newname") 24 | 25 | mock_run.assert_called_once() 26 | self.assertTupleEqual(mock_run.call_args[0], 27 | ([PODMAN_BIN, "cp", "/tmp/oldname", 28 | f"{self.CONTAINER_ID}:/tmp/newname"],)) 29 | 30 | @mock.patch("retrace.backends.podman.run") 31 | def test_exec_no_user(self, mock_run): 32 | mock_run.return_value.returncode = 0 33 | 34 | self.container.exec(["uname", "-r"]) 35 | 36 | mock_run.assert_called_once() 37 | self.assertTupleEqual(mock_run.call_args[0], 38 | ([PODMAN_BIN, "exec", self.CONTAINER_ID, 39 | "uname", "-r"],)) 40 | 41 | @mock.patch("retrace.backends.podman.run") 42 | def test_exec_with_user(self, mock_run): 43 | mock_run.return_value.returncode = 0 44 | 45 | self.container.exec(["uname", "-r"], user="retrace") 46 | 47 | mock_run.assert_called_once() 48 | self.assertTupleEqual(mock_run.call_args[0], 49 | ([PODMAN_BIN, "exec", "--user=retrace", 50 | self.CONTAINER_ID, "uname", "-r"],)) 51 | 52 | @mock.patch("retrace.backends.podman.run") 53 | def test_stop_and_remove_success(self, mock_run): 54 | mock_run.return_value.returncode = 0 55 | 56 | self.container.stop_and_remove() 57 | 58 | mock_run.assert_called_once() 59 | 60 | @mock.patch("retrace.backends.podman.run") 61 | def test_stop_and_remove_failure(self, mock_run): 62 | mock_run.return_value.returncode = 1 63 | 64 | with self.assertRaises(RetraceError): 65 | self.container.stop_and_remove() 66 | 67 | mock_run.assert_called_once() 68 | 69 | 70 | @mock.patch("retrace.backends.podman.PodmanContainer") 71 | class TestLocalPodmanBackend(TestCase): 72 | CONTAINER_ID = "7d191875746407d0e279d34e8627eb20dec4016ca592bd6b7f05273867e4eb2f" 73 | 74 | @mock.patch("retrace.backends.podman.run") 75 | def test_start_container(self, mock_run, MockPodmanContainer): # pylint: disable=invalid-name 76 | config = { 77 | "FafLinkDir": "", 78 | "RequireGPGCheck": False, 79 | "UseFafPackages": False, 80 | } 81 | image_tag = "retrace-image-14141414" 82 | taskid = 14141414 83 | repopath = "/srv/retrace/repos/fedora-39-x86_64" 84 | 85 | mock_container = mock.Mock() 86 | MockPodmanContainer.return_value = mock_container 87 | mock_run.return_value.returncode = 0 88 | mock_run.return_value.stderr = "Something failed" 89 | mock_run.return_value.stdout = self.CONTAINER_ID 90 | 91 | backend = LocalPodmanBackend(retrace_config=config) 92 | container = backend.start_container(image_tag, taskid, repopath) 93 | 94 | self.assertEqual(container, mock_container) 95 | MockPodmanContainer.assert_called_once_with(self.CONTAINER_ID) 96 | -------------------------------------------------------------------------------- /test/test_util.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from retrace.util import human_readable_size 4 | 5 | 6 | class TestGetCanonArch(TestCase): 7 | def test_zero_bytes(self): 8 | self.assertEqual(human_readable_size(0), "0.00 B") 9 | 10 | def test_one_byte(self): 11 | self.assertEqual(human_readable_size(1), "1.00 B") 12 | 13 | def test_one_kilobyte(self): 14 | self.assertEqual(human_readable_size(1000), "1000.00 B") 15 | 16 | def test_one_kibibyte(self): 17 | self.assertEqual(human_readable_size(1024), "1.00 kB") 18 | 19 | def test_1025_bytes(self): 20 | self.assertEqual(human_readable_size(1025), "1.00 kB") 21 | 22 | def test_one_mebibyte(self): 23 | self.assertEqual(human_readable_size(1024 * 1024), "1.00 MB") 24 | 25 | def test_five_mebibytes(self): 26 | self.assertEqual(human_readable_size(5 * 1024 * 1024), "5.00 MB") 27 | 28 | def test_one_pebibyte(self): 29 | self.assertEqual(human_readable_size(1024 ** 5), "1.00 PB") 30 | --------------------------------------------------------------------------------