feat: Initial commit
This commit is contained in:
commit
17973e866b
44 changed files with 1444 additions and 0 deletions
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
* @ZPascal
|
||||||
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "docker"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/.idea/
|
||||||
201
LICENSE
Normal file
201
LICENSE
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
35
README.md
Normal file
35
README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Pretix Docker-Compose setup
|
||||||
|
The repository includes a [Pretix](https://pretix.eu/about/de/) docker-compose configuration for local development.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You can execute `docker-compose up -d --build --force-recreate` to start and build all related containers.
|
||||||
|
|
||||||
|
### Version information
|
||||||
|
|
||||||
|
| **Version** | **Description** |
|
||||||
|
|:-----------:|:----------------------------------------------------------------:|
|
||||||
|
| 1.2.0 | Includes PostgreSQL 17 |
|
||||||
|
| 1.1.1 | Update the Alpine version and the allocated IPs of the databases |
|
||||||
|
| 1.1.0 | Includes PostgreSQL 16 |
|
||||||
|
| 1.0.0 | Includes PostgreSQL 13 |
|
||||||
|
|
||||||
|
### Cronjobs
|
||||||
|
|
||||||
|
It is possible to adapt the `pretixuser` crontab entries by modifying the [crontab](docker/pretix/crontab) file.
|
||||||
|
|
||||||
|
## TLS setup
|
||||||
|
|
||||||
|
You can specify the used TLS certificates by adapting the mounted [certificate](docker/pretix/files/config/ssl/domain.crt) and [key](docker/pretix/files/config/ssl/domain.key) e.g. from Let's Encrypt or generating new self-signed certificates by following the [manual](scripts/EXAMPLE-CERT-CREATION.md) and moving the generated files. It is also possible to adapt the [used](docker/pretix/nginx/nginx.conf) Nginx configuration.
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
If you would like to contribute something, have an improvement request, or want to make a change inside the code, please open a pull request.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
If you need support, or you encounter a bug, please don't hesitate to open an issue.
|
||||||
|
|
||||||
|
## Donations
|
||||||
|
If you want to support my work, I ask you to take an unusual action inside the open source community. Donate the money to a non-profit organization like Doctors Without Borders or the Children's Cancer Aid. I will continue to build tools because I like them, and I am passionate about developing and sharing applications.
|
||||||
|
|
||||||
|
## License
|
||||||
|
This product is available under the Apache 2.0 license.
|
||||||
56
docker-compose.yml
Normal file
56
docker-compose.yml
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
container_name: pretix_app
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
context: ./docker/pretix
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
- cache
|
||||||
|
volumes:
|
||||||
|
- pretix_data:/data
|
||||||
|
- ./docker/pretix/pretix.cfg:/etc/pretix/pretix.cfg
|
||||||
|
- ./docker/pretix/nginx/nginx.conf:/etc/nginx/nginx.conf
|
||||||
|
- ./docker/pretix/crontab:/tmp/crontab
|
||||||
|
- ./plugin:/plugin
|
||||||
|
ports:
|
||||||
|
- "8081:80"
|
||||||
|
- "4434:443"
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
|
||||||
|
database:
|
||||||
|
image: postgres:17-alpine3.22
|
||||||
|
container_name: database
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=pretix
|
||||||
|
- POSTGRES_PASSWORD=pretix
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
|
||||||
|
cache:
|
||||||
|
image: redis:alpine3.22
|
||||||
|
container_name: redis
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
driver: local
|
||||||
|
pretix_data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
backend:
|
||||||
|
driver: bridge
|
||||||
|
driver_opts:
|
||||||
|
com.docker.network.bridge.host_binding_ipv4: "127.0.0.1"
|
||||||
19
docker/pretix/Dockerfile
Normal file
19
docker/pretix/Dockerfile
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
FROM pretix/standalone:stable
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
ENV IMAGE_CRON_DIR="/image/cron" \
|
||||||
|
IMAGE_CONFIG_DIR="/image/config"
|
||||||
|
|
||||||
|
ADD files /image
|
||||||
|
COPY crontab /tmp/crontab
|
||||||
|
|
||||||
|
RUN mv /image/supervisord/crond.conf /etc/supervisord/crond.conf && \
|
||||||
|
pip install crontab && chmod 644 $IMAGE_CONFIG_DIR/ssl/*.crt && chmod +x $IMAGE_CRON_DIR/cron.py
|
||||||
|
|
||||||
|
USER pretixuser
|
||||||
|
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
ENTRYPOINT ["pretix"]
|
||||||
|
CMD ["all"]
|
||||||
24
docker/pretix/crontab
Normal file
24
docker/pretix/crontab
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Edit this file to introduce tasks to be run by cron.
|
||||||
|
#
|
||||||
|
# Each task to run has to be defined through a single line
|
||||||
|
# indicating with different fields when the task will be run
|
||||||
|
# and what command to run for the task
|
||||||
|
#
|
||||||
|
# To define the time you can provide concrete values for
|
||||||
|
# minute (m), hour (h), day of month (dom), month (mon),
|
||||||
|
# and day of week (dow) or use '*' in these fields (for 'any').
|
||||||
|
#
|
||||||
|
# Notice that tasks will be started based on the cron's system
|
||||||
|
# daemon's notion of time and timezones.
|
||||||
|
#
|
||||||
|
# Output of the crontab jobs (including errors) is sent through
|
||||||
|
# email to the user the crontab file belongs to (unless redirected).
|
||||||
|
#
|
||||||
|
# For example, you can run a backup of all your user accounts
|
||||||
|
# at 5 a.m every week with:
|
||||||
|
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
|
||||||
|
#
|
||||||
|
# For more information see the manual pages of crontab(5) and cron(8)
|
||||||
|
#
|
||||||
|
# m h dom mon dow command
|
||||||
|
15,45 * * * * su pretixuser -c "PRETIX_CONFIG_FILE=/etc/pretix/pretix.cfg python -m pretix runperiodic"
|
||||||
0
docker/pretix/files/config/ssl/.placeholder
Normal file
0
docker/pretix/files/config/ssl/.placeholder
Normal file
0
docker/pretix/files/config/ssl/domain.crt
Normal file
0
docker/pretix/files/config/ssl/domain.crt
Normal file
0
docker/pretix/files/config/ssl/domain.key
Normal file
0
docker/pretix/files/config/ssl/domain.key
Normal file
224
docker/pretix/files/cron/cron.py
Normal file
224
docker/pretix/files/cron/cron.py
Normal file
|
|
@ -0,0 +1,224 @@
|
||||||
|
#!/usr/local/bin/python3
|
||||||
|
|
||||||
|
from crontab import CronTab
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_crontab(crontab_file: str) -> list:
|
||||||
|
"""The method includes a functionality to parse the crontab file, and it returns a list of CronTab jobs
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
crontab_file -> Specify the inserted crontab file
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger = logging.getLogger("parser")
|
||||||
|
|
||||||
|
logger.info(f"Reading crontab from {crontab_file}")
|
||||||
|
|
||||||
|
if not os.path.isfile(crontab_file):
|
||||||
|
logger.error(f"Crontab {crontab_file} does not exist. Exiting!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
with open(crontab_file, "r") as crontab:
|
||||||
|
lines: list = crontab.readlines()
|
||||||
|
|
||||||
|
logger.info(f"{len(lines)} lines read from crontab {crontab_file}")
|
||||||
|
|
||||||
|
jobs: list = list()
|
||||||
|
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
line: str = line.strip()
|
||||||
|
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"Parsing line {line}")
|
||||||
|
|
||||||
|
expression: list = line.split(" ", 5)
|
||||||
|
cron_expression: str = " ".join(expression[0:5])
|
||||||
|
|
||||||
|
logger.info(f"Cron expression is {cron_expression}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
cron_entry = CronTab(cron_expression)
|
||||||
|
except ValueError as e:
|
||||||
|
logger.critical(
|
||||||
|
f"Unable to parse crontab. Line {i + 1}: Illegal cron expression {cron_expression}. Error message: {e}"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
command: str = expression[5]
|
||||||
|
|
||||||
|
logger.info(f"Command is {command}")
|
||||||
|
|
||||||
|
jobs.append([cron_entry, command])
|
||||||
|
|
||||||
|
if len(jobs) == 0:
|
||||||
|
logger.error(
|
||||||
|
"Specified crontab does not contain any scheduled execution. Exiting!"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
return jobs
|
||||||
|
|
||||||
|
|
||||||
|
def _get_next_executions(jobs: list):
|
||||||
|
"""The method includes a functionality to extract the execution time and job itself from the submitted job list
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
jobs -> Specify the inserted list of jobs
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger = logging.getLogger("next-exec")
|
||||||
|
|
||||||
|
scheduled_executions: tuple = tuple(
|
||||||
|
(x[1], int(x[0].next(default_utc=True)) + 1) for x in jobs
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(f"Next executions of scheduled are {scheduled_executions}")
|
||||||
|
|
||||||
|
next_exec_time: int = int(min(scheduled_executions, key=lambda x: x[1])[1])
|
||||||
|
|
||||||
|
logger.debug(f"Next execution is in {next_exec_time} second(s)")
|
||||||
|
|
||||||
|
next_commands: list = [x[0] for x in scheduled_executions if x[1] == next_exec_time]
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Next commands to be executed in {next_exec_time} are {next_commands}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return next_exec_time, next_commands
|
||||||
|
|
||||||
|
|
||||||
|
def _loop(jobs: list, test_mode: bool = False):
|
||||||
|
"""The method includes a functionality to loop over all jobs inside the crontab file and execute them
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
jobs -> Specify the inserted jobs as list
|
||||||
|
test_mode -> Specify if you want to use the test mode or not (default False)
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger = logging.getLogger("loop")
|
||||||
|
|
||||||
|
logger.info("Entering main loop")
|
||||||
|
|
||||||
|
if test_mode is False:
|
||||||
|
while True:
|
||||||
|
sleep_time, commands = _get_next_executions(jobs)
|
||||||
|
|
||||||
|
logger.debug(f"Sleeping for {sleep_time} second(s)")
|
||||||
|
|
||||||
|
if sleep_time <= 1:
|
||||||
|
logger.debug("Sleep time <= 1 second, ignoring.")
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
|
for command in commands:
|
||||||
|
_execute_command(command)
|
||||||
|
else:
|
||||||
|
sleep_time, commands = _get_next_executions(jobs)
|
||||||
|
|
||||||
|
logger.debug(f"Sleeping for {sleep_time} second(s)")
|
||||||
|
|
||||||
|
if sleep_time <= 1:
|
||||||
|
logger.debug("Sleep time <= 1 second, ignoring.")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
|
for command in commands:
|
||||||
|
_execute_command(command)
|
||||||
|
|
||||||
|
|
||||||
|
def _execute_command(command: str):
|
||||||
|
"""The method includes a functionality to execute a crontab command
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
command -> Specify the inserted command for the execution
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger = logging.getLogger("exec")
|
||||||
|
|
||||||
|
logger.info(f"Executing command {command}")
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Standard output: {result.stdout}")
|
||||||
|
logger.info(f"Standard error: {result.stderr}")
|
||||||
|
|
||||||
|
|
||||||
|
def _signal_handler():
|
||||||
|
"""The method includes a functionality for the signal handler to exit a process"""
|
||||||
|
|
||||||
|
logger = logging.getLogger("signal")
|
||||||
|
logger.info("Exiting")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""The method includes a functionality to control and execute crontab entries
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
-c -> Specify the inserted crontab file
|
||||||
|
-L -> Specify the inserted log file
|
||||||
|
-C -> Specify the if the output should be forwarded to the console
|
||||||
|
-l -> Specify the log level
|
||||||
|
"""
|
||||||
|
signal.signal(signal.SIGINT, _signal_handler)
|
||||||
|
signal.signal(signal.SIGTERM, _signal_handler)
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="cron")
|
||||||
|
parser.add_argument("-c", "--crontab", required=True, type=str)
|
||||||
|
logging_target = parser.add_mutually_exclusive_group(required=True)
|
||||||
|
logging_target.add_argument("-L", "--logfile", type=str)
|
||||||
|
logging_target.add_argument("-C", "--console", action="store_true")
|
||||||
|
parser.add_argument(
|
||||||
|
"-l",
|
||||||
|
"--loglevel",
|
||||||
|
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
||||||
|
default="INFO",
|
||||||
|
type=str,
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
log_level = getattr(logging, args.loglevel.upper(), logging.INFO)
|
||||||
|
|
||||||
|
if args.console:
|
||||||
|
logging.basicConfig(
|
||||||
|
filemode="w",
|
||||||
|
level=log_level,
|
||||||
|
format="%(asctime)s %(name)-12s %(levelname)-8s %(message)s",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logging.basicConfig(
|
||||||
|
filename=args.logfile,
|
||||||
|
filemode="a+",
|
||||||
|
level=log_level,
|
||||||
|
format="%(asctime)s %(name)-12s %(levelname)-8s %(message)s",
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger("main")
|
||||||
|
|
||||||
|
logger.info("Starting cron")
|
||||||
|
|
||||||
|
jobs: list = _parse_crontab(args.crontab)
|
||||||
|
|
||||||
|
_loop(jobs)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
7
docker/pretix/files/supervisord/crond.conf
Normal file
7
docker/pretix/files/supervisord/crond.conf
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
[program:crond]
|
||||||
|
command = %(ENV_IMAGE_CRON_DIR)s/cron.py --crontab /tmp/crontab --loglevel INFO --logfile /var/log/crond.log
|
||||||
|
autostart = true
|
||||||
|
redirect_stderr = true
|
||||||
|
stdout_logfile = /var/log/crond.log
|
||||||
|
stdout_logfile_maxbytes = 1MB
|
||||||
|
stdout_logfile_backups = 2
|
||||||
85
docker/pretix/nginx/nginx.conf
Normal file
85
docker/pretix/nginx/nginx.conf
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
user www-data www-data;
|
||||||
|
worker_processes auto;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
daemon off;
|
||||||
|
worker_rlimit_nofile 262144;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 16384;
|
||||||
|
multi_accept on;
|
||||||
|
use epoll;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
server_tokens off;
|
||||||
|
sendfile on;
|
||||||
|
charset utf-8;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
|
||||||
|
log_format private '[$time_local] $host "$request" $status $body_bytes_sent';
|
||||||
|
|
||||||
|
types_hash_max_size 2048;
|
||||||
|
server_names_hash_bucket_size 64;
|
||||||
|
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log private;
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
add_header Referrer-Policy same-origin;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_disable "msie6";
|
||||||
|
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/javascript text/xml application/xml application/rss+xml application/atom+xml application/rdf+xml image/svg+xml;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_buffers 16 8k;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80 backlog=4096 default_server;
|
||||||
|
listen [::]:80 ipv6only=on default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
index index.php index.html;
|
||||||
|
root /var/www;
|
||||||
|
|
||||||
|
location /media/ {
|
||||||
|
alias /data/media/;
|
||||||
|
expires 7d;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
location ^~ /media/cachedfiles {
|
||||||
|
deny all;
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
location ^~ /media/invoices {
|
||||||
|
deny all;
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
location /static/ {
|
||||||
|
alias /pretix/src/pretix/static.dist/;
|
||||||
|
access_log off;
|
||||||
|
expires 365d;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
add_header Access-Control-Allow-Origin "*";
|
||||||
|
gzip on;
|
||||||
|
}
|
||||||
|
location / {
|
||||||
|
# Very important:
|
||||||
|
# proxy_pass http://unix:/tmp/pretix.sock:;
|
||||||
|
# is not the same as
|
||||||
|
# proxy_pass http://unix:/tmp/pretix.sock:/;
|
||||||
|
# In the latter case, nginx will apply its URL parsing, in the former it doesn't.
|
||||||
|
# There are situations in which pretix' API will deal with "file names" containing %2F%2F, which
|
||||||
|
# nginx will normalize to %2F, which can break ticket validation.
|
||||||
|
proxy_pass http://unix:/tmp/pretix.sock:;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
docker/pretix/pretix.cfg
Normal file
38
docker/pretix/pretix.cfg
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
[pretix]
|
||||||
|
instance_name=localhost
|
||||||
|
url=http://localhost
|
||||||
|
currency=EUR
|
||||||
|
; DO NOT change the following value, it has to be set to the location of the
|
||||||
|
; directory *inside* the docker container
|
||||||
|
datadir=/data
|
||||||
|
registration=off
|
||||||
|
|
||||||
|
[locale]
|
||||||
|
default=de
|
||||||
|
timezone=Europe/Berlin
|
||||||
|
|
||||||
|
[database]
|
||||||
|
backend=postgresql
|
||||||
|
name=pretix
|
||||||
|
user=pretix
|
||||||
|
password=pretix
|
||||||
|
host=database
|
||||||
|
|
||||||
|
[mail]
|
||||||
|
from=FROM_MAIL
|
||||||
|
host=MAIL_SERVER
|
||||||
|
user=USERNAME
|
||||||
|
password=FOOBAR
|
||||||
|
port=587
|
||||||
|
tls=off
|
||||||
|
ssl=off
|
||||||
|
|
||||||
|
[redis]
|
||||||
|
location=redis://cache/0
|
||||||
|
; Remove the following line if you are unsure about your redis'security
|
||||||
|
; to reduce impact if redis gets compromised.
|
||||||
|
sessions=true
|
||||||
|
|
||||||
|
[celery]
|
||||||
|
backend=redis://cache/1
|
||||||
|
broker=redis://cache/2
|
||||||
62
plugin/.gitignore
vendored
Normal file
62
plugin/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
.ropeproject/
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*,cover
|
||||||
|
.hypothesis/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
#Ipython Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
17
plugin/.gitlab-ci.yml
Normal file
17
plugin/.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
pypi:
|
||||||
|
image:
|
||||||
|
name: pretix/ci-image
|
||||||
|
before_script:
|
||||||
|
- cat $PYPIRC > ~/.pypirc
|
||||||
|
- pip install -U pip uv
|
||||||
|
- uv pip install --system -U wheel setuptools twine build pretix-plugin-build check-manifest
|
||||||
|
script:
|
||||||
|
- python -m build
|
||||||
|
- check-manifest .
|
||||||
|
- twine check dist/*
|
||||||
|
- twine upload dist/*
|
||||||
|
only:
|
||||||
|
- pypi
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- dist/
|
||||||
37
plugin/.update-locales
Executable file
37
plugin/.update-locales
Executable file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/bin/sh
|
||||||
|
COMPONENTS=pretix/pretix-plugin-congressschedule pretix/pretix-plugin-congressschedule-js
|
||||||
|
DIR=pretix_congressschedule/locale
|
||||||
|
# Renerates .po files used for translating the plugin
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
# Lock Weblate
|
||||||
|
for c in $COMPONENTS; do
|
||||||
|
wlc lock $c;
|
||||||
|
done
|
||||||
|
|
||||||
|
# Push changes from Weblate to GitHub
|
||||||
|
for c in $COMPONENTS; do
|
||||||
|
wlc commit $c;
|
||||||
|
done
|
||||||
|
|
||||||
|
# Pull changes from GitHub
|
||||||
|
git pull --rebase
|
||||||
|
|
||||||
|
# Update po files itself
|
||||||
|
make localegen
|
||||||
|
|
||||||
|
# Commit changes
|
||||||
|
git add $DIR/*/*/*.po
|
||||||
|
git add $DIR/*.pot
|
||||||
|
|
||||||
|
git commit -s -m "Update po files
|
||||||
|
[CI skip]"
|
||||||
|
|
||||||
|
# Push changes
|
||||||
|
git push
|
||||||
|
|
||||||
|
# Unlock Weblate
|
||||||
|
for c in $COMPONENTS; do
|
||||||
|
wlc unlock $c;
|
||||||
|
done
|
||||||
13
plugin/LICENSE
Normal file
13
plugin/LICENSE
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright 2025 Vincent 'ViMaSter' Mahnke
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
3
plugin/MANIFEST.in
Normal file
3
plugin/MANIFEST.in
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
recursive-include pretix_congressschedule/static *
|
||||||
|
recursive-include pretix_congressschedule/templates *
|
||||||
|
recursive-include pretix_congressschedule/locale *
|
||||||
10
plugin/Makefile
Normal file
10
plugin/Makefile
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
all: localecompile
|
||||||
|
LNGS:=`find pretix_congressschedule/locale/ -mindepth 1 -maxdepth 1 -type d -printf "-l %f "`
|
||||||
|
|
||||||
|
localecompile:
|
||||||
|
django-admin compilemessages
|
||||||
|
|
||||||
|
localegen:
|
||||||
|
django-admin makemessages --keep-pot -i build -i dist -i "*egg*" $(LNGS)
|
||||||
|
django-admin makemessages -d djangojs --keep-pot -i build -i dist -i "*egg*" $(LNGS)
|
||||||
|
|
||||||
46
plugin/README.rst
Normal file
46
plugin/README.rst
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
pretix-congressschedule
|
||||||
|
=======================
|
||||||
|
|
||||||
|
This is a plugin for `pretix`_. It generates a `c3voc-schema`_ compatible `schedule.xml` endpoint for events.
|
||||||
|
|
||||||
|
Accessing schedule.xml
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
1. Create an `event-series`_ in pretix; a singular event or non-event shop will not work, as products won't have required start and end times associated with them
|
||||||
|
|
||||||
|
2. Visit `/api/v1/event/{organizationSlug}/{eventSlug}/schedule.xml` and replace `{organizationSlug}` and `{eventSlug}` with the respective slugs
|
||||||
|
|
||||||
|
3. Receive either a 200 status code with an XML document adhering to `schedule.xml.xsd`_ or a 400 error code with additional information inside `<error>`
|
||||||
|
|
||||||
|
|
||||||
|
Development setup
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
1. Make sure that you have a working `pretix development setup`_.
|
||||||
|
|
||||||
|
2. Clone this repository, eg to ``local/pretix-congressschedule``.
|
||||||
|
|
||||||
|
3. Activate the virtual environment you use for pretix development.
|
||||||
|
|
||||||
|
4. Execute ``pip install -e .`` within this directory to register this application with pretix's plugin registry.
|
||||||
|
|
||||||
|
5. Execute ``make`` within this directory to compile translations.
|
||||||
|
|
||||||
|
6. Restart your local pretix server. You can now use the plugin from this repository for your events by enabling it in
|
||||||
|
the 'plugins' tab in the settings.
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Copyright 2025 Vincent 'ViMaSter' Mahnke
|
||||||
|
|
||||||
|
Released under the terms of the Apache License 2.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. _pretix: https://github.com/pretix/pretix
|
||||||
|
.. _pretix development setup: https://docs.pretix.eu/en/latest/development/setup.html
|
||||||
|
.. _c3voc-schema: https://c3voc.de/wiki/schedule#schedule_xml
|
||||||
|
.. _schedule.xml.xsd: https://c3voc.de/schedule/schema.xsd
|
||||||
|
.. _event-series: https://docs.pretix.eu/guides/event-series/?h=dates#how-to-create-an-event-series
|
||||||
1
plugin/pretix_congressschedule/__init__.py
Normal file
1
plugin/pretix_congressschedule/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
__version__ = "1.0.0"
|
||||||
193
plugin/pretix_congressschedule/api.py
Normal file
193
plugin/pretix_congressschedule/api.py
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from rest_framework import views
|
||||||
|
from pretix.base.models import Event, SubEvent
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from collections import defaultdict
|
||||||
|
from datetime import timedelta
|
||||||
|
import uuid
|
||||||
|
import re
|
||||||
|
|
||||||
|
from . import __version__
|
||||||
|
|
||||||
|
class CongressScheduleView(views.APIView):
|
||||||
|
def get(self, request, organizer, event, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
ev = Event.objects.get(organizer__slug=organizer, slug=event)
|
||||||
|
except Event.DoesNotExist:
|
||||||
|
return HttpResponse(b'<?xml version="1.0"?><error>Event not found</error>', status=404, content_type='application/xml')
|
||||||
|
|
||||||
|
if not ev.has_subevents:
|
||||||
|
return HttpResponse(
|
||||||
|
b'<?xml version="1.0"?><error>Event is not an event-series: https://docs.pretix.eu/guides/event-series/?h=dates#how-to-create-an-event-series</error>',
|
||||||
|
status=400,
|
||||||
|
content_type='application/xml'
|
||||||
|
)
|
||||||
|
|
||||||
|
subs = SubEvent.objects.filter(event=ev).order_by('date_from')
|
||||||
|
|
||||||
|
root = ET.Element('schedule')
|
||||||
|
|
||||||
|
gen = ET.SubElement(root, 'generator')
|
||||||
|
gen.set('name', 'pretix-congressschedule')
|
||||||
|
gen.set('version', __version__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
feed_url = request.build_absolute_uri()
|
||||||
|
ET.SubElement(root, 'url').text = feed_url
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Version string – keep simple and stable per event
|
||||||
|
ET.SubElement(root, 'version').text = f"{ev.slug}-v1"
|
||||||
|
|
||||||
|
conf = ET.SubElement(root, 'conference')
|
||||||
|
conf_title = ev.name.localize(ev.settings.locale) if hasattr(ev.name, 'localize') else str(ev.name)
|
||||||
|
ET.SubElement(conf, 'title').text = conf_title or str(ev.slug)
|
||||||
|
acronym = f"{organizer}_{event}".lower()
|
||||||
|
ET.SubElement(conf, 'acronym').text = acronym
|
||||||
|
|
||||||
|
# start/end/days based on subevents if available, else fall back to event
|
||||||
|
all_starts = [se.date_from for se in subs if se.date_from]
|
||||||
|
all_ends = [se.date_to for se in subs if se.date_to]
|
||||||
|
|
||||||
|
if all_starts:
|
||||||
|
ET.SubElement(conf, 'start').text = min(all_starts).isoformat()
|
||||||
|
if all_ends:
|
||||||
|
ET.SubElement(conf, 'end').text = max(all_ends).isoformat()
|
||||||
|
|
||||||
|
# days count – unique calendar days from subevents
|
||||||
|
unique_days = sorted({(se.date_from.date() if se.date_from else None) for se in subs} - {None})
|
||||||
|
if unique_days:
|
||||||
|
ET.SubElement(conf, 'days').text = str(len(unique_days))
|
||||||
|
|
||||||
|
# time zone name – try Event.timezone or settings
|
||||||
|
tz_name = getattr(ev, 'timezone', None) or getattr(ev.settings, 'timezone', None)
|
||||||
|
if tz_name:
|
||||||
|
tz_text = tz_name if isinstance(tz_name, str) else str(tz_name)
|
||||||
|
ET.SubElement(conf, 'time_zone_name').text = tz_text
|
||||||
|
|
||||||
|
# Group subevents into days and rooms
|
||||||
|
# days: {date -> {room_name -> [subevents]}}
|
||||||
|
days: dict = defaultdict(lambda: defaultdict(list))
|
||||||
|
|
||||||
|
def get_room_name(se):
|
||||||
|
# Try SubEvent.location if present, else fallback to `Main`
|
||||||
|
loc = getattr(se, 'location', None)
|
||||||
|
if hasattr(loc, 'localize'):
|
||||||
|
try:
|
||||||
|
txt = loc.localize(ev.settings.locale)
|
||||||
|
except Exception:
|
||||||
|
txt = str(loc)
|
||||||
|
else:
|
||||||
|
txt = str(loc) if loc else ''
|
||||||
|
return (txt or 'Main').strip() or 'Main'
|
||||||
|
|
||||||
|
for se in subs:
|
||||||
|
if not se.date_from:
|
||||||
|
# Skip entries without a start
|
||||||
|
continue
|
||||||
|
day_key = se.date_from.date()
|
||||||
|
room = get_room_name(se)
|
||||||
|
days[day_key][room].append(se)
|
||||||
|
|
||||||
|
# Emit <day> elements in chronological order
|
||||||
|
for day_index, (day_date, rooms) in enumerate(sorted(days.items()), start=1):
|
||||||
|
# Compute day start/end from all events this day
|
||||||
|
starts = [se.date_from for r in rooms.values() for se in r if se.date_from]
|
||||||
|
ends = [se.date_to for r in rooms.values() for se in r if se.date_to]
|
||||||
|
day_start = min(starts) if starts else None
|
||||||
|
# If end is missing for any, approximate using +0 duration => start
|
||||||
|
if ends:
|
||||||
|
day_end = max(ends)
|
||||||
|
else:
|
||||||
|
day_end = (day_start + timedelta(minutes=0)) if day_start else None
|
||||||
|
|
||||||
|
day_el = ET.SubElement(root, 'day')
|
||||||
|
if day_date:
|
||||||
|
day_el.set('date', day_date.isoformat())
|
||||||
|
if day_start:
|
||||||
|
day_el.set('start', day_start.isoformat())
|
||||||
|
if day_end:
|
||||||
|
day_el.set('end', day_end.isoformat())
|
||||||
|
day_el.set('index', str(day_index))
|
||||||
|
|
||||||
|
# Emit <room> containers
|
||||||
|
for room_name, events_in_room in sorted(rooms.items(), key=lambda x: x[0].lower()):
|
||||||
|
room_el = ET.SubElement(day_el, 'room')
|
||||||
|
room_el.set('name', room_name)
|
||||||
|
# Optional guid on room – stable UUID5 based on names
|
||||||
|
room_el.set('guid', str(uuid.uuid5(uuid.NAMESPACE_DNS, f"room:{organizer}:{event}:{room_name}")))
|
||||||
|
|
||||||
|
# Emit each <event> in chronological order within the room
|
||||||
|
for se in sorted(events_in_room, key=lambda s: s.date_from or 0):
|
||||||
|
ev_el = ET.SubElement(room_el, 'event')
|
||||||
|
ev_el.set('id', str(se.pk))
|
||||||
|
ev_el.set('guid', str(uuid.uuid5(uuid.NAMESPACE_DNS, f"subevent:{ev.pk}:{se.pk}")))
|
||||||
|
|
||||||
|
# Helper: localize strings
|
||||||
|
def _localize(val):
|
||||||
|
if hasattr(val, 'localize'):
|
||||||
|
try:
|
||||||
|
return val.localize(ev.settings.locale)
|
||||||
|
except Exception:
|
||||||
|
return str(val)
|
||||||
|
return str(val) if val is not None else ''
|
||||||
|
|
||||||
|
# Required children according to schema
|
||||||
|
ET.SubElement(ev_el, 'room').text = room_name
|
||||||
|
title = _localize(se.name)
|
||||||
|
ET.SubElement(ev_el, 'title').text = title
|
||||||
|
ET.SubElement(ev_el, 'subtitle').text = ''
|
||||||
|
ET.SubElement(ev_el, 'type').text = 'subevent'
|
||||||
|
|
||||||
|
# date (full datetime with TZ)
|
||||||
|
if se.date_from:
|
||||||
|
ET.SubElement(ev_el, 'date').text = se.date_from.isoformat()
|
||||||
|
|
||||||
|
# start (HH:MM or HH:MM:SS)
|
||||||
|
if se.date_from:
|
||||||
|
ET.SubElement(ev_el, 'start').text = se.date_from.strftime('%H:%M')
|
||||||
|
|
||||||
|
# duration from date_to - date_from
|
||||||
|
dur_txt = '00:00'
|
||||||
|
if se.date_from and se.date_to and se.date_to >= se.date_from:
|
||||||
|
delta: timedelta = se.date_to - se.date_from
|
||||||
|
total_seconds = int(delta.total_seconds())
|
||||||
|
hours = total_seconds // 3600
|
||||||
|
minutes = (total_seconds % 3600) // 60
|
||||||
|
seconds = total_seconds % 60
|
||||||
|
# prefer HH:MM if no seconds, else HH:MM:SS
|
||||||
|
if seconds == 0:
|
||||||
|
dur_txt = f"{hours:02d}:{minutes:02d}"
|
||||||
|
else:
|
||||||
|
dur_txt = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||||
|
ET.SubElement(ev_el, 'duration').text = dur_txt
|
||||||
|
|
||||||
|
ET.SubElement(ev_el, 'abstract').text = ''
|
||||||
|
|
||||||
|
# slug (pattern: "[a-z0-9_]{4,}-[a-z0-9\-_]{4,}")
|
||||||
|
def slugify(text: str) -> str:
|
||||||
|
text = (text or '').lower()
|
||||||
|
text = re.sub(r'\s+', '-', text)
|
||||||
|
text = re.sub(r'[^a-z0-9\-_]', '', text)
|
||||||
|
text = text.strip('-_')
|
||||||
|
return text or 'item'
|
||||||
|
|
||||||
|
base = f"{organizer}_{event}".lower()
|
||||||
|
second = slugify(title)
|
||||||
|
if len(second) < 4:
|
||||||
|
second = f"{second}-{se.pk}"
|
||||||
|
ET.SubElement(ev_el, 'slug').text = f"{base}-{second}"
|
||||||
|
|
||||||
|
# track – use room name as a simple track assignment
|
||||||
|
ET.SubElement(ev_el, 'track').text = slugify(room_name) or 'general'
|
||||||
|
|
||||||
|
# Optional elements: keep minimal but include language if available
|
||||||
|
lang = getattr(ev.settings, 'locale', None)
|
||||||
|
if lang:
|
||||||
|
ET.SubElement(ev_el, 'language').text = str(lang)
|
||||||
|
|
||||||
|
# Leave optional complex children (persons, recording, links, attachments) empty for now
|
||||||
|
|
||||||
|
xml_bytes = ET.tostring(root, encoding='utf-8', xml_declaration=True)
|
||||||
|
return HttpResponse(xml_bytes, content_type='application/xml')
|
||||||
43
plugin/pretix_congressschedule/apps.py
Normal file
43
plugin/pretix_congressschedule/apps.py
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.translation import gettext_lazy
|
||||||
|
|
||||||
|
from . import __version__
|
||||||
|
|
||||||
|
|
||||||
|
class PassbookApp(AppConfig):
|
||||||
|
name = "pretix_congressschedule"
|
||||||
|
verbose_name = "Congress Schedule"
|
||||||
|
|
||||||
|
class PretixPluginMeta:
|
||||||
|
name = gettext_lazy("Congress Schedule")
|
||||||
|
author = "Vincent Mahnke"
|
||||||
|
description = gettext_lazy("Provides passbook tickets for pretix")
|
||||||
|
category = "API"
|
||||||
|
visible = True
|
||||||
|
featured = True
|
||||||
|
version = __version__
|
||||||
|
compatibility = "pretix>=4.17.0"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from . import signals # NOQA
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def compatibility_errors(self):
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
errs = []
|
||||||
|
if not shutil.which("openssl"):
|
||||||
|
errs.append("The OpenSSL binary is not installed or not in the PATH.")
|
||||||
|
return errs
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def compatibility_warnings(self):
|
||||||
|
errs = []
|
||||||
|
try:
|
||||||
|
from PIL import Image # NOQA
|
||||||
|
except ImportError:
|
||||||
|
errs.append(
|
||||||
|
"Pillow is not installed on this system, which is required for converting and scaling images."
|
||||||
|
)
|
||||||
|
return errs
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# pretix-congressschedule
|
||||||
|
# Copyright (C) 2025 Vincent 'ViMaSter' Mahnke
|
||||||
|
# This file is distributed under the same license as the pretix-congressschedule package.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2019-08-16 15:52+0200\n"
|
||||||
|
"PO-Revision-Date: 2022-03-21 15:45+0000\n"
|
||||||
|
"Last-Translator: Vincent 'ViMaSter' Mahnke <pretix-congress@vincent.mahn.ke>\n"
|
||||||
|
"Language-Team: German <https://translate.pretix.eu/projects/pretix/"
|
||||||
|
"pretix-plugin-congressschedule/de/>\n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
|
"X-Generator: Weblate 4.11.2\n"
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2019-08-16 15:52+0200\n"
|
||||||
|
"PO-Revision-Date: 2017-05-10 13:48+0200\n"
|
||||||
|
"Last-Translator: Vincent 'ViMaSter' Mahnke <pretix-congress@vincent.mahn.ke>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 2.0.1\n"
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# pretix-congressschedule
|
||||||
|
# Copyright (C) 2025 Vincent 'ViMaSter' Mahnke
|
||||||
|
# This file is distributed under the same license as the pretix-congressschedule package.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2019-08-16 15:52+0200\n"
|
||||||
|
"PO-Revision-Date: 2022-03-21 15:45+0000\n"
|
||||||
|
"Last-Translator: Vincent 'ViMaSter' Mahnke <pretix-congress@vincent.mahn.ke>\n"
|
||||||
|
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||||
|
"pretix/pretix-plugin-congressschedule/de_Informal/>\n"
|
||||||
|
"Language: de_Informal\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
|
"X-Generator: Weblate 4.11.2\n"
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2019-08-16 15:52+0200\n"
|
||||||
|
"PO-Revision-Date: 2017-05-10 13:48+0200\n"
|
||||||
|
"Last-Translator: Vincent 'ViMaSter' Mahnke <pretix-congress@vincent.mahn.ke>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 2.0.1\n"
|
||||||
26
plugin/pretix_congressschedule/locale/django.pot
Normal file
26
plugin/pretix_congressschedule/locale/django.pot
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2019-08-16 15:52+0200\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: pretix_congressschedule/__init__.py:11
|
||||||
|
msgid "Congress Schedule"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pretix_congressschedule/__init__.py:13
|
||||||
|
msgid "Provides passbook tickets for pretix"
|
||||||
|
msgstr ""
|
||||||
32
plugin/pretix_congressschedule/locale/djangojs.pot
Normal file
32
plugin/pretix_congressschedule/locale/djangojs.pot
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2019-08-16 15:52+0200\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: pretix_congressschedule/static/pretix_congressschedule/geosuggest.js:4
|
||||||
|
msgid "Loading suggested geolocations…"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pretix_congressschedule/static/pretix_congressschedule/geosuggest.js:13
|
||||||
|
msgid ""
|
||||||
|
"Click on one of the following suggestions to fill in the coordinates "
|
||||||
|
"automatically:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: pretix_congressschedule/static/pretix_congressschedule/geosuggest.js:35
|
||||||
|
msgid "Error while loading suggested geolocations."
|
||||||
|
msgstr ""
|
||||||
0
plugin/pretix_congressschedule/signals.py
Normal file
0
plugin/pretix_congressschedule/signals.py
Normal file
10
plugin/pretix_congressschedule/urls.py
Normal file
10
plugin/pretix_congressschedule/urls.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
from django.urls import path
|
||||||
|
from .api import CongressScheduleView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
'api/v1/event/<str:organizer>/<str:event>/schedule.xml',
|
||||||
|
CongressScheduleView.as_view(),
|
||||||
|
name='schedule-xml',
|
||||||
|
),
|
||||||
|
]
|
||||||
10
plugin/pretixplugin.toml
Normal file
10
plugin/pretixplugin.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# This file is used by the pretix team internally to coordinate releases of this plugin
|
||||||
|
[plugin]
|
||||||
|
package = "pretix-congressschedule"
|
||||||
|
modules = [ "pretix_congressschedule" ]
|
||||||
|
marketplace_name = "congressschedule"
|
||||||
|
pypi = true
|
||||||
|
repository_servers = { origin = "github.com", gitlab = "code.rami.io" }
|
||||||
|
tag_targets = [ "origin", "gitlab" ]
|
||||||
|
branch_targets = [ "origin/master", "gitlab/master", "f:gitlab/pypi" ]
|
||||||
|
|
||||||
39
plugin/pyproject.toml
Normal file
39
plugin/pyproject.toml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
[project]
|
||||||
|
name = "pretix-congressschedule"
|
||||||
|
dynamic = ["version"]
|
||||||
|
description = "c3voc schedule-compatible schedule.xml endpoint"
|
||||||
|
readme = "README.rst"
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
license = {file = "LICENSE"}
|
||||||
|
keywords = ["pretix"]
|
||||||
|
authors = [
|
||||||
|
{name = "Vincent Mahnke", email = "pretix-congress@vincent.mahn.ke"},
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{name = "Vincent Mahnke", email = "pretix-congress@vincent.mahn.ke"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.entry-points."pretix.plugin"]
|
||||||
|
congressschedule = "pretix_congressschedule:PretixPluginMeta"
|
||||||
|
|
||||||
|
[project.entry-points."distutils.commands"]
|
||||||
|
build = "pretix_plugin_build.build:CustomBuild"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = [
|
||||||
|
"setuptools",
|
||||||
|
"pretix-plugin-build"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
homepage = "https://git.hamburg.ccc.de/ViMaSter/pretix-congressschedule"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
include-package-data = true
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
version = {attr = "pretix_congressschedule.__version__"}
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["pretix*"]
|
||||||
|
namespaces = false
|
||||||
2
plugin/pytest.ini
Normal file
2
plugin/pytest.ini
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[pytest]
|
||||||
|
DJANGO_SETTINGS_MODULE=pretix.testutils.settings
|
||||||
46
plugin/setup.cfg
Normal file
46
plugin/setup.cfg
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
[flake8]
|
||||||
|
ignore = N802,W503,E402
|
||||||
|
max-line-length = 160
|
||||||
|
exclude = migrations,.ropeproject,static,_static,build,setup.py
|
||||||
|
|
||||||
|
[isort]
|
||||||
|
combine_as_imports = true
|
||||||
|
default_section = THIRDPARTY
|
||||||
|
include_trailing_comma = true
|
||||||
|
known_third_party = pretix
|
||||||
|
known_standard_library = typing
|
||||||
|
multi_line_output = 3
|
||||||
|
skip = setup.py
|
||||||
|
use_parentheses = True
|
||||||
|
force_grid_wrap = 0
|
||||||
|
line_length = 88
|
||||||
|
known_first_party = pretix_congressschedule
|
||||||
|
|
||||||
|
[tool:pytest]
|
||||||
|
DJANGO_SETTINGS_MODULE = pretix.testutils.settings
|
||||||
|
|
||||||
|
[coverage:run]
|
||||||
|
source = pretix_adyen
|
||||||
|
omit = */migrations/*,*/urls.py,*/tests/*
|
||||||
|
|
||||||
|
[coverage:report]
|
||||||
|
exclude_lines =
|
||||||
|
pragma: no cover
|
||||||
|
def __str__
|
||||||
|
der __repr__
|
||||||
|
if settings.DEBUG
|
||||||
|
NOQA
|
||||||
|
NotImplementedError
|
||||||
|
|
||||||
|
[check-manifest]
|
||||||
|
ignore =
|
||||||
|
.update-locales
|
||||||
|
.update-locales.sh
|
||||||
|
.gitlab-ci.yml
|
||||||
|
.install-hooks.sh
|
||||||
|
pretixplugin.toml
|
||||||
|
Makefile
|
||||||
|
pytest.ini
|
||||||
|
manage.py
|
||||||
|
tests/*
|
||||||
|
|
||||||
3
plugin/setup.py
Normal file
3
plugin/setup.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup()
|
||||||
21
scripts/EXAMPLE-CERT-CREATION.md
Normal file
21
scripts/EXAMPLE-CERT-CREATION.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Example of the cert creation for the Nginx setup
|
||||||
|
|
||||||
|
## Creation
|
||||||
|
|
||||||
|
Please execute the following script `bash create-tls-certs.sh` to create all necessary certificates for the complete setup of all related components.
|
||||||
|
|
||||||
|
## Adaptation
|
||||||
|
|
||||||
|
Please adjust the configuration files inside the [config](./config) folder and adapt the corresponding values for the req_distinguished_names and subjectAltNames based on your organisation and configuration. You can find [here](https://support.dnsimple.com/articles/what-is-common-name/) and [here](https://learn.microsoft.com/en-us/azure/application-gateway/self-signed-certificates) more information about the corresponding values and CA certificates in general.
|
||||||
|
|
||||||
|
## Ca Certificates
|
||||||
|
|
||||||
|
### Nginx
|
||||||
|
|
||||||
|
Describes the Certificate Authority (certificate & key) for the Nginx server.
|
||||||
|
|
||||||
|
## Server Certificates
|
||||||
|
|
||||||
|
### Nginx
|
||||||
|
|
||||||
|
Describes the server certificate and key for the Nginx server, and it's signed by the Nginx CA.
|
||||||
0
scripts/certs/.placeholder
Normal file
0
scripts/certs/.placeholder
Normal file
20
scripts/config/ca_nginx.conf
Normal file
20
scripts/config/ca_nginx.conf
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
[req]
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
default_bits = 4096
|
||||||
|
prompt = no
|
||||||
|
default_md = sha256
|
||||||
|
|
||||||
|
[req_distinguished_name]
|
||||||
|
C = DE
|
||||||
|
ST = Baden-Wuerttemberg
|
||||||
|
L = Mannheim
|
||||||
|
O = TheIOTStudio
|
||||||
|
CN = Pretix Nginx CA
|
||||||
|
emailAddress = info@theiotstudio.com
|
||||||
|
|
||||||
|
[ext]
|
||||||
|
subjectKeyIdentifier=hash
|
||||||
|
authorityKeyIdentifier=keyid:always,issuer
|
||||||
|
basicConstraints = critical, CA:TRUE, pathlen:3
|
||||||
|
keyUsage = critical, cRLSign, keyCertSign
|
||||||
|
nsCertType = sslCA, emailCA
|
||||||
19
scripts/config/server_nginx.conf
Normal file
19
scripts/config/server_nginx.conf
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[req]
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
req_extensions = v3_req
|
||||||
|
default_bits = 4096
|
||||||
|
prompt = no
|
||||||
|
default_md = sha256
|
||||||
|
|
||||||
|
[req_distinguished_name]
|
||||||
|
C = DE
|
||||||
|
ST = Baden-Wuerttemberg
|
||||||
|
L = Mannheim
|
||||||
|
O = TheIOTStudio
|
||||||
|
CN = Pretix Nginx Server
|
||||||
|
emailAddress = info@theiotstudio.com
|
||||||
|
|
||||||
|
[v3_req]
|
||||||
|
keyUsage = keyEncipherment, dataEncipherment, digitalSignature
|
||||||
|
extendedKeyUsage = serverAuth, clientAuth
|
||||||
|
subjectAltName=IP:<IP address of the Nginx server> or DNS:<DNS address of the Nginx server>
|
||||||
18
scripts/create-tls-certs.sh
Executable file
18
scripts/create-tls-certs.sh
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Switch the directory
|
||||||
|
echo "Switch the directory"
|
||||||
|
path=$(pwd)/config
|
||||||
|
cd certs
|
||||||
|
|
||||||
|
# Create the Nginx ca
|
||||||
|
echo "Create the Nginx ca"
|
||||||
|
openssl req -new -x509 -sha256 -newkey rsa:4096 -nodes -keyout ca_nginx.key -out ca_nginx.crt -days 3650 \
|
||||||
|
-extensions ext \
|
||||||
|
-config $path/ca_nginx.conf
|
||||||
|
|
||||||
|
# Create the server certificates
|
||||||
|
echo "Create the Nginx server certificates"
|
||||||
|
openssl genrsa -out nginx.key 4096
|
||||||
|
openssl req -new -key nginx.key -out nginx.csr -extensions v3_req -config $path/server_nginx.conf
|
||||||
|
openssl x509 -inform pem -req -days 1825 -in nginx.csr -CA ca_nginx.crt -CAkey ca_nginx.key -CAcreateserial -out nginx.crt -extensions v3_req -extfile $path/server_nginx.conf
|
||||||
Loading…
Add table
Add a link
Reference in a new issue