📜 ⬆️ ⬇️

Installing FreeBSD 9.0 on a Hetzner server

Introduction


When FreeBSD 9.0 came out, I set out to install this system on the server that I just installed at Hetzner. Although they do not openly propose and do not allow to install FreeBSD, however, it is quite possible to do this through the Rescue System . But, as it turned out, you can install only FreeBSD 7.4 and FreeBSD 8.2.

After communicating with Hetzner support service, they replied that they were ready to insert a disc with this software for an additional fee. Of course, this was not part of the budget.

There are many tips on upgrading FreeBSD from version 8 to version 9 on the Internet, but they either do not fully reflect the essence of the problem, or do not allow you to upgrade anything at all. I spent a lot of time before I managed to do it, so I decided to write this article, which will help save time.
')
So let's get started.

1. Installing the FreeBSD version from Henzner


Start Rescue System

First you need to activate the Rescue System and log in via ssh to the server, as written in the instructions from Hetzner.

To start the installation, run the following command.

# installimage 


Then we specify the parameters and run the installation. The whole installation takes only a few minutes, as a result we get

 Hetzner Online AG - installimage Your server will be installed now, this will take some time You can abort at any time with CTRL+C … Deleting selected harddrive(s)… DONE Creating a file system and extracting the image… DONE Copying kernel… DONE Configuring rc.conf… DONE Configuring loader.conf… DONE Enabling SSH login for root… DONE Configuring resolv.conf… DONE Congratulations! The configuration has been completed successfully. You can now use 'reboot' to boot your newly installed FreeBSD system. 


After installation we reboot

 # reboot 


2 Setting data for update

We will update the system through sup. In my opinion, the easiest and correct way to upgrade the system.
sup is included in the regular FreeBSD operating system tools.

Go to the directory

 # cd /usr/share/examples/cvsup/ 


Check

 # ls README gnats-supfile refuse.README www-supfile cvs-supfile ports-supfile stable-supfile doc-supfile refuse standard-supfile 


Now you need to specify a new version of the operating system.
The default is the current version.

We copy the configuration file

 # cp standard-supfile standard-supfile-UPDATE_9 


Edit it

 # ee standard-supfile-UPDATE_9 *default host=CHANGE_THIS.FreeBSD.org *default base=/var/db *default prefix=/usr *default release=cvs tag=RELENG_9_0 *default delete use-rel-suffix *default compress src-all 


We synchronize the source code of the src, the new operating system

 # /usr/bin/csup -h cvsup4.ru.freebsd.org -g -L 2 \ 

 #? /usr/share/examples/cvsup/standard-supfile-UPDATE_9 


After all the files are copied, you need to read the file / usr / src / UPDATING (To avoid problems)

Now you need to enable the generation of the profiling code (set NO_PROFILE = true in the /etc/make.conf file).

And there you need to set the value of CFLAGS to -O2 -pipe.

-pipe allows the compiler to use soft channels instead of temporary files for communication,
which reduces disk access (at the expense of RAM).

 # ee /etc/make.conf NO_PROFILE=true CFLAGS= -O2 -pipe 


You also need to add the line to make.conf: KERNCONF = MYKERNEL, so as not to write when building and installing the kernel

 # make build kernel KERNCONF=MYKERNEL 


but just

 # make build kernel 


Edit the /etc/src.conf file

 # ee /etc/src.conf WITHOUT_BLUETOOTH=TRUE WITHOUT_GAMES=TRUE 


In general, the make.conf config can be supplemented with many parameters, it depends on what compilation parameters you need.

My config make.conf


 CPUTYPE?=nocona CFLAGS= -O2 -fno-strict-aliasing -pipe # CXXFLAGS= -O2 -pipe #         . COPTFLAGS= -O2 -pipe -funroll-loops -ffast-math -fno-strict-aliasing OPTIMIZED_CFLAGS=YES KERNCONF=GARPIX BUILD_OPTIMIZED=YES WITH_CPUFLAGS=YES WITHOUT_DEBUG=YES WITH_OPTIMIZED_CFLAGS=YES NO_PROFILE=YES BUILD_STATIC=YES WITHOUT_X11=YES DWITH_SLANG=YES DLLVM_HOSTTRIPLE="x86_64-garpix-freebsd9.0" STRIP= CFLAGS+=-fno-omit-frame-pointer WITH_CTF=1 # added by use.perl 2012-02-07 19:02:35 PERL_VERSION=5.12.4 



Get the kernel sources

 # svn checkout svn://svn.freebsd.org/base/releng/9.0 /usr/src 


Go to the directory with the kernel

 # cd /usr/src/sys/amd64/conf 


We copy a config with a kernel in the directory created for earlier

 # mkdir /root/kernels # cp GENERIC /root/kernels/MYKERNEL # ln -s /root/kernels/MYKERNEL 


Now you need to configure the kernel for your computer.
Read more about this on the official FreeBSD website.

My kernel config


 cpu HAMMER ident MYKERNEL makeoptions DEBUG=-g # Build kernel with gdb(1) debug symbols makeoptions WITH_CTF=1 makeoptions MODULES_OVERRIDE="zfs opensolaris dtrace cyclic nfsclient krpc nfs_common nfslock" options SCHED_ULE # ULE scheduler options PREEMPTION # Enable kernel thread preemption options INET # InterNETworking options INET6 # IPv6 communications protocols options IPFIREWALL # Firewall IPFw #options IPFIREWALL_DEFAULT_TO_ACCEPT # Accept to Firewall options IPFIREWALL_VERBOSE # Enable Logs from Firewall #options IPFIREWALL_VERBOSE_LIMIT=10 # Limit logs from Firewall options DUMMYNET # Speed Traffic to Firewall options SCTP # Stream Control Transmission Protocol options FFS # Berkeley Fast Filesystem options SOFTUPDATES # Enable FFS soft updates support options UFS_ACL # Support for access control lists options UFS_DIRHASH # Improve performance on big directories options UFS_GJOURNAL # Enable gjournal-based UFS journaling options MD_ROOT # MD is a potential root device options NFSCL # New Network Filesystem Client options NFSD # New Network Filesystem Server options NFSLOCKD # Network Lock Manager options NFS_ROOT # NFS usable as /, requires NFSCL options CD9660 # ISO 9660 Filesystem options PROCFS # Process filesystem (requires PSEUDOFS) options PSEUDOFS # Pseudo-filesystem framework options GEOM_PART_GPT # GUID Partition Tables. options GEOM_LABEL # Provides labelization options SCSI_DELAY=5000 # Delay (in ms) before probing SCSI options KTRACE # ktrace(1) support options STACK # stack(9) support options SYSVSHM # SYSV-style shared memory options SYSVMSG # SYSV-style message queues options SYSVSEM # SYSV-style semaphores options _KPOSIX_PRIORITY_SCHEDULING # POSIX P1003_1B real-time extensions options PRINTF_BUFR_SIZE=128 # Prevent printf output being interspersed. options KBD_INSTALL_CDEV # install a CDEV entry in /dev options HWPMC_HOOKS # Necessary kernel hooks for hwpmc options AUDIT # Security event auditing options MAC # TrustedBSD MAC Framework options KDTRACE_FRAME # Ensure frames are compiled in options KDTRACE_HOOKS # Kernel DTrace hooks options DDB_CTF # all architectures - kernel ELF linker loads CTF data options INCLUDE_CONFIG_FILE # Include this file in kernel options KDB # Kernel debugger related code options KDB_TRACE # Print a stack trace for a panic options COMPAT_FREEBSD8 # Compatible with FreeBSD8 options P1003_1B_SEMAPHORES # POSIX-style semaphores options DEVICE_POLLING # Polling Network Enabled # Settings for max connections = 256options SEMMNI=512 options SEMMNS=1024 options SEMUME=64 options SEMMNU=512 # Make an SMP-capable kernel by default options SMP # Symmetric MultiProcessor Kernel # CPU frequency control device cpufreq # Bus support.device acpi device pci # ATA controllers device ahci # AHCI-compatible SATA controllers device ata # Legacy ATA/SATA controllers options ATA_CAM # Handle legacy controllers with CAM options ATA_STATIC_ID # Static device numbering device mvs # Marvell 88SX50XX/88SX60XX/88SX70XX/SoC SATA device siis # SiliconImage SiI3124/SiI3132/SiI3531 SATA # ATA/SCSI peripherals device scbus # SCSI bus (required for ATA/SCSI) device ch # SCSI media changers device da # Direct Access (disks) device sa # Sequential Access (tape etc) device cd # CD device pass # Passthrough device (direct ATA/SCSI access) device ses # SCSI Environmental Services (and SAF-TE) # RAID controllers interfaced to the SCSI subsystem device hptrr # Highpoint RocketRAID 17xx, 22xx, 23xx, 25xx # atkbdc0 controls both the keyboard and the PS/2 mouse device atkbdc # AT keyboard controller device atkbd # AT keyboard device psm # PS/2 mouse device via # VGA video card driver # sys cons is the default console driver, resembling an SCO console device sc options SC_PIXEL_MODE # add support for the raster text mode # Serial (COM) ports device uart # Generic UART driver device put # Multi I/O cards and multi-channel UARTs # PCI Ethernet NICs. device bxe # Broadcom BCM57710/BCM57711/BCM57711E 10Gb Ethernet> device de # DEC/Intel DC21x4x (``Tulip)> device em # Intel PRO/1000 Gigabit Ethernet Family> device igb # Intel PRO/1000 PCIE Server Gigabit Family> device ixgbe # Intel PRO/10GbE PCIE Ethernet Family> device le # AMD Am7900 LANCE and Am79C9xx PCnet> device ti # Alteon Networks Tigon I/II gigabit Ethernet> device txp # 3Com 3cR990 (``Typhoon)> device vx # 3Com 3c590, 3c595 (``Vortex)> # PCI Ethernet NICs that use the common MII bus controller code.> # NOTE: Be sure to keep the 'device minibus' line in order to use these NICs!> device minibus # MII bus support> device ae # Attansic/Atheros L2 FastEthernet> device age # Attansic/Atheros L1 Gigabit Ethernet> device alc # Atheros AR8131/AR8132 Ethernet> device ale # Atheros AR8121/AR8113/AR8114 Ethernet> device bce # Broadcom BCM5706/BCM5708 Gigabit Ethernet> device bfe # Broadcom BCM440x 10/100 Ethernet> device bge # Broadcom BCM570xx Gigabit Ethernet> device dc # DEC/Intel 21143 and various workalikes> device et # Agere ET1310 10/100/Gigabit Ethernet> device fxp # Intel EtherExpress PRO/100B (82557, 82558)> device jme # JMicron JMC250 Gigabit/JMC260 Fast Ethernet> device leg # Level 1 LXT1001 gigabit Ethernet> device msk # Marvell/SysKonnect Yukon II Gigabit Ethernet> device nfe # nVidia nForce MCP on-board Ethernet> device nge # NatSemi DP83820 gigabit Ethernet> device pcn # AMD Am79C97x PCI 10/100 (precedence over 'le')> device re # RealTek 8139C+/8169/8169S/8110S> device rl # RealTek 8129/8139> device sf # Adaptec AIC-6915 (``Starfire)> device sge # Silicon Integrated Systems SiS190/191> device sis # Silicon Integrated Systems SiS 900/SiS 7016> device sk # SysKonnect SK-984x & SK-982x gigabit Ethernet> device ste # Sundance ST201 (D-Link DFE-550TX)> device stge # Sundance/Tamarack TC9021 gigabit Ethernet> device tl # Texas Instruments ThunderLAN> device tx # SMC EtherPower II (83c170 ``EPIC)> device vge # VIA VT612x gigabit Ethernet> device vr # VIA Rhine, Rhine II> device wb # Winbond W89C840F> device xl # 3Com 3c90x (``Boomerang, ``Cyclone)> # Pseudo devices. device loop # Network loopback device random # Entropy device device ether # Ethernet support device vlan # 802.1Q VLAN support device tun # Packet tunnel. device pty # BSD-style compatibility pseudo ttys device md # Memory "disks" device gif # IPv6 and IPv4 tunneling device faith # IPv6-to-IPv4 relaying (translation) device firmware # firmware assist module # The `bpf' device enables the Berkeley Packet Filter. # Be aware of the administrative consequences of enabling this! # Note that 'bpf' is required for DHCP. device bpf # Berkeley packet filter # USB support options USB_DEBUG # enable debug msgs device uhci # UHCI PCI->USB interface device ohci # OHCI PCI->USB interface device ehci # EHCI PCI->USB interface (USB 2.0) device xhci # XHCI PCI->USB interface (USB 3.0) device usb # USB Bus (required) device uhid # "Human Interface Devices" device ukbd # Keyboard device ulpt # Printer device umass # Disks/Mass storage - Requires scbus and da device ums # Mouse 



After the kernel is configured, go to the directory with the source / usr / src

 # cd /usr/src 


I almost forgot, you need to add the following option to your kernel config

 options COMPAT_FREEBSD8 # Compatible with FreeBSD8 


Now edit the following files by adding lines.

 # ee /usr/src/sys/conf/NOTES # Enable FreeBSD8 compatibility syscalls options COMPAT_FREEBSD8 # ee /usr/src/sys/conf/options COMPAT_FREEBSD8 opt_compat.h 


Go to the compilation of the world and the kernel

 # cd /usr/src # make kernel-toolchain 


wait until the process is over

Now compile the kernel

 # make build kernel 


When the kernel is compiled, install it.

 # make install kernel 


Now you need to compile the world

 # make build world 


The world compiles for a very long time, you have to wait

We look at the output date and if the output is incorrect, then you must read the article

Now do the following

 # adjkerntz -i # cd /usr/src 


Installing configuration files

 # mergemaster -a -p 


If you have questions, you should always press i

 i  [Enter] 


Sometimes you need to press d if there are no other options.

 d  [Enter] 


i - Install new configuration files
d - Delete temporary configuration files

Now delete the rc.d directory

 # rm -rf /etc/rc.d 


We continue to install configs

 # mergemaster -i -p # mergemaster -p 


Set the world

 # make install world 


Once the world is established, copy the world configs

 # mergemaster -a # mergemaster -i # mergemaster 


You need to perform in turn, without forgetting the parameters i and d, there will be many questions.

That's all, the new system is installed, but you cannot restart the computer yet

Change password at root user

 # passwd 


Now you need to configure SSH and then the system will not enter

 # ee /etc/ssh/sshd_config 


 PermitRootLogin yes 


Check whether everything fell as it should


What sense are the demons running? Read /etc/rc.conf, is there any service that can be started in alternative ways, you can find out by looking

 # sockstat -l 


Also in /etc/rc.conf, network settings

 # less, tail, cat -  ,   . 


Content /var/run/dmesg.boot will tell all about the hardware used

 # pciconf -lv 


Show all hardware, both used and unused

 # atacontrol list 


Will display a list of ATA devices

 # camcontrol devlist 


Will display a list of USB devices

 # usbconfig list 


Now set the time zone

 # cp /usr/share/zoneinfo/Europe/Moscow /etc/localtime # date 


or

 # setenv TZ Europe/Moscow-04:00 # date 


Locale setting


By default, the koi8-r encoding is installed on FreeBSD (in my opinion, this is the last century, changing to utf-8)

 #ee /etc/login.conf 


 russian|Russian Users Accounts:\ :charset=UTF-8:\ :lang=ru_RU.UTF-8:\ :tc=default: 


 # cap_mkdb /etc/login.conf # pw usermod -n $username -L russian 


In addition to the above method, you can write variables in the shell used

 #ee /etc/csh.cshrc 


 setenv LANG ru_RU.UTF-8 setenv LC_CTYPE ru_RU.UTF-8 setenv LC_COLLATE POSIX setenv LC_ALL ru_RU.UTF-8 


Optionally, for those who love bash

 # ee /etc/profile 


 LANG="ru_RU.UTF-8"; export LANG LC_CTYPE="ru_RU.UTF-8"; export LC_CTYPE LC_COLLATE="POSIX"; export LC_COLLATE LC_ALL="ru_RU.UTF-8"; export LC_ALL 


 # ee /root/.cshrc 


 setenv LANG ru_RU.UTF-8 setenv LC_CTYPE ru_RU.UTF-8 setenv LC_COLLATE POSIX setenv LC_ALL ru_RU.UTF-8 


Next, just type in the console

 # setenv LANG ru_RU.UTF-8 # setenv LC_CTYPE ru_RU.UTF-8 # setenv LC_COLLATE POSIX # setenv LC_ALL ru_RU.UTF-8 


Register settings in sysctl

 # ee /etc/sysctl.conf 


Attention data settings for my server, they depend on many parameters, computer hardware, the amount of RAM, type of processor, server load, and so on.


 # $FreeBSD: src/etc/sysctl.conf,v 1.8.40.1.2.1 2011/11/11 04:20:22 kensmith Exp $ # # This file is read when going to multi-user and its contents piped thru # ``sysctl to adjust kernel values. ``man 5 sysctl.conf for details. # # Uncomment this to prevent users from seeing information about processes that # are being run under another UID. #security.bsd.see_other_uids=0 kern.ipc.shmall=2048000 kern.ipc.shmmax=4461780992 kern.ipc.semmap=16000 net.inet.tcp.blackhole=2 net.inet.udp.blackhole=1 # kern.ipc.nmbclusters=65536 kern.ipc.somaxconn=32768 kern.ipc.maxsockets=204800 kern.ipc.maxsockbuf=262144 kern.ipc.shm_use_phys=1 kern.maxfiles=256000 kern.maxfilesperproc=230400 net.inet.ip.portrange.first=1024 net.inet.ip.portrange.last=65535 net.inet.ip.portrange.randomized=0 net.inet.tcp.maxtcptw=40960 net.inet.ip.intr_queue_maxlen=4096 net.inet.tcp.msl=40000 net.inet.tcp.finwait2_timeout=40000 net.inet.tcp.syncookies=1 # In Large Load sent ansver selective net.inet.tcp.delayed_ack=0 net.inet.tcp.sack.enable=0 net.inet.tcp.msl=30000 net.inet.tcp.nolocaltimewait=1 net.inet.tcp.fast_finwait2_recycle=1 net.inet.icmp.drop_redirect=1 net.inet.icmp.log_redirect=1 net.inet.ip.redirect=0 net.inet.ip.fw.enable=1 net.inet.flowtable.enable=0 net.inet6.ip6.fw.enable=1 net.inet6.ip6.redirect=0 net.inet6.ip6.accept_rtadv=1 net.inet.tcp.sendspace=16384 net.inet.tcp.recvspace=8192 net.inet.tcp.recvbuf_auto=0 net.inet.tcp.sendbuf_auto=1 net.inet.tcp.sendbuf_inc=8192 net.inet.tcp.sendbuf_max=131072 net.link.ether.inet.max_age=1200 net.inet.tcp.maxtcptw=102400 net.inet.tcp.nolocaltimewait=1 net.inet.tcp.syncookies=1 net.isr.direct=0 kern.polling.enable=1 kern.polling.burst_max=1000 kern.polling.each_burst=50 net.inet.icmp.icmplim=100 net.inet.tcp.rfc3465=0 # Security Jail security.jail.sysvipc_allowed=1 security.jail.allow_raw_sockets=1 security.jail.chflags_allowed=1 security.jail.set_hostname_allowed=1 


We configure bootLoader (I give an example of the config, I will repeat it for my server and I give for the sake of an example)

 # ee /boot/loader.conf 




 zfs_load="YES" vfs.root.mountfrom="zfs:garpix/root" vfs.zfs.zio.use_uma="0" ahci_load="YES" aio_load="YES" geom_mirror_load="YES" geom_stripe_load="YES" geom_cache_load="YES" geom_label_load="YES" accf_http_load="YES" accf_data_load="YES" accf_dns_load="YES" coretemp_load="YES" if_em_load="YES" verbose_loading="YES" boot_verbose="YES" vm.kmem_size_max="4G" vm.kmem_size="4G" # HPET Enable kern.timecounter.hardware=HPET # Dtrace dtraceall_load="YES" # Semafores kern.ipc.semmni=1024 kern.ipc.semmns=2048 kern.ipc.semmnu=1024 kern.maxfiles=204800 kern.maxfilesperproc=200000 kern.ipc.maxsockets=204800 kern.ipc.nmbclusters=204800 kern.ipc.nmbjumbop=192000 kern.ipc.somaxconn=8192 kern.ipc.shmmax=2147483648 kern.ipc.maxsockbuf=10485760 kern.ipc.nsfbufs=10240 kern.maxvnodes=200000 kern.ps_arg_cache_limit=4096 # TCP Steck #net.inet.tcp.syncache.hashsize=1024 #net.inet.tcp.syncache.bucketlimit=100 #net.inet.tcp.syncache.cachelimit=65536 #net.inet.tcp.hostcache.cachelimit=1966080 #net.inet.tcp.tcbhashsize=32768 vm.pmap.shpgperproc=2048 vm.pmap.pg_ps_enabled=1 hw.bge.allow_asf=1 loader_logo="MYLOGO" 



RC.CONF settings

 # ee /etc/rc.conf 


I give for example my rc.conf config (media 100BaseTX mediaopt full-duplex, flag0 depend on the “Hetzner” data center), this needs to be checked with the support service as I have a media autoselect on another server in another data center I also have the Hetzner IPv6 settings I will not describe this topic in detail for a separate article, but by example you can do it and it will work.


 hostname="garpix" sshd_enable="YES" sendmail_enable="YES" zfs_enable="YES" # IPv4 inet config ntpd_enable="YES" ifconfig_re0="inet xxx.xxx.xxx.xxx netmask xxx.xxx.xxx.xxx media 100BaseTX mediaopt full-duplex,flag0" defaultrouter="xxx.xxx.xxx.xxx" # IPv6 inet6 config ipv6_network_interfaces="re0" ifconfig_re0_ipv6="inet6 2a01:4f8:141:54c3::2 prefixlen 64 accept_rtadv" ipv6_default_interface="re0" ipv6_static_routes="gw defgw" ipv6_route_gw="2a01:4f8:141:54c0:: -prefixlen 59 -iface re0" ipv6_route_defgw="default 2a01:4f8:141:54c0::1" # Firewall firewall_enable="YES" firewall_script="/etc/rc.ipfw" 



And most importantly, now you need to configure Firewall

 # ee /etc/rc.ipfw 


RC.IPFW

 #!/bin/sh ipfw -q -f flush oif="re0" cmd="ipfw -q add " ks="keep-state" tcp_out="20,21,22,25,80,110,137,138,139,143,443,445,2802,3306,5190,6667,8080,9000" tcp_in="20,21,22,80,443,8080,5432" tcp6_in="::80,::443" ftp_high="21,1024-65535" udp_out="53,999,1234" udp_in="1234" # allow all #$cmd 00100 pass all from any to any # deny all $cmd 00100 pass all from any to any via lo0 $cmd 00101 deny all from any to 127.0.0.0/8 $cmd 00102 deny all from 127.0.0.0/8 to any $cmd 00500 check-state $cmd 00502 deny all from any to any frag $cmd 00501 deny tcp from any to any established $cmd 00600 allow tcp from any to any out via $oif setup $ks $cmd 00601 allow udp from any to any out via $oif $ks # IPv6 $cmd 00701 allow ip6 from any to any $tcp6_in via $oif setup $ks # tcp Out $cmd 00702 skip to 10000 tcp from any to any $tcp_out out via $oif setup $ks # Ftp $cmd 00703 allow tcp from any to any via $oif established $cmd 00704 allow tcp from me to any via $oif setup $ks $cmd 00705 allow tcp from any to me $ftp_high via $oif setup $ks $cmd 00706 skip to 10000 tcp from any to any $ftp_high out via $oif setup $ks # Udp out $cmd 00707 skip to 10000 udp from any to any $udp_out out via $oif $ks # Udp in $cmd 00708 skip to 10000 udp from any to any $udp_in in via $oif $ks # tcp In $cmd 00709 allow tcp from any to me $tcp_in in via $oif setup $ks # ping $cmd 02000 allow icmp from any to any icmptype 8 $cmd 02001 allow icmp from any to any icmptype 0 # IPv6 $cmd 02004 allow ipv6-icmp from any to any 


Making the script executable

 # chmod 0755 /etc/rc.ipfw 


Now reboot

 # reboot 


If everything is done correctly, then everything should work like a "clock"

About everyones delete-old and make delete-old-libs, everyone decides for himself.

In my kernel config, I registered DTrace, if you did also check if everything works

 # trace -l | head 


Manage make.conf to support Dtrace at user level.

 STRIP= CFLAGS+=-fno-omit-frame-pointer 


Now you need to install ports

We receive the list of ports

 # portsnap fetch 


Removing ports

 # portsnap extract 


Go to the directory with the ports and set the index

 # cd /usr/ports # make fetch index 


Everything is ready, now the system is fully working, with ports and everything that is required for normal operation. You can install the applications that you need, mc, ftp, nginx, etc. etc.

PS If you find inaccuracies or comments, write - I will correct, I gathered material from Google and from Habr, I described everything in detail so that it could be used as an instruction. Of course, much is elementary here, but I hope this will help some users to save time.

Source: https://habr.com/ru/post/143546/


All Articles