-
Notifications
You must be signed in to change notification settings - Fork 21
Hosting reports
This guide documents the exact production pattern used to host heavy Quarto report outputs, such as Husna and Lotta, on the datascience.utu.fi server.
The important lesson from the original debugging session is:
The live site is served by Apache on the production server, not directly by GitHub Pages. GitHub Actions may run, but copying report output inside GitHub Actions does not update production unless the Apache server also receives those files.
The production site is updated by server-side cron. The main openresearchlabs.github.io repo is pulled and rebuilt by the server at minute 02 each hour, using this pattern:
2 * * * * cd /home/hoster/openresearchlabs.github.io; git reset --hard; git pull; hugo -D; rm -rf /var/www/html/*; cp -r public/* /var/www/htmlThat means /var/www/html is rebuilt from Hugo output and should not be used as the permanent home for heavy report outputs. The report outputs should instead live in their own report repositories and be served directly by Apache through stable URLs.
The main website still uses the existing Hugo deployment flow:
openresearchlabs.github.io repo
|
| server cron at hh:02
v
hugo -D
|
v
/var/www/html
|
v
https://datascience.utu.fi/
This is for the main public site, pages, menus, and links.
Heavy report outputs are not stored inside openresearchlabs.github.io.
They stay in their own repositories, for example:
/home/hoster/collaboration/reports/fopp-husna
/home/hoster/collaboration/reports/fopp-lotta
Apache serves their rendered output/ folders directly:
https://datascience.utu.fi/reports/husna/
-> /home/hoster/collaboration/reports/fopp-husna/output/
https://datascience.utu.fi/reports/lotta/
-> /home/hoster/collaboration/reports/fopp-lotta/output/
This keeps the main Hugo repo lightweight and lets Git LFS remain in the report repositories, where it belongs. The earlier conclusion was that Husna and Lotta should no longer be served through the base Hugo build output; Apache should map stable report URLs directly to the report output folders.
For a new report, you need five pieces:
- A report repository that contains or produces the rendered report output.
- The rendered output committed/pushed to that repo, usually with Git LFS if large.
- A local clone of that repo on the production server under the
hosteraccount. - A cron job that keeps the server clone updated.
- An Apache mapping from a public URL to the report output directory.
Optional but recommended:
- Apache Basic Auth if the report is collaborator-only.
Use a short lowercase report key.
Examples:
husna
lotta
newreport
For a new report called newreport, use:
Repo directory:
/home/hoster/collaboration/reports/fopp-newreport
Rendered output:
/home/hoster/collaboration/reports/fopp-newreport/output
Public URL:
https://datascience.utu.fi/reports/newreport/
Password file:
/home/hoster/.htpasswd-newreport
Cron log:
/home/hoster/logs/fopp-newreport-cron.log
SSH into the production server.
ssh <your-user>@<server>Confirm the site is Apache-served:
curl -I https://datascience.utu.fiYou can also inspect Apache vhosts:
sudo apachectl -SDuring the original debugging, the domain was confirmed to be served by Apache on the production server, with local server-side deployment rather than GitHub Pages being authoritative.
The report clones should be owned and updated by hoster, because the existing production update flow uses that account.
Do not manually create folders in /home/hoster as another user. If you see this:
mkdir: cannot create directory 'collaboration': Permission deniedthat means you are not operating as the right user or need sudo -u hoster.
Create the reports directory like this:
sudo -u hoster mkdir -p /home/hoster/collaboration/reports
sudo -u hoster ls -ld /home/hoster/collaboration/reportsThe original session hit exactly this permission issue, and the fix was to create/manage the directory as hoster.
Before cloning private repos, make sure the hoster account trusts GitHub’s SSH host keys.
Run:
sudo -u hoster mkdir -p /home/hoster/.ssh
sudo -u hoster chmod 700 /home/hoster/.ssh
sudo -u hoster cp /home/hoster/.ssh/known_hosts /home/hoster/.ssh/known_hosts.bak.$(date +%F-%H%M%S) 2>/dev/null || true
sudo -u hoster ssh-keygen -f /home/hoster/.ssh/known_hosts -R github.com
sudo -u hoster bash -c 'ssh-keyscan -t ed25519,ecdsa,rsa github.com >> /home/hoster/.ssh/known_hosts'
sudo -u hoster chmod 600 /home/hoster/.ssh/known_hostsTest SSH access:
sudo -u hoster ssh -T git@github.comIf GitHub says permission is denied, create a deploy key or user key:
sudo -u hoster ssh-keygen -t ed25519 -C "hoster-datascience-utu" -f /home/hoster/.ssh/id_ed25519 -N ""
sudo -u hoster cat /home/hoster/.ssh/id_ed25519.pubAdd the printed public key to GitHub, either as a deploy key on the report repo or under the GitHub account that owns the repo access.
This exact trust-key repair was needed because strict host checking blocked the clone with an old GitHub host key.
Clone as hoster.
For Husna and Lotta, the original commands were:
sudo -u hoster git -C /home/hoster/collaboration/reports clone git@github.com:0xMuluh/fopp-husna.git fopp-husna
sudo -u hoster git -C /home/hoster/collaboration/reports clone git@github.com:0xMuluh/fopp-lotta.git fopp-lottaFor a new report, use the same pattern:
sudo -u hoster git -C /home/hoster/collaboration/reports clone git@github.com:<ORG_OR_USER>/<REPO>.git fopp-newreportThen verify:
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-newreport remote -v
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-newreport branch --show-current
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-newreport log -1 --date=iso --pretty=fullerHeavy report outputs may use Git LFS. If the server does not have Git LFS installed, this command fails:
git: 'lfs' is not a git commandInstall Git LFS once:
sudo apt-get update
sudo apt-get install -y git-lfs
sudo git lfs install --system
sudo -u hoster git lfs install --forceVerify:
sudo -u hoster git lfs versionPull LFS content:
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-newreport lfs pullFor Husna and Lotta, the verified pattern was:
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-husna lfs pull
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-lotta lfs pullCheck that LFS files are visible:
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-newreport lfs ls-files | headThis was a real issue in the setup: git lfs pull failed until Git LFS was installed and initialized for the server environment.
Apache will serve the rendered output/ folder. Before changing Apache, confirm the report has an index.html.
For a new report:
ls -la /home/hoster/collaboration/reports/fopp-newreport/output/index.htmlFor Husna and Lotta:
ls -la /home/hoster/collaboration/reports/fopp-husna/output/index.html
ls -la /home/hoster/collaboration/reports/fopp-lotta/output/index.htmlIf index.html is missing, the report is not ready to be served. Render locally, commit/push the output to the report repo, then pull again on the server.
The original realization was: cron only keeps files updated; to see them in production, Apache must expose those folders as URLs.
Cron goes in the hoster user’s crontab, not root’s crontab unless there is a specific reason.
Create a log directory:
sudo -u hoster mkdir -p /home/hoster/logsOpen the hoster crontab:
sudo -u hoster crontab -eAdd the environment lines if not already present:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""Add a job for the new report:
9 * * * * cd /home/hoster/collaboration/reports/fopp-newreport && git reset --hard && git pull --ff-only && git lfs pull >> /home/hoster/logs/fopp-newreport-cron.log 2>&1For Husna and Lotta, the documented jobs were:
7 * * * * cd /home/hoster/collaboration/reports/fopp-husna && git reset --hard && git pull --ff-only && git lfs pull >> /home/hoster/logs/fopp-husna-cron.log 2>&1
8 * * * * cd /home/hoster/collaboration/reports/fopp-lotta && git reset --hard && git pull --ff-only && git lfs pull >> /home/hoster/logs/fopp-lotta-cron.log 2>&1Save and verify:
sudo -u hoster crontab -lTest the command immediately instead of waiting for cron:
sudo -u hoster bash -lc 'cd /home/hoster/collaboration/reports/fopp-newreport && git reset --hard && git pull --ff-only && git lfs pull'Check logs after the scheduled run:
sudo -u hoster tail -n 50 /home/hoster/logs/fopp-newreport-cron.logThe reason for git lfs pull in cron is important: without it, the server may only fetch LFS pointer files instead of the real report assets.
Edit the active Apache vhost that serves datascience.utu.fi.
In the original server, relevant config was found under Apache site files such as:
/etc/apache2/sites-available/000-default.conf
/etc/apache2/sites-available/default-ssl.conf
The exact active file should be confirmed with:
sudo apachectl -SMake a backup before editing:
sudo cp /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/000-default.conf.bak.$(date +%F-%H%M)Open the file:
sudoedit /etc/apache2/sites-available/000-default.confAdd this pattern for a public report:
# New report
RedirectMatch 301 ^/reports/newreport$ /reports/newreport/
Alias /reports/newreport/ /home/hoster/collaboration/reports/fopp-newreport/output/
<Directory /home/hoster/collaboration/reports/fopp-newreport/output>
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>For Husna and Lotta, the same idea was:
# Husna
RedirectMatch 301 ^/reports/husna$ /reports/husna/
Alias /reports/husna/ /home/hoster/collaboration/reports/fopp-husna/output/
<Directory /home/hoster/collaboration/reports/fopp-husna/output>
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>
# Lotta
RedirectMatch 301 ^/reports/lotta$ /reports/lotta/
Alias /reports/lotta/ /home/hoster/collaboration/reports/fopp-lotta/output/
<Directory /home/hoster/collaboration/reports/fopp-lotta/output>
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>Then validate and reload:
sudo apachectl configtest
sudo systemctl reload apache2Verify:
curl -I https://datascience.utu.fi/reports/newreport/Expected result for a public report:
HTTP/2 200
The report URLs do not need to exist in Hugo for Apache to serve them; Hugo links are only for discoverability. Apache can serve the report paths directly if the aliases exist and the folders are updated.
Use Apache Basic Auth if the report is collaborator-only.
Install tools:
sudo apt-get update
sudo apt-get install -y apache2-utilsCreate the password file:
sudo htpasswd -c /home/hoster/.htpasswd-newreport newreport_userDo not use -c when adding later users, because -c recreates the file:
sudo htpasswd /home/hoster/.htpasswd-newreport another_userSet permissions:
sudo chown root:www-data /home/hoster/.htpasswd-newreport
sudo chmod 640 /home/hoster/.htpasswd-newreportUpdate the Apache block:
# New protected report
RedirectMatch 301 ^/reports/newreport$ /reports/newreport/
Alias /reports/newreport/ /home/hoster/collaboration/reports/fopp-newreport/output/
<Directory /home/hoster/collaboration/reports/fopp-newreport/output>
Options FollowSymLinks
AllowOverride None
AuthType Basic
AuthName "New Report Collaboration"
AuthUserFile /home/hoster/.htpasswd-newreport
Require valid-user
</Directory>Validate and reload:
sudo apachectl configtest
sudo systemctl reload apache2Verify that unauthenticated access is blocked:
curl -I https://datascience.utu.fi/reports/newreport/Expected result:
HTTP/2 401
Verify credentials work:
curl -I -u newreport_user:YOUR_PASSWORD https://datascience.utu.fi/reports/newreport/Expected result:
HTTP/2 200
For Husna and Lotta, separate password files were recommended:
sudo htpasswd -c /home/hoster/.htpasswd-husna husna_user
sudo htpasswd -c /home/hoster/.htpasswd-lotta lotta_user
sudo chown root:www-data /home/hoster/.htpasswd-husna /home/hoster/.htpasswd-lotta
sudo chmod 640 /home/hoster/.htpasswd-husna /home/hoster/.htpasswd-lottaand the corresponding Apache blocks used separate AuthUserFile entries for each report.
This is optional.
The report works without a Hugo link if Apache is configured correctly. Add a Hugo link only when users should discover it from the main website.
For collaborator-only reports, it may be better not to list them publicly.
The stable links should be:
https://datascience.utu.fi/reports/newreport/
https://datascience.utu.fi/reports/husna/
https://datascience.utu.fi/reports/lotta/
After the one-time server setup, the report owner does this:
1. Render locally.
2. Confirm the local output works.
3. Commit and push the rendered output to the report repo.
4. Wait for the server cron job, or manually run the cron command once.
5. Open the stable production URL.
6. Confirm auth and assets work.
Manual update command for a new report:
sudo -u hoster bash -lc 'cd /home/hoster/collaboration/reports/fopp-newreport && git reset --hard && git pull --ff-only && git lfs pull'Then verify:
curl -I https://datascience.utu.fi/reports/newreport/With auth:
curl -I -u newreport_user:YOUR_PASSWORD https://datascience.utu.fi/reports/newreport/Check whether Apache knows the route:
sudo apachectl -S
sudo grep -R "reports/newreport" /etc/apache2/sites-available /etc/apache2/sites-enabledCheck the output exists:
ls -la /home/hoster/collaboration/reports/fopp-newreport/output/index.htmlCheck Apache config and reload:
sudo apachectl configtest
sudo systemctl reload apache2A 404 usually means Apache does not have the Alias mapping, the vhost was not reloaded, or the target folder does not contain an index.html.
Check permissions:
namei -l /home/hoster/collaboration/reports/fopp-newreport/output/index.htmlApache needs execute permission on parent directories and read permission on files.
Also check the Apache <Directory> block:
Require all grantedor, for protected reports:
Require valid-userThat is expected if Basic Auth is enabled.
Test with credentials:
curl -I -u newreport_user:YOUR_PASSWORD https://datascience.utu.fi/reports/newreport/Check the password file:
sudo ls -l /home/hoster/.htpasswd-newreportExpected ownership/permissions:
root:www-data
640
Check whether LFS pulled real files:
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-newreport lfs pull
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-newreport lfs ls-files | headCheck the cron log:
sudo -u hoster tail -n 100 /home/hoster/logs/fopp-newreport-cron.logIf LFS was not installed, install it:
sudo apt-get update
sudo apt-get install -y git-lfs
sudo git lfs install --system
sudo -u hoster git lfs install --forceCheck the crontab:
sudo -u hoster crontab -lRun the command manually:
sudo -u hoster bash -lc 'cd /home/hoster/collaboration/reports/fopp-newreport && git reset --hard && git pull --ff-only && git lfs pull'Check logs:
sudo -u hoster tail -n 100 /home/hoster/logs/fopp-newreport-cron.logRefresh GitHub host keys:
sudo -u hoster ssh-keygen -f /home/hoster/.ssh/known_hosts -R github.com
sudo -u hoster bash -c 'ssh-keyscan -t ed25519,ecdsa,rsa github.com >> /home/hoster/.ssh/known_hosts'
sudo -u hoster chmod 600 /home/hoster/.ssh/known_hostsTest again:
sudo -u hoster ssh -T git@github.comThis exact issue occurred during setup and was caused by stale GitHub host keys in /home/hoster/.ssh/known_hosts.
Run Git as the repository owner instead of globally disabling the protection:
sudo -u hoster git -C /home/hoster/openresearchlabs.github.io remote -v
sudo -u hoster git -C /home/hoster/openresearchlabs.github.io branch --show-current
sudo -u hoster git -C /home/hoster/openresearchlabs.github.io log -1 --date=iso --pretty=fullerThe earlier debug session hit this with /home/hoster/openresearchlabs.github.io, and the recommended approach was to use sudo -u hoster rather than blindly adding safe.directory.
Go to the report repo clone:
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-newreport log --oneline -n 10Reset to a known good commit:
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-newreport reset --hard <GOOD_COMMIT>
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-newreport lfs pullNote: the next cron run will pull the latest remote state again. For a durable rollback, revert or fix the remote repo too.
Restore the backup:
sudo cp /etc/apache2/sites-available/000-default.conf.bak.YYYY-MM-DD-HHMM /etc/apache2/sites-available/000-default.conf
sudo apachectl configtest
sudo systemctl reload apache2Copy this section into the wiki as the quick operator checklist.
# 1. Create report parent directory
sudo -u hoster mkdir -p /home/hoster/collaboration/reports
# 2. Make sure GitHub SSH works
sudo -u hoster ssh -T git@github.com
# 3. Clone report repo
sudo -u hoster git -C /home/hoster/collaboration/reports clone git@github.com:<ORG_OR_USER>/<REPO>.git fopp-newreport
# 4. Install Git LFS if missing
sudo apt-get update
sudo apt-get install -y git-lfs
sudo git lfs install --system
sudo -u hoster git lfs install --force
# 5. Pull LFS assets
sudo -u hoster git -C /home/hoster/collaboration/reports/fopp-newreport lfs pull
# 6. Confirm output exists
ls -la /home/hoster/collaboration/reports/fopp-newreport/output/index.html
# 7. Add cron log folder
sudo -u hoster mkdir -p /home/hoster/logs
# 8. Edit hoster cron
sudo -u hoster crontab -e
# Add:
# 9 * * * * cd /home/hoster/collaboration/reports/fopp-newreport && git reset --hard && git pull --ff-only && git lfs pull >> /home/hoster/logs/fopp-newreport-cron.log 2>&1
# 9. Test update manually
sudo -u hoster bash -lc 'cd /home/hoster/collaboration/reports/fopp-newreport && git reset --hard && git pull --ff-only && git lfs pull'
# 10. Add Apache Alias and Directory block
sudo cp /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/000-default.conf.bak.$(date +%F-%H%M)
sudoedit /etc/apache2/sites-available/000-default.conf
# 11. Validate and reload Apache
sudo apachectl configtest
sudo systemctl reload apache2
# 12. Verify public or protected URL
curl -I https://datascience.utu.fi/reports/newreport/For future maintainers, the model is:
Main site:
openresearchlabs.github.io
-> server cron
-> Hugo build
-> /var/www/html
-> https://datascience.utu.fi/
Heavy reports:
fopp-husna / fopp-lotta / fopp-newreport
-> server cron under hoster
-> git pull + git lfs pull
-> repo output/ directory
-> Apache Alias
-> https://datascience.utu.fi/reports/<report>/
The setup only “just works” after these pieces exist: server clone, GitHub SSH access, Git LFS, cron update, Apache Alias, and optional Basic Auth. This was the path established in the original debugging session.