Skip to content

Commit b593bff

Browse files
authored
Merge branch 'mixxxdj:website' into feat/add-nightly-flatpak-to-download-page
2 parents 0f4e91e + 190ce21 commit b593bff

18 files changed

Lines changed: 246 additions & 31 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Deployment
2+
3+
on:
4+
push:
5+
branches:
6+
- website
7+
pull_request_target:
8+
types: [opened, synchronize, reopened, closed, labeled]
9+
workflow_dispatch:
10+
11+
jobs:
12+
deploy:
13+
if: github.event_name == 'push' || (github.event.action != 'closed' && github.event.action != 'labeled')
14+
uses: ./.github/workflows/ovh.yaml
15+
with:
16+
environment_name: ${{ contains(fromJSON('["push", "workflow_dispatch"]'), github.event_name) && 'production' || format('pull/{0}', github.event.pull_request.number) }}
17+
url: ${{ contains(fromJSON('["push", "workflow_dispatch"]'), github.event_name) && format('https://{0}', vars.MAIN_WEBSITE) || format('https://{0}/pr/{1}', vars.PREVIEW_WEBSITE, github.event.pull_request.number) }}
18+
target: ${{ contains(fromJSON('["push", "workflow_dispatch"]'), github.event_name) && 'www' || format('pulls/pr/{0}', github.event.pull_request.number) }}
19+
action: deploy
20+
secrets: inherit
21+
22+
cleanup:
23+
if: |
24+
github.event_name == 'pull_request_target' && (github.event.action == 'closed' || contains(github.event.pull_request.labels.*.name, 'stale'))
25+
uses: ./.github/workflows/ovh.yaml
26+
with:
27+
environment_name: ${{ format('pull/{0}', github.event.pull_request.number) }}
28+
target: ${{ format('pulls/pr/{0}', github.event.pull_request.number) }}
29+
action: teardown
30+
secrets: inherit

.github/workflows/ovh.yaml

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
name: Website Deploy
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
environment_name:
7+
required: true
8+
type: string
9+
url:
10+
required: false
11+
type: string
12+
target:
13+
required: false
14+
type: string
15+
default: 'www'
16+
action:
17+
required: false
18+
type: string
19+
default: 'deploy' # Or 'teardown'
20+
secrets:
21+
OVH_USERNAME:
22+
required: true
23+
OVH_HOSTNAME:
24+
required: true
25+
OVH_PASSWORD:
26+
required: true
27+
28+
jobs:
29+
apply:
30+
runs-on: ubuntu-latest
31+
concurrency:
32+
group: website-ovh-${{ inputs.target }}
33+
environment:
34+
name: ${{ inputs.environment_name }}
35+
url: ${{ inputs.url }}
36+
steps:
37+
- name: Forbid tearing down production
38+
if: ${{ inputs.target == 'www' && inputs.action == 'teardown' }}
39+
run: |
40+
echo "Tearing down production is not allowed!"
41+
exit 1
42+
- name: Check out repository
43+
if: ${{ inputs.action != 'teardown' }}
44+
uses: actions/checkout@v4.2.2
45+
with:
46+
fetch-depth: 0
47+
- name: Set up Python
48+
if: ${{ inputs.action != 'teardown' }}
49+
uses: actions/setup-python@v6
50+
with:
51+
python-version: 3.12
52+
cache: 'pip' # caching pip dependencies
53+
- name: Install Python dependencies
54+
if: ${{ inputs.action != 'teardown' }}
55+
run: |
56+
pip install -r requirements.txt
57+
- name: Install dependencies
58+
run: |
59+
sudo apt-get update
60+
sudo apt-get install -y lftp
61+
62+
- name: Setup PR website HTACCESS
63+
if: ${{ inputs.target == 'www' }}
64+
env:
65+
OVH_USERNAME: ${{ secrets.OVH_USERNAME }}
66+
OVH_HOSTNAME: ${{ secrets.OVH_HOSTNAME }}
67+
LFTP_PASSWORD: ${{ secrets.OVH_PASSWORD }}
68+
MAIN_WEBSITE: ${{ vars.MAIN_WEBSITE }}
69+
run: |
70+
mkdir -p ${HOME}/.ssh/
71+
ssh-keyscan "${OVH_HOSTNAME}" >> "${HOME}/.ssh/known_hosts"
72+
cat > .htaccess <<EOF
73+
Options -Indexes
74+
ErrorDocument 403 https://${MAIN_WEBSITE}
75+
EOF
76+
lftp --env-password -u ${OVH_USERNAME} sftp://${OVH_HOSTNAME} \
77+
-e "put .htaccess -o /home/${OVH_USERNAME}/pulls/.htaccess"
78+
79+
- name: Build
80+
if: ${{ inputs.action != 'teardown' }}
81+
env:
82+
SITEURL: ${{ inputs.url }}
83+
run: |
84+
pelican -e SITEURL="\"$SITEURL\""
85+
sed -E '/\{% (end)?verbatim %\}/d' htaccess > ./output/.htaccess
86+
# Migrated from netlify.toml
87+
cat >> ./output/.htaccess <<EOF
88+
<IfModule mod_headers.c>
89+
# Global security headers
90+
Header set Content-Security-Policy "default-src 'self'; style-src 'self'; script-src 'self'"
91+
Header set Permissions-Policy "ambient-light-sensor=(); autoplay=(); accelerometer=(); camera=(); display-capture=(); document-domain=(); encrypted-media=(); fullscreen=(); gyroscope=(); magnetometer=(); microphone=(); midi=(); payment=(); picture-in-picture=(); sync-xhr=(); usb=(); wake-lock=(); xr-spatial-tracking=()"
92+
Header set Referrer-Policy "no-referrer-when-downgrade"
93+
Header set X-Content-Type-Options "nosniff"
94+
Header set X-Frame-Options "DENY"
95+
Header set X-XSS-Protection "1; mode=block"
96+
97+
# Override CSP for /news/* pages
98+
SetEnvIf Request_URI "^/news/" is_news
99+
Header set Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'sha256-UPkidoMErzWw1gW/eY4LhAi9ZkPch3PP31d6KQoJ6Yc=' 'sha256-G40wI6OaLZXCtrb02xUq1H1kEVWjstzoQ0FXKwsWxPw=' 'sha256-1CldwzdEg2k1wTmf7s5RWVd7NMXI/7nxxjJM2C4DqII=' https://mixxx.discourse.group/javascripts/embed.js *.discourse-cdn.com; frame-src 'self' https://www.youtube-nocookie.com https://mixxx.discourse.group ; img-src 'self' https://i.ytimg.com https://raw.githubusercontent.com/mixxxdj/ ; connect-src 'self' https://mixxx.discourse.group https://*.discourse-cdn.com" env=is_news
100+
</IfModule>
101+
EOF
102+
sed -E '/\{% (end)?verbatim %\}/d' content/robots.txt > ./output/robots.txt
103+
- name: Deploy website
104+
if: ${{ inputs.action != 'teardown' }}
105+
env:
106+
OVH_USERNAME: ${{ secrets.OVH_USERNAME }}
107+
OVH_HOSTNAME: ${{ secrets.OVH_HOSTNAME }}
108+
LFTP_PASSWORD: ${{ secrets.OVH_PASSWORD }}
109+
TARGET: ${{ inputs.target }}
110+
run: |
111+
set -exo pipefail
112+
mkdir -p ${HOME}/.ssh/
113+
ssh-keyscan "${OVH_HOSTNAME}" >> "${HOME}/.ssh/known_hosts"
114+
lftp --env-password -u ${OVH_USERNAME} sftp://${OVH_HOSTNAME} \
115+
-e "set sftp:connect-program \"ssh -o Compression=no\"; set mirror:parallel-transfer-count 16; set mirror:parallel-directories yes; mirror -vv --delete -P 16 -R ./output /home/${OVH_USERNAME}/${TARGET}; quit"
116+
117+
- name: Delete website
118+
if: ${{ inputs.action == 'teardown' && inputs.target != 'www' }}
119+
env:
120+
OVH_USERNAME: ${{ secrets.OVH_USERNAME }}
121+
OVH_HOSTNAME: ${{ secrets.OVH_HOSTNAME }}
122+
LFTP_PASSWORD: ${{ secrets.OVH_PASSWORD }}
123+
TARGET: ${{ inputs.target }}
124+
run: |
125+
set -exo pipefail
126+
mkdir -p ${HOME}/.ssh/
127+
mkdir -p /tmp/ovh_sshfs
128+
mkdir empty
129+
ssh-keyscan "${OVH_HOSTNAME}" >> "${HOME}/.ssh/known_hosts"
130+
lftp --env-password -u ${OVH_USERNAME} sftp://${OVH_HOSTNAME} -e "set sftp:connect-program \"ssh -o Compression=no\"; set mirror:parallel-transfer-count 16; set mirror:parallel-directories yes; mirror -vv --delete -P 16 -R ./empty /home/${OVH_USERNAME}/${TARGET}; rmdir /home/${OVH_USERNAME}/${TARGET}; quit"

content/pages/privacy-policy.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ For privacy policies and conformance to GDPR, a few distinctions will be made be
1212

1313
1. Mixxx application
1414
2. Main Mixxx website
15-
3. Community Mixxx websites
15+
3. Mixxx Shop website
16+
4. Community Mixxx websites
1617

1718
## 1. Mixxx Application
1819

@@ -81,8 +82,11 @@ please refer to those platforms for information about the privacy policies.
8182
The main website of Mixxx, which can be accessed via the `mixxx.org` domain, does not create or use any user account. The Mixxx main page does not use cookies or similar tracking methods.
8283
Therefore, no personal information is stored at any moment by Mixxx.
8384

85+
## 3. Mixxx Shop Website
8486

85-
## 3. Mixxx Community Websites
87+
The Mixxx Shop website, which can be accessed via the `shop.mixxx.org` domain, has a separate privacy policy you find here: [Mixxx Shop Privacy Policy](https://shop.mixxx.org/content/7-privacy-policy)
88+
89+
## 4. Mixxx Community Websites
8690

8791
The Mixxx community websites are related to development and community, and are the following:
8892

pelicanconf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
]
8686

8787
PLUGINS = [
88+
"auto_siteurl",
8889
"author_metadata",
8990
"download_metadata",
9091
"draft_override",

plugins/auto_siteurl/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .auto_siteurl import register
2+
3+
__all__ = ["register"]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""
2+
auto_siteurl — Automatically prefix root-absolute paths in content with SITEURL.
3+
4+
When deployed at a subpath (e.g. https://example.com/pr/428), root-absolute
5+
paths like href="/download" break because they resolve to the domain root
6+
instead of the subpath. This plugin prepends SITEURL so they become
7+
href="https://example.com/pr/428/download".
8+
9+
Scope — what this plugin covers:
10+
- href and src attributes in rendered article/page content (the HTML
11+
generated from .md files). This avoids having to patch every content
12+
file with {filename} syntax.
13+
14+
What this plugin does NOT cover (handled separately):
15+
- Template files (*.html under theme/templates/): these use Jinja2
16+
directly and need explicit {{ SITEURL }} interpolation at the point
17+
of use (e.g. href="{{ SITEURL }}/path").
18+
- Static CSS files (*.css under theme/static/): these are not processed
19+
by Pelican at all. Use CSS-relative paths instead (e.g. url('../images/')
20+
rather than url('/theme/images/')).
21+
- feed.xml: generated by Pelican's feed writer, which constructs URLs
22+
independently of the content pipeline.
23+
"""
24+
25+
import re
26+
from pelican import signals
27+
28+
29+
def prefix_absolute_paths(content_object):
30+
siteurl = content_object.settings.get("SITEURL", "")
31+
if not siteurl:
32+
return
33+
34+
if not hasattr(content_object, "_content") or not content_object._content:
35+
return
36+
37+
# Replace href="/path" → href="SITEURL/path" and src="/path" → src="SITEURL/path".
38+
# The negative lookahead (?!/) skips protocol-relative URLs like //cdn.example.com.
39+
content_object._content = re.sub(
40+
r'(href|src)="/(?!/)',
41+
lambda m: f'{m.group(1)}="{siteurl}/',
42+
content_object._content,
43+
)
44+
45+
46+
def register():
47+
signals.content_object_init.connect(prefix_absolute_paths)

theme/static/css/custom.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ html.transition-theme *:after {
5757
max-width: 384px;
5858
height: auto;
5959
float: right;
60-
shape-outside: url('/theme/images/ev/umbrella.png');
60+
shape-outside: url('../images/ev/umbrella.png');
6161
shape-margin: 2rem;
6262
margin-left: 2rem;
6363
margin-top: 60px;
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#hardware.cover-bg {
2-
background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('/theme/images/cover/hardware-bg.jpg');
2+
background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('../../images/cover/hardware-bg.jpg');
33
}
44

55
#library.cover-bg {
6-
background-image: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url('/theme/images/cover/library-bg.jpg');
6+
background-image: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url('../../images/cover/library-bg.jpg');
77
}
88

99
#broadcast.cover-bg {
10-
background-image: url('/theme/images/cover/broadcast-bg.jpg');
10+
background-image: url('../../images/cover/broadcast-bg.jpg');
1111
}

theme/static/css/pages/index.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.splash.cover-bg {
2-
background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.9)), url('/theme/images/cover/splash-bg.jpg');
2+
background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.9)), url('../../images/cover/splash-bg.jpg');
33
}
44

55
.splash .splash-logo {
@@ -28,5 +28,5 @@
2828
}
2929

3030
#opensource.cover-bg {
31-
background-image: url('/theme/images/cover/code-bg.jpg');
31+
background-image: url('../../images/cover/code-bg.jpg');
3232
}

theme/templates/article.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ <h1>{{ article.title }}</h1>
4848
{% if article.authors %}
4949
<div class="docs-section author-box">
5050
{% for author in article.authors %}
51-
{{ utils.author_box(author) }}
51+
{{ utils.author_box(author, siteurl=SITEURL) }}
5252
{% endfor %}
5353
</div>
5454
{% endif %}

0 commit comments

Comments
 (0)