Create an HTML 5 RDP/SSH Frontend Using Guacamole on Ubuntu 16.04 LTS

Introduction

The goal of this tutorial is to get rid of the public SSH and public RDP connections. By placing this all behind a very convenient HTML5 client, we can add a layer of security for accessing our cloud.

Guacamole also logs any remote access, so unauthorized access becomes much more traceable.

Note: For Let's encrypt (option B) we need a domain name. If you don't have one, you can skip this step and just execute option A.

Step 1 - Preparing the system

Start by spinning up a VPS in your desired Vultr zone. A 1024 MB VPS will be enough, as Guacamole is not that demanding.

Enabling the private IP

Start by enabling the private network on the VPS. This is well-documented here

Preparing the firewall

First let’s harden the image a little bit. And let’s check if the image that has been provisioned has ufw enabled.

root@vultr:~# ufw status
Status: inactive

By default it's disabled, so we will need to add a few rules.

  • Rule 1: ssh: TCP port 22
  • Rule 2: http: TCP port 8080 (temporary testing rule for Guacamole)

Let's start with configuring these ports.

ufw allow 22/tcp
ufw allow 8080/tcp

Next enable the firewall.

ufw enable

Don't worry if you receive a warning. If you added port 22, you will not face any issues.

root@vultr:~# ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

Once enabled, request the status of the firewall and we will see our port configuration.

ufw status

Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
8080/tcp                   ALLOW       Anywhere
22/tcp (v6)                ALLOW       Anywhere (v6)
8080/tcp (v6)              ALLOW       Anywhere (v6)

Step 2 - Installing Guacamole

Installing all dependencies

Before we start installing we need to update and upgrade the repo. With packages such as Tomcat, which is Java based, there is a constant stream of discovered bugs and their associated bugfixes. It is usually a good idea to do this first instead of rushing directly into our installation.

apt-get update
apt-get -y upgrade 

Next up is all the dependencies. Guacamole has quite a few of them. (A full list of dependencies and their function can be found here). Let's continue by installing all of them.

apt-get -y install build-essential tomcat8 freerdp libcairo2-dev libjpeg-turbo8-dev libpng12-dev libossp-uuid-dev libavcodec-dev libavutil-dev libfreerdp-dev libpango1.0-dev libssh2-1-dev libtelnet-dev libvorbis-dev libwebp-dev mysql-server mysql-client mysql-common mysql-utilities libswscale-dev libvncserver-dev libpulse-dev libssl-dev

When the installer asks for a MySQL root password, provide one and be sure to take note of it. We will be using this password later on to create the Guacamole database.

Downloading Guacamole

Now that we have all of our dependencies, we can continue with downloading Guacamole. Guacamole itself comes mostly in a source form, and not a binary. First we will move to the /tmp folder to avoid cluttering other parts of the disk. Then download all of the source code.

There are four source/binary files to download:

  • guacamole-0.9.13-incubating.war: This is the web application. A WAR file is a zipped web package providing a single website hosted on a Tomcat website
  • guacamole-server-0.9.13-incubating.tar.gz: This file will provide the backend guacd application. This creates the streams through RDP and SSH.
  • guacamole-auth-jdbc-0.9.13-incubating.tar.gz: We will be using a local MySQL database, so we need the associated JDBC connector.
  • mysql-connector-java-5.1.43.tar.gz: Without a database driver the JDBC connector does nothing. This file is provided by the MySQL team themselves.

Note: downloads resolved to the closest server.

cd /tmp
wget http://apache.belnet.be/incubator/guacamole/0.9.13-incubating/binary/guacamole-0.9.13-incubating.war
wget http://apache.cu.be/incubator/guacamole/0.9.13-incubating/source/guacamole-server-0.9.13-incubating.tar.gz
wget http://apache.cu.be/incubator/guacamole/0.9.13-incubating/binary/guacamole-auth-jdbc-0.9.13-incubating.tar.gz
wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.43.tar.gz

Once we have downloaded all these files, extract the tar.gz's.

tar -xzvf guacamole-server-0.9.13-incubating.tar.gz
tar -xzvf guacamole-auth-jdbc-0.9.13-incubating.tar.gz
tar -xzvf mysql-connector-java-5.1.43.tar.gz

Compiling Guacamole

Now that we have extracted all of the source code, let's make a few guacamole folders, these will be used by the guacamole application and its dependencies.

mkdir -p /etc/guacamole/lib
mkdir -p /etc/guacamole/extensions

Everything is ready for our new Guacamole binaries. We can now start the compilation and installation process. Move over to the extracted Guacamole Server folder.

cd /tmp/guacamole-server-0.9.13-incubating

Configure the application to also create an init.d file for running it as a service later on.

./configure --with-init-dir=/etc/init.d

The command should end with a 'yes' on all libraries and protocols. If not, go back and check the apt-get command to make sure you didn't miss any package.

------------------------------------------------
guacamole-server version 0.9.13-incubating
------------------------------------------------

   Library status:

     freerdp ............. yes
     pango ............... yes
     libavcodec .......... yes
     libavutil ........... yes
     libssh2 ............. yes
     libssl .............. yes
     libswscale .......... yes
     libtelnet ........... yes
     libVNCServer ........ yes
     libvorbis ........... yes
     libpulse ............ yes
     libwebp ............. yes

   Protocol support:

      RDP ....... yes
      SSH ....... yes
      Telnet .... yes
      VNC ....... yes

   Services / tools:

      guacd ...... yes
      guacenc .... yes

   Init scripts: /etc/init.d

Type "make" to compile guacamole-server.

Next up compile and install the Gucamole server.

make && make install

Once this all is done, run ldconfig to rebuild the search path for libraries that have been added.

ldconfig

Proceed by using systemctl to setup guacd (Guacamole Daemon) to start on boot.

systemctl enable guacd

Guacamole binaries are now installed. Now we will get the web application ready for Tomcat.

Start by moving the war file to the guacamole folder we just created, once this is done create a logical link in the tomcat directory to point to our war file.

cd /tmp
mv guacamole-0.9.13-incubating.war /etc/guacamole/guacamole.war
ln -s /etc/guacamole/guacamole.war /var/lib/tomcat8/webapps/

Then we need the mysql connector and JDBC. The JDBC driver is needed in the extensions folder, the connector in the lib folder.

cp mysql-connector-java-5.1.43/mysql-connector-java-5.1.43-bin.jar /etc/guacamole/lib/
cp guacamole-auth-jdbc-0.9.13-incubating/mysql/guacamole-auth-jdbc-mysql-0.9.13-incubating.jar /etc/guacamole/extensions/

Configuring Guacamole and Tomcat

Once the connector and JDBC are in place, we need to edit the tocamt8 file. This file contains a lot of tomcat8 settings, and in our case we need to add the GUACAMOLE_HOME variable at the end of the file.

nano /etc/default/tomcat8

Append with the following.

GUACAMOLE_HOME=/etc/guacamole

Creating the database

Next up is creating the database. Guacamole stores its connection configuration in a database, not inside a file.

Login with the root password you used during the installation.

mysql -u root -p

The first step is to create a database called 'guacamole_db'.

create database guacamole_db;

Then run the create user command. This will create a user with a password mysupersecretpassword, this user will only be able to connect from localhost.

create user 'guacamole_user'@'localhost' identified by "mysupersecretpassword";

Grant CRUD operations to this user for the database guacamole_db.

GRANT SELECT,INSERT,UPDATE,DELETE ON guacamole_db.* TO 'guacamole_user'@'localhost';

Flush privileges and exit the shell.

flush privileges;
exit

Finish up by adding the Guacamole schema to our newly created database.

cat /tmp/guacamole-auth-jdbc-0.9.13-incubating/mysql/schema/*.sql | mysql -u root -p guacamole_db

Once this is done, we need to edit the guacamole.properties file. This file contains our recently created MySQL server configuration.

nano /etc/guacamole/guacamole.properties

Append the MySQL connection details and credentials.

mysql-hostname: localhost
mysql-port: 3306
mysql-database: guacamole_db
mysql-username: guacamole_user
mysql-password: mysupersecretpassword

Finish up by creating a symbolic link to the tomcat share folder, as this is where the WAR file will search these properties.

ln -s /etc/guacamole /usr/share/tomcat8/.guacamole

Testing the setup

End by restarting the tomcat8 server and start the guacd server daemon.

service tomcat8 restart
service guacd start

You can verify by using the status command.

service tomcat8 status
service guacd status

Now you can browse to your VPS on port 8080

http://<yourpublicip>:8080/guacamole/

Use the username guacadmin and the same password guacadmin. This will grant you access to an empty Guacamole server.

Click in the top right corner on your username guacadmin and select Settings. Once you are in the settings page go to the Users tab and select the user guacadmin.

Now change your password to something else or create a new admin user and delete the default guacadmin one.

Step 3 - Fine tuning and cleanup

These are the final steps: cleaning up after you are done.

Delete the downloaded source code and binaries from the /tmp folder.

rm -rf /tmp/guacamole-*
rm -rf /tmp/mysql-connector-java-*

Also, make the Guacamole web application the default one. In the tomcat ecosystem the application that gets the ROOT folder is the one that is started by default when you access the website.

Delete the old ROOT placeholder.

rm -rf /var/lib/tomcat8/webapps/ROOT

And make a symbolic link for the guacamole server to be the ROOT one.

ln -s /var/lib/tomcat8/webapps/guacamole /var/lib/tomcat8/webapps/ROOT

This requires a tomcat restart.

service tomcat8 restart

Step 4 (option A) - Running on HTTP only

  • If you are not going to use Let's Encrypt certificates and not use a DNS, execute the actions in this step and afterwards go directly to Step 6. - Option A
  • If you want to create a more secure site and you have a DNS ready, you can skip this and go straight to option B (Step 5).

Edit the tomcat8/server.xml file and change the connector port.

nano /etc/tomcat8/server.xml

Search for the Connector port.

<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           URIEncoding="UTF-8"
           redirectPort="8443" />

And replace 8080 with 80.

By default, tomcat doesn't allow the binding of ports below 1024. To enable this we need to tell tomcat8 to create authenticated binds.

Edit the default file of tomcat8 and uncomment the AUTHBIND line and use the option yes

nano /etc/default/tomcat8

AUTHBIND=yes

Once this is done, intall authbind.

apt-get install authbind

Configure it so that port 80 can be claimed by tomcat8.

touch /etc/authbind/byport/80
chmod 500 /etc/authbind/byport/80
chown tomcat8 /etc/authbind/byport/80

Allow port 80 through the firewall and delete the rule for 8080.

ufw allow 80/tcp
ufw delete allow 8080/tcp

Restart tomcat.

service tomcat8 restart

That's it, now Guacamole should be running on port 80.

Step 5 (option B) - Setting up Nginx

Installation and configuration of Nginx

Tomcat really isn't one of the best and most robust applications to use with certbot. Luckily Nginx is. We will just to proxy tomcat to Nginx. It uses the out-of-the-box functionality of certbot at the cost of sacrificing a little bit of RAM.

apt-get install nginx

Once installed, edit the default configuration.

nano /etc/nginx/sites-available/default

Delete all example configurations and add the following configuration.

server {      
  listen 0.0.0.0:80;

  proxy_request_buffering off;
  proxy_buffering off;

  location / {
     proxy_pass http://127.0.0.1:8080;
     proxy_redirect     off;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
  }
}

This will create a proxy for the website running at 8080. Restart Nginx, and enable it at boot.

systemctl restart nginx
systemctl enable nginx

Check if everything is working.

systemctl status nginx

Disable the testing port 8080 and allow traffic on port 80.

ufw allow 80/tcp
ufw delete allow 8080/tcp

Installing Let's Encrypt

Before we can use certbot, we need to add the correct ppa to the system containing our certbot packages.

add-apt-repository ppa:certbot/certbot

Press "ENTER" to accept the configuration change.

Update apt to gather the new packages.

apt-get update

Finally, install the Nginx module for assigning the certificates.

apt-get -y install python-certbot-nginx

Configure Nginx to use certificates

Configure the firewall to allow HTTPS.

ufw allow 443/tcp

Before we can request new certificates, we need a DNS name.

nano /etc/nginx/sites-available/default

Add the following server_name setting.

server_name rdp.example.com;

Change the configuration to reflect this new setting.

server {
  server_name rdp.example.com;

  listen 0.0.0.0:80;

  proxy_request_buffering off;
  proxy_buffering off;

  location / {
     proxy_pass http://127.0.0.1:8080;
     proxy_redirect     off;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
  }
}

Check if all is working and restart Nginx.

nginx -t
service nginx restart

Now request a certificate with certbot.

certbot --nginx -d rdp.example.com

Provide your email and agree to the questions asked by the installer. (You can safely choose "No" to sharing your email.) Certbot will automatically ask what it needs to do with HTTPS. We will be using option 2: redirect to HTTPS.

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
-------------------------------------------------------------------------------
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
-------------------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2

The last thing we will be doing is updating the DH parameters. These are, by default, a little bit weak for 2017 standards.

Create some new ones.

openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Next, add them to the default site in Nginx.

nano /etc/nginx/sites-available/default

Add them to the server configuration.

server {
  server_name rdp.example.com;

  listen 0.0.0.0:80;
  ssl_dhparam /etc/ssl/certs/dhparam.pem;

  proxy_request_buffering off;
  proxy_buffering off;

  location / {
     proxy_pass http://127.0.0.1:8080;
     proxy_redirect     off;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
  }
}

Check for errors.

nginx -t

Apply the changes by restarting the server.

service nginx restart

Cleanup the old 8080 rule

ufw delete allow 8080/tcp

Note: if you should recieve a "502 Bad Gateway" you will need restart tomcat8.

service tomcat8 restart

Auto renew certificates

Let's Encrypt certificates require renewal. We can create a cron job for this. Start by editing the crontab.

crontab -e

Add the following line.

00 2 * * * /usr/bin/certbot renew --quiet

This will check at 2:00 AM if any certificates require a renewal and will renew them if they do.

Step 6 - Testing it all out

Go to your Guacamole server (either http://<ip>/ or https://rdp.example.com)).

For this test, you will need two more instances: one Linux VM and another Windows Server 2012 R2 with a private IP enabled on both.

Adding the Windows RDP connection

Click the "username" on the top right corner and go to "Settings". Then go to "Connections" and select "New Connection".

Fill in following settings (you can leave the others default).

Name: Windows Server 2012 R2
Location: ROOT
Protocol: RDP
Maximum number of connections: 1
Maximum number of connections per user: 1
Parameters > Hostname: 10.99.0.12
Parameters > Port: 3389
Username: Administrator
Password: <password> (provided by Vultr)
Security mode: Any
Ignore server certificate: <checked>

Press "save" and go back to the home screen. Now you can click on the "Windows Server 2012 R2" connection and it will RDP to this machine.

Adding the Linux SSH connection

Press "Ctrl+Shift+Alt". This will pop out the menu on the side. Here you can disconnect or perform other administrative tasks for Guacamole.

Click the username on the top in the menu and go to "Settings". Then go to the "Connections" tab and select "New Connection".

Fill in following settings (you can leave the others default).

Name: Linux
Location: ROOT
Protocol: SSH
Maximum number of connections: 5
Maximum number of connections per user: 2
Parameters > Hostname: 10.99.0.11
Parameters > Port: 22
Username: root
Password: <password> (provided by Vultr)

Press "save" and go back to the home screen. Now you can click this newly created connection and be connected to your Linux server via SSH.

Conclusion

You now have a web RDP/SSH HTML5 gateway. Now you can firewall the public RDP and SSH access of your platform and access your environment from any modern browser. For more information on what Guacamole can offer, there is a great video showing all of the possiblities of the platform here.


How to Setup Unattended Upgrades on Debian 9 (Stretch)

How to Setup Unattended Upgrades on Debian 9 (Stretch)

Using a Different System? If you purchase a Debian server, then you should always have the latest security patches and updates, whether youre asleep or not

Återställ MySQL Root Password på Debian/Ubuntu

Återställ MySQL Root Password på Debian/Ubuntu

Om du har glömt ditt MySQL root-lösenord kan du återställa det genom att följa stegen i den här artikeln. Processen är ganska enkel och fungerar på dessa

Installera Wordpress med Apache, PHP och MySQL (Automated Startup Script)

Installera Wordpress med Apache, PHP och MySQL (Automated Startup Script)

Du kan kopiera och klistra in följande bash-skript i startskriptområdet på Vultr-kontrollpanelen. Det kommer att installera alla nödvändiga paket för att ru

Säkerhetskopiera flera MySQL- eller MariaDB-databaser automatiskt

Säkerhetskopiera flera MySQL- eller MariaDB-databaser automatiskt

Inledning I den här artikeln, gå igenom hur man säkerhetskopierar flera MySQL- eller MariaDB-databaser som sitter på samma maskin med hjälp av ett anpassat bash-skript

Använda MySQL Views på Debian 7

Använda MySQL Views på Debian 7

Introduktion MySQL har en fantastisk funktion som kallas vyer. Visningar är lagrade frågor. Se dem som ett alias för en annars lång fråga. I den här guiden,

Run Your Own Arma 3 Server on Ubuntu 14

Run Your Own Arma 3 Server on Ubuntu 14

Overview This guide will cover the installation and operation of your Arma 3 server using SteamCMD and screen. For additional information regarding th

Create an HTML 5 RDP/SSH Frontend Using Guacamole on Ubuntu 16.04 LTS

Create an HTML 5 RDP/SSH Frontend Using Guacamole on Ubuntu 16.04 LTS

Introduction The goal of this tutorial is to get rid of the public SSH and public RDP connections. By placing this all behind a very convenient HTML5 client

Hur man installerar GruntJS på Debian 9

Hur man installerar GruntJS på Debian 9

GruntJS är en JavaScript-uppdragskörare skriven ovanpå NodeJS. Den kan användas för att automatisera repetitiva uppgifter för din applikation som minifiering, kompilering

Securing SSH on Ubuntu 14.04

Securing SSH on Ubuntu 14.04

After you create a new server, there are some configuration tweaks that you should make to harden the security of your server. Create a new user As the roo

Installera MariaDB 10 på CentOS 6

Installera MariaDB 10 på CentOS 6

I skrivande stund är MariaDB 10.1 utvecklingsversionen av MariaDB. Den är baserad på MariaDB 5.5 och inkluderar backportade funktioner från MySQL 5.6. Ther

Installera Percona på Debian 7

Installera Percona på Debian 7

MySQL multi-master replikering är en utmärkt funktion inom MySQL. Det finns dock bara ett problem; standard multi-master replikering verkar aldrig vara en

Hur man installerar Cauldron (Minecraft) på en CentOS 6-server

Hur man installerar Cauldron (Minecraft) på en CentOS 6-server

Inledning Denna handledning kommer att täcka processen för att installera Caudron. För de som inte är bekanta med Caudron är det en typ av Minecraft Server.

Installera MariaDB på CentOS 7

Installera MariaDB på CentOS 7

Forked från MySQL, MariaDB har varit känt för att vara en drop-in ersättare som ger förbättringar och prestandaoptimeringar över MySQL. Denna handledning kommer

How to Install InvoicePlane on Ubuntu 16.04

How to Install InvoicePlane on Ubuntu 16.04

Using a Different System? InvoicePlane is a free and open source invoicing application. Its source code can be found on this Github repository. This guid

Hur man distribuerar Ghost på Fedora 25

Hur man distribuerar Ghost på Fedora 25

Använder du ett annat system? Ghost är en bloggplattform med öppen källkod som vinner popularitet bland utvecklare och vanliga användare sedan lanseringen 2013. jag

Ställ in MySQL Master-Slave-replikering på Debian/Ubuntu

Ställ in MySQL Master-Slave-replikering på Debian/Ubuntu

Introduktion När du kör en kritisk webbplats är det viktigt att se till att du har minst en redundant backupserver. Detta säkerställer att du

Hur man installerar RubyGems på Linux

Hur man installerar RubyGems på Linux

RubyGems är en pakethanterare för Ruby. En pakethanterare organiserar paket under utvecklingen av en applikation. I det här dokumentet kommer du att lära dig hur du installerar

Använder Chef-solo för att konfigurera en Django-app på Ubuntu

Använder Chef-solo för att konfigurera en Django-app på Ubuntu

Det finns många sätt att automatisera processen för att ställa in och konfigurera en box. Av någon anledning, om hela vårt system vid denna tidpunkt består av bara

Installera Prosody på Debian 7

Installera Prosody på Debian 7

Installera Prosody på Debian 7 Prosody är en XMPP-kommunikationsserver skriven i LUA. Det syftar till att vara lätt att konfigurera och konfigurera, och effektivt med system

Plesk: Uppgradera MariaDB från version 5.50 till 10.2 på CentOS 7

Plesk: Uppgradera MariaDB från version 5.50 till 10.2 på CentOS 7

Den här artikeln skrevs för Plesk 12.5 och Plesk Onyx 17.x. I dessa versioner levereras Plesk med MariaDB v5.50. Nackdelen med denna MariaDB-version i

The Rise of Machines: Real World Applications of AI

The Rise of Machines: Real World Applications of AI

Artificiell intelligens är inte i framtiden, det är här i nuet I den här bloggen Läs hur Artificiell intelligens-applikationer har påverkat olika sektorer.

DDOS-attacker: En kort översikt

DDOS-attacker: En kort översikt

Är du också ett offer för DDOS-attacker och förvirrad över de förebyggande metoderna? Läs den här artikeln för att lösa dina frågor.

Har du någonsin undrat hur hackare tjänar pengar?

Har du någonsin undrat hur hackare tjänar pengar?

Du kanske har hört att hackare tjänar mycket pengar, men har du någonsin undrat hur de tjänar den typen av pengar? låt oss diskutera.

Revolutionerande uppfinningar från Google som gör ditt liv lätt.

Revolutionerande uppfinningar från Google som gör ditt liv lätt.

Vill du se revolutionerande uppfinningar av Google och hur dessa uppfinningar förändrade livet för varje människa idag? Läs sedan till bloggen för att se uppfinningar av Google.

Fredag ​​Essential: Vad hände med AI-drivna bilar?

Fredag ​​Essential: Vad hände med AI-drivna bilar?

Konceptet med att självkörande bilar ska ut på vägarna med hjälp av artificiell intelligens är en dröm vi har ett tag nu. Men trots flera löften finns de ingenstans att se. Läs den här bloggen för att lära dig mer...

Technological Singularity: A Distant Future of Human Civilization?

Technological Singularity: A Distant Future of Human Civilization?

När vetenskapen utvecklas i snabb takt och tar över en hel del av våra ansträngningar, ökar också riskerna för att utsätta oss för en oförklarlig singularitet. Läs, vad singularitet kan betyda för oss.

Utveckling av datalagring – Infographic

Utveckling av datalagring – Infographic

Lagringsmetoderna för data har utvecklats kan vara sedan födelsen av data. Den här bloggen tar upp utvecklingen av datalagring på basis av en infografik.

Funktioner för Big Data Reference Architecture Layers

Funktioner för Big Data Reference Architecture Layers

Läs bloggen för att känna till olika lager i Big Data Architecture och deras funktionaliteter på enklaste sätt.

6 fantastiska fördelar med att ha smarta hemenheter i våra liv

6 fantastiska fördelar med att ha smarta hemenheter i våra liv

I denna digitala värld har smarta hemenheter blivit en avgörande del av livet. Här är några fantastiska fördelar med smarta hemenheter om hur de gör vårt liv värt att leva och enklare.

macOS Catalina 10.15.4 tilläggsuppdatering orsakar fler problem än att lösa

macOS Catalina 10.15.4 tilläggsuppdatering orsakar fler problem än att lösa

Nyligen släppte Apple macOS Catalina 10.15.4, en tilläggsuppdatering för att åtgärda problem, men det verkar som om uppdateringen orsakar fler problem som leder till att mac-datorer blir murade. Läs den här artikeln för att lära dig mer