<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Meniko IT]]></title><description><![CDATA[Part of Meniko Project that is dedicated to Information Technologies and is written in English ]]></description><link>https://it.meniko.ru/</link><image><url>https://it.meniko.ru/favicon.png</url><title>Meniko IT</title><link>https://it.meniko.ru/</link></image><generator>Ghost 5.4</generator><lastBuildDate>Thu, 09 Apr 2026 09:12:57 GMT</lastBuildDate><atom:link href="https://it.meniko.ru/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Configuring private V2Ray server for bypassing internet censorship (TLS configuration)]]></title><description><![CDATA[Today many countries are trying to block access to internet resourses that are not convenient for them – oppositional websites, social resourses and so on. In this article you will find solution that will works at most of such circumstances]]></description><link>https://it.meniko.ru/configuring-private-v2ray-server-for-bypassing-internet-censorship-tls-configuration/</link><guid isPermaLink="false">638898c76bd836a5d160a550</guid><category><![CDATA[Hack]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Thu, 01 Dec 2022 12:15:29 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2022/12/v2ray_bg.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h1 id="little-preface">Little preface</h1>
<img src="https://it.meniko.ru/content/images/2022/12/v2ray_bg.png" alt="Configuring private V2Ray server for bypassing internet censorship (TLS configuration)"><p>When I lived in Russia I very often met a lot of censorship in internet &#x2013; you could not access opositional websites, news sites, could not use some social media platforms (yes, instagram, facebook and linkedIn is blocked there). Later when I move to other countries, I understand that not only my country cleans up internet, others do the same (of course they do it differ, but anyway). So I started searching for some solution that would work at most of the time. And I find it &#x2013; it is <strong>V2Ray</strong>.</p>
<p>As it is writen on <a href="https://www.v2ray.com/en/index.html">the official website</a> V2Ray is part of the Project V toolkit, that allows you to create your own private network that will work over internet.  It is hard to be discovered and to be blocked even with <a href="https://en.wikipedia.org/wiki/Deep_packet_inspection">DPI systems</a>, that goverments use today for censorship. V2Ray is popular even in China, where the scale of censor <a href="https://en.wikipedia.org/wiki/Great_Firewall">beats all limits</a>.</p>
<p>One of the great posibilies of V2ray is ability to masqarade your traffic as usual HTTPs. In such case your ISP provider will think that you just access this site. I use such configuration and will disclose installation process for you here. Also there are different ways to configure V2ray, you can read about them <a href="https://guide.v2fly.org/en_US/advanced/advanced.html">here</a>.</p>
<h1 id="explanation">Explanation</h1>
<h2 id="how-does-it-works">How does it works?</h2>
<p>Here is little schema that disclose some common parts of the configuration that we will implement.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-wide"><img src="https://it.meniko.ru/content/images/2022/12/v2ray.jpg" class="kg-image" alt="Configuring private V2Ray server for bypassing internet censorship (TLS configuration)" loading="lazy" width="2000" height="514" srcset="https://it.meniko.ru/content/images/size/w600/2022/12/v2ray.jpg 600w, https://it.meniko.ru/content/images/size/w1000/2022/12/v2ray.jpg 1000w, https://it.meniko.ru/content/images/size/w1600/2022/12/v2ray.jpg 1600w, https://it.meniko.ru/content/images/2022/12/v2ray.jpg 2000w" sizes="(min-width: 1200px) 1200px"></figure><!--kg-card-begin: markdown--><ol>
<li><strong>V2Ray client</strong> on your personal computer wraps all your internet traffic into HTTPs and sends it to your server. <strong>ISP</strong> (and all censorship middleware) does not see anything strange in your traffic as it looks like normal HTTPs traffic;</li>
<li>All encrypted trafic arrives to <strong>Nginx</strong> server on your VPS server. Nginx decrypt it and send it forward to V2Ray server;</li>
<li><strong>V2Ray server</strong> checks authentification of traffic using internal mechanisms and sends it to global internet if everything is good.</li>
</ol>
<h2 id="why-not-to-use-classic-vpn-protocols">Why not to use classic VPN protocols?</h2>
<p>Of course you can use classic VPN protocols if they work in your situation. But because of there popularity goverments with there friendly technical companies invest a lot of money to find solutions to block them. For example:</p>
<ol>
<li><a href="https://www.wireguard.com/">WireGuard</a>, <a href="https://openvpn.net/">OpenVPN</a> can be blocked with DPI (and is blocked in <a href="https://www.reddit.com/r/WireGuard/comments/y68ky2/wireguard_completely_banned_in_iran/">Iran</a>, <a href="https://torguard.net/blog/egypt-blocks-major-websites-and-vpns-censorship-crackdown/">Egypt</a>, Uzbekistan and in many other countries). Of course there a lot of different bypassing methods to make it work &#x2013; for example use additional obfuscation methods and so on. But it can be difficult to make it work sometimes or have some techinal limitations;</li>
<li>A lot of good solutions, such as <a href="https://getoutline.org/ru/">Outline VPN</a> are using UDP protocol, that can be blocked by some companies (it&apos;s strange to hear, but I&apos;ve experienced it). And you need to find some other solution.</li>
</ol>
<p>When you understand that, you will find out that having one more solution is nice opportunity.</p>
<h2 id="what-will-you-need">What will you need?</h2>
<p>To make V2Ray work as it is explained in this article, you will need:</p>
<ol>
<li>Dedicated or virtual private server in some region where there is no censorship or it is weak (Germany, Netherlands, Latvia and so on). Personally I use Digitalocean, I like there price politics (it starts from 4$ per month and it is enough for V2Ray to work) and excellent level of service, but you can choose other service provider</li>
<li>Valid internet domain, that you can control and for which you can obtain SSL serficate</li>
<li>Some skills in administation of linux servers</li>
<li>Time to deploy and configure everything<br>
<em>In case if you have not got account in DigitalOcean, here are my <a href="https://m.do.co/c/46a0202bc4b1">referal link</a>. If you will sign up with it you will get up to 200$ bonus for your first server and will support me as author. Thanks!</em></li>
</ol>
<h1 id="installation-guide">Installation guide</h1>
<h2 id="step-1-initial-setups">Step 1. Initial setups</h2>
<p>First of all create VPS server and obtain SSH access to it. For better security I recommend to secure your service creating non-root user and enabling <code>ufw</code> firewall. You can view how to do it in this <a href="https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-22-04">instruction</a>. Just do not forget to open 443 port for our configuration of <code>v2ray</code>.</p>
<h2 id="step-2-installing-v2ray">Step 2. Installing v2ray</h2>
<ol start="3">
<li>After all initial setup, we need to load and install v2ray on our server</li>
</ol>
<pre><code class="language-bash">$ bash &lt;(curl -L https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-release.sh)
</code></pre>
<ol start="4">
<li>Now we can enable <code>v2ray</code> and check does it works or not (status of server must be <code>active</code>)</li>
</ol>
<pre><code class="language-bash">$ sudo systemctl enable v2ray
$ sudo systemctl start v2ray
$ sudo systemctl status v2ray
&#x25CF; v2ray.service - V2Ray Service
     Loaded: loaded (/etc/systemd/system/v2ray.service; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/v2ray.service.d
             &#x2514;&#x2500;10-donot_touch_single_conf.conf
     Active: active (running) since Tue 2022-11-29 09:32:50 UTC; 4s ago
       Docs: https://www.v2fly.org/
   Main PID: 2244 (v2ray)
      Tasks: 6 (limit: 512)
     Memory: 8.2M
        CPU: 29ms
     CGroup: /system.slice/v2ray.service
             &#x2514;&#x2500;2244 /usr/local/bin/v2ray run -config /usr/local/etc/v2ray/config.json
</code></pre>
<p>As you can see service was successfully started and is using configuration file that is located at <code>/usr/local/etc/v2ray/config.json</code>. We will return to this later.</p>
<h2 id="step-3-setting-up-domain-dns-records">Step 3. Setting up domain DNS records</h2>
<ol start="5">
<li>Next step is to create <code>A</code>-type DNS record for your domain, so that our server can receive trafic that is going to it. I use third level domain for this <code>bypass.meniko.ru</code> (but it can be anything you like). After you do this, you need to wait until new DNS record would be global wide available, it can take from 30 minutes to hours, need to wait a bit.</li>
</ol>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://it.meniko.ru/content/images/2022/12/third_level_domain.jpeg" class="kg-image" alt="Configuring private V2Ray server for bypassing internet censorship (TLS configuration)" loading="lazy" width="1506" height="1117" srcset="https://it.meniko.ru/content/images/size/w600/2022/12/third_level_domain.jpeg 600w, https://it.meniko.ru/content/images/size/w1000/2022/12/third_level_domain.jpeg 1000w, https://it.meniko.ru/content/images/2022/12/third_level_domain.jpeg 1506w" sizes="(min-width: 1200px) 1200px"><figcaption>How new A-type DNS line looks in my admin panel on DigitalOcean</figcaption></figure><!--kg-card-begin: markdown--><h2 id="step-4-creating-ssl-sertificate">Step 4. Creating SSL sertificate</h2>
<ol start="6">
<li>When DNS records was successfully updated and we can ping our new domain, we can go further and setup HTTPs sertificate with LetsEncrypt and <a href="https://certbot.eff.org/">CertBot</a> and setup our <a href="https://www.nginx.com/">Nginx</a> server on our VPS server. First install everything with this:</li>
</ol>
<pre><code class="language-bash">$ sudo apt-get install nginx -y
$ sudo apt-get install certbot python3-certbot-nginx
</code></pre>
<ol start="7">
<li>Now is the time to get SSL sertificate using CertBot, it will ask for your email address, agree with terms and tell it domain address that we created before</li>
</ol>
<pre><code class="language-bash">$ sudo certbot --nginx
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter &apos;c&apos; to cancel): ***@meniko.ru

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let&apos;s Encrypt project and the non-profit organization that
develops Certbot? We&apos;d like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: n
Account registered.
Please enter the domain name(s) you would like on your certificate (comma and/or
space separated) (Enter &apos;c&apos; to cancel): bypass.meniko.ru
Requesting a certificate for bypass.meniko.ru

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/bypass.meniko.ru/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/bypass.meniko.ru/privkey.pem
This certificate expires on 2023-02-27.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Deploying certificate
Successfully deployed certificate for bypass.meniko.ru to /etc/nginx/sites-enabled/default
Congratulations! You have successfully enabled HTTPS on https://bypass.meniko.ru

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let&apos;s Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
</code></pre>
<ol start="8">
<li>We can go to our domain and check that connection is now secure (CertBot automatically updates Nginx config for our domain)</li>
</ol>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-wide"><img src="https://it.meniko.ru/content/images/2022/12/secure_domain.png" class="kg-image" alt="Configuring private V2Ray server for bypassing internet censorship (TLS configuration)" loading="lazy" width="1714" height="974" srcset="https://it.meniko.ru/content/images/size/w600/2022/12/secure_domain.png 600w, https://it.meniko.ru/content/images/size/w1000/2022/12/secure_domain.png 1000w, https://it.meniko.ru/content/images/size/w1600/2022/12/secure_domain.png 1600w, https://it.meniko.ru/content/images/2022/12/secure_domain.png 1714w" sizes="(min-width: 1200px) 1200px"></figure><!--kg-card-begin: markdown--><h2 id="step-5-configure-nginx">Step 5. Configure Nginx</h2>
<ol start="10">
<li>Great lets configure our Nginx. All its configs are located in the folder <code>/etc/nginx/sites-available</code>. If you are making changes on new server, default config file for your new domain will be called <code>default</code>. In this case open this config file and replace it content with this lines. <strong>Do not forget to change my domain <code>bypass.meniko.ru</code> to yours.</strong></li>
</ol>
<pre><code class="language-bash">server {
  listen 80;
  server_name bypass.meniko.ru;
  return 301 https://bypass.meniko.ru$request_uri;
}

server {
  listen 443 ssl;
  server_name bypass.meniko.ru;

  ssl_certificate /etc/letsencrypt/live/bypass.meniko.ru/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/bypass.meniko.ru/privkey.pem;

  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

  location / {
    root /var/www/html;
    index index.html index.htm index.nginx-debian.html;
  }
  
  location /v2ray {
    if ($http_upgrade != &quot;websocket&quot;) {
        return 404;
    }
    proxy_redirect off;
    proxy_pass http://127.0.0.1:10000;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection &quot;upgrade&quot;;
    proxy_set_header Host $host;
    
    # Show real IP in v2ray access.log
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }    
}
</code></pre>
<ol start="10">
<li>Time to check is everything ok with our config and restart the server. Also check that status of nginx is <code>active</code>.</li>
</ol>
<pre><code class="language-bash">$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl restart nginx
$ sudo systemctl status nginx
&#x25CF; nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Tue 2022-11-29 11:40:44 UTC; 4min 25s ago
       Docs: man:nginx(8)
    Process: 4190 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
    Process: 4191 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
   Main PID: 4192 (nginx)
      Tasks: 2 (limit: 512)
     Memory: 3.3M
        CPU: 49ms
     CGroup: /system.slice/nginx.service
             &#x251C;&#x2500;4192 &quot;nginx: master process /usr/sbin/nginx -g daemon on; master_process on;&quot;
             &#x2514;&#x2500;4193 &quot;nginx: worker process&quot;
</code></pre>
<h2 id="configure-v2ray-server">Configure V2Ray server</h2>
<ol start="11">
<li>
<p>Now we need to update config file for our V2ray server that we installed previously. Default V2ray config is located at <code>/usr/local/etc/v2ray/config.json</code>, open it and insert this lines.</p>
<p><strong>You need change <code>id</code> of client (you can use <a href="https://guidgenerator.com/online-guid-generator.aspx">this service</a> to generating new one). Also note path <code>/v2ray</code>, you can change it, but do not forget to change <code>nginx</code> and clients config respectively. For educational purposes I recommend to leave it as is.</strong></p>
<p>As you can see here we generate 1 inbound and 1 outbound routing. In these example we use <code>vmess</code> protocol for extra encryption of data. For comunication beetween client and server we will use websockets. As I said previously there are other options, you can read about them on official site</p>
</li>
</ol>
<pre><code class="language-bash">{
  &quot;inbounds&quot;: [
    {
      &quot;port&quot;: 10000,
      &quot;listen&quot;:&quot;127.0.0.1&quot;, 
      &quot;protocol&quot;: &quot;vmess&quot;,
      &quot;settings&quot;: {
        &quot;clients&quot;: [
          {
            &quot;id&quot;: &quot;95880cb0-0b23-4e87-9042-c8a1bc4e5c0f&quot;,  
            &quot;alterId&quot;: 0
          }
        ]
      },
      &quot;streamSettings&quot;: {
        &quot;network&quot;: &quot;ws&quot;,
        &quot;wsSettings&quot;: {
        &quot;path&quot;: &quot;/v2ray&quot;
        }
      }
    }
  ],
  &quot;outbounds&quot;: [
    {
      &quot;protocol&quot;: &quot;freedom&quot;,
      &quot;settings&quot;: {}
    }
  ]
}
</code></pre>
<ol start="12">
<li>Now we can restart V2ray service and check is it works (as usual status must be <code>active</code>):</li>
</ol>
<pre><code class="language-bash">$ sudo systemctl restart v2ray
$ sudo systemctl status v2ray
&#x25CF; v2ray.service - V2Ray Service
     Loaded: loaded (/etc/systemd/system/v2ray.service; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/v2ray.service.d
             &#x2514;&#x2500;10-donot_touch_single_conf.conf
     Active: active (running) since Tue 2022-11-29 11:57:13 UTC; 4s ago
       Docs: https://www.v2fly.org/
   Main PID: 4346 (v2ray)
      Tasks: 5 (limit: 512)
     Memory: 14.1M
        CPU: 29ms
     CGroup: /system.slice/v2ray.service
             &#x2514;&#x2500;4346 /usr/local/bin/v2ray run -config /usr/local/etc/v2ray/config.json
</code></pre>
<p>We successfully configured our server side, now time to setup our client.</p>
<h2 id="configure-our-v2ray-client">Configure our V2Ray client</h2>
<p>In this part of article I will show you how to configure clients on Windows (GUI client) and Mac OS (CLI client) machines respectively. Also I would like to note that there are a lot of different clients, you can choose from. You can find full list <a href="https://www.v2ray.com/en/awesome/tools.html">here at official site</a>.</p>
<p>Before we begin, here is client configuration file that would be used in both configurations for Windows and Mac OS. You will need to save it with extension <code>.json</code>.</p>
<pre><code class="language-json">{
  &quot;inbounds&quot;: [
    {
      &quot;port&quot;: 1080,
      &quot;listen&quot;: &quot;127.0.0.1&quot;,
      &quot;protocol&quot;: &quot;socks&quot;,
      &quot;sniffing&quot;: {
        &quot;enabled&quot;: true,
        &quot;destOverride&quot;: [&quot;http&quot;, &quot;tls&quot;]
      },
      &quot;settings&quot;: {
        &quot;auth&quot;: &quot;noauth&quot;,
        &quot;udp&quot;: false
      }
    }
  ],
  &quot;outbounds&quot;: [
    {
      &quot;protocol&quot;: &quot;vmess&quot;,
      &quot;settings&quot;: {
        &quot;vnext&quot;: [
          {
            &quot;address&quot;: &quot;bypass.meniko.ru&quot;,
            &quot;port&quot;: 443,
            &quot;users&quot;: [
              {
                &quot;id&quot;: &quot;95880cb0-0b23-4e87-9042-c8a1bc4e5c0f&quot;,
                &quot;alterId&quot;: 0
              }
            ]
          }
        ]
      },
      &quot;streamSettings&quot;: {
        &quot;network&quot;: &quot;ws&quot;,
        &quot;security&quot;: &quot;tls&quot;,
        &quot;wsSettings&quot;: {
          &quot;path&quot;: &quot;/v2ray&quot;
        }
      }
    }
  ]
}
</code></pre>
<h3 id="configuration-of-qv2ray-gui-client-for-windows">Configuration of <code>Qv2ray</code> GUI client for Windows</h3>
<p>As I said previously there are a lot of different options to choose as v2ray client for Windows. I found that <a href="https://github.com/Qv2ray/Qv2ray">Qv2ray</a> is one of the most intresting solutions for this, as it is popular and have good localization. Here I will show you how to configure it, but you can choose any other that you like the most.</p>
<ol>
<li>
<p>First of all you will need to download <code>v2ray core</code> from official Github account. <a href="https://github.com/v2ray/v2ray-core/releases">https://github.com/v2ray/v2ray-core/releases</a></p>
</li>
<li>
<p>Unpack it and place whenever you want on your Windows computer;</p>
</li>
<li>
<p>Next download <code>Qv2ray</code> from <a href="https://github.com/Qv2ray/Qv2ray/releases">this page</a>. At the moment of writing this article there was 2.7.0 version and I download <code>Qv2ray-v2.7.0-Windows-Installer.exe</code></p>
</li>
<li>
<p>Install <code>Qv2Ray</code> that was downloaded on previous step</p>
</li>
<li>
<p>Open installed <code>Qv2Ray</code> client</p>
</li>
<li>
<p>Now you need to define where your <code>v2core</code> is located (that we downloaded on first two steps). In <code>Qv2Ray</code> open <code>Preferences</code> menu and go to <code>Kernel settings</code> and define paths as shown on image</p>
</li>
</ol>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-wide"><img src="https://it.meniko.ru/content/images/2022/12/v2ray_config.jpg" class="kg-image" alt="Configuring private V2Ray server for bypassing internet censorship (TLS configuration)" loading="lazy" width="2000" height="1435" srcset="https://it.meniko.ru/content/images/size/w600/2022/12/v2ray_config.jpg 600w, https://it.meniko.ru/content/images/size/w1000/2022/12/v2ray_config.jpg 1000w, https://it.meniko.ru/content/images/size/w1600/2022/12/v2ray_config.jpg 1600w, https://it.meniko.ru/content/images/size/w2400/2022/12/v2ray_config.jpg 2400w" sizes="(min-width: 1200px) 1200px"></figure><!--kg-card-begin: markdown--><ol start="7">
<li>Now it&#x2019;s time to add our client <code>v2ray config</code> to <code>Qv2ray</code>. Create <code>v2ray.json</code> file whenever you want and copy <code>client config</code> to it (you can find it little a bit above). <strong>Do not forget to change <code>address</code> and <code>id</code> to match your configurations)</strong>. Lastly add it to <code>Qv2ray</code> via <code>+ Import</code> &#x2192; <code>Advanced</code> menu as shown on image</li>
</ol>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-wide"><img src="https://it.meniko.ru/content/images/2022/12/qv2ray_client_config.jpg" class="kg-image" alt="Configuring private V2Ray server for bypassing internet censorship (TLS configuration)" loading="lazy" width="2000" height="1528" srcset="https://it.meniko.ru/content/images/size/w600/2022/12/qv2ray_client_config.jpg 600w, https://it.meniko.ru/content/images/size/w1000/2022/12/qv2ray_client_config.jpg 1000w, https://it.meniko.ru/content/images/size/w1600/2022/12/qv2ray_client_config.jpg 1600w, https://it.meniko.ru/content/images/2022/12/qv2ray_client_config.jpg 2008w" sizes="(min-width: 1200px) 1200px"></figure><!--kg-card-begin: markdown--><ol start="8">
<li>If you do everything correctly you can connect to your server and check does your IP address was changed to new one</li>
</ol>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-wide"><img src="https://it.meniko.ru/content/images/2022/12/connected_v2ray.jpg" class="kg-image" alt="Configuring private V2Ray server for bypassing internet censorship (TLS configuration)" loading="lazy" width="2000" height="1352" srcset="https://it.meniko.ru/content/images/size/w600/2022/12/connected_v2ray.jpg 600w, https://it.meniko.ru/content/images/size/w1000/2022/12/connected_v2ray.jpg 1000w, https://it.meniko.ru/content/images/size/w1600/2022/12/connected_v2ray.jpg 1600w, https://it.meniko.ru/content/images/size/w2400/2022/12/connected_v2ray.jpg 2400w" sizes="(min-width: 1200px) 1200px"></figure><!--kg-card-begin: markdown--><p>Now you can use your <code>v2ray</code> and access any site that is available from network where your server is located. Congratulations!</p>
<h3 id="configuration-for-mac-os-with-cli">Configuration for Mac OS with CLI</h3>
<p>There are a lot of good GUI software for Mac OS as well. But here I would not go this way, we will use <code>v2ray</code> in CLI mode and configure everything manually.</p>
<ol>
<li>To install V2Ray you can use <a href="https://brew.sh/">brew</a> package manager (if you have not use it before, it&#x2019;s time for it; you can find instructions on <a href="https://brew.sh/">official site</a>)</li>
</ol>
<pre><code class="language-bash">$ brew install v2ray
</code></pre>
<ol start="2">
<li>If installation was successfull, this command should return version of V2ray:</li>
</ol>
<pre><code class="language-bash">$ v2ray -version
V2Ray 4.45.2 (V2Fly, a community-driven edition of V2Ray.) Custom (go1.17.11 darwin/amd64)
A unified platform for anti-censorship.
</code></pre>
<ol start="3">
<li>
<p>Now we need to create configuration file for our client, create file <code>config.json</code> in any directory and insert lines of client configuration that was shown before (change <code>address</code> and <code>id</code> to match your configurations)</p>
</li>
<li>
<p>Now it&#x2019;s time to start your <code>v2ray client</code>. Just call <code>v2ray</code> service and define path to config file with in <code>-config</code> parameter</p>
</li>
</ol>
<pre><code class="language-bash">$ v2ray -config config.json
V2Ray 4.45.2 (V2Fly, a community-driven edition of V2Ray.) Custom (go1.17.11 darwin/amd64)
A unified platform for anti-censorship.
2022/12/01 11:28:24 [Info] main/jsonem: Reading config: config.json
2022/12/01 11:28:24 [Warning] V2Ray 4.45.2 started
</code></pre>
<ol start="5">
<li>
<p><code>V2ray</code> is now working and has started Proxy5 server on 127.0.0.1:1080 to which you need send traffic of your computer</p>
</li>
<li>
<p>Define this proxy server in Mac OS settings. Go to <code>Preferences</code> &#x2192; <code>Network</code> &#x2192; Choose your active interface and go to <code>advanced</code> options &#x2192; Open <code>Proxies</code> tab &#x2192; <code>SOCKS Proxy</code> and add Socks Proxy server address and port as show on image</p>
</li>
</ol>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-width-wide"><img src="https://it.meniko.ru/content/images/2022/12/mac_os_config.png" class="kg-image" alt="Configuring private V2Ray server for bypassing internet censorship (TLS configuration)" loading="lazy" width="1560" height="1488" srcset="https://it.meniko.ru/content/images/size/w600/2022/12/mac_os_config.png 600w, https://it.meniko.ru/content/images/size/w1000/2022/12/mac_os_config.png 1000w, https://it.meniko.ru/content/images/2022/12/mac_os_config.png 1560w" sizes="(min-width: 1200px) 1200px"></figure><!--kg-card-begin: markdown--><ol start="7">
<li>Now everything should work and you can go and check whenever your address was changed. We&#x2019;re done!</li>
</ol>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://it.meniko.ru/content/images/2022/12/ip_chicken_mac.png" class="kg-image" alt="Configuring private V2Ray server for bypassing internet censorship (TLS configuration)" loading="lazy" width="2000" height="1062" srcset="https://it.meniko.ru/content/images/size/w600/2022/12/ip_chicken_mac.png 600w, https://it.meniko.ru/content/images/size/w1000/2022/12/ip_chicken_mac.png 1000w, https://it.meniko.ru/content/images/size/w1600/2022/12/ip_chicken_mac.png 1600w, https://it.meniko.ru/content/images/size/w2400/2022/12/ip_chicken_mac.png 2400w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><hr>
<p><strong>I hope that this article was helpful for you and you succesfully configure your own <code>v2ray</code> server. This configuration must allow you to unlock internet even in most difficult and restricted situations &#x2013; such as blocked ports on your local network, resticted UDP packages and many other.</strong></p>
<p><strong>For better security you also need to think about hiding your DNS requests using DNS over HTTPs or some other technics. But this is not topic of this acticle.</strong></p>
<p><strong>If you have any questions or comments leave them below.</strong></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Bash script to automate generation of certificates in WireGuard VPN]]></title><description><![CDATA[<p>In this post you will find two easy bash scripts that can help you in process of generation multiple certificates in WireGuard VPN service. I create it to reduce time of certificate generation for myself, but it can be useful for you, as I could not find such solution with</p>]]></description><link>https://it.meniko.ru/bash-script-to-automation/</link><guid isPermaLink="false">62d166ccd6badc95992a2413</guid><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Thu, 03 Mar 2022 14:41:36 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2022/03/Untitled-1-copy.png" medium="image"/><content:encoded><![CDATA[<img src="https://it.meniko.ru/content/images/2022/03/Untitled-1-copy.png" alt="Bash script to automate generation of certificates in WireGuard VPN"><p>In this post you will find two easy bash scripts that can help you in process of generation multiple certificates in WireGuard VPN service. I create it to reduce time of certificate generation for myself, but it can be useful for you, as I could not find such solution with Google.</p><p>To use these scripts you need to have configured WireGuard service and access to <code>wg</code> tool.</p><p>Next create folder and create bash file, give him executable rights and put this code in it. Change <code>PublicKey</code> property of your servers WireGuard config. Create folder <code>serts</code> in the same folder.</p><pre><code class="language-bash">#!/bin/bash

file=&quot;publickey&quot;;
[ -e $file ] &amp;&amp; rm $file;
file=&quot;privatekey&quot;;
[ -e $file ] &amp;&amp; rm $file; 

wg genkey | tee privatekey | wg pubkey &gt; publickey

privatekey=`cat privatekey`
publickey=`cat publickey`

echo Print name of sert with lowcases;
read name;

echo Print last ip number;
read iplast;

echo $name&quot; &quot;$iplast;

wg set wg0 peer $publickey allowed-ips 10.0.0.$iplast

#print to sert
sertname=$name&quot;.meniko.vpn.conf&quot;;
folder=&apos;./serts&apos;;

echo &quot;[Interface]&quot; &gt;&gt; $folder&quot;/&quot;$sertname
echo &quot;PrivateKey = &quot;$privatekey &gt;&gt; $folder&quot;/&quot;$sertname
echo &quot;Address = 10.0.0.&quot;$iplast&quot;/24&quot; &gt;&gt; $folder&quot;/&quot;$sertname
echo &quot;DNS = 1.1.1.1, 8.8.8.8&quot; &gt;&gt; $folder&quot;/&quot;$sertname

echo &quot;[Peer]&quot; &gt;&gt; $folder&quot;/&quot;$sertname
echo &quot;PublicKey = [public key of server]&quot; &gt;&gt; $folder&quot;/&quot;$sertname
echo &quot;AllowedIPs = 0.0.0.0/0&quot; &gt;&gt; $folder&quot;/&quot;$sertname
echo &quot;Endpoint = vpn.meniko.ru:51820&quot; &gt;&gt; $folder&quot;/&quot;$sertname</code></pre><p>When you executes this bash script, you will be prompted for name and ip address of your user. After that script will create certificate for you and register it in WireGuard. Execute it with admin rights.</p><p>Next script can do restoring work, if something goes wrong, it reads every cert and resets it in WireGuard.</p><pre><code>#!/bin/bash

for file in ./serts/*
do
    echo &quot;----&quot;
    echo $file

    private=&apos;&apos;
    public=&apos;&apos;
    iplast=&apos;&apos;


    while IFS= read -r line
    do
        # find private key
        pat=&quot;^PrivateKey = (.+)$&quot;
        if [[ $line =~ $pat ]];
        then
            echo $line
            private=&quot;${BASH_REMATCH[1]}&quot;
        fi

        # find ip adress
        pat2=&quot;^Address = 10\.0\.0\.(.+)\/24$&quot;
        if [[ $line =~ $pat2 ]];
        then
            echo $line
            iplast=&quot;${BASH_REMATCH[1]}&quot;
        fi
       echo $line
    done &lt; &quot;$file&quot;

    echo $private
    public=`echo $private | wg pubkey`
    echo $public
    echo $iplast

    wg set wg0 peer $public allowed-ips 10.0.0.$iplast
done</code></pre>]]></content:encoded></item><item><title><![CDATA[Best MacOS apps for advanced users (personal recomendations)]]></title><description><![CDATA[<p>In this post I would like to share all my favourite apps for Mac OS, which I use in my life and which I would like to recommend to Mac users. </p><hr><h2 id="productivity">Productivity</h2><p><a href="https://www.notion.so/">Notion</a> <code>(freemium model)</code> &#x2013; app that allows to keep notes, run projects, create knowledge databases in one place</p>]]></description><link>https://it.meniko.ru/mac-os-apps-for-newbuys/</link><guid isPermaLink="false">62d166ccd6badc95992a2412</guid><category><![CDATA[Hack]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Tue, 08 Jun 2021 17:49:02 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2021/06/Untitled-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://it.meniko.ru/content/images/2021/06/Untitled-1.png" alt="Best MacOS apps for advanced users (personal recomendations)"><p>In this post I would like to share all my favourite apps for Mac OS, which I use in my life and which I would like to recommend to Mac users. </p><hr><h2 id="productivity">Productivity</h2><p><a href="https://www.notion.so/">Notion</a> <code>(freemium model)</code> &#x2013; app that allows to keep notes, run projects, create knowledge databases in one place and share what you need with team or world. Previously I use Evernote, but Notion is much more productive focus ready app.</p><p><a href="https://rogueamoeba.com/audiohijack/">Audio Hijack</a> <code>(71$)</code> &#x2013; app that allows capture audio from any app on your mac, I use it to record audio from conferences.</p><figure class="kg-card kg-image-card"><img src="https://it.meniko.ru/content/images/2021/06/Screen-Shot-2021-06-08-at-20.53.42.png" class="kg-image" alt="Best MacOS apps for advanced users (personal recomendations)" loading="lazy" width="2000" height="1276" srcset="https://it.meniko.ru/content/images/size/w600/2021/06/Screen-Shot-2021-06-08-at-20.53.42.png 600w, https://it.meniko.ru/content/images/size/w1000/2021/06/Screen-Shot-2021-06-08-at-20.53.42.png 1000w, https://it.meniko.ru/content/images/size/w1600/2021/06/Screen-Shot-2021-06-08-at-20.53.42.png 1600w, https://it.meniko.ru/content/images/size/w2400/2021/06/Screen-Shot-2021-06-08-at-20.53.42.png 2400w" sizes="(min-width: 720px) 720px"></figure><p><a href="https://typora.io/">Typora</a> <code>(free)</code> &#x2013; one of best markdown editors I have tried. I use it for writing all my posts and documents in MarkDown</p><figure class="kg-card kg-image-card"><img src="https://it.meniko.ru/content/images/2021/06/image-6.png" class="kg-image" alt="Best MacOS apps for advanced users (personal recomendations)" loading="lazy" width="1908" height="1536" srcset="https://it.meniko.ru/content/images/size/w600/2021/06/image-6.png 600w, https://it.meniko.ru/content/images/size/w1000/2021/06/image-6.png 1000w, https://it.meniko.ru/content/images/size/w1600/2021/06/image-6.png 1600w, https://it.meniko.ru/content/images/2021/06/image-6.png 1908w" sizes="(min-width: 720px) 720px"></figure><hr><h2 id="entertainment">Entertainment</h2><p><a href="https://beamer-app.com/">Beamer</a> <code>(19,99$)</code> &#x2013; app that allows streaming your video files directly from your Mac to Apple TV or Chromecast. It supports all common video formats (mkv, mov and other). </p><figure class="kg-card kg-image-card"><img src="https://it.meniko.ru/content/images/2021/06/image-4.png" class="kg-image" alt="Best MacOS apps for advanced users (personal recomendations)" loading="lazy" width="1134" height="1010" srcset="https://it.meniko.ru/content/images/size/w600/2021/06/image-4.png 600w, https://it.meniko.ru/content/images/size/w1000/2021/06/image-4.png 1000w, https://it.meniko.ru/content/images/2021/06/image-4.png 1134w" sizes="(min-width: 720px) 720px"></figure><p><a href="https://iina.io/">IINA</a> <code>(open source)</code> &#x2013; one of the best video players for mac os with large support of different video formats.</p><figure class="kg-card kg-image-card"><img src="https://it.meniko.ru/content/images/2021/06/image-3.png" class="kg-image" alt="Best MacOS apps for advanced users (personal recomendations)" loading="lazy" width="1504" height="944" srcset="https://it.meniko.ru/content/images/size/w600/2021/06/image-3.png 600w, https://it.meniko.ru/content/images/size/w1000/2021/06/image-3.png 1000w, https://it.meniko.ru/content/images/2021/06/image-3.png 1504w" sizes="(min-width: 720px) 720px"></figure><hr><h2 id="security">Security</h2><p><a href="https://www.obdev.at/products/littlesnitch/index.html">Little Snitch</a> <code>(47,25$)</code> &#x2013; app that allow to monitor apps network activity and helps to organise it.</p><p><a href="https://www.enpass.io/">Enpass</a> <code>(2,40$ per month)</code> &#x2013; password manager to keep your passwords organised and secure.</p><hr><h2 id="programming">Programming</h2><p><a href="https://www.postman.com/">Postman</a> <code>(free for personal use)</code> &#x2013; one of best tools to test API for your projects.</p><figure class="kg-card kg-image-card"><img src="https://it.meniko.ru/content/images/2021/06/image-7.png" class="kg-image" alt="Best MacOS apps for advanced users (personal recomendations)" loading="lazy" width="2000" height="1261" srcset="https://it.meniko.ru/content/images/size/w600/2021/06/image-7.png 600w, https://it.meniko.ru/content/images/size/w1000/2021/06/image-7.png 1000w, https://it.meniko.ru/content/images/size/w1600/2021/06/image-7.png 1600w, https://it.meniko.ru/content/images/2021/06/image-7.png 2204w" sizes="(min-width: 720px) 720px"></figure><hr><h2 id="other">Other</h2><p><a href="https://bjango.com/mac/istatmenus/">iStat Menus</a> <code>(11,99$)</code> &#x2013; app helps you to monitor your system health and has a lot of useful features.</p><figure class="kg-card kg-image-card"><img src="https://it.meniko.ru/content/images/2021/06/image.png" class="kg-image" alt="Best MacOS apps for advanced users (personal recomendations)" loading="lazy" width="1084" height="50" srcset="https://it.meniko.ru/content/images/size/w600/2021/06/image.png 600w, https://it.meniko.ru/content/images/size/w1000/2021/06/image.png 1000w, https://it.meniko.ru/content/images/2021/06/image.png 1084w" sizes="(min-width: 720px) 720px"></figure><p><a href="https://www.vmware.com/ru/products/fusion.html">VMWare Fusion</a> <code>(150$)</code> &#x2013; app for virtualisation operation systems on Mac OS. Previously I use VirtualBox, but it was buggy and slow. VMWare is much much better.</p><p><a href="https://github.com/Toxblh/MTMR">MTMR</a> <code>(open source)</code> &#x2013; app for customisation touch bar, you can use existing themes or create custom buttons and blocks.</p><p><a href="https://folivora.ai/">BetterTouchTool</a> <code>(9$)</code> &#x2013; app to create custom shortcuts for keyboard, TrackPad and Apple MagicMouse and a lot more.</p><figure class="kg-card kg-image-card"><img src="https://it.meniko.ru/content/images/2021/06/Screen-Shot-2021-06-08-at-20.57.14.png" class="kg-image" alt="Best MacOS apps for advanced users (personal recomendations)" loading="lazy" width="2000" height="1078" srcset="https://it.meniko.ru/content/images/size/w600/2021/06/Screen-Shot-2021-06-08-at-20.57.14.png 600w, https://it.meniko.ru/content/images/size/w1000/2021/06/Screen-Shot-2021-06-08-at-20.57.14.png 1000w, https://it.meniko.ru/content/images/size/w1600/2021/06/Screen-Shot-2021-06-08-at-20.57.14.png 1600w, https://it.meniko.ru/content/images/size/w2400/2021/06/Screen-Shot-2021-06-08-at-20.57.14.png 2400w" sizes="(min-width: 720px) 720px"></figure><p><a href="https://www.balena.io/etcher/">belenaEtcher</a> <code>(open source)</code> &#x2013; automatic tool to flash OS to external hard drives.</p><figure class="kg-card kg-image-card"><img src="https://it.meniko.ru/content/images/2021/06/image-1.png" class="kg-image" alt="Best MacOS apps for advanced users (personal recomendations)" loading="lazy" width="1824" height="1184" srcset="https://it.meniko.ru/content/images/size/w600/2021/06/image-1.png 600w, https://it.meniko.ru/content/images/size/w1000/2021/06/image-1.png 1000w, https://it.meniko.ru/content/images/size/w1600/2021/06/image-1.png 1600w, https://it.meniko.ru/content/images/2021/06/image-1.png 1824w" sizes="(min-width: 720px) 720px"></figure><p><a href="https://handbrake.fr/">HandBrake</a> <code>(open source)</code> &#x2013; tool for converting videos from one format to another, I usually use it to compress videos from Adobe Premiere. </p><p><a href="https://karabiner-elements.pqrs.org/">Karabiner-Elements</a> <code>(open source)</code> &#x2013; A powerful app for keyboard customising. I use it for customising keys on external keyboards.</p><figure class="kg-card kg-image-card"><img src="https://it.meniko.ru/content/images/2021/06/image-2.png" class="kg-image" alt="Best MacOS apps for advanced users (personal recomendations)" loading="lazy" width="2000" height="1243" srcset="https://it.meniko.ru/content/images/size/w600/2021/06/image-2.png 600w, https://it.meniko.ru/content/images/size/w1000/2021/06/image-2.png 1000w, https://it.meniko.ru/content/images/size/w1600/2021/06/image-2.png 1600w, https://it.meniko.ru/content/images/2021/06/image-2.png 2224w" sizes="(min-width: 720px) 720px"></figure><p><a href="https://obsproject.com/">OBS</a> <code>(open source)</code> &#x2013; software for capturing and steaming videos from mac os, gamers use it for streaming, but I usually use it for capturing video of mac os videos together with <a href="https://rogueamoeba.com/audiohijack/">Audio Hijack</a> (see above).</p>]]></content:encoded></item><item><title><![CDATA[Convert PDF to PDF with only images to prevent it from editing]]></title><description><![CDATA[<p>This article with describe how to convert PDF file to PDF consisting only from images. This operation permits future editing PDF using such converters as <a href="https://www.ilovepdf.com/pdf_to_powerpoint">iLovePDF</a>.</p><p>To do this you need to install imagemagick from there <a href="https://imagemagick.org/script/download.php">official site</a>, or install via package managers. For Mac OS you can use brew</p>]]></description><link>https://it.meniko.ru/convert-pdf-to-pdf-with-only-images/</link><guid isPermaLink="false">62d166ccd6badc95992a2411</guid><category><![CDATA[Hack]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Mon, 07 Jun 2021 17:18:02 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2021/06/1200x630wa-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://it.meniko.ru/content/images/2021/06/1200x630wa-1.png" alt="Convert PDF to PDF with only images to prevent it from editing"><p>This article with describe how to convert PDF file to PDF consisting only from images. This operation permits future editing PDF using such converters as <a href="https://www.ilovepdf.com/pdf_to_powerpoint">iLovePDF</a>.</p><p>To do this you need to install imagemagick from there <a href="https://imagemagick.org/script/download.php">official site</a>, or install via package managers. For Mac OS you can use brew for it:</p><pre><code class="language-bash">brew install imagemagick</code></pre><p>Next you can execute next command, that will convert PDF to PNG images</p><pre><code>convert -density 1200 name_of_pdf.pdf -resample 300 -scale 4096x4096 page.png</code></pre><p>Next you need convert PNG images from first command to PDF, using:</p><pre><code>convert *.png -scale 4096x4096 output.pdf</code></pre><p><strong>This two commands can take a time to execute, depending from the size of your PDF file.</strong></p><p>Also you can create alias in bash, adding these command to yours <code>~/.zshrc</code> (or another shell)</p><pre><code>pdftopng(){
   mkdir -p topng
   convert -density 1200 $1 -resample 300 -scale 4096x4096 topng/page.png
   convert topng/*.png -scale 4096x4096 comp.$1
}</code></pre><p>Don&apos;t forget to do <code>source ~/.zshrc</code>. </p>]]></content:encoded></item><item><title><![CDATA[Compress PDF files using GhostScript]]></title><description><![CDATA[<p>This post describes method how to automate process of compressing PDF files in Mac or Linux environments using <a href="https://www.ghostscript.com/">GhostScript</a> library.</p><p>First of all you need to install GhostScript library on your computer. On mac os you can do it with brew: </p><figure class="kg-card kg-code-card"><pre><code class="language-bash">brew install ghostscript</code></pre><figcaption>Install GhostScript on Mac OS with</figcaption></figure>]]></description><link>https://it.meniko.ru/compress-pdf-files-using/</link><guid isPermaLink="false">62d166ccd6badc95992a2410</guid><category><![CDATA[Hack]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Mon, 07 Jun 2021 16:45:32 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2021/06/1200x630wa.png" medium="image"/><content:encoded><![CDATA[<img src="https://it.meniko.ru/content/images/2021/06/1200x630wa.png" alt="Compress PDF files using GhostScript"><p>This post describes method how to automate process of compressing PDF files in Mac or Linux environments using <a href="https://www.ghostscript.com/">GhostScript</a> library.</p><p>First of all you need to install GhostScript library on your computer. On mac os you can do it with brew: </p><figure class="kg-card kg-code-card"><pre><code class="language-bash">brew install ghostscript</code></pre><figcaption>Install GhostScript on Mac OS with Brew</figcaption></figure><p>Or using apt on Linux systems</p><figure class="kg-card kg-code-card"><pre><code class="language-bash">sudo apt update
sudo apt install ghostscript</code></pre><figcaption>Install GhostScipt on Linux using apt</figcaption></figure><p>Now you can do converting using <code>gs</code> command in terminal. There are a lot of options, but here I would give you my solution.</p><pre><code class="language-bash">gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/default \
   -dNOPAUSE -dQUIET -dBATCH -dDetectDuplicateImages \
   -dCompressFonts=true -r150 -sOutputFile=output.pdf input.pdf</code></pre><p>If you would like to make shortcut in your terminal to start this commands quicker, you can add this code to your <code>~/.zshrc</code> file.</p><pre><code>nano ~/.zshrc</code></pre><pre><code>pdfcompress ()
{
   mkdir -p compressed
        gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/default \
                -dNOPAUSE -dQUIET -dBATCH -dDetectDuplicateImages \
                -dCompressFonts=true -r150 -sOutputFile=compressed/$1 $1
}
pdfcompressall (){
   for i in $(ls | grep .pdf); do pdfcompress &quot;$i&quot;; done
}</code></pre><p>This will allow you call GhostScript command as <code>pdfcompress name_of_pdf.pdf</code> to compress one pdf file, or using <code>pdfcompressall</code> to compress all pdf files in current folder.</p><figure class="kg-card kg-image-card"><img src="https://it.meniko.ru/content/images/2021/06/Untitled.gif" class="kg-image" alt="Compress PDF files using GhostScript" loading="lazy" width="800" height="501" srcset="https://it.meniko.ru/content/images/size/w600/2021/06/Untitled.gif 600w, https://it.meniko.ru/content/images/2021/06/Untitled.gif 800w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[Convert XLSX file with multi level header to plain CSV and JSON]]></title><description><![CDATA[Convert XLSX file with multi level header to plain CSV or JSON file with all headers information.]]></description><link>https://it.meniko.ru/convert_multiheader_excel_to_csv/</link><guid isPermaLink="false">62d166ccd6badc95992a240f</guid><category><![CDATA[NodeJS]]></category><category><![CDATA[Hack]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Tue, 31 Dec 2019 18:26:51 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2019/12/wall.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://it.meniko.ru/content/images/2019/12/wall.jpg" alt="Convert XLSX file with multi level header to plain CSV and JSON"><p>Some systems exports data in table format with multiheader structure, that looks something like this.</p>
<p><img src="https://img.meniko.ru/2019/12/convert_multiheader_excel_to_csv/001.png" alt="Convert XLSX file with multi level header to plain CSV and JSON" loading="lazy"></p>
<p>If you need to use this data in some of your applications you can manually merge headers, but if this file is updated frequently these can be tediously. In this case I can offer you JS script that will do this work for you.</p>
<p>At the end of itexecution you will get csv like this. As you can see all headers are grouped and merged into one level:</p>
<p><img src="https://img.meniko.ru/2019/12/convert_multiheader_excel_to_csv/002.png" alt="Convert XLSX file with multi level header to plain CSV and JSON" loading="lazy"></p>
<p>Or JSON like this:</p>
<p><img src="https://img.meniko.ru/2019/12/convert_multiheader_excel_to_csv/003.png" alt="Convert XLSX file with multi level header to plain CSV and JSON" loading="lazy"></p>
<p>Script uses two packages: <a href="https://www.npmjs.com/package/xlsx"><code>XLSX</code>,</a> <a href="https://www.npmjs.com/package/json2csv"><code>json2csv</code></a>. To install packages use:</p>
<pre><code class="language-bash">npm install --save json2csv xlsx
</code></pre>
<p>Function that makes all work is:</p>
<pre><code class="language-javascript">const XLSX = require(&apos;xlsx&apos;);
const fs = require(&apos;fs&apos;);
const util = require(&apos;util&apos;);
const {Parser} = require(&apos;json2csv&apos;);

const xlxsToCSVSync = (file, fileOutput, skipTopLines) =&gt; {
	const letterToNumber = (val) =&gt; {
		var base = &apos;ABCDEFGHIJKLMNOPQRSTUVWXYZ&apos;, i, j, result = 0;
		for (i = 0, j = val.length - 1; i &lt; val.length; i += 1, j -= 1) {
			result += Math.pow(base.length, j) * (base.indexOf(val[i]) + 1);
		}
		return result;
	};

	const cellParsing = (cellName) =&gt; {
		let match = cellName.match(/([A-Za-z]+)(\d+)/);
		if(!match) return false;
		return {c: letterToNumber(match[1])-1, r: match[2]-1}
	}

	if(!fs.existsSync(file)){
		throw `File ${file} does not exists`
	}

	const xlsxData = fs.readFileSync(file);
	const workbook = XLSX.read(xlsxData, {type:&apos;buffer&apos;});
	
	if(Object.keys(workbook.Sheets).length &gt; 1) throw `Excel contains more than one page`;
	let sheet = Object.keys(workbook.Sheets)[0];

	// define header height
	let headerHeight = 1
	let headerLeftTop, headerRightBottom;

	for(let cell in workbook.Sheets[sheet]){
		let coor = cellParsing(cell);
		if(!coor) continue;
		if(coor.r &lt; skipTopLines) continue;
		if(coor.r &gt;= (skipTopLines + headerHeight)) break;

		let findMerge = workbook.Sheets[sheet][&apos;!merges&apos;].find(x =&gt; x.s.c == coor.c &amp;&amp; x.s.r == coor.r);
		if(findMerge){
			let width = findMerge.e.c - findMerge.s.c + 1
			let height = findMerge.e.r - findMerge.s.r + 1
			if(height &gt; 1){
				headerHeight = coor.r - skipTopLines + height
			}
		}

		if(typeof headerLeftTop == &apos;undefined&apos; || (coor.c &lt; headerLeftTop.coor.c &amp;&amp; coor.r &lt; headerLeftTop.coor.r)) headerLeftTop = {cell, coor};
		if(typeof headerRightBottom == &apos;undefined&apos; || (coor.c &gt; headerLeftTop.coor.c &amp;&amp; coor.r &gt; headerLeftTop.coor.r)) headerRightBottom = {cell, coor};
	}

	// define header cells
	for(let cell in workbook.Sheets[sheet]){
		let coor = cellParsing(cell);
		if(!coor) continue;
		if(coor.r &lt; skipTopLines) continue;
		if(coor.r &gt;= (skipTopLines + headerHeight)) break;

		let findMerge = workbook.Sheets[sheet][&apos;!merges&apos;].find(x =&gt; x.s.c == coor.c &amp;&amp; x.s.r == coor.r);
		if(findMerge){
			let width = findMerge.e.c - findMerge.s.c + 1
			let height = findMerge.e.r - findMerge.s.r + 1
		}
	}

	console.log(`Header height: ${headerHeight}`);

	// header define
	let headers = []
	const headerParser = (topLevel, line, leftOffset, widthParent) =&gt; {
		if(typeof leftOffset == &apos;undefined&apos;) leftOffset = 0;
		if(typeof widthParent == &apos;undefined&apos;) widthParent = 0;
		// let childLeftOffset = leftOffset;

		for (let cell in workbook.Sheets[sheet]){
			let coor = cellParsing(cell);
			if(!coor) continue;

			// row filter
			if(coor.r &lt; skipTopLines) continue;
			if((coor.r + 1) != (skipTopLines + topLevel)) continue;
			if(coor.r &gt;= (skipTopLines + headerHeight)) break;

			// column filter
			if(coor.c &lt; leftOffset) continue;
			if(widthParent &gt; 0 &amp;&amp; (coor.c + 1) &gt; (leftOffset + widthParent)) break;

			let height, width;
			let findMerge = workbook.Sheets[sheet][&apos;!merges&apos;].find(x =&gt; x.s.c == coor.c &amp;&amp; x.s.r == coor.r);
			if(findMerge){
				width = findMerge.e.c - findMerge.s.c + 1
				height = findMerge.e.r - findMerge.s.r + 1
			} else {
				height = 1
				width = 1
			}

			let cellValue = workbook.Sheets[sheet][cell].v;
			if(!cellValue) continue;

			let curLine = line
			if(line != &apos;&apos;) curLine += &apos;.&apos;;
			curLine += cellValue;

			if((height + topLevel - 1) == headerHeight){
				headers.push({
					name: curLine,
					column: coor.c, 
					width
				})
				// headers.push(curLine)
			} else if((height + topLevel - 1) &lt; headerHeight){
				headerParser((topLevel + height), curLine, coor.c, width);
			}
		}
	}
	headerParser(1, &apos;&apos;);

	console.log(`Number of data columns: ${headers.length}`)

	// default row object
	let defaultRow = {}
	let headersList = [] 
	headers.forEach(h =&gt; {
		defaultRow[h.name] = null;
		headersList.push(h.name);
	})

	// define header cells
	let data = []

	for(let cell in workbook.Sheets[sheet]){
		let coor = cellParsing(cell);
		if(coor.r &lt; (skipTopLines + headerHeight)) continue;
		if(!coor) continue;

		let rowNumber = coor.r - headerHeight - skipTopLines;
		if(rowNumber &lt; 0){
			console.log(`${cell} is lower 0 (${rowNumber})`)
			process.exit();
		}
		if(typeof data[rowNumber] == &apos;undefined&apos;){
			data[rowNumber] = Object.assign({}, defaultRow);
		}
		
		let cellValue = workbook.Sheets[sheet][cell].v;
		if(!cellValue) continue;		

		let column = headers.find(x =&gt; x.column == coor.c);
		if(column){
			data[rowNumber][column.name] = cellValue;
		}
	}

	const json2csvParser = new Parser({ headersList });
	const csv = json2csvParser.parse(data);

	console.log(`Data rows: ${data.length}`);

	fs.writeFileSync(fileOutput, csv)
	return data;
}

module.exports = xlxsToCSVSync;
</code></pre>
<p>To execute it use next script. Make sure that previous function is located in the same directory and is called <code>excelToCSV.js</code> or make appropriate changes.</p>
<p>Also you need to change path to file on 11 line. Additionally you can specify number of rows that should be skipped in source xlsx file.</p>
<pre><code class="language-javascript">const fs = require(&apos;fs&apos;)
const path = require(&apos;path&apos;);

const xlxsToCSVSync = require(path.join(__dirname,&apos;excelToCSV.js&apos;));

console.log(`To convert XLSX file to CSV and JSON formats change variables &apos;fileName&apos; and &apos;skipLines&apos; in script.\n` + 
			`  &apos;skipLines&apos; variable defines number of lines that will be skipped at the top of xlxs file\n`+
			`XLSX file must contain only one page sheet to be procedeed\n`);

// -------------- CHANGED VARIABLES --------------
const fileName = &apos;./table.xlsx&apos;
const skipLines = 0
// -----------------------------------------------

const filePath = path.join(__dirname, fileName);
const parse = path.parse(filePath);

console.log(`Try to convert file &apos;${fileName}&apos; (${filePath})`)

if(!fs.existsSync(filePath)){
	console.log(`File &apos;${filePath}&apos; does not exists`);
	process.exit();
}

(async () =&gt; {
	let data = xlxsToCSVSync(filePath, path.join(parse.dir, `${parse.base}.csv`), skipLines);
	fs.writeFileSync(path.join(parse.dir, `${parse.base}.json`), JSON.stringify(data))
})()
</code></pre>
<p>In my case files structure of folder with scripts looks like:</p>
<pre><code>&#x251C;&#x2500;&#x2500; excelToCSV.js
&#x251C;&#x2500;&#x2500; exeScript.js
&#x251C;&#x2500;&#x2500; node_modules
&#x2502;&#xA0;&#xA0; &#x251C;&#x2500;&#x2500; ...
&#x251C;&#x2500;&#x2500; package-lock.json
&#x251C;&#x2500;&#x2500; package.json
&#x2514;&#x2500;&#x2500; table.xlsx
&#x251C;&#x2500;&#x2500; table.xlsx.csv
&#x2514;&#x2500;&#x2500; table.xlsx.json
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Light Node.js script to send large files P2P]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In this article you will find two lightweight Node.js scripts that will allow you to send very large files from one computer to another peer-to-peer (P2P). There is a lot of different scenarious when they can be usefull: for example if your file is too big to be send</p>]]></description><link>https://it.meniko.ru/send_large_files_p2p_with_nodejs/</link><guid isPermaLink="false">62d166ccd6badc95992a240d</guid><category><![CDATA[Hack]]></category><category><![CDATA[NodeJS]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Tue, 06 Aug 2019 10:02:40 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2019/08/wallpaper-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://it.meniko.ru/content/images/2019/08/wallpaper-1.jpg" alt="Light Node.js script to send large files P2P"><p>In this article you will find two lightweight Node.js scripts that will allow you to send very large files from one computer to another peer-to-peer (P2P). There is a lot of different scenarious when they can be usefull: for example if your file is too big to be send via RDP protocol (as it has limit at 2Gb per file).</p>
<h3 id="prerequisites">Prerequisites:</h3>
<p>What you need:</p>
<ul>
<li>You need Node.JS interpreter to be installed on both computers (<a href="https://nodejs.org/en/download/">you can download it from here</a>): server (from which you will load file) and client (from where you will load it);</li>
<li>Ability to open ports on server machine;</li>
<li>Ability to reach server from client (firewall should no break off connection).</li>
</ul>
<h2 id="howdoesitworks">How does it works?</h2>
<p>There is two scripts:</p>
<ul>
<li><strong>Server side script</strong> &#x2013; reads specified file from computer and sends it to client, when client requests it;</li>
<li><strong>Client side</strong> &#x2013; sends requests to server and downloads file to current folder;</li>
</ul>
<h4 id="serverjssidescript">Server.js side script</h4>
<p>To execute server side script open terminal (command line), go to folder where you saved file and write: <code>node node server.js --path pathToFile</code>, change <strong>pathToFile</strong> with path to file that you would like to send to client. Path to file can be absolute or relative.</p>
<p>By default script will use <code>8000</code> port, if you would like to use another one, change it in the script (4 line).</p>
<p><img src="https://img.meniko.ru/2019/08/send_large_files_p2p_with_nodejs/001.png" alt="Light Node.js script to send large files P2P" loading="lazy"></p>
<pre><code class="language-javascript">const fs = require(&apos;fs&apos;);
const path = require(&apos;path&apos;);

const port = 8000;

;(() =&gt; {
	const filePathI = process.argv.indexOf(&apos;--path&apos;);
	if(filePathI == -1){
      console.log(&apos;Please call script with --path arg.&apos;);
      process.exit();
   }

	const filepath = process.argv[filePathI + 1];

	if(!fs.existsSync(filepath)){ console.log(`File does not exists!`); process.exit(); }
	const stats = fs.statSync(filepath);

	if(stats.isDirectory()){
      console.log(`File is Directory. You could not transfer directories with this script`);
      process.exit();
   }

	console.log(`You are about to transfer file: &apos;${filepath}&apos;`);
	console.log(`File size is ${Math.round(stats.size/1024/1024*100)/100} Mb`)

	const server = require(&apos;http&apos;).createServer();
	server.on(&apos;request&apos;, (req, res) =&gt; {
		if(req.url == &apos;/info&apos;){
			console.log(`Send information on file`);
			res.end(JSON.stringify({path: filepath, pathDesc: path.parse(filepath), stats}));
		} else if(req.url == &apos;/file&apos;){
			console.log(`Start tranfering of file`);

			const src = fs.createReadStream(filepath);
			src.pipe(res);
			src.on(&apos;end&apos;, () =&gt; {
				console.log(&quot;Transfer successfully&quot;);
				process.exit();
			});
		}
	});

	server.on(&apos;error&apos;, (e) =&gt; {
      console.error(e);
      process.exit();
   })
	server.listen(port);
})()
</code></pre>
<h4 id="clientjssidescript">Client.js side script</h4>
<p>Now is the time to load file to client machine:</p>
<ol>
<li>Save <code>client.js</code> file to some place on machine;</li>
<li>Open file and change <code>server</code> variable, put yours machine address and port that <code>server.js</code> is using (by default 8000 port will be used);</li>
<li>Open terminal and navigate to the folder from step 1;</li>
<li>Execute <code>node client.js</code>. Script will start loading process. After loading process you should see message <code>Loading is finished</code>, if so everything is OK and you successfully load file. If You see some other message something wrong happened.</li>
</ol>
<p><img src="https://img.meniko.ru/2019/08/send_large_files_p2p_with_nodejs/002.gif" alt="Light Node.js script to send large files P2P" loading="lazy"></p>
<pre><code class="language-javascript"> const fs = require(&apos;fs&apos;);
 const http = require(&apos;http&apos;);
 const url = require(&apos;url&apos;);
 const path = require(&apos;path&apos;);
 
 var server = &apos;http://serverAddress:8000/&apos;
 
 const getInfo = ()=&gt;{
   return new Promise((resolve, reject) =&gt; {
     console.log(`-- Request information about the file from server`)
     http.get(url.resolve(server, &apos;/info&apos;), (res) =&gt; {
       let rawData = &apos;&apos;;
       res.on(&apos;data&apos;, (chunk) =&gt; { rawData += chunk; });
       res.on(&apos;end&apos;, () =&gt; {
         try {
           return resolve(JSON.parse(rawData))
         } catch (e) {
             return reject(e);
         }
       });
     }).on(&quot;error&quot;, (err) =&gt; {
       return reject(err);
     });
   })
 }
 
 function processBar(fullsize, loaded) {
   var bars = 20;
   var loadBars = Math.round(bars * (loaded / fullsize));
   var remainBars = bars - loadBars;
 
   console.log(`[${&apos;=&apos;.repeat(loadBars)}&gt;${&apos;.&apos;.repeat(remainBars)}] ${Math.round(loaded/1024/1024*100)/100}/${Math.round(fullsize/1024/1024*100)/100}Mb`);
 }
 
 const loadFile = (info) =&gt; {
   return new Promise((resolve, reject) =&gt; {
     console.log(`-- Start file loading`)
     var pathParsed = path.parse(info.path)
     console.log(`File name is &apos;${info.pathDesc.base}&apos;`)
 
     if(fs.existsSync(info.pathDesc.base)){
       return reject(`Such file already exists in current folder`);
     }
 
     console.log(`File size is ${Math.round(info.stats.size/1024/1024*100)/100} Mb`)
 
     processBar(info.stats.size, 0)
 
     http.get(url.resolve(server, &apos;/file&apos;), (resp) =&gt; {
       var file = fs.createWriteStream(&apos;./&apos;+info.pathDesc.base)
       var getSize = 0;
       resp.pipe(file);
       resp.on(&apos;data&apos;, chunk =&gt; {
         getSize += chunk.length
         processBar(info.stats.size, getSize)
       })
       resp.on(&apos;end&apos;, () =&gt; {
         console.log(&quot;\nLoading is finished&quot;);
         return resolve();
       });
     }).on(&quot;error&quot;, (err) =&gt; {
       return reject(err.message);
     });
   })
 }
 
 getInfo().then((info) =&gt; loadFile(info))
 .catch(e =&gt; {
   console.log(`Error in processing, stop of script execution:`)
   console.error(e)
 })
</code></pre>
<p>Good luck!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Definition of system lines in Cognos TM1 Views files (.vue)]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In this article I will disclose information about how Cognos TM1 stores information about cubes views in <code>.vue</code> files. For Cognos TM1 developers this information can be usefull in many different situations, for example:</p>
<ol>
<li>For automatic updates of views &#x2013; changing used subsets, chosen elements of context dimensions;</li>
<li>Finding what</li></ol>]]></description><link>https://it.meniko.ru/cognostm1_views_files_code_definition/</link><guid isPermaLink="false">62d166ccd6badc95992a240c</guid><category><![CDATA[Cognos]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Wed, 31 Jul 2019 15:32:56 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2019/07/viewWallpaper.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://it.meniko.ru/content/images/2019/07/viewWallpaper.jpg" alt="Definition of system lines in Cognos TM1 Views files (.vue)"><p>In this article I will disclose information about how Cognos TM1 stores information about cubes views in <code>.vue</code> files. For Cognos TM1 developers this information can be usefull in many different situations, for example:</p>
<ol>
<li>For automatic updates of views &#x2013; changing used subsets, chosen elements of context dimensions;</li>
<li>Finding what views uses some dimensions subset and etc.</li>
</ol>
<blockquote>
<p>There are 2 more simular articles about Cognos TM1 system codes in files:</p>
<ol>
<li><a href="https://it.meniko.ru/cognostm1_subset_files_code_definition/">Definition of system lines in Cognos TM1 Subset files (.sub)</a></li>
<li><a href="https://it.meniko.ru/cognos-tm1-process-file-definition-of-system-codes/">Definition of system lines in Cognos TM1 Process files</a>.</li>
</ol>
</blockquote>
<p><img src="https://img.meniko.ru/2019/07/cognostm1_views_files_code_definition/001.png" alt="Definition of system lines in Cognos TM1 Views files (.vue)" loading="lazy"></p>
<p>In the image above you can see the view and below you can see the code of it from <code>.vue</code> file.</p>
<p>There are multiple ways how developer can define appearance of some dimension in the view: with predefined subset, as custom list of elements and as list of all elements. To make the description of file more clear, I replace this information and explain it at the end of this article, defining all available options. Places in the code are replaced with lines: <code>&lt;-- Block of code that defines dimension, look below --&gt;</code>.</p>
<h4 id="codedescription">Code description</h4>
<table>
<thead>
<tr>
<th>Code</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>389</code>,100</td>
<td>Encoding of file, <strong>100</strong> for utf8</td>
</tr>
<tr>
<td><code>390</code>,&quot;View&quot;</td>
<td>Name of the view</td>
</tr>
<tr>
<td><code>370</code>,0</td>
<td>?</td>
</tr>
<tr>
<td><code>361</code>,1</td>
<td>?</td>
</tr>
<tr>
<td><code>362</code>,1</td>
<td>?</td>
</tr>
<tr>
<td><code>363</code>,0</td>
<td>?</td>
</tr>
<tr>
<td><code>364</code>,0</td>
<td>?</td>
</tr>
<tr>
<td><code>365</code>,</td>
<td>?</td>
</tr>
<tr>
<td><code>366</code>,</td>
<td>?</td>
</tr>
<tr>
<td><code>367</code>,0</td>
<td>?</td>
</tr>
<tr>
<td><code>376</code>,0</td>
<td><strong>1</strong> if auto recalculate is turned on, <strong>0</strong> otherwise</td>
</tr>
<tr>
<td><code>375</code>,b:0.0</td>
<td>Default format for numbers in cells</td>
</tr>
<tr>
<td></td>
<td><strong>Start of dimensions in context</strong></td>
</tr>
<tr>
<td><code>374</code>,3</td>
<td>Number of dimension that are in the <strong>context</strong> positions. In this example there are 3 dimensions. After this code list of dimensions in context and there definitions are coming</td>
</tr>
<tr>
<td><code>7</code>,D1</td>
<td>Name of dimension that is located in context position</td>
</tr>
<tr>
<td></td>
<td>&lt;-- Block of code that defines dimension, look below --&gt;</td>
</tr>
<tr>
<td><code>7</code>,D2</td>
<td>Look <code>7</code> above</td>
</tr>
<tr>
<td></td>
<td>&lt;-- Block of code that defines dimension, look below --&gt;</td>
</tr>
<tr>
<td><code>7</code>,D3</td>
<td>Look <code>7</code> above</td>
</tr>
<tr>
<td></td>
<td>&lt;-- Block of code that defines dimension, look below --&gt;</td>
</tr>
<tr>
<td></td>
<td><strong>Start of dimensions in columns</strong></td>
</tr>
<tr>
<td><code>360</code>,1</td>
<td>Number of dimensions in <strong>columns</strong>. In this example. In this example there is only one dimension in rows. After this code list of dimensions in columns and there definitions are coming</td>
</tr>
<tr>
<td><code>7</code>,DMeasure</td>
<td>Look <code>7</code> above</td>
</tr>
<tr>
<td></td>
<td>&lt;-- Block of code that defines dimension, look below --&gt;</td>
</tr>
<tr>
<td></td>
<td><strong>Start of dimensions in rows</strong></td>
</tr>
<tr>
<td><code>371</code>,1</td>
<td>Number of dims in <strong>rows</strong> positions. In this example there is only one dimension in rows. After this code list of dimensions in rows and there definitions are coming</td>
</tr>
<tr>
<td><code>7</code>,D4</td>
<td>Look <code>7</code> above</td>
</tr>
<tr>
<td></td>
<td>&lt;-- Block of code that defines dimension, look below --&gt;</td>
</tr>
<tr>
<td><code>373</code>,3</td>
<td><strong>Default values</strong> for dimensions that are located in context are coming after this code. 3 is number of such dimensions in current example</td>
</tr>
<tr>
<td>1,Total</td>
<td>Default value for first dimension in Context (in this example for dimension D1). 1 is index of this element in dimension</td>
</tr>
<tr>
<td>1,Total</td>
<td>Default value for second dimension in context</td>
</tr>
<tr>
<td>1,D3.EL1</td>
<td>Default value for third dimension in context</td>
</tr>
<tr>
<td><code>372</code>,0</td>
<td>Suppress zeros for <strong>rows</strong></td>
</tr>
<tr>
<td><code>372</code>,00</td>
<td>Suppress zeros for <strong>rows</strong> (first number) and <strong>columns</strong> (second number)</td>
</tr>
<tr>
<td><code>384</code>,0</td>
<td>Suppress zeros for <strong>rows</strong> (again);</td>
</tr>
<tr>
<td><code>385</code>,0</td>
<td>Suppress zeros for <strong>columns</strong></td>
</tr>
<tr>
<td><code>377</code>,4</td>
<td>?</td>
</tr>
<tr>
<td></td>
<td>&lt;-- Some numbers --&gt;</td>
</tr>
<tr>
<td><code>378</code>,0</td>
<td>?</td>
</tr>
<tr>
<td><code>382</code>,255</td>
<td>?</td>
</tr>
<tr>
<td><code>379</code>,5</td>
<td>?</td>
</tr>
<tr>
<td><code>11</code>,20190731144247</td>
<td>Time of last edit in format (YYYYMMDDHHMMSS)</td>
</tr>
<tr>
<td><code>381</code>,0</td>
<td>?</td>
</tr>
</tbody>
</table>
<h3 id="defininglistofelementsorsubsetforsomedimensionafter7code">Defining list of elements or subset for some dimension after <code>7</code> code</h3>
<h4 id="usesubsetasdimensionslist">Use subset as dimensions list</h4>
<table>
<thead>
<tr>
<th>Code</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>6</code>,Name of subset</td>
<td>If you specify subset for dimension it&apos;s name will be placed in this code</td>
</tr>
</tbody>
</table>
<h4 id="usecustomlistofelementsasdimensionslist">Use custom list of elements as dimensions list</h4>
<p>When you specify custom list of element for dimension in view this syntax is used. <a href="https://it.meniko.ru/cognostm1_subset_files_code_definition/">For more information look article about codes in <code>.sub</code> files.</a></p>
<table>
<thead>
<tr>
<th>Code</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>270</code>,3</td>
<td>Define number of elements in custom list. In this example there are 3 elements in it</td>
</tr>
<tr>
<td>&lt;-- List of elements --&gt;</td>
<td>Here comes list of element, one element for each line</td>
</tr>
<tr>
<td><code>274</code>,AliasName</td>
<td>User can define what alias will be used for custom list. In this example alias &quot;AliasName&quot; is specified</td>
</tr>
<tr>
<td><code>275</code>,</td>
<td>If subset was created by system using MDX expression, then MDX is placed here</td>
</tr>
<tr>
<td><code>281</code>,0</td>
<td>Is expanded above turned on or off</td>
</tr>
<tr>
<td><code>282</code>,</td>
<td>End of definition</td>
</tr>
</tbody>
</table>
<h4 id="useallelementsasdimensionslist">Use all elements as dimensions list</h4>
<p>When you press <code>All</code> button in Subset Editor Cognos uses special system <code>All</code> subset, that contains all elements of dimension.</p>
<table>
<thead>
<tr>
<th>Code</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>6</code>,ALL</td>
<td><code>ALL</code> is special system subset that contains all elements in any dimension.</td>
</tr>
<tr>
<td><code>274</code>,AliasName</td>
<td>For <code>ALL</code> subset user can define what alias will be used. In this example alias &quot;AliasName&quot; will be specified</td>
</tr>
<tr>
<td><code>281</code>,0</td>
<td>Is expanded above turned on or off</td>
</tr>
<tr>
<td><code>282</code>,</td>
<td>End of definition</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Definition of system lines in Cognos TM1 Subset files (.sub)]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In this article I will disclose information about how Cognos TM1 stores information about dimensions subsets in <code>.sub</code> files. This information can be usefull if you are Cognos TM1 developer and need to automatically change many subsets in some way, for example: turn on/off <code>expand above</code> in multiple subsets,</p>]]></description><link>https://it.meniko.ru/cognostm1_subset_files_code_definition/</link><guid isPermaLink="false">62d166ccd6badc95992a240b</guid><category><![CDATA[Cognos]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Tue, 30 Jul 2019 10:47:18 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2019/07/subWallpaper.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://it.meniko.ru/content/images/2019/07/subWallpaper.jpg" alt="Definition of system lines in Cognos TM1 Subset files (.sub)"><p>In this article I will disclose information about how Cognos TM1 stores information about dimensions subsets in <code>.sub</code> files. This information can be usefull if you are Cognos TM1 developer and need to automatically change many subsets in some way, for example: turn on/off <code>expand above</code> in multiple subsets, find what subsets of dimension have some element. Of cource there are a lot more ways how you can use this information.</p>
<blockquote>
<p>There are 2 more simular articles about Cognos TM1 system codes in files:</p>
<ol>
<li><a href="https://it.meniko.ru/cognostm1_views_files_code_definition/">Definition of system lines in Cognos TM1 Views files (.vue)</a>;</li>
<li><a href="https://it.meniko.ru/cognos-tm1-process-file-definition-of-system-codes/">Definition of system lines in Cognos TM1 Process files</a>.</li>
</ol>
</blockquote>
<p><img src="https://img.meniko.ru/2019/07/cognostm1_subset_files_code_definition/001.png" alt="Definition of system lines in Cognos TM1 Subset files (.sub)" loading="lazy"></p>
<p>Cognos TM1 subsets can be created in three different ways:</p>
<ul>
<li>as list of all elements;</li>
<li>as custom list of elements;</li>
<li>using MDX expression.</li>
</ul>
<p>Consequently files for these three types of subsets have some difference, thus I will divide description in three sections.</p>
<h3 id="allelements">All elements</h3>
<p><code>283</code>,100 &#x2013; encoding of file, <strong>100</strong> for utf8;<br>
<code>284</code>,&quot;SubD1&quot; &#x2013; name of subset;<br>
<code>11</code>,20190730094532 &#x2013; time of last edit in format (YYYYMMDDHHMMSS);<br>
<code>274</code>,AliasName &#x2014; name of alias that is used for this subset or <strong>empty</strong> if alias does not used;<br>
<code>18</code>,0 &#x2013; in my models it usually equals to 0 and I don&apos;t know it purpose;<br>
<code>275</code>, &#x2013; used when created with <strong>MDX</strong> or empty otherwise;<br>
<code>278</code>,1 &#x2013; <strong>1</strong> if subset contains <strong>all elements</strong> of dimension, <strong>0</strong> otherwise;<br>
<code>281</code>,0 &#x2013; <strong>1</strong> if subset is <em>expanded above</em>, <strong>0</strong> otherwise.</p>
<h3 id="customlistofelements">Custom list of elements</h3>
<p>Distinguished codes from &quot;<strong>All elements</strong>&quot; syntax: <code>281</code> and <code>270</code>.</p>
<p><code>283</code>,100 &#x2013; encoding of file, <strong>100</strong> for utf8;<br>
<code>284</code>,&quot;SubD1&quot; &#x2013; name of subset;<br>
<code>11</code>,20190730094532 &#x2013; time of last edit in format (YYYYMMDDHHMMSS);<br>
<code>274</code>,AliasName &#x2014; name of alias that is used for this subset or <strong>empty</strong> if alias does not used;<br>
<code>18</code>,0 &#x2013; in my models it usually equals to 0 and I don&apos;t know it purpose;<br>
<code>275</code>, &#x2013; used when created with <strong>MDX</strong> or empty otherwise;<br>
<code>278</code>,0 &#x2013; <strong>1</strong> if subset contains <strong>all elements</strong> of dimension, <strong>0</strong> otherwise;<br>
<code>281</code>,0 - <strong>1</strong> if subset is <em>expanded above</em>, <strong>0</strong> otherwise.<br>
<code>270</code>,4 - after this code list of custom chosen elements is located;<br>
Total<br>
D1.EL1<br>
D1.EL2<br>
D1.EL3</p>
<h3 id="createdwithmds">Created with MDS</h3>
<p>Distinguished codes from &quot;<strong>All elements</strong>&quot; syntax:  <code>275</code>, <code>281</code>, <code>270</code>.</p>
<p><code>283</code>,100 &#x2013; encoding of file, <strong>100</strong> for utf8;<br>
<code>284</code>,&quot;SubD1&quot; &#x2013; name of subset;<br>
<code>11</code>,20190730094532 &#x2013; time of last edit in format (YYYYMMDDHHMMSS);<br>
<code>274</code>,AliasName &#x2014; name of alias that is used for this subset or <strong>empty</strong> if alias does not used;<br>
<code>18</code>,0 &#x2013; in my models it usually equals to 0 and I don&apos;t know it purpose;<br>
<code>275</code>,14 &#x2013; If number is presented after this code it tells Cognos that MDX is used for creating this subset. 14 is length of MDX string.<br>
{[D1].members}<br>
<code>278</code>,0 &#x2013; <strong>1</strong> if subset contains <strong>all elements</strong> of dimension, <strong>0</strong> otherwise;<br>
<code>281</code>,0 &#x2013; <strong>1</strong> if subset is <em>expanded above</em>, <strong>0</strong> otherwise.<br>
<code>270</code>,0 &#x2013; as subset created as MDX this code have 0 custom choosen elements;</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Installation and using of Cognos BigFix and ILMT – tips, bugs and features]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In these article I would like to disclose some extra information about installation and work of Cognos BigFix and ILMT systems.</p>
<h2 id="problemwithobtainingevaluationlicensekey">Problem with obtaining evaluation license key</h2>
<p>While installing of BigFix Server you can encounter some problem with obtaining the key from IBM with error related to timeout: &quot;<strong>Unable</strong></p>]]></description><link>https://it.meniko.ru/it_bigfix_install_and_maintenanceuntitled/</link><guid isPermaLink="false">62d166ccd6badc95992a240a</guid><category><![CDATA[Cognos]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Wed, 17 Jul 2019 21:16:49 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2019/07/wallpaper-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://it.meniko.ru/content/images/2019/07/wallpaper-1.jpg" alt="Installation and using of Cognos BigFix and ILMT &#x2013; tips, bugs and features"><p>In these article I would like to disclose some extra information about installation and work of Cognos BigFix and ILMT systems.</p>
<h2 id="problemwithobtainingevaluationlicensekey">Problem with obtaining evaluation license key</h2>
<p>While installing of BigFix Server you can encounter some problem with obtaining the key from IBM with error related to timeout: &quot;<strong>Unable to submit license request HTTP: Error 28: Timeout was reached</strong>&quot;. I did not find straight way to fix it, but after a lot of tries I see success page.</p>
<p><img src="https://img.meniko.ru/2019/07/it_bigfix_install_and_maintenance/001.gif" alt="Installation and using of Cognos BigFix and ILMT &#x2013; tips, bugs and features" loading="lazy"></p>
<h2 id="howtoinstallilmt">How to install ILMT</h2>
<ol>
<li>Follow instructions on <a href="https://www.ibm.com/support/knowledgecenter/SS8JFY_9.2.0/com.ibm.lmt.doc/Inventory/planinconf/t_enabling_fixlet_internet_win.html">https://www.ibm.com/support/knowledgecenter/SS8JFY_9.2.0/com.ibm.lmt.doc/Inventory/planinconf/t_enabling_fixlet_internet_win.html</a>
<ol>
<li>Open BigFix Console, on the left bottom corner click BigFix Management</li>
<li>Choose License Overview in left top window</li>
<li>Read and accept license</li>
<li>Find &quot;IBM License Reporting&quot; in list and click &quot;Enable&quot;</li>
</ol>
</li>
<li>Then follow this instruction: <a href="https://www.ibm.com/support/knowledgecenter/en/SS8JFY_9.2.0/com.ibm.lmt.doc/Inventory/planinconf/t_installing_server_interactive_win.html">https://www.ibm.com/support/knowledgecenter/en/SS8JFY_9.2.0/com.ibm.lmt.doc/Inventory/planinconf/t_installing_server_interactive_win.html</a>
<ul>
<li><strong>If you don&apos;t see needed task in &quot;IBM License Reporting (ILMT)&quot; group &#x2013; click &quot;Refresh Console&quot;</strong></li>
<li><strong>Wait until BigFix downloades ILMT. You can view status on the summary tab of the task</strong></li>
</ul>
</li>
</ol>
<p><img src="https://img.meniko.ru/2019/07/it_bigfix_install_and_maintenance/002.png" alt="Installation and using of Cognos BigFix and ILMT &#x2013; tips, bugs and features" loading="lazy"></p>
<ol>
<li>When task finish executing you will find file In folder &quot;C:\Program Files (x86)\BigFix Enterprise\BES Installers\ILMT_installer&quot; find zip file, unarchive it and execute &quot;setup-server-windows-x86_64.bat&quot; with Admin rights;</li>
<li>When installation completes, choose &quot;Open browser and complete installation&quot;, follow installation steps.</li>
</ol>
<h2 id="setupilmt">Setup ILMT</h2>
<ul>
<li>Create new user in SQL server with sysadmin rights.</li>
</ul>
<p><img src="https://img.meniko.ru/2019/07/it_bigfix_install_and_maintenance/003.png" alt="Installation and using of Cognos BigFix and ILMT &#x2013; tips, bugs and features" loading="lazy"></p>
<ul>
<li>
<p>If you create new user that will connect to MSSQL, then check whevever auth with login and password is permitted in MS SQL Server. You can check this by connecting to MS Server with new login. If you see such error, you need to fix it.</p>
<pre><code class="language-sql">A connection was successfully established with the server, but then an error occurred
during the login process. (provider: Shared Memory Provider, error: 0 - No process is
on the other end of the pipe.)

(Microsoft SQL Server, Error: 233) 
</code></pre>
<p>Do following:</p>
<blockquote>
<p>To solve this, connect to SQL Management Studio using Windows Authentication, then right-click on server node &#x2192; &quot;Properties&quot; &#x2192; &quot;Security&quot; and enable SQL Server and Windows Authentication mode.</p>
</blockquote>
</li>
</ul>
<p>On the next step specify databases users (by default this will look like this, only change SQL user name.</p>
<p><img src="https://img.meniko.ru/2019/07/it_bigfix_install_and_maintenance/004.png" alt="Installation and using of Cognos BigFix and ILMT &#x2013; tips, bugs and features" loading="lazy"></p>
<h2 id="howtoinstallclientofbigfix">How to install client of BigFix</h2>
<ol>
<li>Copy folder &quot;<strong>C:\Program Files (x86)\BigFix Enterprise\BES Installers\Client</strong>&quot; from machine where BigFix server is installed to client (not only setup.exe, but also actionsite.afxm file)</li>
<li>Execute &quot;<strong>setup.exe</strong>&quot; to install BigFix client on client machine;</li>
<li>After some time (1-5 minutes), you will see new machine in BigFix Console.</li>
</ol>
<p><img src="https://img.meniko.ru/2019/07/it_bigfix_install_and_maintenance/005.png" alt="Installation and using of Cognos BigFix and ILMT &#x2013; tips, bugs and features" loading="lazy"></p>
<h2 id="howtoexecutemachinescannerforilmt">How to execute machine scanner for ILMT</h2>
<ol>
<li>Open &quot;<strong>Systems Lifecycle</strong>&quot; in BigFix Console</li>
<li>Execute these fixlets (to execute find them in &quot;<strong>Fixlets and Tasks</strong>&quot; list, choose one, press <strong>Take action</strong> and then <strong>OK</strong>). List of fixlets to complete machine scanning:</li>
</ol>
<ul>
<li>Install or Update Scanner (&#x43E;&#x448;&#x438;&#x431;&#x43A;&#x430; &#x43D;&#x430; &#x44D;&#x442;&#x43E;&#x43C; &#x44D;&#x442;&#x430;&#x43F;&#x435; &#x432; Windows 2008 R2)</li>
<li>Initiate Software Scan</li>
<li>Upload Software Scan Results</li>
<li>Run Capacity Scan and Upload Results</li>
</ul>
<h2 id="findnewmachineinibmlicensemetrictoolpage">Find new machine in IBM License Metric Tool page</h2>
<ol>
<li>Go to &quot;IBM License Metric Tool&quot; page (https://[name of machine]:9081/)</li>
<li>Open &quot;Management&quot; &#x2192; &quot;Data Imports&quot;</li>
<li>Press &quot;Import Now&quot;.</li>
</ol>
<h2 id="addinformationaboutvmstomakecalculationsofpvusmoreaccurate">Add information about VMs to make calculations of PVUs more accurate</h2>
<ol>
<li>Go to &quot;IBM License Metric Tool&quot; page (https://[name of machine]:9081/)</li>
<li>Open &quot;Management&quot; &#x2192; &quot;VM Managers&quot;</li>
<li>Add information about VM</li>
<li>Give BigFix time to gather information about VMs to update reports (in my work information updates after 12 AM of the next day).</li>
</ol>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Change server address in Cognos Analysis for Microsoft Excel (Cafe) files (manually and using NodeJS)]]></title><description><![CDATA[Instruction how to quickly and without pain change links to Cognos TM1 server in Cafe excel files using Sublime and script. ]]></description><link>https://it.meniko.ru/change_link_to_server_in_cafe_excel_files/</link><guid isPermaLink="false">62d166ccd6badc95992a2409</guid><category><![CDATA[Cognos]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Tue, 09 Jul 2019 14:04:52 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2019/07/wallpaper.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://it.meniko.ru/content/images/2019/07/wallpaper.jpg" alt="Change server address in Cognos Analysis for Microsoft Excel (Cafe) files (manually and using NodeJS)"><p>Case: You have a lot of excels created with Cafe tool. But at once you move your servers to another address or may be there is a need to create copy of excel to connect to dev/test server. Cognos TM1 does not give us easy way to do this and the most obvious way is to recreate these files. In this article I will show how to do it another (more geek) way.</p>
<p><img src="https://img.meniko.ru/2019/07/change_link_to_server_in_cafe_excel_files/002.jpg" alt="Change server address in Cognos Analysis for Microsoft Excel (Cafe) files (manually and using NodeJS)" loading="lazy"></p>
<p>First of all &#x2013; excel file is just a zip archive that contains a lot of different files inside. There are also files that are generated by Cafe extension, which one we will change to get the desired results.</p>
<p>I will show two ways how to do this: first one &#x2013; manually using text editor Sublime and second one using small NodeJS script.</p>
<h2 id="manualchangewithsublime3">Manual change with Sublime 3</h2>
<ol>
<li>Change extension of <code>.xlsx</code> or <code>.xls</code> or <code>.xlsm</code> file to <code>.zip</code>;</li>
<li>Unzip archive;</li>
<li>Open Sublime;</li>
<li>Open <strong>find and replace</strong> panel <code>Shift</code>+<code>Cmd</code>+<code>F</code> (on Mac OS) or <code>Shift</code>+<code>Ctrl</code>+<code>F</code> (on Windows);</li>
<li>In <code>Find</code> field insert <strong>current server</strong> name which is used in Excel file (for example <code>http://oldserver:9510</code>;</li>
<li>In <code>Where</code> field you need to insert path to folder which contains unzipped excel;</li>
<li>In <code>Replace</code> field insert new <strong>name of server</strong> (for example <code>http://newserver:9510</code>);</li>
<li>Press <code>Replace</code> button. Sublime will replace old server name to new one in all files;</li>
<li>Save and close all tabs that Sublime opens. These are files where Sublime finds the name of old server;</li>
<li>Create new <code>.zip</code> archive with all excels content. Make sure that <code>_rels</code>, <code>docProps</code>, <code>xl</code> and other files/folders are the top level objects in <code>.zip</code></li>
</ol>
<p><img src="https://img.meniko.ru/2019/07/change_link_to_server_in_cafe_excel_files/001.jpg" alt="Change server address in Cognos Analysis for Microsoft Excel (Cafe) files (manually and using NodeJS)" loading="lazy"></p>
<ol start="11">
<li>Rename back <code>.zip</code> to <code>.xlxs</code>, <code>.xls</code> or <code>.xlxm</code>.</li>
</ol>
<p>Thats all, now your new Excel is ready to be used with new server.</p>
<h2 id="automaticchangeusingnodejsscript">Automatic change using NodeJS script</h2>
<p>Also there are another way to do this changes. Here is the <strong>NodeJS async function</strong> that you can use to do all staff. Just call <code>cafeServerChanger</code> function with 3 parameters:</p>
<ol>
<li>Path to the excel file;</li>
<li>Previous server name (<code>http://oldserver:9510</code>);</li>
<li>Name of server that you would like to use in new file (<code>http://newserver:9510</code>)</li>
</ol>
<p>Do not forget to install 4 npm packages: <code>extract-zip</code>, <code>moment</code>, <code>archiver</code>, <code>rimraf</code> that are used in script.</p>
<pre><code class="language-javascript">const fs = require(&apos;fs&apos;);
const path = require(&apos;path&apos;);
const util = require(&apos;util&apos;)

const extract = require(&apos;extract-zip&apos;);
const moment = require(&apos;moment&apos;);
const archiver = require(&apos;archiver&apos;);
const rimraf = require(&quot;rimraf&quot;)

const walk = (dir, done) =&gt; {
  var results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      if (!file) return done(null, results);
      file = dir + &apos;/&apos; + file;
      fs.stat(file, function(err, stat) {
        if (stat &amp;&amp; stat.isDirectory()) {
          walk(file, function(err, res) {
            results = results.concat(res);
            next();
          });
        } else {
          results.push(file);
          next();
        }
      });
    })();
  });
}

const escapeRegExp = (string) =&gt; {
  return string.replace(/[.*+?^${}()|[\]\\]/g, &apos;\\$&amp;&apos;); // $&amp; means the whole matched string
}

const cafeServerChanger = async (file, serverFrom, serverTo) =&gt; {
  try {
    if(!fs.existsSync(file)) throw `Could not find file &apos;${file}&apos;`

    const fileParse = path.parse(file);
    
    const tempName = &apos;temp_&apos; + moment().format(&apos;YYYYMMDDHHmmssSSS&apos;) + &apos;_&apos; + Math.round((Math.random() * (10000 - 1) + 1));
    const tempFolder = path.join(fileParse.dir, tempName);
    fs.mkdirSync(tempFolder)

    // unzip excel to temp folder
    await new Promise((resolve, reject) =&gt; {
      extract(file, { dir: tempFolder }, function (err) {
        if(err) return reject(err);
        console.log(&apos;Unzip successfully&apos;);
        return resolve();
      })
    })
    
    // create bin buffer for server line
    const nullHEX = 0x00;
    let lineLetterBuffArray = [];
    for(let i = 0; i &lt; serverTo.length; i++){
      let l = serverTo[i];
      let buffer = Buffer.from([l.charCodeAt(0), nullHEX]);
      lineLetterBuffArray.push(buffer)
    }
    const finalBuffer = new Buffer.concat(lineLetterBuffArray);

    // replace all findings
    await new Promise((resolve, reject) =&gt; {
      let numberOfChanged = 0;
      walk(tempFolder, (err, zipFiles) =&gt; {
        if(err) return reject(err);
  
        for(let i = 0; i &lt; zipFiles.length; i++){
          let file = zipFiles[i];
          let fileParse = path.parse(file);
          let content = fs.readFileSync(file).toString()
          let regExp = new RegExp(serverFrom, &apos;ig&apos;);

          if(fileParse.base.match(/^customProperty\d+.bin$/)){
            content = content.replace(/\0/g, &apos;&apos;);
            if(content.match(regExp)){
              fs.writeFileSync(file, finalBuffer);
              numberOfChanged++;
            }
          } else {
            if(content.match(regExp)){
              content = content.replace(new RegExp(escapeRegExp(serverFrom), &apos;ig&apos;), serverTo)
              fs.writeFileSync(file, content);
              numberOfChanged++;
            }
          }
        }
        console.log(`Change ${numberOfChanged} files`);
        return resolve(zipFiles)
      });
    })
    
    // Zip to new file
    const filePathResult = path.join(fileParse.dir, `${fileParse.name}.changed${fileParse.ext}`);
    if(fs.existsSync(filePathResult)) fs.unlinkSync(filePathResult);

    console.log(`Start compression`)
    return await new Promise((resolve, reject) =&gt; {
      var output = fs.createWriteStream(filePathResult);
      var archive = archiver(&apos;zip&apos;, {
        zlib: { level: 5 } // Sets the compression level.
      });

      archive.on(&apos;warning&apos;, (err) =&gt; {
        if (err.code !== &apos;ENOENT&apos;){ 
          return reject(err);
        }
      });
      archive.on(&apos;error&apos;, (err) =&gt; reject(err));
      output.on(&apos;close&apos;, () =&gt; {
        console.log(archive.pointer() + &apos; total bytes&apos;);
        console.log(`New file is &apos;${filePathResult}&apos;`)
        console.log(`Delete temp folder`)
        rimraf.sync(tempFolder);
        return resolve(filePathResult);
      });

      output.on(&apos;end&apos;, () =&gt; console.log(&apos;Data has been drained&apos;));

      archive.directory(tempFolder, false);
      archive.pipe(output);
      archive.finalize();
    })
  } catch(e){
    return Promise.reject(e);
  }
}

module.exports = cafeServerChanger;
</code></pre>
<p>You can use and modify these code in any way. Good luck!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Compare folders states with Nodejs]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Case: You have large folder and some process that makes changes in it. As there are too many nested folders and files there, manual comparison can take too much time, so you would like to find quicker and easier way.</p>
<p>In this post I would like to share with you</p>]]></description><link>https://it.meniko.ru/compare-folders-state-with-nodejs/</link><guid isPermaLink="false">62d166ccd6badc95992a2408</guid><category><![CDATA[Hack]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Wed, 17 Apr 2019 12:53:17 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2019/04/wallpaper-2.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://it.meniko.ru/content/images/2019/04/wallpaper-2.jpg" alt="Compare folders states with Nodejs"><p>Case: You have large folder and some process that makes changes in it. As there are too many nested folders and files there, manual comparison can take too much time, so you would like to find quicker and easier way.</p>
<p>In this post I would like to share with you my light Nodejs script, that can snapshot folders state. So you can take one snapshot before and one after some action, compare them and find changed files.</p>
<pre><code class="language-javascript">const fs = require(&apos;fs&apos;);
const path = require(&apos;path&apos;);

var walk = function(dir, done) {
  var results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      if (!file) return done(null, results);
      file = dir + &apos;/&apos; + file;
      fs.stat(file, function(err, stat) {
        if (stat &amp;&amp; stat.isDirectory()) {
          walk(file, function(err, res) {
            results = results.concat(res);
            next();
          });
        } else {
          file = file.replace(folder, &apos;&apos;)
          results.push({file, changetime: stat.ctime, size: stat.size});
          next();
        }
      });
    })();
  });
}

if(typeof process.argv[2] == &apos;undefined&apos;){
  console.log(`Please provide path to director as the first argument, like:\n./script.js /some/path/`);
  process.exit()
}

const folder = process.argv[2];
if(!fs.existsSync(folder)){
  console.log(`File does not exists!`);
  process.exit()
}

const stats = fs.statSync(folder);
if(!stats.isDirectory()){
  console.log(`File is not directory`);
  process.exit()
}

const pathDesc = path.parse(folder);
const folderName = pathDesc.name;

walk(folder, function(err, results) {
  if (err) throw err;

  const fileName = `${folderName}.${Math.floor(Date.now()/1000)}.json`;
  fs.writeFileSync(`./${fileName}`, JSON.stringify(results, undefined, &apos;  &apos;));
  console.log(`Done. Write folder &apos;${folder}&apos; snapshot to file &apos;${fileName}&apos;`);
});
</code></pre>
<p>To execute this script provide path to folder as the first argument like this:</p>
<pre><code class="language-javascript">node script.js /way/to/folder
</code></pre>
<p>After this, script will recursivly walk through all nested folders and prepare list of files with information about there current size and time of last change.This information would be written to the file in JSON format.</p>
<pre><code class="language-json">[
  {
    &quot;file&quot;: &quot;/1 - create currency dimension.pro&quot;,
    &quot;changetime&quot;: &quot;2019-04-17T11:46:55.381Z&quot;,
    &quot;size&quot;: 1887
  },
  {
    &quot;file&quot;: &quot;/1 - create margin range dimension.pro&quot;,
    &quot;changetime&quot;: &quot;2019-04-17T11:46:55.397Z&quot;,
    &quot;size&quot;: 1899
  },...
]
</code></pre>
<p>Later you can compare two such files and find difference. Personally, for this purpose, I use <a href="https://packagecontrol.io/packages/DiffTabs">DiffTabs</a> package for sublime text editor:</p>
<p><img src="https://img.meniko.ru/2019/04/compare_folders_states_with_nodejs/001.png" alt="Compare folders states with Nodejs" loading="lazy"></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Return custom error message from attached process in Cognos TM1 Application]]></title><description><![CDATA[Instruction how to return custom error message to Cognos TM1 Application user from attached process]]></description><link>https://it.meniko.ru/cognos_return_error_message/</link><guid isPermaLink="false">62d166ccd6badc95992a2407</guid><category><![CDATA[Cognos]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Mon, 25 Mar 2019 13:18:57 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2019/03/wallpaper.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://it.meniko.ru/content/images/2019/03/wallpaper.jpg" alt="Return custom error message from attached process in Cognos TM1 Application"><p>TI process can be terminated using one of these functions:</p>
<ul>
<li><code>ProcessBreak;</code></li>
<li><code>ProcessError;</code></li>
<li><code>ProcessQuit;</code></li>
</ul>
<p>Two of them <code>ProcessError</code> and <code>ProcessQuit</code> will terminate process with error message, that will look like this:</p>
<p><img src="https://img.meniko.ru/2019/03/cognos_return_error_message/001.jpg" alt="Return custom error message from attached process in Cognos TM1 Application" loading="lazy"></p>
<p>As you can see, this message does not give any concrete information to the end user, especially if you have multiple conditions that can generate the error in TI code.</p>
<p>In this tutorial I will show you how to customize this message and show popup that looks like this:</p>
<p><img src="https://img.meniko.ru/2019/03/cognos_return_error_message/002.jpg" alt="Return custom error message from attached process in Cognos TM1 Application" loading="lazy"></p>
<p>First of all you need to <strong>create new eror code</strong> in <code>}tp_process_errors_localization</code> dimension. To do this use this TI template or add element to dimension manually:</p>
<pre><code>sErrorCode = &apos;CustomErrorCode&apos;;
sErrorCodeCaption = &apos;Custom Error Header&apos;;
sErrorType = &apos;Error&apos;;
cDim = &apos;}tp_process_errors_localization&apos;;

DimensionElementInsertDirect(cDim, &apos;&apos;, sErrorCode, &apos;N&apos;);
AttrPutS(&apos;en&apos;, cDim, sErrorCode, &apos;CaptionDefaultLocale&apos;);
AttrPutS(sErrorCodeCaption, cDim, sErrorCode, &apos;Caption&apos;);
AttrPutS(sErrorType, cDim, sErrorCode, &apos;ErrorType&apos;);
</code></pre>
<p>Change:</p>
<ul>
<li><code>sErrorCode</code> - principal name of the error code, that will be used later, when you will execute process to show message;</li>
<li><code>sErrorCodeCaption</code> - user friendly name of the message, that will be displayed at the popup before semicolon;</li>
<li><code>sErrorType</code> - could be &apos;error&apos; or &apos;warning&apos;. Choose any.</li>
</ul>
<p>After this step <strong>restart</strong> <code>Cognos Application</code> service, as sometimes it will not recognise new error codes and will show you message, that will contain only error code, without description:</p>
<p><img src="https://img.meniko.ru/2019/03/cognos_return_error_message/003.jpg" alt="Return custom error message from attached process in Cognos TM1 Application" loading="lazy"></p>
<p>Now you can <strong>modify process</strong> that is assigned to Applications <code>Commit</code> or <code>Submit</code> actions. Add this code before  <code>ProcessQuit</code> or <code>ProcessError</code> functions. Write your message to <code>pErrorDetails</code> parameter.</p>
<pre><code>ExecuteProcess(&apos;}tp_error_update_error_cube&apos;, 
  &apos;pGuid&apos;, pExecutionId, 
  &apos;pProcess&apos;, GetProcessName(),
  &apos;pErrorCode&apos;, &apos;CustomErrorCode&apos;,
  &apos;pErrorDetails&apos;, &apos;Custom message, that will help user to correct data&apos;,
  &apos;pControl&apos;, &apos;Y&apos;);
</code></pre>
<p><strong>Now Cognos must show you error message with yours description.</strong></p>
<p>If you need to delete your custom error code, delete it with <code>DimensionElementDeleteDirect</code> and restart <code>Cognos Application</code> service.</p>
<pre><code>sErrorCode = &apos;CustomErrorCode&apos;;
DimensionElementDeleteDirect(&apos;}tp_process_errors_localization&apos;, sErrorCode);
</code></pre>
<p>Also you can read about this Cognos feature in <a href="https://www.ibm.com/support/knowledgecenter/en/SS9RXT_10.2.2/com.ibm.swg.ba.cognos.prfmdl_ug.10.2.2.doc/t_tm1_apps_configuringatiworkflowaction.html">official IBM documentation</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Error in TM1 Applications: "View is corrupted or user doesn't have read permissions"]]></title><description><![CDATA[User could not access view in Cognos TM1 Applications after recreating of the cube.]]></description><link>https://it.meniko.ru/clear-applications-blbs/</link><guid isPermaLink="false">62d166ccd6badc95992a2406</guid><category><![CDATA[Cognos]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Tue, 19 Mar 2019 12:58:00 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2019/03/1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h3 id="case">Case</h3>
<img src="https://it.meniko.ru/content/images/2019/03/1.jpg" alt="Error in TM1 Applications: &quot;View is corrupted or user doesn&apos;t have read permissions&quot;"><p>You have some Cognos TM1 Application that works well. But for some reason you had need to recreate some cube (for example to delete or add a dimension) that has view in this Application. You did all work, republish Application with the new view (or do nothing, as Cognos by default can see it and add automatically). Later when you try to access this view in Cognos Application you see such error message:</p>
<p><img src="https://img.meniko.ru/2019/03/view_is_corrupted_or_user_doesnt_have_read_permissions/2.png" alt="Error in TM1 Applications: &quot;View is corrupted or user doesn&apos;t have read permissions&quot;" loading="lazy"></p>
<p>Now you are confused and don&apos;t know how to fix this!</p>
<h3 id="reason">Reason</h3>
<p>This error could have many reasons, for example:</p>
<ul>
<li>Corrupted user view</li>
<li>Wrong security settings</li>
<li>Not deleted <code>.blb</code> files. <em>In this post I will write about this one</em>.</li>
</ul>
<p>Such <code>.blb</code> files has following syntax and are located in root of data folder:</p>
<pre><code class="language-tm1">CubeName.ViewName_ApplicationID_\[}tp_tasks}ApplicationID].\[HierarchyLevel].CAMID(CamidOfUser).blb
</code></pre>
<p>Where <code>CubeName</code>, <code>ViewName</code>, <code>ApplicationID</code>, <code>HierarchyLevel</code>, <code>CamidOfUser</code> are variables.</p>
<p>Cognos creates them when user <em>opens some application, make changes in the view and closes the window</em>. Unlike other <code>.blb</code> files, these Cognos does not delete by default (this is correct for Cognos TM1 10.2 Pack 6) when you destroy cube.</p>
<p>So to fix the problem you need to find all wrong <code>.blb</code> files that are connected with certain cube, view and application and delete them.</p>
<h3 id="removefileswithpowershell">Remove files with Powershell</h3>
<p>I prefer to do this with Powershell:</p>
<ol>
<li>Move to data folder (change <code>dataFilesFolder</code> with yours):</li>
</ol>
<pre><code>cd datafilesFolder
</code></pre>
<ol start="2">
<li>Allocate all .blb files that are connected with some cube and view (change <code>CubeName</code> and <code>ViewName</code> with yours) in the folder. Important: <code>_</code> is needed to exclude .blb file for cubes view (they are alright).</li>
</ol>
<pre><code>Get-ChildItem &apos;CubeName.ViewName_*.blb&apos;
</code></pre>
<ol start="3">
<li>When you make sure, that listed files is what you was looking for, you can delete them. To do that &#x2013; add code <code>-File | Foreach-Object {Remove-Item -LiteralPath $_.FullName}</code> to previous command and execute it.</li>
</ol>
<pre><code>Get-ChildItem &apos;CubeName.ViewName_*.blb&apos; -File | Foreach-Object {Remove-Item -LiteralPath $_.FullName}
</code></pre>
<p>Thats all. This should solve the problem, and you should have access to view in Application. If not try to log off from Cognos Tm1 Applications.</p>
<p>If you still does not have access &#x2013; chech security cubes, may be you forget to grant some access to user.</p>
<p><strong>I hope this helps to solve your problem!</strong></p>
<!--Sometimes it can happened that you will need timeout to solve the problem--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[IBM Cognos TM1 code completion for Sublime Text editor]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>If you tied from absence of code completion for Cognos TM1, I have good news for you. Today I publish <a href="https://github.com/menikoDev/TM1SublimeCompletion">TM1SublimeCompletion</a> package for Sublime 3 that can solve this problem.</p>
<h2 id="description">Description</h2>
<p>Package contains completions for all TI, Rules, Worksheet and MDX function, global and local variables of IBM Cognos TM1</p>]]></description><link>https://it.meniko.ru/menikodev_tm1sublimecompletion/</link><guid isPermaLink="false">62d166ccd6badc95992a23fe</guid><category><![CDATA[Cognos]]></category><dc:creator><![CDATA[vladimir meniko]]></dc:creator><pubDate>Mon, 25 Feb 2019 00:01:00 GMT</pubDate><media:content url="https://it.meniko.ru/content/images/2018/12/tm1completer.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://it.meniko.ru/content/images/2018/12/tm1completer.jpg" alt="IBM Cognos TM1 code completion for Sublime Text editor"><p>If you tied from absence of code completion for Cognos TM1, I have good news for you. Today I publish <a href="https://github.com/menikoDev/TM1SublimeCompletion">TM1SublimeCompletion</a> package for Sublime 3 that can solve this problem.</p>
<h2 id="description">Description</h2>
<p>Package contains completions for all TI, Rules, Worksheet and MDX function, global and local variables of IBM Cognos TM1 system. Here are the main features of package:</p>
<ul>
<li>Covers TM1 versions from 10.1.0 up to 10.3.0 (Cognos Analitycs);</li>
<li>If function is valid in multiple places (for example in TI and Rule), but has different syntax, package will show you both variances.</li>
</ul>
<p><img src="https://it.meniko.ru/content/images/2018/10/Screenshot-2018-10-24-at-02.29.29.png" alt="IBM Cognos TM1 code completion for Sublime Text editor" loading="lazy"></p>
<p><em>This is just first version of it, so if you will find some mistakes &#x2013; please let me know.</em></p>
<h2 id="installation">Installation</h2>
<p>To install packages:</p>
<ol>
<li>Install some package for highlighting Cognos TM1 code in Sublime, I prefer to use these one <a href="https://github.com/hermie64/tm1-sublime">hermie64/tm1-sublime</a></li>
<li>Download Tm1SublimeCodeCompletion.sublime-completions file from git repository and put it in any place in package folder of Sublime</li>
<li>You are ready, have fun!</li>
</ol>
<p>More information you can find on <a href="https://github.com/menikoDev/TM1SublimeCompletion">GitHub page of this project</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>