# Developer Cookbook

# How to start a replication in MYSQL

##### 1. Βackup βάσης με master data για να μπορούμε να κάνουμε 

Για έλεγχο χώρου κάνουμε:

1\. df -h

Και έπειτα εφόσον έχουμε αρκετό χώρο κάνουμε

2\. mysqldump -u root -p emplemdb --single-transaction --master-data=2 --routines --events --triggers | gzip &gt; emplemdb.sql.gz

Το --master-data=2 περιέχει δεδομένα για το σημείο απο το οποίο θα ξεκινήσει το replication να τρέχει

Το --single-transaction σε innodb tables κάνει το backup χωρίς να χρειαστεί να LOCK τα table

<span style="background-color: rgb(224, 62, 45); color: rgb(255, 255, 255);">ΠΡΟΣΟΧΗ: ΝΑ ΓΙΝΕΤΑΙ ΕΛΕΓΧΟΣ ΟΤΙ ΥΠΑΡΧΕΙ ΧΩΡΟΣ ΣΤΟ ΣΕΡΒΕΡ ΠΡΙΝ ΓΙΝΕΙ ΤΟ BACKUP. ΑΝ ΕΧΕΙΣ 0% ΧΩΡΟ ΕΧΕΙΣ ΠΡΟΒΛΗΜΑ</span>

##### 2. Δημιουργία replication user στη master db

CREATE USER 'repl'@'%' IDENTIFIED BY 'StrongPassword';  
GRANT REPLICATION SLAVE ON \*.\* TO 'repl'@'%';  
FLUSH PRIVILEGES;

##### 3. Move the database you backed up from one server to another

<span style="background-color: rgb(224, 62, 45); color: rgb(255, 255, 255);">ΠΡΟΣΟΧΗ: ΝΑ ΓΙΝΕΤΑΙ ΕΛΕΓΧΟΣ ΟΤΙ ΥΠΑΡΧΕΙ ΧΩΡΟΣ ΣΤΟ ΣΕΡΒΕΡ ΠΡΙΝ ΓΙΝΕΙ Η ΜΕΤΑΦΟΡΑ. ΑΝ ΕΧΕΙΣ 0% ΧΩΡΟ ΕΧΕΙΣ ΠΡΟΒΛΗΜΑ</span>

scp dump.sql replica:/

Επίσης υπάρχει περίπτωση τα bin logs να πιάσουμε πολύ χώρο. Για διαγραφή των bin logs μπορεί να χρησιμοποιηθεί το

PURGE BINARY LOGS BEFORE NOW();

##### 4 Import database to replication server

gunzip &lt; emplemdb.sql.gz | mysql -u root -p emplemdb

Αυτή η διαδικασία μπορεί να πάρει αρκετή ώρα ανάλογα το σερβερ

##### 5. Point replica to master

CHANGE REPLICATION SOURCE TO  
 SOURCE\_HOST='PRIMARY\_IP',  
 SOURCE\_USER='repl',  
 SOURCE\_PASSWORD='StrongPassword',  
 SOURCE\_LOG\_FILE='mysql-bin.000012',  
 SOURCE\_LOG\_POS=45678;

Το SOURCE\_LOG\_FILE και το SOURCE\_LOG\_POS το βρίσκεις στο κάνωντας head -n 50 στο backup αρχειο που έχουμε πάρει.

##### TIPS &amp; INFO

1\. Δεν μπορείς να κάνεις στο replication alter table και να βρίσκεται σε διαφορετική κατάσταση απο τον παραγωγικό πίνακα. Δε το επιτρέπει η MYSQL ακόμα και αν μιλάμε για ίδιο είδος column type σε ιδιο ειδος διαφορετικού μεγέθους

# How to generate a self encrypted SSL file for non wildcard domains with certbot

```
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --apache
```

Για να μπορέσει να βρει το certification το certbot θα πρέπει να βρίσκεται στα sites-available το domain με τον παρακάτω τρόπο στο

<div id="bkmrk-%3Cvirtualhost-%2A%3A80%3E-s"><div>**&lt;VirtualHost \*:80&gt;**</div>  
<div>**ServerName emblem-ergani.gr**</div>  
<div>**ServerAdmin webmaster@localhost**</div><div>**DocumentRoot /var/www/html/public**</div>  
<div>**ErrorLog ${APACHE\_LOG\_DIR}/error.log**</div><div>**CustomLog ${APACHE\_LOG\_DIR}/access.log combined**</div><div>**ErrorDocument 404 /error-404.html**</div>  
<div>**RewriteEngine on**</div>  
**RewriteCond %{SERVER\_PORT} ^80$**<div>**RewriteRule ^(.\*)$ https://%{SERVER\_NAME}%{REQUEST\_URI} \[L,R\]**</div><div>**RewriteCond %{HTTP\_HOST} ^www\\.(.\*)$ \[NC\]**</div><div>**RewriteRule ^(.\*)$ https://%1/$1 \[R=301,L\]**</div>  
<div>**&lt;/VirtualHost&gt;**</div></div><div id="bkmrk-"></div><div id="bkmrk-to-%CF%80%CE%B1%CF%81%CE%B1%CF%80%CE%AC%CE%BD%CF%89-%CE%B4%CE%B5%CE%AF%CF%87%CE%BD%CE%B5%CE%B9-">To παραπάνω δείχνει ως root αρχείο το /var/www/html/public. Το certbot θα κοιτάξει το ServerName emblem-ergani.gr και θα δημιουργήσει το ssl conf.</div><div id="bkmrk--1"></div><div id="bkmrk-%CE%A4%CE%AD%CE%BB%CE%BF%CF%82-%CF%80%CF%81%CE%AD%CF%80%CE%B5%CE%B9-%CE%BD%CE%B1-%CE%BA%CE%AC%CE%BD%CE%B5">Τέλος πρέπει να κάνεις</div><div id="bkmrk--2"></div><div id="bkmrk-service-apache2-rest">**service apache2 restart**</div><div id="bkmrk--3"></div><div id="bkmrk----%CE%98%CE%B1-%CF%80%CF%81%CE%AD%CF%80%CE%B5%CE%B9-%CE%BD%CE%B1-%CE%B4%CE%B5%CE%B9%CF%87">**--** Θα πρέπει να δειχνει το A record του DNS στο server σου αλλιως δε θα μπορέσει να γίνει πιστοποίηση</div>

# Τι είναι το proxysql και πως μπορεί να χρησιμοποιηθεί

Το **ProxySQL** είναι ένα **high-performance proxy** για **MySQL** και **MariaDB**. Στην ουσία λειτουργεί ως ενδιάμεσος μεταξύ των εφαρμογών σου και των βάσεων δεδομένων σου και προσφέρει αρκετά πλεονεκτήματα. Ας το εξηγήσουμε αναλυτικά:

---

### 1️⃣ Τι κάνει

- Δέχεται **SQL queries** από την εφαρμογή σου και τα προωθεί στους κατάλληλους **MySQL servers**.
- Μπορεί να λειτουργήσει ως **load balancer**, δηλαδή να μοιράζει τα queries ανάμεσα σε πολλαπλούς servers για καλύτερη απόδοση.
- Υποστηρίζει **replication-aware routing**, δηλαδή μπορεί να στείλει τα **read queries** στους replicas και τα **write queries** στον master.
- Παρέχει **caching**, **connection pooling**, και **query routing** για καλύτερη απόδοση και λιγότερα connections στη βάση.

---

### 2️⃣ Κύρια χαρακτηριστικά

- **Load balancing:** Μοιράζει τα queries σε πολλούς servers για υψηλή διαθεσιμότητα και απόδοση.
- **Query routing:** Καθορίζει σε ποιο server θα πάει κάθε query (π.χ., writes στον master, reads στους replicas).
- **Connection pooling:** Κρατάει ανοιχτές συνδέσεις για να μην ανοίγουν/κλείνουν συνεχώς.
- **Query caching:** Αποθηκεύει προσωρινά αποτελέσματα queries για να μειώσει το load στη βάση.
- **Failover &amp; High Availability:** Αν πέσει ένας server, τα queries μπορούν να συνεχίσουν να εκτελούνται στους υπόλοιπους.

---

### 3️⃣ Πού χρησιμοποιείται

- Σε συστήματα με πολλούς **MySQL servers** (master + replicas).
- Όταν θέλεις **απλή κλιμάκωση** (scale reads) χωρίς να αλλάζεις την εφαρμογή.
- Για **monitoring** και **statistics** των queries, καθώς μπορεί να καταγράφει ποιες queries πάνε που.

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D-%CE%A3%CF%84%CE%B1-%CF%83"><div class="overflow-y-auto p-4" dir="ltr"></div><div class="overflow-y-auto p-4" dir="ltr">==============</div><div class="overflow-y-auto p-4" dir="ltr">Στα συστήματα μας το χρησιμοποιούμε κυρίως για να έχουμε λιγότερα max connections στη βάση δεδομένων. Αυτό που κάνει είναι αντί να ανοίγει νέο connection για κάθε νέο αρχείο PHP που ανοίγει κρατάει ενα ζωντανό connection και το διατηρεί μεταξύ κλήσεων επιταχύνωντας πολύ τη διαδικασία σύνδεσης και δεν χρειάζεται επιπλέον connections (tcp ktlp)</div></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk--3"></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-%CE%93%CE%B9%CE%B1%CF%84%CE%AF-%CE%B4%CE%B5-%CF%84%CE%BF-%CF%87%CF%81%CE%B7%CF%83%CE%B9%CE%BC%CE%BF%CF%80">Γιατί δε το χρησιμοποιούμε για load balancing?</div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk--4"></div><div class="overflow-y-auto p-4" dir="ltr" id="bkmrk-%CE%95%CE%BC%CE%B5%CE%AF%CF%82-%CF%83%CF%84%CE%B1-%CF%80%CF%81%CF%8C%CF%84%CE%B6%CE%B5%CE%BA%CF%84-%CE%BC">Εμείς στα πρότζεκτ μας τώρα δεν κάνουμε transactions για τα CRUD actions αλλά τα κάνουμε απευθείας. Το proxysql για να μπορεί να διανείμει σωστά τα select και τα writes θα πρέπει να έχουμε transactions. Αν σε κάποιο προτζεκτ μελλοντικά τρέξουμε με transactions (που πρέπει) θα μπορούμε με proxysql να δηλώσουμε 2+ servers και με μία σύνδεση να αναλαμβάνει το proxysql το load balancing </div>

# Τι είναι το redis και πως μπορεί να χρησιμοποιηθεί

Το redis είναι μια in-memory key-value βάση δεδομένων η οποία χρησιμοποιείτε κυρίως για διαδικασίες caching. Ως cache την χρησιμοποιούμε και εμείς σε σελίδες όπως το mydata.semantic.gr &amp; ergani.emblem.gr. Δεδομένα που παραμένουν συχνά ίδια μπορούμε να τα βάλουμε στο Redis ώστε αντί να γίνεται επικοινωνία με τη βάση δεδομένων και με το σκληρό δίσκο κατεπέκταση να γίνεται το διάβασμα των δεδομένων απο τη μνήμη η οποία είναι πολύ πιο γρήγορη.

Επιπλέον αν έχουμε πολλαπλές εφαρμογές που μπορεί να βρίσκονται σε άλλους σερβερ και πρέπει να επικοινωνήσουμε με τον κεντρικό σέρβερ αν έχεις ένα redis μπορείς να κρατήσεις "ζεστά" τα δεδομένα που σε ενδιαφέρουν άμεσα χωρίς επικοινωνία με τη βάση.

Ειδικά σε σερβερ με πολύ διαθέσιμη RAM είναι αρκετά εύκολη επιλογή το Redis για ελάφρυνση του φόρτου της βάσης δεδομένων και επιτάχυνση της εφαρμογής. Παρακάτω είναι η τεχνοτροπία που χρησιμοποιείτε στο ergani.emblem.gr για να διαβάζουμε και να γράφουμε στη cache

```php
public function getLimitedNotificationsByUser(int $iduser): array
    {
        $redisClient = $this->getRedisClient();
        $cacheKey = "NotificationsModel:getLimitedNotificationsByUser:" . $iduser . ":" . ($_SESSION['usertype'] ?? 'prod');
        $cacheTTL = 1800; // seconds
        $cachedData = $redisClient->get($cacheKey);

        if ($cachedData !== false) {
            return json_decode($cachedData, true);
        }

        $arr = $this->SelectSqlSlave("SELECT * FROM notifications WHERE iduser = ? ORDER BY seen ASC, id DESC LIMIT 30 ", ['i', $iduser]);

        $redisClient->setex($cacheKey, $cacheTTL, json_encode($arr));

        return $arr;
    }
```

Στο cache key βάζουμε όλες τις μεταβλητές της λειτουργίας ώστε να μην cacharoume δεδομένα απο άλλους χρήστες και πάρουμε δεδομένα απο άλλους. Ελέγχουμε αρχικά αν υπάρχουν στην cache τα δεδομένα βάση κλειδιού και αν υπάρχουν επιστρέφουμε τα δεδομένα της cache. Διαφορετικά κάνουμε το select στη βάση και επιστρέφουμε τα δεδομένα της βάσης. Τέλος εφόσον πήραμε τα δεδομένα απο τη βάση τα κάνουμε cache.

Είναι σημαντικό σε περίπτωση που γίνουν αλλαγή στα δεδομένα να κάνουμε invalidate της cache

```php
public function add(int $iduser, string $message): array
    {
        $redisClient = $this->getRedisClient();
        $cacheKey = "NotificationsModel:getLimitedNotificationsByUser:" . $iduser . ":" . ($_SESSION['usertype'] ?? 'prod');
        $redisClient->del($cacheKey);

        return $this->otherSql("INSERT INTO notifications (type, iduser, seen, message) VALUES (?,?,?,?)", ["siis", "Info", $iduser, 0, $message]);
    }
```

# Τι να προσέξουμε σε alter table σε παραγωγική βάση | MYSQL

Το alter table είναι μια διαδικασία που αν δεν γίνει προσεκτικά μπορεί να φέρει σε κατάσταση WRITE LOCK του table για πολύ ώρα με αποτέλεσμα να μη μπορεί να γράψει κανείς στη βάση. Θα πρέπει να προσέξουμε σε περίπτωση που κάνουμε alter table αν κάνουμε προσθήκη column ή αλλαγή column ή drop index κτλπ.

##### Αλλαγή column type σε ιδιο column type αλλά μεγαλύτερο μέγεθος

Είναι ασφαλές διαδικασία που γίνεται γρήγορα και δε θα επιφέρει lock για μεγάλο χρονικό διάστημα. Μπορεί να γίνει σε παραγωγική βάση

##### Αλλαγή column type σε ιδιο column type σε διαφορετικό είδος. Πχ VARCHAR -&gt; TEXT

Οι αλλαγές σε διαφορετικό column type μπορούν να αργήσουν πάρα πολυ καθως χρειάζεται να γίνει διαγραφή ολοκληρη του table και μεταφορά σε άλλο table όπου ο πίνακας επαναδημιουργείτε. Θα πρέπει να γίνει προσεκτικός έλεγχος αν θέλουμε να κάνουμε τέτοια αλλαγή και να σκεφτούμε το χρόνο που μπορεί να διαρκέσει. Συνήθως τέτοια αλλαγές δε πρέπει να γίνουν ποτέ σε παραγωγικό πίνακα

##### Προσθηκη column

Μπορεί να γίνει με ασφάλεια σε παραγωγικό πίνακα

# How to do safely ALTER TABLE for changes in column types with pt-online-change-schema

pt-online-schema-change will do alter tables without affecting at all the production table locks. It runs completely silent and is pretty safe.

If we want to check what exactly the pt-online-schema-change will do you can first do a dry run which will output what exactly will run

pt-online-schema-change --alter "MODIFY COLUMN xmlSent MEDIUMTEXT" --dry-run --print --alter-foreign-keys-method=auto D=emplemdb,t=novusXMLTrans -u root -p"!....!"

<span style="background-color: rgb(224, 62, 45); color: rgb(255, 255, 255);">ΠΡΟΣΟΧΗ: ΝΑ ΓΙΝΕΤΑΙ ΕΛΕΓΧΟΣ ΟΤΙ ΥΠΑΡΧΕΙ ΧΩΡΟΣ ΣΤΟ ΣΕΡΒΕΡ. ΑΝ ΕΧΕΙΣ ΛΙΓΟ ΧΩΡΟ ΕΧΕΙΣ ΠΡΟΒΛΗΜΑ</span>

To actually run it later on

pt-online-schema-change --alter "MODIFY COLUMN xmlSent MEDIUMTEXT" --execute --print --alter-foreign-keys-method=auto --progress percentage,1 D=emplemdb,t=novusXMLTrans -u root -p"!....!"

# How to swap a replication and a master database with each other efficiently

##### On the master database

**1. set the master database on read only**

SET GLOBAL super\_read\_only = ON;  
SET GLOBAL read\_only = ON;

##### On slave database

**find the last bin log position and file to later make this the replica**

SHOW MASTER STATUS**;**

mysql&gt; SHOW MASTER STATUS;  
+------------------+----------+--------------+------------------+-------------------+  
| File | Position | Binlog\_Do\_DB | Binlog\_Ignore\_DB | Executed\_Gtid\_Set |  
+------------------+----------+--------------+------------------+-------------------+  
| mysql-bin.011112 | 5895164 | emplemdb | | |  
+------------------+----------+--------------+------------------+-------------------+  
1 row in set (0.00 sec)

Save this output to a text file

stop the slave status and check the read only status. It should stop being read only

STOP SLAVE;

RESET SLAVE ALL;

Now this is your new master database

##### Change the app credentials to look at the slave database

##### On the old server setup the slave status and start slave

CHANGE REPLICATION SOURCE TO  
 SOURCE\_HOST='PRIMARY\_IP',  
 SOURCE\_USER='repl',  
 SOURCE\_PASSWORD='StrongPassword',  
 SOURCE\_LOG\_FILE='mysql-bin.000012',  
 SOURCE\_LOG\_POS=45678;

start slave;

# Αντικατάσταση Google CAPTCHA με cloudflare turnstile

1. Σύνδεση στο [https://dash.cloudflare.com/](https://dash.cloudflare.com/)
2. Μπαίνουμε στο application security &gt; turnstile
3. Κάνουμε click add widget
4. Προσθέτουμε hostnames
5. Αποθηκεύουμε το widget και κρατάμε το public/private key που μας δινει γιατι θα τα χρησιμοποιήσουμε αργότερα
6. ```javascript
    <body>
        <div id="captcha" data-size="invisible"></div>
    
        <button id="send">Send</button>
    
        <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstile&render=explicit" async defer></script>
    
        <script>
            let widgetId;
    
            // Cloudflare Turnstile (global callback referenced by the API script URL)
            window.turnstileWidgetId = null;
            window.turnstileToken = "";
            window.turnstileAutoSubmit = false;
      
            window.setSignInDisabled = function (disabled) {
              const btn = document.getElementById("sign-in");
              btn.disabled = !!disabled;
              btn.style.opacity = disabled ? "0.7" : "1";
              btn.style.cursor = disabled ? "not-allowed" : "pointer";
            };
      
            window.onloadTurnstile = function () {
              const render = () => {
                const el = document.getElementById("cf-turnstile");
    
                window.turnstileWidgetId = turnstile.render(el, {
                  sitekey: "PUBLIC_KEY",
                  // If you want to use ONLY your own "Είσοδος" button, keep this invisible.
                  size: "invisible",
                  callback: function (token) {
                    window.turnstileToken = token || "";
                    window.setSignInDisabled(false);
                    window.turnstileAutoSubmit = false;
                    document.getElementById("sign-in")?.click();
                  },
                  "expired-callback": function () {
                    window.turnstileToken = "";
                    window.turnstileAutoSubmit = false;
                    window.setSignInDisabled(false);
                  },
                  "error-callback": function () {
                    window.turnstileToken = "";
                    window.turnstileAutoSubmit = false;
                    window.setSignInDisabled(false);
                  },
                });
              };
      
              if (document.readyState === "loading") {
                document.addEventListener("DOMContentLoaded", render);
              } else {
                render();
              }
            };
    
            document.getElementById("send").addEventListener("click", () => {
                event.preventDefault();
    
                let fields = {
                  $email: document.getElementById("email"),
                  $password: document.getElementById("password"),
                };
          
                const turnstileToken =
                  window.turnstileToken ||
                  (window.turnstileWidgetId !== null && typeof turnstile !== "undefined"
                    ? turnstile.getResponse(window.turnstileWidgetId)
                    : "");
          
                if (!turnstileToken) {
                  // Disable your login button while captcha runs
                  window.setSignInDisabled(true);
                  window.turnstileAutoSubmit = true;
          
                  // For invisible Turnstile, execute it now
                  try {
                    turnstile.execute(window.turnstileWidgetId);
                  } catch (e) {
                    window.setSignInDisabled(false);
                    window.turnstileAutoSubmit = false;
                  }
                  return;
                }
          
                // Disable while the login request is in-flight (prevents double submit)
                window.setSignInDisabled(true);
      
                postData("/api.php?op=signIn", {
                  email: fields.$email.value,
                  password: fields.$password.value,
                  turnstileToken: turnstileToken,
                  "cf-turnstile-response": turnstileToken,
                })
            });
        </script>
    </body>
    ```
7. Βάζουμε στο script πάνω και όταν κάνουμε send έχουμε πλέον το token που θα σταλθεί στο backend
8. Στο backend μετά κάνουμε το παρακάτω για επιβεβαίωση οτι έιναι οκ να προχωρήσουμε
9. ```php
    $token = $_POST['cf-turnstile-response'];   // <-- Token from Turnstile
    $secret = "YOUR_SECRET_KEY";
    
    $verify = curl_init("https://challenges.cloudflare.com/turnstile/v0/siteverify");
    curl_setopt($verify, CURLOPT_POST, true);
    curl_setopt($verify, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($verify, CURLOPT_POSTFIELDS, [
        'secret' => $secret,
        'response' => $token
    ]);
    
    $response = json_decode(curl_exec($verify), true);
    
    if ($response["success"]) {
        echo "HUMAN VERIFIED!";
    } else {
        echo "FAILED";
    }
    ```

Μερικές επιπλέον επεξηγήσεις για το παραπάνω. Κάνουμε οπω ειναι copy paste αυτο το κομματι

```javascript
let widgetId;

        // Cloudflare Turnstile (global callback referenced by the API script URL)
        window.turnstileWidgetId = null;
        window.turnstileToken = "";
        window.turnstileAutoSubmit = false;
  
        window.setSignInDisabled = function (disabled) {
          const btn = document.getElementById("sign-in");
          btn.disabled = !!disabled;
          btn.style.opacity = disabled ? "0.7" : "1";
          btn.style.cursor = disabled ? "not-allowed" : "pointer";
        };
  
        window.onloadTurnstile = function () {
          const render = () => {
            const el = document.getElementById("cf-turnstile");

            window.turnstileWidgetId = turnstile.render(el, {
              sitekey: "PUBLIC_KEY",
              // If you want to use ONLY your own "Είσοδος" button, keep this invisible.
              size: "invisible",
              callback: function (token) {
                window.turnstileToken = token || "";
                window.setSignInDisabled(false);
                window.turnstileAutoSubmit = false;
                document.getElementById("sign-in")?.click();
              },
              "expired-callback": function () {
                window.turnstileToken = "";
                window.turnstileAutoSubmit = false;
                window.setSignInDisabled(false);
              },
              "error-callback": function () {
                window.turnstileToken = "";
                window.turnstileAutoSubmit = false;
                window.setSignInDisabled(false);
              },
            });
          };
  
          if (document.readyState === "loading") {
            document.addEventListener("DOMContentLoaded", render);
          } else {
            render();
          }
        };
```

Δε θα χρειαστει να αλλαξουμε κατι εδω εκτος απο το document.getElementById("sign-in") να δειχνει το κουμπι συνδεσης. και έπειτα παίρνουμε οπως ειναι αυτο το κομματι πριν το request συνδεσης:

```javascript
const turnstileToken =
  window.turnstileToken ||
  (window.turnstileWidgetId !== null && typeof turnstile !== "undefined"
    ? turnstile.getResponse(window.turnstileWidgetId)
    : "");

if (!turnstileToken) {
  // Disable your login button while captcha runs
  window.setSignInDisabled(true);
  window.turnstileAutoSubmit = true;

  // For invisible Turnstile, execute it now
  try {
    turnstile.execute(window.turnstileWidgetId);
  } catch (e) {
    window.setSignInDisabled(false);
    window.turnstileAutoSubmit = false;
  }
  return;
}

// Disable while the login request is in-flight (prevents double submit)
window.setSignInDisabled(true);
```

Και απλα συμπεριλαμβανουμε στην αποστολη μετα το

<div id="bkmrk-turnstiletoken"><div>turnstileToken</div></div></body>

# Generate SSL certificate for Wildcard

sudo certbot certonly --manual --preferred-challenges=dns --email thanasismpalatsoukas@gmail.com --agree-tos -d "\*.emblem-ergani.gr"

# How to run mysql clone on a server

## 1. Prerequisites

Before you start, ensure both servers meet these requirements:

- **Version:** Both must be MySQL 8.0.17 or higher.
- **Operating System:** Must be the same (e.g., both Linux).
- **Plugins:** The `clone_plugin` must be installed on both.

## 2. Step-by-Step Configuration

### Phase A: Prepare the Donor (Primary)

Log into your **Primary** server and run:

<div _ngcontent-ng-c3152465465="" class="code-block ng-tns-c3152465465-38 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjKievl_ZSUAxUAAAAAHQAAAAAQVA" decode-data-ved="1" id="bkmrk-sql" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_f3284bc5be0effe3","c_9e4a6d487ea664b9",null,"rc_41181f01d082e820",null,null,"",null,1,null,null,1,0]]"><div _ngcontent-ng-c3152465465="" class="code-block-decoration header-formatted gds-title-s ng-tns-c3152465465-38 ng-star-inserted"><span class="ng-tns-c3152465465-38">SQL</span><div _ngcontent-ng-c3152465465="" class="buttons ng-tns-c3152465465-38 ng-star-inserted"><button aria-label="Download code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger download-button ng-tns-c3152465465-38 mat-unthemed ng-star-inserted"></button><button aria-label="Copy code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c3152465465-38 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c3152465465="" class="formatted-code-block-internal-container ng-tns-c3152465465-38"><div _ngcontent-ng-c3152465465="" class="animated-opacity ng-tns-c3152465465-38"></div></div></div>```
-- Install the plugin
INSTALL PLUGIN clone SONAME 'mysql_clone.so';

-- Create a replication user with cloning permissions
CREATE USER 'clone_user'@'%' IDENTIFIED BY 'password';
GRANT BACKUP_ADMIN ON *.* TO 'clone_user'@'%';

```

<div _ngcontent-ng-c3152465465="" class="code-block ng-tns-c3152465465-38 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjKievl_ZSUAxUAAAAAHQAAAAAQVA" decode-data-ved="1" id="bkmrk--1" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_f3284bc5be0effe3","c_9e4a6d487ea664b9",null,"rc_41181f01d082e820",null,null,"",null,1,null,null,1,0]]"><div _ngcontent-ng-c3152465465="" class="formatted-code-block-internal-container ng-tns-c3152465465-38"><div _ngcontent-ng-c3152465465="" class="animated-opacity ng-tns-c3152465465-38"></div></div></div>### Phase B: Prepare the Recipient (Replica)

Log into your **New Replica** server and run:

<div _ngcontent-ng-c3152465465="" class="code-block ng-tns-c3152465465-39 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjKievl_ZSUAxUAAAAAHQAAAAAQVQ" decode-data-ved="1" id="bkmrk-sql-1" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_f3284bc5be0effe3","c_9e4a6d487ea664b9",null,"rc_41181f01d082e820",null,null,"",null,1,null,null,1,0]]"><div _ngcontent-ng-c3152465465="" class="code-block-decoration header-formatted gds-title-s ng-tns-c3152465465-39 ng-star-inserted"><span class="ng-tns-c3152465465-39">SQL</span><div _ngcontent-ng-c3152465465="" class="buttons ng-tns-c3152465465-39 ng-star-inserted"><button aria-label="Download code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger download-button ng-tns-c3152465465-39 mat-unthemed ng-star-inserted"></button><button aria-label="Copy code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c3152465465-39 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c3152465465="" class="formatted-code-block-internal-container ng-tns-c3152465465-39"><div _ngcontent-ng-c3152465465="" class="animated-opacity ng-tns-c3152465465-39"></div></div></div>```
-- Install the plugin
INSTALL PLUGIN clone SONAME 'mysql_clone.so';

-- Allow the recipient to be overwritten by the donor
SET GLOBAL clone_valid_donor_list = 'primary-ip-address:3306';

```

<div _ngcontent-ng-c3152465465="" class="code-block ng-tns-c3152465465-39 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjKievl_ZSUAxUAAAAAHQAAAAAQVQ" decode-data-ved="1" id="bkmrk--4" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_f3284bc5be0effe3","c_9e4a6d487ea664b9",null,"rc_41181f01d082e820",null,null,"",null,1,null,null,1,0]]"><div _ngcontent-ng-c3152465465="" class="formatted-code-block-internal-container ng-tns-c3152465465-39"><div _ngcontent-ng-c3152465465="" class="animated-opacity ng-tns-c3152465465-39"></div></div></div>### Phase C: Execute the Clone

On the **Recipient** server, trigger the data transfer. **Warning:** This will delete all existing data on the recipient and restart the MySQL service automatically after completion.

<div _ngcontent-ng-c3152465465="" class="code-block ng-tns-c3152465465-40 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjKievl_ZSUAxUAAAAAHQAAAAAQVg" decode-data-ved="1" id="bkmrk-sql-2" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_f3284bc5be0effe3","c_9e4a6d487ea664b9",null,"rc_41181f01d082e820",null,null,"",null,1,null,null,1,0]]"><div _ngcontent-ng-c3152465465="" class="code-block-decoration header-formatted gds-title-s ng-tns-c3152465465-40 ng-star-inserted"><span class="ng-tns-c3152465465-40">SQL</span><div _ngcontent-ng-c3152465465="" class="buttons ng-tns-c3152465465-40 ng-star-inserted"><button aria-label="Download code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger download-button ng-tns-c3152465465-40 mat-unthemed ng-star-inserted"></button><button aria-label="Copy code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c3152465465-40 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c3152465465="" class="formatted-code-block-internal-container ng-tns-c3152465465-40"><div _ngcontent-ng-c3152465465="" class="animated-opacity ng-tns-c3152465465-40"></div></div></div>```
CLONE INSTANCE FROM 'clone_user'@'primary-ip-address':3306 IDENTIFIED BY 'password';

```

<div _ngcontent-ng-c3152465465="" class="code-block ng-tns-c3152465465-40 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjKievl_ZSUAxUAAAAAHQAAAAAQVg" decode-data-ved="1" id="bkmrk--7" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_f3284bc5be0effe3","c_9e4a6d487ea664b9",null,"rc_41181f01d082e820",null,null,"",null,1,null,null,1,0]]"><div _ngcontent-ng-c3152465465="" class="formatted-code-block-internal-container ng-tns-c3152465465-40"><div _ngcontent-ng-c3152465465="" class="animated-opacity ng-tns-c3152465465-40"></div></div></div>---

## 3. Completing the Replication

The beauty of the Clone plugin is that it automatically captures the **Binary Log** coordinates (GTID or filename/position) during the transfer.

Once the recipient restarts, log back in and check the status:

<div _ngcontent-ng-c3152465465="" class="code-block ng-tns-c3152465465-41 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjKievl_ZSUAxUAAAAAHQAAAAAQVw" decode-data-ved="1" id="bkmrk-sql-3" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_f3284bc5be0effe3","c_9e4a6d487ea664b9",null,"rc_41181f01d082e820",null,null,"",null,1,null,null,1,0]]"><div _ngcontent-ng-c3152465465="" class="code-block-decoration header-formatted gds-title-s ng-tns-c3152465465-41 ng-star-inserted"><span class="ng-tns-c3152465465-41">SQL</span><div _ngcontent-ng-c3152465465="" class="buttons ng-tns-c3152465465-41 ng-star-inserted"><button aria-label="Download code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger download-button ng-tns-c3152465465-41 mat-unthemed ng-star-inserted"></button><button aria-label="Copy code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c3152465465-41 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c3152465465="" class="formatted-code-block-internal-container ng-tns-c3152465465-41"><div _ngcontent-ng-c3152465465="" class="animated-opacity ng-tns-c3152465465-41"></div></div></div>```
SELECT BINLOG_FILE, BINLOG_POS FROM performance_schema.clone_status;

```

<div _ngcontent-ng-c3152465465="" class="code-block ng-tns-c3152465465-41 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjKievl_ZSUAxUAAAAAHQAAAAAQVw" decode-data-ved="1" id="bkmrk--11" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_f3284bc5be0effe3","c_9e4a6d487ea664b9",null,"rc_41181f01d082e820",null,null,"",null,1,null,null,1,0]]"><div _ngcontent-ng-c3152465465="" class="formatted-code-block-internal-container ng-tns-c3152465465-41"><div _ngcontent-ng-c3152465465="" class="animated-opacity ng-tns-c3152465465-41"></div></div></div>### Option 1: If using GTIDs (Recommended)

If your primary uses GTIDs, replication might start automatically or simply require:

<div _ngcontent-ng-c3152465465="" class="code-block ng-tns-c3152465465-42 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjKievl_ZSUAxUAAAAAHQAAAAAQWA" decode-data-ved="1" id="bkmrk-sql-4" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_f3284bc5be0effe3","c_9e4a6d487ea664b9",null,"rc_41181f01d082e820",null,null,"",null,1,null,null,1,0]]"><div _ngcontent-ng-c3152465465="" class="code-block-decoration header-formatted gds-title-s ng-tns-c3152465465-42 ng-star-inserted"><span class="ng-tns-c3152465465-42">SQL</span><div _ngcontent-ng-c3152465465="" class="buttons ng-tns-c3152465465-42 ng-star-inserted"><button aria-label="Download code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger download-button ng-tns-c3152465465-42 mat-unthemed ng-star-inserted"></button><button aria-label="Copy code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c3152465465-42 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c3152465465="" class="formatted-code-block-internal-container ng-tns-c3152465465-42"><div _ngcontent-ng-c3152465465="" class="animated-opacity ng-tns-c3152465465-42"></div></div></div>```
CHANGE REPLICATION SOURCE TO 
    SOURCE_HOST='primary-ip-address', 
    SOURCE_USER='repl_user', 
    SOURCE_PASSWORD='password', 
    SOURCE_AUTO_POSITION=1;
START REPLICA;

```

<div _ngcontent-ng-c3152465465="" class="code-block ng-tns-c3152465465-42 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjKievl_ZSUAxUAAAAAHQAAAAAQWA" decode-data-ved="1" id="bkmrk--14" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_f3284bc5be0effe3","c_9e4a6d487ea664b9",null,"rc_41181f01d082e820",null,null,"",null,1,null,null,1,0]]"><div _ngcontent-ng-c3152465465="" class="formatted-code-block-internal-container ng-tns-c3152465465-42"><div _ngcontent-ng-c3152465465="" class="animated-opacity ng-tns-c3152465465-42"></div></div></div>### Option 2: If using Binary Log Position

<div _ngcontent-ng-c3152465465="" class="code-block ng-tns-c3152465465-43 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahcKEwjKievl_ZSUAxUAAAAAHQAAAAAQWQ" decode-data-ved="1" id="bkmrk-sql-5" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_f3284bc5be0effe3","c_9e4a6d487ea664b9",null,"rc_41181f01d082e820",null,null,"",null,1,null,null,1,0]]"><div _ngcontent-ng-c3152465465="" class="code-block-decoration header-formatted gds-title-s ng-tns-c3152465465-43 ng-star-inserted"><span class="ng-tns-c3152465465-43">SQL</span><div _ngcontent-ng-c3152465465="" class="buttons ng-tns-c3152465465-43 ng-star-inserted"><button aria-label="Download code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger download-button ng-tns-c3152465465-43 mat-unthemed ng-star-inserted"></button><button aria-label="Copy code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c3152465465-43 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c3152465465="" class="formatted-code-block-internal-container ng-tns-c3152465465-43"><div _ngcontent-ng-c3152465465="" class="animated-opacity ng-tns-c3152465465-43"></div></div></div>```
-- Use the coordinates found in performance_schema.clone_status
CHANGE REPLICATION SOURCE TO 
    SOURCE_HOST='primary-ip-address', 
    SOURCE_USER='repl_user', 
    SOURCE_PASSWORD='password', 
    SOURCE_LOG_FILE='binlog.00000X', 
    SOURCE_LOG_POS=123;
START REPLICA;
```

# emblem ergani disaster response for traffic

Εαν βρεθουμε στη δυσαρεστη θεση να πεσει ο κεντρικος σερβερ που φιλεξει το emblem ergani (88.198.203.86) έχουμε έτοιμο 2ο σερβερ ο οποιος ειναι ετοιμος να λαβει τη θεση του (167.235.135.104).

Βήματα για την μεταφορα λειτουργιας απο τον 88.198.203.86 -&gt; 167.235.135.104

**- Αλλαγή A Record στον 167.235.135.104**

\- Ενεργοποιηση apache conf a2ensite

**a2ensite /etc/apache/sites-available/ergani.emblem.gr.conf**

Υπαρχει μια περιπτωση εδω να εχουμε modules στο ergani.emblem.gr.conf τα οποια δεν υπαρχουν στο σερβερ. Κατεβαστε τα

\- **Δημιουργια νεων SSL certificates με certbot ή μεταφορα certs απο το προηγουμενο σερβερ**

**- Ανοιγμα cronjob και κανε comment out αυτα που βαλαμε σε comments για εκτακτη αναγκη**

# Automate git pull securely

# ✅ Best setup for your repo server (recommended): SSH deploy key

This is the standard production approach.

## 1. On the server: create an SSH key

```
ssh-keygen -t ed25519 -C "emblem-ergani-server"
```

<div class="relative w-full mt-4 mb-1" id="bkmrk-"><div class=""><div class="relative"><div class="h-full min-h-0 min-w-0"><div class="h-full min-h-0 min-w-0"><div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"><div class="h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback"><div class="relative"><div class=""><div class="relative z-0 flex max-w-full"><div class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr" id="bkmrk--1"><div class="cm-scroller"></div></div></div></div></div></div></div></div></div><div class=""><div class="">  
</div></div></div></div></div>Press Enter for default path:

```
/root/.ssh/id_ed25519
```

<div class="relative w-full mt-4 mb-1" id="bkmrk--2"><div class=""><div class="relative"><div class="h-full min-h-0 min-w-0"><div class="h-full min-h-0 min-w-0"><div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"><div class="h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback"><div class="relative"><div class="pe-11 pt-3"><div class="relative z-0 flex max-w-full"><div class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr" id="bkmrk--3"><div class="cm-scroller"></div></div></div></div></div></div></div></div></div><div class=""><div class="">  
</div></div></div></div></div>---

## 2. Show the public key

```
cat ~/.ssh/id_ed25519.pub
```

<div class="relative w-full mt-4 mb-1" id="bkmrk--5"><div class=""><div class="relative"><div class="h-full min-h-0 min-w-0"><div class="h-full min-h-0 min-w-0"><div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"><div class="h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback"><div class="relative"><div class=""><div class="relative z-0 flex max-w-full"><div class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr" id="bkmrk--6"><div class="cm-scroller"></div></div></div></div></div></div></div></div></div><div class=""><div class="">  
</div></div></div></div></div>Copy it.

---

## 3. Add it to your GitHub repo

Go to:

👉 Repo → **Settings → Deploy keys → Add key**

Paste the key.

### Important choice:

- ✔️ Enable **“Allow write access”** if server will push
- Otherwise leave it read-only (safer for production pulls)

---

## 4. Switch repo remote to SSH (on server)

Inside your project:

```
git remote set-url origin git@github.com:semantic-software/emblem-ergani.git
```

<div class="relative w-full mt-4 mb-1" id="bkmrk--9"><div class=""><div class="relative"><div class="h-full min-h-0 min-w-0"><div class="h-full min-h-0 min-w-0"><div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"><div class="h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback"><div class="relative"><div class=""><div class="relative z-0 flex max-w-full"><div class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr" id="bkmrk--10"><div class="cm-scroller"></div></div></div></div></div></div></div></div></div><div class=""><div class="">  
</div></div></div></div></div>---

## 5. Test authentication

```
ssh -T git@github.com
```

<div class="relative w-full mt-4 mb-1" id="bkmrk--12"><div class=""><div class="relative"><div class="h-full min-h-0 min-w-0"><div class="h-full min-h-0 min-w-0"><div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"><div class="h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback"><div class="relative"><div class=""><div class="relative z-0 flex max-w-full"><div class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr" id="bkmrk--13"><div class="cm-scroller"></div></div></div></div></div></div></div></div></div><div class=""><div class="">  
</div></div></div></div></div>You should see:

```
Hi semantic-software! You've successfully authenticated...
```

<div class="relative w-full mt-4 mb-1" id="bkmrk--14"><div class=""><div class="relative"><div class="h-full min-h-0 min-w-0"><div class="h-full min-h-0 min-w-0"><div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"><div class="h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback"><div class="relative"><div class="pe-11 pt-3"><div class="relative z-0 flex max-w-full"><div class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr" id="bkmrk--15"><div class="cm-scroller"></div></div></div></div></div></div></div></div></div><div class=""><div class="">  
</div></div></div></div></div>---

## 6. Now automation works (no password ever)

```
git pull origin main
```

<div class="relative w-full mt-4 mb-1" id="bkmrk--17"><div class=""><div class="relative"><div class="h-full min-h-0 min-w-0"><div class="h-full min-h-0 min-w-0"><div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"><div class="h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback"><div class="relative"><div class=""><div class="relative z-0 flex max-w-full"><div class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr" id="bkmrk--18"><div class="cm-scroller"></div></div></div></div></div></div></div></div></div><div class=""><div class="">  
</div></div></div></div></div>---

# 🤖 Automate it (cron / deploy hook)

### Example cron (pull every minute)

```
crontab -e
```

<div class="relative w-full mt-4 mb-1" id="bkmrk--20"><div class=""><div class="relative"><div class="h-full min-h-0 min-w-0"><div class="h-full min-h-0 min-w-0"><div class="border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl"><div class="h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback"><div class="relative"><div class=""><div class="relative z-0 flex max-w-full"><div class="q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼd ͼr" dir="ltr" id="bkmrk--21"><div class="cm-scroller"></div></div></div></div></div></div></div></div></div><div class=""><div class="">  
</div></div></div></div></div>```
* * * * * cd /var/www/emblem-ergani && git pull origin main
```

# Οδηγός setup για το cookie widget (semantic cookie)

## Οδηγός setup για το cookie widget



1) Βάλε το widget script στο τέλος της σελίδας Τοποθέτησέ το μία φορά, λίγο πριν το κλείσιμο του `</body>`: ```html <script src="https://cdn.emblem.gr/semantic-cookie/cookie-widget.js"></script> ```

2) Σημαντικό για scripts που θες να μπλοκάρονται μέχρι consent Τα tracking scripts πρέπει να δηλώνονται ως **text/plain** (όχι κανονικό JS), με `data-*` attribute κατηγορίας. Παράδειγμα: ```html <script type="text/plain" data-cookiecategory="analytics"> // Facebook Pixel, GA, Clarity, κτλ. </script> ``` Μπορείς αντί για `analytics` να βάλεις άλλη κατηγορία, ανάλογα με το script. 

3) Consent update προς Google (προαιρετικό αλλά προτεινόμενο) Αν χρησιμοποιείς GTM/gtag, κράτησε listener για το event `cookieConsentSaved`: ```html <script> document.addEventListener('cookieConsentSaved', function(event) { var consent = event && event.detail && event.detail.consent ? event.detail.consent : null; if (!consent || typeof gtag !== 'function') return; var marketingGranted = !!consent.marketing; var analyticsGranted = !!consent.analytics; gtag('consent', 'update', { ad_storage: marketingGranted ? 'granted' : 'denied', ad_user_data: marketingGranted ? 'granted' : 'denied', ad_personalization: marketingGranted ? 'granted' : 'denied', analytics_storage: analyticsGranted ? 'granted' : 'denied', functionality_storage: 'granted', personalization_storage: marketingGranted ? 'granted' : 'denied', security_storage: 'granted' }); }); </script> ``` 

4) Σειρά φόρτωσης που προτείνεται 1. Consent Mode default (denied by default) 2. GTM/gtag 3. Listener `cookieConsentSaved` 4. Cookie widget script (πριν το `</body>`) 

5) Γρήγορο checklist - Το widget μπαίνει μόνο μία φορά. - Τα scripts που θέλεις να ελέγχονται από consent είναι `type="text/plain"` και έχουν `data-cookiecategory="..."`. - Με accept analytics/marketing, ενημερώνεται σωστά το consent στο gtag. - Με reject, τα αντίστοιχα storage μένουν `denied`.