All posts by Roman

Convert 2FAuth TOTP codes to Bitwarden Authenticator

  1. Select codes in 2FAuth and export them as a list of otpauth URIs.
  2. Save the Python script below as convert-2fauth-to-bitwarden.py
  3. Run the command below to create Bitwarden Authenticator JSON export file.
python3 convert-2fauth-to-bitwarden.py -o bitwarden_export.json 2fauth_export_otpauth.txt
#!/usr/bin/env python3
import json
import uuid
import argparse

def parse_otpauth_uri(uri):
    parts = uri.split('?')
    label = parts[0].split('/')[-1]
    service, account = label.split(':', 1) if ':' in label else (label, '')
    return {'service': service, 'account': account}

def convert_otpauth_to_bitwarden(input_file, output_file):
    with open(input_file, 'r', encoding='utf-8') as f:
        uris = [line.strip() for line in f if line.strip().startswith('otpauth://')]
    
    items = []
    for uri in uris:
        parsed = parse_otpauth_uri(uri)
        bw_item = {
            "id": str(uuid.uuid4()).upper(),
            "type": 1,
            "name": f"{parsed['service']}",
            "favorite": False,
            "login": {
                "username": parsed['account'],
                "totp": uri,
            }
        }
        items.append(bw_item)
    
    output = {"encrypted": False, "items": items}
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(output, f, indent=2, ensure_ascii=False)
    
    print(f"Converted {len(items)} items to {output_file}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Convert otpauth URIs → Bitwarden JSON")
    parser.add_argument("input", help="File with otpauth URIs")
    parser.add_argument("-o", "--output", default="bitwarden_export.json")
    args = parser.parse_args()
    convert_otpauth_to_bitwarden(args.input, args.output)

curl

# Headers only using GET
curl -s -D - -o /dev/null --url https://www.romanstefko.com/

# HEAD request
curl -I https://www.romanstefko.com/

# Test with local IP address
curl --resolve 'www.romanstefko.com:443:192.168.1.10' -s -D - -o /dev/null --url https://www.romanstefko.com/

# HTTP Proxy
curl -x http://proxy:3128 https://www.romanstefko.com

List active user T-SQL connections

SELECT
    DB_NAME(dbid) as DBName,
    COUNT(dbid) as NumberOfConnections,
    loginame as LoginName
FROM
    sys.sysprocesses
WHERE
    dbid > 0
    AND DB_NAME(dbid) = 'mydb'
GROUP BY
    dbid, loginame

mysql

Backup

mysqldump -u root dbname > backup.sql

Restore

mysql -u root dbname < backup.sql

Commands

# Create database
CREATE DATABASE dbname;

# Create user
CREATE USER 'user'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON dbname.* TO 'user'@'localhost';

dd

Backup / Clone

# clone disk
dd if=/dev/sdX of=/dev/sdY bs=64K conv=noerror,sync status=progress

# backup to file using gzip compression
dd if=/dev/sdX bs=64K conv=noerror,sync status=progress | gzip -c  > /PATH/TO/DRIVE/backup_image.img.gz

# restore from file
gunzip -c /PATH/TO/DRIVE/backup_image.img.gz | dd of=/dev/sdX status=progress

# restore from file without gzip
dd if=/PATH/TO/DRIVE/backup_image.img of=/dev/sdX status=progress

# clone only to the end of last partition
SECTOR_SIZE=$(blockdev --getss /dev/sdX)
# LAST_END=start + size - 1
sfdisk -d /dev/sdX
# parameters for dd
bs="$SECTOR_SIZE" count="$LAST_END"

Mount Image

# Scan
losetup --partscan --find --show backup_image.img

# Free-up
losetup -d /dev/loop0

Nginx

Configuration

# HTTP to HTTPS
if ($scheme = http) {
    return 301 https://$host$request_uri;
}

# Proxy
location / {
    include /etc/nginx/proxy_params;
    proxy_pass http://127.0.0.1:5000/;
}

# Custom robots.txt
location = /robots.txt {
    add_header Content-Type text/plain;
    return 200 "User-agent: *\nDisallow: /\n";
}

# Activate HTTP2 (1.9.5+)
listen 443 ssl http2;

Rate limiting

# Use $http_cf_connecting_ip instead of $binary_remote_addr when behind Cloudflare
limit_req_zone $http_cf_connecting_ip zone=php_limit:10m rate=10r/s;
limit_req_log_level warn;

location ~ \.php$ {
    limit_req zone=php_limit burst=50;
}

# Test using bash
for i in $(seq 1 30); do curl -I -s "https://[HOST]/" | head -n 1; done

Real IP

To get current IPv4 Cloudflare ranges see the official list.

# Get Real IP from Cloudflare
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;

AutoMySQLBackup

Configuration

```
# Set rotation of daily backups. VALUE*24hours
# If you want to keep only today's backups, you could choose 1, i.e. everything older than 24hours will be removed.
CONFIG_rotation_daily=6

# Set rotation for weekly backups. VALUE*24hours
CONFIG_rotation_weekly=30

# Set rotation for monthly backups. VALUE*24hours
CONFIG_rotation_monthly=90
```