<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Konstantin Bogomolov</title>
    <description>The latest articles on DEV Community by Konstantin Bogomolov (@bogkonstantin).</description>
    <link>https://dev.to/bogkonstantin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F552894%2F3de4937e-abb0-48eb-beec-e5e32fe668ca.jpeg</url>
      <title>DEV Community: Konstantin Bogomolov</title>
      <link>https://dev.to/bogkonstantin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bogkonstantin"/>
    <language>en</language>
    <item>
      <title>What is a TLS Certificate? With an example</title>
      <dc:creator>Konstantin Bogomolov</dc:creator>
      <pubDate>Fri, 04 Apr 2025 21:46:49 +0000</pubDate>
      <link>https://dev.to/bogkonstantin/what-is-a-tls-certificate-with-an-example-i1j</link>
      <guid>https://dev.to/bogkonstantin/what-is-a-tls-certificate-with-an-example-i1j</guid>
      <description>&lt;p&gt;A TLS certificate (also called an SSL certificate) is a digital certificate that proves a website is secure and trustworthy. It enables HTTPS, which encrypts the data between a user's browser and the server.&lt;/p&gt;

&lt;p&gt;When you visit a website with HTTPS (like &lt;a href="https://example.com" rel="noopener noreferrer"&gt;https://example.com&lt;/a&gt;), your browser checks the TLS certificate to make sure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's valid&lt;/li&gt;
&lt;li&gt;It's issued by a trusted Certificate Authority (CA)&lt;/li&gt;
&lt;li&gt;It's not expired&lt;/li&gt;
&lt;li&gt;It matches the domain name&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real Example
&lt;/h3&gt;

&lt;p&gt;Let's look at the TLS certificate for &lt;a href="https://www.google.com" rel="noopener noreferrer"&gt;https://www.google.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using CLI, you can get a certificate details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-showcerts&lt;/span&gt; &lt;span class="nt"&gt;-servername&lt;/span&gt; google.com &lt;span class="nt"&gt;-connect&lt;/span&gt; google.com:443 2&amp;gt;/dev/null | openssl x509 &lt;span class="nt"&gt;-inform&lt;/span&gt; pem &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will get decoded certificate, which looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            9a:59:e3:69:20:54:81:cc:09:2f:9e:71:4d:cf:0b:42
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C=US, O=Google Trust Services, CN=WE2
        Validity
            Not Before: Mar 20 11:18:50 2025 GMT
            Not After : Jun 12 11:18:49 2025 GMT
        Subject: CN=*.google.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:81:de:88:48:63:00:73:1b:60:b7:5f:7a:d1:93:
                    a1:a8:50:ea:59:f0:eb:f8:3d:aa:41:7e:48:e3:1d:
                    f1:16:57:c9:cf:41:c5:b0:c7:3e:4b:bc:00:c9:75:
                    25:91:b7:eb:e6:a3:03:73:cf:25:59:98:5f:76:d7:
                    3c:06:5e:9e:26
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier:
                92:E6:2F:BA:C3:C2:DF:E6:3F:94:AE:58:48:6C:BB:B5:80:ED:AF:91
            X509v3 Authority Key Identifier:
                75:BE:C4:77:AE:89:F6:44:37:7D:CF:B1:68:1F:1D:1A:EB:DC:34:59
            Authority Information Access:
                OCSP - URI:http://o.pki.goog/we2
                CA Issuers - URI:http://i.pki.goog/we2.crt
            X509v3 Subject Alternative Name:
                DNS:*.google.com, DNS:*.appengine.google.com, DNS:*.bdn.dev, DNS:*.origin-test.bdn.dev, DNS:*.cloud.google.com, DNS:*.crowdsource.google.com, DNS:*.datacompute.google.com, DNS:*.google.ca, DNS:*.google.cl, DNS:*.google.co.in, DNS:*.google.co.jp, DNS:*.google.co.uk, DNS:*.google.com.ar, DNS:*.google.com.au, DNS:*.google.com.br, DNS:*.google.com.co, DNS:*.google.com.mx, DNS:*.google.com.tr, DNS:*.google.com.vn, DNS:*.google.de, DNS:*.google.es, DNS:*.google.fr, DNS:*.google.hu, DNS:*.google.it, DNS:*.google.nl, DNS:*.google.pl, DNS:*.google.pt, DNS:*.googleapis.cn, DNS:*.googlevideo.com, DNS:*.gstatic.cn, DNS:*.gstatic-cn.com, DNS:googlecnapps.cn, DNS:*.googlecnapps.cn, DNS:googleapps-cn.com, DNS:*.googleapps-cn.com, DNS:gkecnapps.cn, DNS:*.gkecnapps.cn, DNS:googledownloads.cn, DNS:*.googledownloads.cn, DNS:recaptcha.net.cn, DNS:*.recaptcha.net.cn, DNS:recaptcha-cn.net, DNS:*.recaptcha-cn.net, DNS:widevine.cn, DNS:*.widevine.cn, DNS:ampproject.org.cn, DNS:*.ampproject.org.cn, DNS:ampproject.net.cn, DNS:*.ampproject.net.cn, DNS:google-analytics-cn.com, DNS:*.google-analytics-cn.com, DNS:googleadservices-cn.com, DNS:*.googleadservices-cn.com, DNS:googlevads-cn.com, DNS:*.googlevads-cn.com, DNS:googleapis-cn.com, DNS:*.googleapis-cn.com, DNS:googleoptimize-cn.com, DNS:*.googleoptimize-cn.com, DNS:doubleclick-cn.net, DNS:*.doubleclick-cn.net, DNS:*.fls.doubleclick-cn.net, DNS:*.g.doubleclick-cn.net, DNS:doubleclick.cn, DNS:*.doubleclick.cn, DNS:*.fls.doubleclick.cn, DNS:*.g.doubleclick.cn, DNS:dartsearch-cn.net, DNS:*.dartsearch-cn.net, DNS:googletraveladservices-cn.com, DNS:*.googletraveladservices-cn.com, DNS:googletagservices-cn.com, DNS:*.googletagservices-cn.com, DNS:googletagmanager-cn.com, DNS:*.googletagmanager-cn.com, DNS:googlesyndication-cn.com, DNS:*.googlesyndication-cn.com, DNS:*.safeframe.googlesyndication-cn.com, DNS:app-measurement-cn.com, DNS:*.app-measurement-cn.com, DNS:gvt1-cn.com, DNS:*.gvt1-cn.com, DNS:gvt2-cn.com, DNS:*.gvt2-cn.com, DNS:2mdn-cn.net, DNS:*.2mdn-cn.net, DNS:googleflights-cn.net, DNS:*.googleflights-cn.net, DNS:admob-cn.com, DNS:*.admob-cn.com, DNS:googlesandbox-cn.com, DNS:*.googlesandbox-cn.com, DNS:*.safenup.googlesandbox-cn.com, DNS:*.gstatic.com, DNS:*.metric.gstatic.com, DNS:*.gvt1.com, DNS:*.gcpcdn.gvt1.com, DNS:*.gvt2.com, DNS:*.gcp.gvt2.com, DNS:*.url.google.com, DNS:*.youtube-nocookie.com, DNS:*.ytimg.com, DNS:android.com, DNS:*.android.com, DNS:*.flash.android.com, DNS:g.cn, DNS:*.g.cn, DNS:g.co, DNS:*.g.co, DNS:goo.gl, DNS:www.goo.gl, DNS:google-analytics.com, DNS:*.google-analytics.com, DNS:google.com, DNS:googlecommerce.com, DNS:*.googlecommerce.com, DNS:ggpht.cn, DNS:*.ggpht.cn, DNS:urchin.com, DNS:*.urchin.com, DNS:youtu.be, DNS:youtube.com, DNS:*.youtube.com, DNS:music.youtube.com, DNS:*.music.youtube.com, DNS:youtubeeducation.com, DNS:*.youtubeeducation.com, DNS:youtubekids.com, DNS:*.youtubekids.com, DNS:yt.be, DNS:*.yt.be, DNS:android.clients.google.com, DNS:*.android.google.cn, DNS:*.chrome.google.cn, DNS:*.developers.google.cn, DNS:*.aistudio.google.com
            X509v3 Certificate Policies:
                Policy: 2.23.140.1.2.1
            X509v3 CRL Distribution Points:
                Full Name:
                  URI:http://c.pki.goog/we2/xuzt3PU9F_w.crl

            CT Precertificate SCTs:
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : CF:11:56:EE:D5:2E:7C:AF:F3:87:5B:D9:69:2E:9B:E9:
                                1A:71:67:4A:B0:17:EC:AC:01:D2:5B:77:CE:CC:3B:08
                    Timestamp : Mar 20 12:18:56.618 2025 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:44:02:20:3A:82:2A:9B:01:F1:18:46:DA:4F:C8:74:
                                83:8E:07:93:86:AD:FF:DE:E8:49:E4:C2:68:D4:C0:85:
                                76:ED:9A:D3:02:20:0B:6A:90:A0:FE:FB:C4:DA:CF:61:
                                C0:EC:62:EE:76:73:EF:C0:96:1D:63:F9:B5:3C:A0:3E:
                                35:0A:BC:C1:B0:17
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : E6:D2:31:63:40:77:8C:C1:10:41:06:D7:71:B9:CE:C1:
                                D2:40:F6:96:84:86:FB:BA:87:32:1D:FD:1E:37:8E:50
                    Timestamp : Mar 20 12:18:57.585 2025 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:45:02:21:00:DA:4A:51:5F:E0:D9:3F:7B:BA:DE:8F:
                                F7:1D:67:79:83:13:68:D8:40:F6:80:6A:2D:C5:2C:AE:
                                A1:26:40:BA:C6:02:20:61:24:F1:2F:0D:66:23:88:4A:
                                13:CB:AA:F9:84:77:72:7F:CF:23:7D:7A:81:52:59:7A:
                                83:7D:E5:C5:25:C5:26
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:45:02:20:19:91:27:bb:9e:cd:0b:d5:18:c4:67:2e:70:43:
        59:b0:79:39:4b:1e:ec:ad:03:81:10:15:b9:bd:78:af:c8:4f:
        02:21:00:cb:c0:0a:81:91:00:73:d6:31:54:d3:7f:28:eb:ec:
        60:e6:7f:7d:1d:9e:b4:5f:f0:98:7b:25:ca:de:1f:2c:5d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Main details of Google's TLS certificate
&lt;/h3&gt;

&lt;p&gt;Common Name (CN): *.google.com&lt;br&gt;
→ This is a wildcard certificate that covers all Google subdomains (like mail.google.com, docs.google.com, etc.).&lt;/p&gt;

&lt;p&gt;Issued By: Google Trust Services, CN=WE2&lt;br&gt;
→ The certificate was issued by Google’s own trusted Certificate Authority.&lt;/p&gt;

&lt;p&gt;Validity Period:&lt;br&gt;
Start: March 20, 2025&lt;br&gt;
End: June 12, 2025&lt;br&gt;
→ The certificate is valid for about 3 months.&lt;/p&gt;

&lt;p&gt;Signature Algorithm: ECDSA with SHA-256&lt;br&gt;
→ A modern, secure digital signature algorithm.&lt;/p&gt;

&lt;p&gt;Public Key Type: Elliptic Curve (P-256) with a 256-bit key&lt;br&gt;
→ Efficient and secure cryptography.&lt;/p&gt;

&lt;p&gt;Key Usage: Digital Signature&lt;/p&gt;

&lt;p&gt;Extended Key Usage: TLS Web Server Authentication&lt;br&gt;
→ The certificate is used for authenticating secure web servers.&lt;/p&gt;

&lt;p&gt;Subject Alternative Names (SAN):&lt;br&gt;
→ Covers hundreds of domains and subdomains, including:&lt;br&gt;
*.google.com, *.youtube.com, *.gstatic.com, *.android.com, google.com, youtu.be, etc.&lt;br&gt;
(Also includes many .cn domains for Chinese services.)&lt;/p&gt;

&lt;p&gt;Certificate Policies: Standard public certificate policy OID: 2.23.140.1.2.1&lt;/p&gt;

&lt;p&gt;🧾 OCSP &amp;amp; CRL URLs:&lt;br&gt;
→ For real-time revocation checks and CRL (Certificate Revocation List)&lt;/p&gt;

&lt;p&gt;Certificate Transparency (CT) Logs:&lt;br&gt;
→ Contains signed timestamps from two CT logs, proving the certificate was publicly logged.&lt;/p&gt;
&lt;h3&gt;
  
  
  How to Know if an Issuer is Trustable
&lt;/h3&gt;

&lt;p&gt;When you see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Issuer: C=US, O=Google Trust Services, CN=WE2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're looking at the Certificate Authority (CA) that signed the certificate. But how do you know it's legit?&lt;/p&gt;

&lt;p&gt;Here’s how to check.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check If It’s in the Trusted Root Store
&lt;/h3&gt;

&lt;p&gt;Operating systems (like Windows, macOS, Linux) and browsers (Chrome, Firefox) have a built-in list of trusted CAs, called a trusted root store.&lt;/p&gt;

&lt;p&gt;If the issuer’s root or intermediate certificate is in that list, it's considered trustworthy.&lt;/p&gt;

&lt;p&gt;How to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open your browser.&lt;/li&gt;
&lt;li&gt;Go to the website using the cert (e.g., &lt;a href="https://www.google.com" rel="noopener noreferrer"&gt;https://www.google.com&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Click the padlock icon → View certificate details.&lt;/li&gt;
&lt;li&gt;Check the certificate chain — if it says “trusted”, your browser already trusts the issuer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, Google Trust Services (CN=WE2) is trusted because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It chains up to a trusted root certificate.&lt;/li&gt;
&lt;li&gt;Google Trust Services is a publicly recognized Certificate Authority.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Look Up the CA in the Certificate Transparency Logs
&lt;/h3&gt;

&lt;p&gt;You can search for Google Trust Services / WE2 in public Certificate Transparency (CT) logs:&lt;br&gt;
&lt;a href="https://crt.sh/" rel="noopener noreferrer"&gt;crt.sh&lt;/a&gt;&lt;br&gt;
&lt;a href="https://certificate.transparency.dev/" rel="noopener noreferrer"&gt;Google Certificate Transparency&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This shows how many certificates the CA has issued and confirms it’s active and used widely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify on CA’s Official Site
&lt;/h3&gt;

&lt;p&gt;You can check the issuer by name: &lt;br&gt;
Google Trust Services CA info:&lt;br&gt;
&lt;a href="https://pki.goog" rel="noopener noreferrer"&gt;pki.goog&lt;/a&gt;&lt;br&gt;
This is Google's official site for their public CAs, including WE2.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get Notified with a Telegram Bot
&lt;/h3&gt;

&lt;p&gt;Want to automate TLS monitoring and get notified before a certificate expires? Use Telegram Bot that can &lt;a href="https://uptime.onl/knowledge/monitor-ssl-certificate-expiration/" rel="noopener noreferrer"&gt;monitor TLS certificate expiration&lt;/a&gt; for you.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to check domain expiration date in Linux using CLI</title>
      <dc:creator>Konstantin Bogomolov</dc:creator>
      <pubDate>Wed, 16 Jun 2021 03:52:41 +0000</pubDate>
      <link>https://dev.to/bogkonstantin/how-to-check-domain-expiration-date-in-linux-using-cli-452e</link>
      <guid>https://dev.to/bogkonstantin/how-to-check-domain-expiration-date-in-linux-using-cli-452e</guid>
      <description>&lt;p&gt;You can check domain expiration using whois command.&lt;/p&gt;

&lt;p&gt;Let’s say we want to get medium.com domain expiration date. Here is a command example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;whois medium.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Domain Name: MEDIUM.COM
   Registry Domain ID: 1329658_DOMAIN_COM-VRSN
   Registrar WHOIS Server: whois.registrar.amazon.com
   Registrar URL: http://registrar.amazon.com
   Updated Date: 2021-04-21T23:16:31Z
   Creation Date: 1998-05-27T04:00:00Z
   Registry Expiry Date: 2022-05-26T04:00:00Z
   Registrar: Amazon Registrar, Inc.
   Registrar IANA ID: 468
   Registrar Abuse Contact Email: abuse@amazonaws.com
   Registrar Abuse Contact Phone: +1.2067406200
   Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
   Name Server: ALINA.NS.CLOUDFLARE.COM
   Name Server: KIP.NS.CLOUDFLARE.COM
   DNSSEC: unsigned
   URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
&amp;gt;&amp;gt;&amp;gt; Last update of whois database: 2021-06-10T03:46:55Z &amp;lt;&amp;lt;&amp;lt;
For more information on Whois status codes, please visit https://icann.org/epp
NOTICE: The expiration date displayed in this record is the date the
registrar's sponsorship of the domain name registration in the registry is
currently set to expire. This date does not necessarily reflect the expiration
date of the domain name registrant's agreement with the sponsoring
registrar.  Users may consult the sponsoring registrar's Whois database to
view the registrar's reported date of expiration for this registration.
TERMS OF USE: You are not authorized to access or query our Whois
database through the use of electronic processes that are high-volume and
automated except as reasonably necessary to register domain names or
modify existing registrations; the Data in VeriSign Global Registry
Services' ("VeriSign") Whois database is provided by VeriSign for
information purposes only, and to assist persons in obtaining information
about or related to a domain name registration record. VeriSign does not
guarantee its accuracy. By submitting a Whois query, you agree to abide
by the following terms of use: You agree that you may use this Data only
for lawful purposes and that under no circumstances will you use this Data
to: (1) allow, enable, or otherwise support the transmission of mass
unsolicited, commercial advertising or solicitations via e-mail, telephone,
or facsimile; or (2) enable high volume, automated, electronic processes
that apply to VeriSign (or its computer systems). The compilation,
repackaging, dissemination or other use of this Data is expressly
prohibited without the prior written consent of VeriSign. You agree not to
use electronic processes that are automated and high-volume to access or
query the Whois database except as reasonably necessary to register
domain names or modify existing registrations. VeriSign reserves the right
to restrict your access to the Whois database in its sole discretion to ensure
operational stability.  VeriSign may restrict or terminate your access to the
Whois database for failure to abide by these terms of use. VeriSign
reserves the right to modify these terms at any time.
The Registry database contains ONLY .COM, .NET, .EDU domains and
Registrars.
Domain Name: medium.com
Registry Domain ID: 1329658_DOMAIN_COM-VRSN
Registrar WHOIS Server: whois.registrar.amazon.com
Registrar URL: https://registrar.amazon.com
Updated Date: 2021-04-21T23:16:32.535Z
Creation Date: 1998-05-27T04:00:00Z
Registrar Registration Expiration Date: 2022-05-26T04:00:00Z
Registrar: Amazon Registrar, Inc.
Registrar IANA ID: 468
Registrar Abuse Contact Email: abuse@amazonaws.com
Registrar Abuse Contact Phone: +1.2067406200
Reseller:
Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
Domain Status: renewPeriod https://icann.org/epp#renewPeriod
Registry Registrant ID:
Registrant Name: On behalf of medium.com owner
Registrant Organization: Whois Privacy Service
Registrant Street: P.O. Box 81226
Registrant City: Seattle
Registrant State/Province: WA
Registrant Postal Code: 98108-1226
Registrant Country: US
Registrant Phone: +1.2065771368
Registrant Phone Ext:
Registrant Fax:
Registrant Fax Ext:
Registrant Email: owner-4504344@medium.com.whoisprivacyservice.org
Registry Admin ID:
Admin Name: On behalf of medium.com administrative contact
Admin Organization: Whois Privacy Service
Admin Street: P.O. Box 81226
Admin City: Seattle
Admin State/Province: WA
Admin Postal Code: 98108-1226
Admin Country: US
Admin Phone: +1.2065771368
Admin Phone Ext:
Admin Fax:
Admin Fax Ext:
Admin Email: admin-4504344@medium.com.whoisprivacyservice.org
Registry Tech ID:
Tech Name: On behalf of medium.com technical contact
Tech Organization: Whois Privacy Service
Tech Street: P.O. Box 81226
Tech City: Seattle
Tech State/Province: WA
Tech Postal Code: 98108-1226
Tech Country: US
Tech Phone: +1.2065771368
Tech Phone Ext:
Tech Fax:
Tech Fax Ext:
Tech Email: tech-4504344@medium.com.whoisprivacyservice.org
Name Server: alina.ns.cloudflare.com
Name Server: kip.ns.cloudflare.com
DNSSEC: unsigned
URL of the ICANN WHOIS Data Problem Reporting System: http://wdprs.internic.net/
&amp;gt;&amp;gt;&amp;gt; Last update of WHOIS database: 2021-04-21T23:16:32.902Z &amp;lt;&amp;lt;&amp;lt;
For more information on Whois status codes, please visit https://www.icann.org/resources/pages/epp
By submitting a query to the Amazon Registrar, Inc. WHOIS database, you agree to abide by the following terms. The data in Amazon Registrar, Inc.'s WHOIS database is provided by Amazon Registrar, Inc. for the sole purpose of assisting you in obtaining information about domain name accuracy. You agree to use this data only for lawful purposes and further agree not to use this data for any unlawful purpose or to: (1) enable, allow, or otherwise support the transmission by email, telephone, or facsimile of commercial advertising or unsolicited bulk email, or (2) enable high volume, automated, electronic processes to collect or compile this data for any purpose, including mining this data for your own personal or commercial purposes. Amazon Registrar, Inc. reserves the right to restrict or terminate your access to the data if you fail to abide by these terms of use. Amazon Registrar, Inc. reserves the right to modify these terms at any time.
Visit Amazon Registrar, Inc. at https://registrar.amazon.com
Contact information available here: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-contact-support.html
© 2021, Amazon.com, Inc., or its affiliates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can read line by line and find the row with a domain expiration date.&lt;/p&gt;

&lt;p&gt;Or we could use CLI tools to get it filtered. Here is a command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;whois medium.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Expiration Date"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>tutorial</category>
      <category>tooling</category>
      <category>domain</category>
    </item>
    <item>
      <title>How to get HTTPS working on localhost</title>
      <dc:creator>Konstantin Bogomolov</dc:creator>
      <pubDate>Sun, 23 May 2021 10:34:08 +0000</pubDate>
      <link>https://dev.to/bogkonstantin/how-to-get-https-working-on-localhost-2h6</link>
      <guid>https://dev.to/bogkonstantin/how-to-get-https-working-on-localhost-2h6</guid>
      <description>&lt;p&gt;The simplest way is to use &lt;a href="https://github.com/FiloSottile/mkcert"&gt;mkcert&lt;/a&gt; utility.&lt;/p&gt;

&lt;p&gt;It is easy to use tool for making local development SSL/TLS certificates. &lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;At first, you need to install it. You can download a binary file from &lt;a href="https://github.com/FiloSottile/mkcert/releases"&gt;here&lt;/a&gt; to use it immediately without installation. Or follow the installation &lt;a href="https://github.com/FiloSottile/mkcert#installation"&gt;guide&lt;/a&gt; for your OS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Run command to create local CA (Certificate Authority).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mkcert &lt;span class="nt"&gt;-install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run command to create certificate files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mkcert localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;/p&gt;

&lt;p&gt;You have two SSL certificate files now: *.pem which is a certificate file and *key.pem which is a key file. &lt;/p&gt;

&lt;p&gt;Use these files in the configuration of your local webserver.&lt;/p&gt;

&lt;p&gt;If you want to know how to generate it manually, read the &lt;a href="https://bogomolov.tech/localhost-https/"&gt;How to get HTTPS working on localhost&lt;/a&gt; article on my website. The article has a simple Nginx example of configuration to use generated SSL certificate.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>devops</category>
      <category>nginx</category>
      <category>linux</category>
    </item>
    <item>
      <title>How to create a blog using Hexo static site generator and free web hosting on GitLab Pages</title>
      <dc:creator>Konstantin Bogomolov</dc:creator>
      <pubDate>Sat, 27 Mar 2021 07:05:36 +0000</pubDate>
      <link>https://dev.to/bogkonstantin/how-to-create-a-blog-using-hexo-static-site-generator-and-free-web-hosting-on-gitlab-pages-57mg</link>
      <guid>https://dev.to/bogkonstantin/how-to-create-a-blog-using-hexo-static-site-generator-and-free-web-hosting-on-gitlab-pages-57mg</guid>
      <description>&lt;p&gt;This is a complete tutorial on how to create a blog using a static website generator and free web hosting in 2021. It is better to have at least basic programming experience to proceed with the tutorial.&lt;/p&gt;

&lt;p&gt;We will use &lt;a href="https://hexo.io/" rel="noopener noreferrer"&gt;Hexo&lt;/a&gt; as a blog framework, &lt;a href="https://docs.gitlab.com/ee/user/project/pages/" rel="noopener noreferrer"&gt;GitLab Pages&lt;/a&gt; as a free hosting with HTTPS and a custom domain, &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;Node JS&lt;/a&gt; and &lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the end, I will give you a recommendation about website monitoring.&lt;/p&gt;

&lt;p&gt;Let’s get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a static site generator
&lt;/h2&gt;

&lt;p&gt;A static website is a website that is not generated on every request on the server-side. Every time you visit a page, the server will return the same pre-generated content. &lt;/p&gt;

&lt;p&gt;Dynamic web pages, in contrast, may generate new content on every request. It may get data from the database or use business logic on the server-side to generate content.&lt;/p&gt;

&lt;p&gt;A static site generator is an application that generates a website from templates or any other source. For example, Hexo generates HTML files from Markdown documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the best static website generator
&lt;/h2&gt;

&lt;p&gt;There are a lot of static site generators. Choosing the best is not an easy task. Many of them use Javascript frameworks like ReactJS or VueJS. Not everyone knows those frameworks. So there is another category, which uses Markdown as an input.&lt;/p&gt;

&lt;p&gt;Here are the most known generators I found with some stats from Github. Stats actual for March 2021.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexo.io/" rel="noopener noreferrer"&gt;Hexo&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpatnv0k45a0qo9fy9q12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpatnv0k45a0qo9fy9q12.png" alt="Hexo GitHub stats"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;used by 83.3K, 856 watchers&lt;/li&gt;
&lt;li&gt;32.4K stars, 10.46 avg. stars/day&lt;/li&gt;
&lt;li&gt;83 open issues, 3650 total issues &lt;/li&gt;
&lt;li&gt;152 contributors, 956 total pull requests &lt;/li&gt;
&lt;li&gt;primary language is Javascript &lt;/li&gt;
&lt;li&gt;last release version is 5.4.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7okm2zdbl2yvdfdye6xp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7okm2zdbl2yvdfdye6xp.png" alt="Hugo GitHub stats"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;used by 65K, 1059 watchers&lt;/li&gt;
&lt;li&gt;50.7K stars, 18.02 avg. stars/day&lt;/li&gt;
&lt;li&gt;592 open issues, 5223 total issues&lt;/li&gt;
&lt;li&gt;700 contributors, 3052 total pull requests &lt;/li&gt;
&lt;li&gt;primary language is Go&lt;/li&gt;
&lt;li&gt;last release version is 0.81.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ph9dnh4an1i46kx2zq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ph9dnh4an1i46kx2zq2.png" alt="Jekyll GitHub stats"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;used by 1.1M, 1473 watchers&lt;/li&gt;
&lt;li&gt;42.4K stars, 9.35 avg. stars/day&lt;/li&gt;
&lt;li&gt;80 open issues, 4367 total issues&lt;/li&gt;
&lt;li&gt;949 contributors, 4060 total pull requests&lt;/li&gt;
&lt;li&gt;primary language is Ruby&lt;/li&gt;
&lt;li&gt;last release version 4.2.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Jekyll looks the best based on these simple stats. Hugo's major version number is still 0, and it has more issues than others.&lt;/p&gt;

&lt;p&gt;The main reason for me is a primary language. I am using NodeJS a lot, so this technology may be easier for me in case of any bugs or whenever I need to extend some functionality with a plugin. &lt;/p&gt;

&lt;p&gt;That's why I choose Hexo there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hexo installation
&lt;/h2&gt;

&lt;p&gt;At first, you need to install &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;Node JS&lt;/a&gt; and &lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt; version control system, if you don't have it. I am using NodeJS version 14. You can install specific NodeJS using &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;NVM&lt;/a&gt; (Node Version Manager).&lt;/p&gt;

&lt;p&gt;Then install Hexo globally. Run this command to install hexo-cli package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; hexo-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am using Hexo version 5.4.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a new Project with Hexo
&lt;/h2&gt;

&lt;p&gt;Initialize new Hexo project. Change "blog" to your desired project name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hexo init blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to the new folder and install project dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;blog
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a simple post with the command below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hexo new post &lt;span class="s2"&gt;"My first post title"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see the new post file in the output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;INFO  Created: /app/source/_posts/My-first-post-title.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add some content to our first page. Copy content below to the "My-first-post-title.md" file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;first&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;post&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;title"&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2021-03-16 06:19:49&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="gh"&gt;# This is H1 header&lt;/span&gt;

This is content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, run Hexo server to preview your website and post. Enter the command below in your terminal to run a server locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hexo server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will generate your website and serve generated files locally. So you can check how your website will look. If no errors, you will see this output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;INFO  Hexo is running at http://localhost:4000 &lt;span class="nb"&gt;.&lt;/span&gt; Press Ctrl+C to stop.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open provided ULR in a browser and check your website. &lt;/p&gt;

&lt;p&gt;That's it. Our simple website is ready to deploy.&lt;/p&gt;

&lt;p&gt;For more information see Hexo &lt;a href="https://hexo.io/docs/index.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. Otherwise, use the help command instead of documentation. Just run &lt;code&gt;hexo help&lt;/code&gt; in the terminal to see all available commands.&lt;/p&gt;

&lt;p&gt;Let's continue with the deployment process to GitLab Pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is GitLab Pages
&lt;/h2&gt;

&lt;p&gt;GitLab Pages is a simple hosting for static sites. You can host your website for free here. The main difference with a traditional hosting is that you publish a website directly from the repository.&lt;/p&gt;

&lt;p&gt;We will use GitLab Pages here as a free web hosting in the tutorial and set up it with a custom domain and HTTPS. &lt;/p&gt;

&lt;p&gt;Here is the main alternative if you want to have a look: &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a new GitLab repository
&lt;/h2&gt;

&lt;p&gt;At first, create a new repository on the &lt;a href="https://gitlab.com/" rel="noopener noreferrer"&gt;GitLab website&lt;/a&gt;. Then run the command below in the project folder to initialize Git repository locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your created remote GitLab repository to your local repository with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote add origin &amp;lt;your_repository_link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can get your repository link from the GitLab new repository. After you created the repository, scroll down a little, and you will see the commands listed under the "Push an existing folder" section. &lt;/p&gt;

&lt;p&gt;Just copy commands from there. Here is my test repository commands screenshot as an example:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgv8dqcfdr7ruwrmmyvm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgv8dqcfdr7ruwrmmyvm.png" alt="GitLab initialization commands"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Let's proceed with a deployment configuration.&lt;/p&gt;
&lt;h2&gt;
  
  
  Add GitLab Deployment Configuration to the Project
&lt;/h2&gt;

&lt;p&gt;The next step is to prepare a deployment configuration. &lt;/p&gt;

&lt;p&gt;Hexo is a static website generator. It doesn't store generated HTML files in the Git repository. That's why we need to re-generate files on every website update. &lt;/p&gt;

&lt;p&gt;Static files will be generated automatically at the GitLab side, every time you send updates to the remote repository with GitLab Continuous Delivery (CD) tool.   &lt;/p&gt;

&lt;p&gt;Add the new file &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; to the root of your project with the content below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:14&lt;/span&gt;
&lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules/&lt;/span&gt;

&lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install hexo-cli -g&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;

&lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;hexo generate&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;public&lt;/span&gt;
  &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to understand what this config does, here is a simple explanation:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;image&lt;/code&gt; - here we specify Docker image. &lt;code&gt;node:14&lt;/code&gt; is the official &lt;a href="https://hub.docker.com/_/node/" rel="noopener noreferrer"&gt;Node JS Docker image&lt;/a&gt; with NodeJS version 14&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cache:path:&lt;/code&gt; - contains a folder to cache between jobs
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;before_script&lt;/code&gt; - contains scripts we want to run before any job
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages&lt;/code&gt; - contains job configuration
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages:script&lt;/code&gt; - script to run in the job. We will generate static pages with Hexo here
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;artifacts:paths&lt;/code&gt; - this folder with a generated website will be hosted at GitLab Pages and will be available in GitLab UI after the job finishes
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;only&lt;/code&gt; - conditions to run jobs, i.e. run this job only on the master branch
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reference:&lt;br&gt;
&lt;a href="https://hexo.io/docs/gitlab-pages" rel="noopener noreferrer"&gt;Actual Hexo config&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://docs.gitlab.com/ee/ci/yaml/README.html" rel="noopener noreferrer"&gt;Actual GitLab yaml reference&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Commit your Project
&lt;/h2&gt;

&lt;p&gt;Commit is saving your changes to the local repository. To save the state of your project, run the commands below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nt"&gt;--all&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Commit message, describing your changes"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are ready for the deployment. Next, we need to set up the GitLab project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Page on GitLab
&lt;/h2&gt;

&lt;p&gt;Go to your GitLab repository and open &lt;code&gt;Settings - Pages&lt;/code&gt;. Ensure that checkbox "Force HTTPS" is checked. Then press &lt;code&gt;New Domain&lt;/code&gt; button and fill in your domain name.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqqsr6uhsjb23nb8ylqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqqsr6uhsjb23nb8ylqh.png" alt="GitLab Pages New Domain"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Make your page available: go to &lt;code&gt;Settings - General&lt;/code&gt;, click to &lt;code&gt;Visibility, project features, permissions&lt;/code&gt; and change configuration for Pages to &lt;strong&gt;Everyone&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpcnmjmlyn2j0ru08qfgi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpcnmjmlyn2j0ru08qfgi.png" alt="GitLab Pages visibility"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Set up DNS records
&lt;/h2&gt;

&lt;p&gt;The next step is to configure DNS records. Add &lt;strong&gt;TXT&lt;/strong&gt; record in a domain DNS configuration to verify domain ownership. Then add &lt;strong&gt;A&lt;/strong&gt; record with IP 35.185.44.232 to map your domain to GitLab Pages. &lt;/p&gt;

&lt;p&gt;Check the actual &lt;a href="https://docs.gitlab.com/ee/user/project/pages/custom_domains_ssl_tls_certification/index.html#for-root-domains" rel="noopener noreferrer"&gt;GitLab Pages IP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is how it looks for my domain in the CloudFlare Admin panel.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy42xfot9orp8503fhnik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy42xfot9orp8503fhnik.png" alt="CloudFlare DNS GitLab Pages TXT record example"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkzkt4eika1kuprfqu71.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkzkt4eika1kuprfqu71.png" alt="CloudFlare DNS GitLab Pages A record example"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Enable GitLab Runners
&lt;/h2&gt;

&lt;p&gt;Go to &lt;code&gt;Settings -&amp;gt; CI / CD -&amp;gt; Shared Runners&lt;/code&gt; and click &lt;code&gt;Expand&lt;/code&gt; in &lt;strong&gt;Runners&lt;/strong&gt;. Enable &lt;strong&gt;Shared Runners&lt;/strong&gt; if it is disabled.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxfe8qmbwmckdhfrwsrx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foxfe8qmbwmckdhfrwsrx.png" alt="GitLab Enable runners switch"&gt;&lt;/a&gt; &lt;br&gt;
A runner is an application that runs build and deployment jobs.   &lt;/p&gt;
&lt;h2&gt;
  
  
  Upload your website to GitLab
&lt;/h2&gt;

&lt;p&gt;Upload your local changes to the remote repository with the &lt;code&gt;push&lt;/code&gt; command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After pushing GitLab CD automatically generate static files and update your website. You can see the running job in project &lt;code&gt;Settings - Pipelines&lt;/code&gt; or &lt;code&gt;Jobs&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4x9othv8sxov61oks2s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4x9othv8sxov61oks2s.png" alt="GitLab running pipeline example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It may take up to 30 minutes before the site is available after the first deployment. Then your website should be available by your domain.&lt;/p&gt;

&lt;p&gt;Also, you can check it by GitLab URL. You can check URLs in the &lt;code&gt;Settings - Pages&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7cnv43o1x2oiuhu8k2e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7cnv43o1x2oiuhu8k2e.png" alt="GitLab Pages available urlst"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read my post &lt;a href="https://bogomolov.tech/how-to-monitor-website/" rel="noopener noreferrer"&gt;How to monitor the website&lt;/a&gt; to know more.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>node</category>
      <category>gitlab</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Facebook login with Symfony</title>
      <dc:creator>Konstantin Bogomolov</dc:creator>
      <pubDate>Tue, 12 Jan 2021 11:25:42 +0000</pubDate>
      <link>https://dev.to/bogkonstantin/facebook-login-with-symfony-1d5a</link>
      <guid>https://dev.to/bogkonstantin/facebook-login-with-symfony-1d5a</guid>
      <description>&lt;p&gt;My goal was to add &lt;strong&gt;Continue with Facebook&lt;/strong&gt; (Log In) button into the existing Symfony application. I do not provide here full code. Only the main parts to creating your Facebook login button. Here is what I did.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;Workflow overview is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User clicks the Facebook login button&lt;/li&gt;
&lt;li&gt;Facebook authenticate user&lt;/li&gt;
&lt;li&gt;App sends facebook token (auth data) to Symfony backend&lt;/li&gt;
&lt;li&gt;Backend gets user data (name and email) using auth data and logs in with Symfony handlers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So Facebook is only necessary for the only first part - checking the person who wants to log in.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://symfony.com/doc/current/security/guard_authentication.html"&gt;Guard&lt;/a&gt; to auth user in the backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Facebook libraries
&lt;/h3&gt;

&lt;p&gt;Create new app &lt;a href="https://developers.facebook.com/apps"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You need Facebook Javascript SDK for the frontend and PHP library for the backend.&lt;br&gt;
Install PHP Facebook library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require facebook/graph-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Include Facebook Javascript SDK to your template. It should look like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;
        &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://connect.facebook.net/en_US/sdk.js#xfbml=1&amp;amp;version=v7.0&amp;amp;appId=&amp;lt;your app id here&amp;gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The simplest way is to generate this snippet from &lt;a href="https://developers.facebook.com/apps"&gt;Facebook developers&lt;/a&gt; page in &lt;strong&gt;Facebook Login&lt;/strong&gt; - &lt;strong&gt;Quickstart&lt;/strong&gt; section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;p&gt;Next, you need to add a button and handle login result. Button example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fb-login-button"&lt;/span&gt;
     &lt;span class="na"&gt;data-size=&lt;/span&gt;&lt;span class="s"&gt;"large"&lt;/span&gt;
     &lt;span class="na"&gt;data-button-type=&lt;/span&gt;&lt;span class="s"&gt;"continue_with"&lt;/span&gt;
     &lt;span class="na"&gt;data-layout=&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt;
     &lt;span class="na"&gt;data-auto-logout-link=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;
     &lt;span class="na"&gt;data-use-continue-as=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;
     &lt;span class="na"&gt;data-scope=&lt;/span&gt;&lt;span class="s"&gt;"public_profile,email"&lt;/span&gt;
     &lt;span class="na"&gt;onlogin=&lt;/span&gt;&lt;span class="s"&gt;"fbGetLoginStatus();"&lt;/span&gt;
     &lt;span class="na"&gt;data-width=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://developers.facebook.com/docs/facebook-login/web/login-button/"&gt;Here&lt;/a&gt; is a button configurator.&lt;/p&gt;

&lt;p&gt;I created a Javascript module with that code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fbGetLoginStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;FB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getLoginStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isConnected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;fbLogIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isConnected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fbLogIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;loginForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.login-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getHiddenInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fbAuthResponse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authResponse&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;    
    &lt;span class="nx"&gt;loginForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;loginForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getHiddenInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a user clicks to *&lt;em&gt;Continue with Facebook&lt;/em&gt; button and successfully logged in, then I submit a login (or registration) form and send data to backend via POST request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend - Symfony Guard
&lt;/h3&gt;

&lt;p&gt;I had already one Guard for usual login-password authentication. So I add the second one. Here is my security.yaml config for Guard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;firewalls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;guard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;authenticators&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;App\Security\LoginFormAuthenticator&lt;/span&gt;
                    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;App\Security\FacebookAuthenticator&lt;/span&gt;
                &lt;span class="na"&gt;entry_point&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;App\Security\LoginFormAuthenticator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you need to create Guard file, which extends AbstractFormLoginAuthenticator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FacebookAuthenticator&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractFormLoginAuthenticator&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main methods of the FacebookAuthenticator will be:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;supports&lt;/strong&gt; - it checks if you should use that Guard. I used the same logic for the login and register page. So it looks like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;supports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'_route'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$isLoginOrRegister&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;in_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'app_login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'app_register'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$isLoginOrRegister&lt;/span&gt; 
        &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fbAuthResponse'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;getCredentials&lt;/strong&gt; - gets Facebook auth response and decode it to an array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fbAuthResponse'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;onAuthenticationSuccess&lt;/strong&gt; - I redirect the user to the landing page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;onAuthenticationSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="kt"&gt;TokenInterface&lt;/span&gt; &lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nv"&gt;$providerKey&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RedirectResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;urlGenerator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app_landing'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to use a url generator, add it to constructor (see below).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;getUser&lt;/strong&gt; - the main part, where you gets email and name from Facebook Graph API and return User entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;UserProviderInterface&lt;/span&gt; &lt;span class="nv"&gt;$userProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$credentials&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'accessToken'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomUserMessageAuthenticationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Your message here'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$fbUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fbService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$credentials&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'accessToken'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fbUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEmail&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomUserMessageAuthenticationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Your message here'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$userProvider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;loadUserByUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fbUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEmail&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use email and name for registration when the user does not exist. Here is only the login part, without registration for simplicity.&lt;/p&gt;

&lt;p&gt;Here is the way you can autowire services in Guard constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;FacebookService&lt;/span&gt; &lt;span class="nv"&gt;$fbService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="kt"&gt;UrlGeneratorInterface&lt;/span&gt; &lt;span class="nv"&gt;$urlGenerator&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fbService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$fbService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;urlGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$urlGenerator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My fbService looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Entity\FacebookUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Facebook\Facebook&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FacebookService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$fbAppId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$fbAppSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$fbGraphVersion&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Facebook&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'app_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$fbAppId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'app_secret'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$fbAppSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'default_graph_version'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$fbGraphVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;FacebookUser&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FacebookUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$fbUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/me?fields=name,email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$fbUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDecodedBody&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nv"&gt;$user&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// handle exception here&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to autowire FacebookService constructor arguments, add variables to .env file (according to your environment) and add parameters to services.yaml config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;App\Service\FacebookService&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="s"&gt;$fbAppId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(FB_APP_ID)%'&lt;/span&gt;
            &lt;span class="s"&gt;$fbAppSecret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(FB_APP_SECRET)%'&lt;/span&gt;
            &lt;span class="s"&gt;$fbGraphVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(FB_GRAPH_VERSION)%'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a FacebookUser entity. You can use an array instead of an entity, but OOP is more convenient for me.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Entity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FacebookUser&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getEmail&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.  &lt;/p&gt;

&lt;p&gt;Actually, there are some more methods in Guard, but it shouldn't be complex to create them.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>php</category>
      <category>symfony</category>
      <category>login</category>
    </item>
    <item>
      <title>Setup your own email server with Postfix on Ubuntu</title>
      <dc:creator>Konstantin Bogomolov</dc:creator>
      <pubDate>Wed, 06 Jan 2021 05:08:37 +0000</pubDate>
      <link>https://dev.to/bogkonstantin/how-to-install-and-configure-send-only-postfix-smtp-server-on-ubuntu-18-04-4fm1</link>
      <guid>https://dev.to/bogkonstantin/how-to-install-and-configure-send-only-postfix-smtp-server-on-ubuntu-18-04-4fm1</guid>
      <description>&lt;p&gt;I was needed to send emails for my new project. I have explored SaaS services first. Free packages provided by them have a small amount of sending emails. So as long as I am a programmer, I decided to host my own sending email server.&lt;/p&gt;

&lt;p&gt;After some research, I decided to stay with Postfix send-only configuration. Continue to read how to install and configure SMTP server to send emails and not send them to a spam folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;I used a single-core VPS with Ubuntu 18.04 for just 2.5 USD per month. My main app uses a table in DB as an email queue, so I have two NodeJS apps: one to handle the queue and second to get delivery status and update it in the main app DB. Tell me if you want to see it. Here is only the Postfix part.&lt;/p&gt;

&lt;p&gt;I also have a domain. Let it be &lt;strong&gt;example.com&lt;/strong&gt; here. Since it will be used for a website, I am using a subdomain &lt;strong&gt;email.example.com&lt;/strong&gt; for the email server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Postfix installation
&lt;/h3&gt;

&lt;p&gt;Install Postfix using commands below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get update
sudo apt install mailutils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During installation, select &lt;strong&gt;Internet Site&lt;/strong&gt; option for type of mail configuration. As a &lt;strong&gt;System mail name&lt;/strong&gt; enter your domain, e.g. example.com.  &lt;/p&gt;

&lt;p&gt;You can see domain later with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat /etc/mailname
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server configuration
&lt;/h3&gt;

&lt;p&gt;Open the main config file with an editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/postfix/main.cf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find &lt;strong&gt;inet_interfaces&lt;/strong&gt; parameter and change it to &lt;strong&gt;loopback-only&lt;/strong&gt;. With that parameter, Postfix will not listen for any connection from outside of VPS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inet_interfaces = loopback-only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other parameters and values to change for now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mydestination = $myhostname, localhost.$mydomain, localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set your mail domain as a server hostname:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo hostnamectl set-hostname email.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the hostname with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hostname --f
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;strong&gt;/etc/hosts&lt;/strong&gt; file and add that subdomain with your remote IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.2.3.4    email.example.com email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Command to restart Postfix after configuration change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl restart postfix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Command to check current configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postconf -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create DKIM signature
&lt;/h3&gt;

&lt;p&gt;Install DKIM tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get install opendkim opendkim-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the user to the group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo gpasswd -a postfix opendkim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open configuration file /etc/opendkim.conf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/opendkim.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add or update parameters below in the configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Socket              inet:8892@localhost
Canonicalization    simple
Mode                sv
SubDomains          no
AutoRestart         yes
AutoRestartRate     10/1M
Background          yes
DNSTimeout          5
SignatureAlgorithm  rsa-sha256
UserID              opendkim
KeyTable            refile:/etc/opendkim/key.table
SigningTable        refile:/etc/opendkim/signing.table
ExternalIgnoreList  /etc/opendkim/trusted.hosts
InternalHosts       /etc/opendkim/trusted.hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a folder for DKIM keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo mkdir -p /etc/opendkim/keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change folder permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo chown -R opendkim:opendkim /etc/opendkim
sudo chmod go-rw /etc/opendkim/keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create opendkim signing table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/opendkim/signing.table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With content inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*@example.com    default._domainkey.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create key table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/opendkim/key.table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With content inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default._domainkey.example.com     example.com:default:/etc/opendkim/keys/example.com/default.private
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add trusted hosts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/opendkim/trusted.hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With content inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1
localhost
*.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create keys folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo mkdir /etc/opendkim/keys/example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then generate keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;opendkim-genkey &lt;span class="nt"&gt;-b&lt;/span&gt; 2048 &lt;span class="nt"&gt;-d&lt;/span&gt; example.com &lt;span class="nt"&gt;-D&lt;/span&gt; /etc/opendkim/keys/example.com &lt;span class="nt"&gt;-s&lt;/span&gt; default &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the private key file owner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chown &lt;/span&gt;opendkim:opendkim /etc/opendkim/keys/example.com/default.private
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restart opendkim service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo service opendkim restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo opendkim-testkey -d example.com -s default -vvv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add or update parameters in the Postfix configuration file (/etc/postfix/main.cf):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;milter_default_action = accept
milter_protocol = 2
smtpd_milters = inet:localhost:8892
non_smtpd_milters = inet:localhost:8892
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restart Postfix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl restart postfix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  DNS configuration
&lt;/h3&gt;

&lt;p&gt;You need to configure some DNS records. With that configuration, email services will not treat your emails as spam.&lt;/p&gt;

&lt;p&gt;Add A record for the email.example.com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type: A
Name: email.example.com
Value: 1.2.3.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add SPF record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type: TXT
Name: example.com
Value: v=spf1 mx ~all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add DMARC record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type: TXT
Name: _dmarc
Value: v=DMARC1; p=none
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add DKIM record. Use previously generated DKIM signature (/etc/opendkim/keys/example.com/default.txt):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type: TXT
Name: default._domainkey
Value: v=DKIM1; h=sha256; k=rsa; p=you-key-here-without-spaces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set PTR record (rDNS). You need to set it in your hosting provider's control panel.&lt;/p&gt;

&lt;p&gt;Set email.example.com as a hostname and 4.3.2.1.in-addr.arpa as IP address. IP should be in reversed order.  &lt;/p&gt;

&lt;p&gt;Check if it set correctly with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;host 1.2.3.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Email encryption
&lt;/h3&gt;

&lt;p&gt;Install Certbot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then generate keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo certbot certonly --standalone -d email.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure Postfix to use that keys. Add or edit parameters below in /etc/postfix/main.cf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smtpd_tls_cert_file = /etc/letsencrypt/live/email.example.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/email.example.com/privkey.pem
smtpd_tls_security_level=may
smtp_tls_CApath=/etc/letsencrypt/live/email.example.com
smtp_tls_security_level=may
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restart Postfix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;service postfix restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Send a test email with command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"This is the body of the email"&lt;/span&gt; | mail &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;@example.com &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"This is the subject"&lt;/span&gt; your_email@gmail.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check spam score with online services, e.g.: &lt;a href="https://www.mail-tester.com/"&gt;https://www.mail-tester.com/&lt;/a&gt; (not an ad).&lt;/p&gt;

</description>
      <category>ubuntu</category>
      <category>tutorial</category>
      <category>mail</category>
      <category>postfix</category>
    </item>
    <item>
      <title>Send message to Telegram on any SSH login</title>
      <dc:creator>Konstantin Bogomolov</dc:creator>
      <pubDate>Tue, 05 Jan 2021 05:08:21 +0000</pubDate>
      <link>https://dev.to/bogkonstantin/send-message-to-telegram-on-any-ssh-login-24c8</link>
      <guid>https://dev.to/bogkonstantin/send-message-to-telegram-on-any-ssh-login-24c8</guid>
      <description>&lt;p&gt;In this article, you will walk through creation a simple shell script to send messages to Telegram messenger. Then you will use this script to send a notification on every ssh login into your server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create telegram bot
&lt;/h3&gt;

&lt;p&gt;To send a message to Telegram group or channel, you should first create your own bot. Just open Telegram, find &lt;a class="comment-mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt;
 and type &lt;code&gt;/start&lt;/code&gt;. Then follow instructions to create bot and get token to access the HTTP API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Channel
&lt;/h3&gt;

&lt;p&gt;Create a new Channel in Telegram and &lt;strong&gt;add your bot as a member&lt;/strong&gt;. So your bot could send messages to the Channel.  &lt;/p&gt;

&lt;p&gt;In order to get Channel Id, first, post any message to the Channel. Then use this link template to get Channel Id:&lt;br&gt;&lt;br&gt;
&lt;code&gt;https://api.telegram.org/bot&amp;lt;YourBOTToken&amp;gt;/getUpdates&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here is a response example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"update_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"channel_post"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"chat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;-123123123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;channel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Notifications"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"channel"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1574485277&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Script to send message
&lt;/h3&gt;

&lt;p&gt;In order to send a message we could use simple command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'https://api.telegram.org/bot&amp;lt;YourBOTToken&amp;gt;/sendMessage?chat_id=&amp;lt;channel_id&amp;gt;&amp;amp;text=&amp;lt;text&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in programming, it is good practice to hide the low-level implementation. So we will create a linux terminal command &lt;code&gt;telegram-send&lt;/code&gt; and could send messages with this simple command.  &lt;/p&gt;

&lt;p&gt;Lets create file &lt;code&gt;telegram-send.sh&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;telegram-send.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add script to this file. Set your group id and token in script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;GROUP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;group_id&amp;gt;
&lt;span class="nv"&gt;BOT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;bot_token&amp;gt;

&lt;span class="c"&gt;# this 3 checks (if) are not necessary but should be convenient&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"-h"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;text message&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Add message text as second arguments"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"$#"&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 1 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"You can pass only one argument. For string with spaces put it on quotes"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s2"&gt;"text=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s2"&gt;"chat_id=&lt;/span&gt;&lt;span class="nv"&gt;$GROUP_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'https://api.telegram.org/bot'&lt;/span&gt;&lt;span class="nv"&gt;$BOT_TOKEN&lt;/span&gt;&lt;span class="s1"&gt;'/sendMessage'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not a good practice to store your token in that place, but for now, it is ok. Also, you could limit actions your bot could do in the Channel only to send messages.  &lt;/p&gt;

&lt;p&gt;To run this script we should add permission&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x telegram-send.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can test it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./telegram-send.sh &lt;span class="s2"&gt;"Test message"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to use this script from everywhere and type &lt;code&gt;telegram-send&lt;/code&gt; instead &lt;code&gt;./telegram-send.sh&lt;/code&gt; add it to /usr/bin/ folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mv &lt;/span&gt;telegram-send.sh /usr/bin/telegram-send
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Owner of all files in /usr/bin is root user. So let's do the same with our script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chown &lt;/span&gt;root:root /usr/bin/telegram-send
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can test it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;telegram-send &lt;span class="s2"&gt;"Test message"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Send notification on SSH login
&lt;/h3&gt;

&lt;p&gt;All files with .sh extension in /etc/profile.d/ folder will be executed whenever a bash login shell is entered or the desktop session loads.&lt;/p&gt;

&lt;p&gt;Let's add a new script to send the notification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;login-notify.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this code to script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# prepare any message you want&lt;/span&gt;
&lt;span class="nv"&gt;login_ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SSH_CONNECTION&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;" "&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; 1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;login_date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="s2"&gt;"%e %b %Y, %a %r"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;login_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;whoami&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# For new line I use $'\n' here&lt;/span&gt;
&lt;span class="nv"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"New login to server"&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$login_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$login_ip&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$login_date&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;#send it to telegram&lt;/span&gt;
telegram-send &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then move this script to /etc/profile.d/ folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mv &lt;/span&gt;login-notify.sh /etc/profile.d/login-notify.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now re-login to your web server and check it works. &lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>ubuntu</category>
      <category>bash</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
