Share your "hooks" to apply custom configurations- Partagez vos "hooks" pour appliquer des configurations personnelles

@p_7 Yes, that is correct.

Tailscale is no longer clobbering my resolv.conf. you might need to re-symlink it as per YunoHost Diagnosis.

My /etc/resolv.conf is symlinked to /run/resolvconf/resolv.conf

I am glad I found this thread because I found a lot of information that was not in the documentation.
That’s why I decided to improve the documentation and now I would like you to review my PR and tell me what I could have missed, what is wrong or what could be better written.
Edit: already merged, no need for review

3 Likes

This is not a hook, but a customisation : Get notified when new updates are available

#!/bin/bash

# Place this file in /etc/cron.daily and make it executable
# Configuration
RECIPIENT="root"
SUBJECT="YunoHost Updates Available - $(date +'%Y-%m-%d')"

# Run update check
OUTPUT=$(yunohost tools update --json 2>&1)
JSON=$(echo "$OUTPUT" | grep -E '^\{"system"|^\{' | head -1)

# Verify JSON
if ! jq -e . >/dev/null 2>&1 <<<"$JSON"; then
    echo "Error parsing JSON output" | mail -s "YunoHost Update Error" "$RECIPIENT"
    exit 1
fi

# Check for updates
SYSTEM_UPDATES=$(jq -e '.system | length > 0' <<<"$JSON")
APP_UPDATES=$(jq -e '[.apps[] | select(.upgradable == "yes")] | length > 0' <<<"$JSON")
YUNOHOST_UPDATE=$(jq -e '.important_yunohost_upgrade == true' <<<"$JSON")

# Exit if no updates
if ! $SYSTEM_UPDATES && ! $APP_UPDATES && ! $YUNOHOST_UPDATE; then
    exit 0
fi

# Generate HTML content
HTML_HEADER='
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
  * { font-family: "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }
  body { background-color: #f7f9fc; margin: 0; padding: 20px; }
  .container { max-width: 650px; margin: 0 auto; }
  .card { background: white; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.05); margin-bottom: 25px; overflow: hidden; }
  .header { background: #3f51b5; padding: 25px; text-align: center; }
  h1 { color: white; font-weight: 600; font-size: 22px; margin: 0; }
  .update-section { padding: 25px 30px; }
  h2 { color: #2c3e50; font-size: 18px; margin-top: 0; margin-bottom: 18px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
  table { width: 100%; border-collapse: collapse; }
  th { text-align: left; color: #7f8c8d; font-weight: 500; font-size: 13px; text-transform: uppercase; padding: 8px 10px; }
  td { padding: 10px; border-bottom: 1px solid #f1f5f9; }
  tr:last-child td { border-bottom: none; }
  .package-name { color: #2c3e50; font-weight: 500; }
  .version { font-family: "SFMono-Regular", Consolas, monospace; font-size: 13px; }
  .current-version { color: #e74c3c; }
  .new-version { color: #27ae60; font-weight: 500; }
  .badge { display: inline-block; padding: 3px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }
  .badge-system { background: #e3f2fd; color: #1565c0; }
  .badge-app { background: #e8f5e9; color: #2e7d32; }
  .badge-critical { background: #ffebee; color: #c62828; }
  .footer { text-align: center; padding: 20px; color: #95a5a6; font-size: 12px; }
  .update-count { background: #4caf50; color: white; border-radius: 50%; width: 22px; height: 22px; display: inline-block; text-align: center; line-height: 22px; font-size: 12px; margin-left: 8px; }
  .no-updates { color: #7f8c8d; font-style: italic; text-align: center; padding: 20px; }
</style>
</head>
<body>
<div class="container">
  <div class="card">
    <div class="header">
      <h1>YunoHost Server Updates</h1>
    </div>
    
    <div class="update-section">
      <h2>System Packages'

# System updates section
if $SYSTEM_UPDATES; then
    COUNT=$(jq '.system | length' <<<"$JSON")
    HTML_HEADER+="<span class='update-count'>$COUNT</span>"
fi

HTML_HEADER+='</h2>'

if $SYSTEM_UPDATES; then
    HTML_SYSTEM='<table><tr><th>Package</th><th>Current Version</th><th>New Version</th></tr>'
    while read -r row; do
        name=$(jq -r '.name' <<<"$row")
        current=$(jq -r '.current_version' <<<"$row")
        new=$(jq -r '.new_version' <<<"$row")
        HTML_SYSTEM+="<tr><td class='package-name'><span class='badge badge-system'>system</span> $name</td>"
        HTML_SYSTEM+="<td class='version current-version'>$current</td>"
        HTML_SYSTEM+="<td class='version new-version'>$new</td></tr>"
    done < <(jq -c '.system[]' <<<"$JSON")
    HTML_SYSTEM+='</table>'
else
    HTML_SYSTEM='<div class="no-updates">No system package updates available</div>'
fi

# Application updates section
HTML_APPS='<div class="card"><div class="update-section"><h2>Applications'

if $APP_UPDATES; then
    COUNT=$(jq '[.apps[] | select(.upgradable == "yes")] | length' <<<"$JSON")
    HTML_APPS+="<span class='update-count'>$COUNT</span>"
fi

HTML_APPS+='</h2>'

if $APP_UPDATES; then
    HTML_APPS+='<table><tr><th>Application</th><th>Domain</th><th>Current Version</th><th>New Version</th></tr>'
    while read -r row; do
        name=$(jq -r '.name' <<<"$row")
        domain=$(jq -r '.domain_path' <<<"$row")
        current=$(jq -r '.current_version' <<<"$row")
        new=$(jq -r '.new_version' <<<"$row")
        HTML_APPS+="<tr><td class='package-name'><span class='badge badge-app'>app</span> $name</td>"
        HTML_APPS+="<td>$domain</td>"
        HTML_APPS+="<td class='version current-version'>$current</td>"
        HTML_APPS+="<td class='version new-version'>$new</td></tr>"
    done < <(jq -c '.apps[] | select(.upgradable == "yes")' <<<"$JSON")
    HTML_APPS+='</table>'
else
    HTML_APPS+='<div class="no-updates">No application updates available</div>'
fi

HTML_APPS+='</div></div>'

# Critical update section
HTML_CRITICAL=''
if $YUNOHOST_UPDATE; then
    HTML_CRITICAL='<div class="card"><div class="update-section"><h2><span class="badge badge-critical">Critical</span> YunoHost Core Update Available</h2><p>Important YunoHost system update is available. Please upgrade as soon as possible.</p></div></div>'
fi

# Footer
HTML_FOOTER="<div class='footer'><p>Generated on $(date +'%Y-%m-%d %H:%M:%S')</p></div></div></body></html>"

# Combine all HTML parts
FULL_HTML="${HTML_HEADER}${HTML_SYSTEM}</div></div>${HTML_APPS}${HTML_CRITICAL}${HTML_FOOTER}"

# Send email
echo "$FULL_HTML" | mail -a "Content-Type: text/html" -s "$SUBJECT" "$RECIPIENT"

And yes, I used some ai help, because I was overwhelmed by work, family, contribs, etc…

Don’t forget to read and understand code before you use it.

Edit : a screenshot of the email notification

Edit 2: update here

5 Likes

Aïe aïe aïe ! Attention, la mise à jour de yunohost 12.1 casse ces hooks !! le règles et les fichiers postfix ont changer de manière notoire !

Il n’y a plus de fichier /etc/postfix/ldap-domains.cf

ni de lignes mx dans les fichiers /etc/dnsmasq.d/domaine.tld

Du coup je cherche a rétablir comment y arriver ??

Hi @jarod5001,

This script only gives me Yunohost packages but not standard packages. I don’t try to debug it, is it the same for you and are you planning to do an update?


and the result of yunohost tools update (system only) :

system: 
  misc utils and libs: 
    0: 
      current_version: 10.0.0~dfsg-11+deb12u7
      name: ghostscript
      new_version: 10.0.0~dfsg-11+deb12u8
    1: 
      current_version: 10.0.0~dfsg-11+deb12u7
      name: libgs-common
      new_version: 10.0.0~dfsg-11+deb12u8
    2: 
      current_version: 10.0.0~dfsg-11+deb12u7
      name: libgs10-common
      new_version: 10.0.0~dfsg-11+deb12u8
    3: 
      current_version: 10.0.0~dfsg-11+deb12u7
      name: libgs10
      new_version: 10.0.0~dfsg-11+deb12u8
    4: 
      current_version: 4.5.0-6+deb12u2
      name: libtiff6
      new_version: 4.5.0-6+deb12u3
  yunohost: 
    0: 
      current_version: 12.1.11
      name: yunohost-admin
      new_version: 12.1.12
    1: 
      current_version: 12.1.27
      name: yunohost
      new_version: 12.1.28

Thanks anyway for this script, it’s a beautiful presentation of updates. Don’t you think it would be better to make it more visible with a dedicated topic? And why not, propose it for inclusion in Yunohost by default?

Yeah, that’s what I needed because I already have apticron that sends system updates but not yunohost apps updates. I’ll think about it.

1 Like

Hey everyone,

Thanks a lot for sharing all these examples — this thread is a goldmine for anyone trying to customize YunoHost safely.

I recently started experimenting with hooks too, mainly to:

  • tweak SSH settings (e.g. disable password auth but keep PAM as fallback for testing),
  • apply minor nginx custom headers automatically after updates,
  • and manage local network allowlists dynamically using a small script.

It’s honestly impressive how flexible the conf_regen mechanism is once you get the hang of it.

If anyone’s curious, I’d also recommend versioning your hooks in a small git repo — it helps track changes and roll back experiments easily.

Thanks again for the inspiration — especially to @metyun and @ljf for the detailed examples and explanations! :raising_hands:

2 Likes

A hook to use the Spamhaus DQS service to avoid the error “open resolver”

First, create a DQS service account and create the /etc/postfix/dnsbl-reply-map file. Information here —> Configuring DQS on Postfix — Spamhaus Technology Documentation 2.0 documentation

Read this thread : Blocked using zen.spamhaus.org; Error: open resolver; - #22 by Gwylohm

Activate antispam :

yunohost settings set email.antispam.enable_blocklists -v yes
yunohost tools regen-conf postfix

Create a hook 20-postfix_spamhaus in /etc/yunohost/hooks.d/conf_regen/

:warning: modify PERSONALKEY by DQS Spamhaus key

#!/bin/bash

action=$1
pending_dir=$4
postfix_conf=$pending_dir/../postfix/etc/postfix/main.cf

[[ $action == "pre" ]] || exit 0
[[ -e $postfix_conf ]] || exit 0

sed -i '/smtpd_client_restrictions/a\    reject_rhsbl_sender         PERSONALKEY.dbl.dq.spamhaus.net=127.0.1.[2..99],\
reject_rhsbl_helo           PERSONALKEY.dbl.dq.spamhaus.net=127.0.1.[2..99],\
reject_rhsbl_reverse_client PERSONALKEY.dbl.dq.spamhaus.net=127.0.1.[2..99],\
reject_rhsbl_sender         PERSONALKEY.zrd.dq.spamhaus.net=127.0.2.[2..24],\
reject_rhsbl_helo           PERSONALKEY.zrd.dq.spamhaus.net=127.0.2.[2..24],\
reject_rhsbl_reverse_client PERSONALKEY.zrd.dq.spamhaus.net=127.0.2.[2..24],\
reject_rbl_client           PERSONALKEY.zen.dq.spamhaus.net=127.0.0.[2..255],' $postfix_conf

sed -i '/zen.spamhaus.org/d' $postfix_conf

echo '
rbl_reply_maps = hash:/etc/postfix/dnsbl-reply-map' >> $postfix_conf

Activate the hook :

yunohost tools regen-conf postfix --force

Don’t forget to postmap /etc/postfix/dnsbl-reply-map (see spamhaus doc) and reload the postfix service.

Thanks to @Gwylohm for this solution. :slightly_smiling_face:

4 Likes

Hello guys,

Wouldn’t it be a good idea to centralize and share these hooks in a github repo (maybe tied to the yunohost organisation) somewhere instead of in this forum topic?

It was a good idea at the begining, but nearly 5 year into it, the topic is now very long (success!!): Browsing it to find a hook and/or get an update is quite cumbersome, and subjects for different hooks are mixed and intertwined, which make it still harder…

3 Likes

Really? 5 years? It feels like it was yesterday

It’s a good idea but :

1 - I don’t know how to use Github

2 - I don’t want to create an account on Github.

3 - For this reasons, i prefer to post here

But…. If yunohost staff or anyone else wants to create a git repo and can report the interesting hooks, no problems. A lot of them are now obsolete because there’s more settings now in yunohost, it would be a good opportunity to do some cleaning.

For those like me, it may still be useful to keep this topic open, even if it means that someone should be volonteers to report the few new hooks that can be post here in the Git repository. Or maybe i should get started to use Git but be patient, it’s in my resolutions for the year 2016. No, no error…. Really? 10 years? It feels like it was the day before yesterday :rofl:

Well, I can do that, I just need someone from the yunohost organization to initiate the repo and give me some authorization on it : anyone from there reading this?

2 Likes

I don’t know what to think, it’s supposed to a community discussion with relatively technical knowledge (not having to use git/github) … if we start creating a repo on our organization, it starts having some sort of “official flavor” like we somehow “endorse” the hooks in the repo, and then we have to maintain it somehow, because discussion will surely keep happening here or elsewhere, so we’ll have to “keep things in sync” between the repo and the topics, etc …

You can always explicitly state it is a comminity-led repo and is to be used at your own risk.

Another option would be a wiki.

I do understand your point. Maybe something in the same flavor as the Yunohost-Apps organization? Which is around Yunohost but is not Yunohost itself?

Thanks for providing that hook!
I tried using it, but somehow it didn’t work. What worked for me to solve this stupid Error: open resolver; was to add the following to my /etc/postfix/main.cf file:

reject_rhsbl_helo PERSONALKEY.dbl.dq.spamhaus.net=127.0.1.[2..99]
reject_rhsbl_reverse_client PERSONALKEY.dbl.dq.spamhaus.net=127.0.1.[2..99]
reject_rhsbl_sender PERSONALKEY.zrd.dq.spamhaus.net=127.0.2.[2..24]
reject_rhsbl_helo PERSONALKEY.zrd.dq.spamhaus.net=127.0.2.[2..24]
reject_rhsbl_reverse_client PERSONALKEY.zrd.dq.spamhaus.net=127.0.2.[2..24]
reject_rbl_client PERSONALKEY.zen.dq.spamhaus.net=127.0.0.[2..255]

however, like others I am concerned that with an update of yunohost, I would need to manually add those lines again, so I wonder what I did wrong.

First I created an account at Spamhaus Customer Portal to then generate my own key.

Then I did:
yunohost settings set email.antispam.enable_blocklists -v yes
yunohost tools regen-conf postfix

Then:
vim /etc/yunohost/hooks.d/conf_regen/20-postfix_spamhaus

Then:

  • copy and paste the script you provided into that file and change the PERSONALKEY accordingly

Finally:

yunohost tools regen-conf postfix --force

I wonder about the permissions and ownership? Who should own that file and I guess it needs to be executed with a chmod +xfor the according user?

By the way this server is on Hetzner Cloud… it’s weird that for some people this issue is solved and I also did read this thread Trouble with spamhaus marking any mail as spam - #26 and Blocked using zen.spamhaus.org; Error: open resolver; - #34 by Aleks

@Aleks what should be in that file? /etc/resolv.dnsmasq.conf

when I do a cat /etc/resolv.dnsmasq.conf I have so many entries in that file:
nameserver IPv4
nameserver IPv4
nameserver IPv6
nameserver IPv6
nameserver IPv4
nameserver IPv4
nameserver IPv6
nameserver IPv6
nameserver IPv6
nameserver IPv6
nameserver IPv6
nameserver IPv6
nameserver IPv6

should it be like that?

permissions : -rw-r–r--

owner : root root

Hmmm…. all my hooks are own by root but is it recommended? I hadn’t thought about that before you asked or i forgot since i did this tuto. /etc/postfix/main.cf is own by root i guess, or perharps by postfix? i haven’t checked.

Edit: the documentation confirms that it is indeed Root who must be the owner

For permissions, i only give 644 rights and all is fine. The doc says to give execution rights, but it works without it.

Yes, that’s the shuffled pool of DNS servers to use

Beware, the copy/paste don’t work with quotes and double-quotes. I’ll try to correct it.

Edit: Now, it’s ok, i hope so.