Installing Nextcloud and Collabora

Install Nextcloud

Prepare the system

1
2
3
4
$ sudo apt install unzip imagemagick php-imagick php8.1-common php8.1-pgsql php8.1-fpm php8.1-gd php8.1-curl php8.1-imagick php8.1-zip php8.1-xml php8.1-mbstring php8.1-bz2 php8.1-intl php8.1-bcmath php8.1-gmp
$ wget https://download.nextcloud.com/server/releases/latest.zip
$ sudo unzip latest.zip -d /var/www/
$ sudo chown www-data:www-data /var/www/nextcloud/ -R

Prepare the database

1
2
3
4
$ sudo -u postgres psql
postgres=# create database nextcloud;
postgres=# create user nextcloud with encrypted password 'pass';
postgres=# grant all privileges on database nextcloud to nextcloud;

Prepare nginx

Create /etc/nginx/sites-available/nextcloud.conf with the following contents

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
upstream php-handler {
server unix:/var/run/php/php-fpm.sock;
}

# Set the `immutable` cache control options only for assets with a cache busting `v` argument
map $arg_v $asset_immutable {
"" "";
default "immutable";
}


server {
listen 80;
listen [::]:80;
server_name drive.YOUR_DOMAIN.com;

# Path to the root of your installation
root /var/www/nextcloud;

# Prevent nginx HTTP Server Detection
server_tokens off;

# HSTS settings
# WARNING: Only add the preload option once you read about
# the consequences in https://hstspreload.org/. This option
# will add the domain to a hardcoded list that is shipped
# in all major browsers and getting removed from this list
# could take several months.
#add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;

# set max upload size and increase upload timeout:
client_max_body_size 512M;
client_body_timeout 300s;
fastcgi_buffers 64 4K;

# Enable gzip but do not remove ETag headers
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

# Pagespeed is not supported by Nextcloud, so if your server is built
# with the `ngx_pagespeed` module, uncomment this line to disable it.
#pagespeed off;

# The settings allows you to optimize the HTTP2 bandwitdth.
# See https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/
# for tunning hints
client_body_buffer_size 512k;

# HTTP response headers borrowed from Nextcloud `.htaccess`
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Download-Options "noopen" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "noindex, nofollow" always;
add_header X-XSS-Protection "1; mode=block" always;

# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;

# Specify how to handle directories -- specifying `/index.php$request_uri`
# here as the fallback means that Nginx always exhibits the desired behaviour
# when a client requests a path that corresponds to a directory that exists
# on the server. In particular, if that directory contains an index.php file,
# that file is correctly served; if it doesn't, then the request is passed to
# the front-end controller. This consistent behaviour means that we don't need
# to specify custom rules for certain paths (e.g. images and other assets,
# `/updater`, `/ocm-provider`, `/ocs-provider`), and thus
# `try_files $uri $uri/ /index.php$request_uri`
# always provides the desired behaviour.
index index.php index.html /index.php$request_uri;

# Rule borrowed from `.htaccess` to handle Microsoft DAV clients
location = / {
if ( $http_user_agent ~ ^DavClnt ) {
return 302 /remote.php/webdav/$is_args$args;
}
}

location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}

# Make a regex exception for `/.well-known` so that clients can still
# access it despite the existence of the regex rule
# `location ~ /(\.|autotest|...)` which would otherwise handle requests
# for `/.well-known`.
location ^~ /.well-known {
# The rules in this block are an adaptation of the rules
# in `.htaccess` that concern `/.well-known`.

location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav { return 301 /remote.php/dav/; }

location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
location /.well-known/pki-validation { try_files $uri $uri/ =404; }

# Let Nextcloud's API for `/.well-known` URIs handle all other
# requests by passing them to the front-end controller.
return 301 /index.php$request_uri;
}

# Rules borrowed from `.htaccess` to hide certain paths from clients
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }

# Ensure this block, which passes PHP files to the PHP process, is above the blocks
# which handle static assets (as seen below). If this block is not declared first,
# then Nginx will encounter an infinite rewriting loop when it prepends `/index.php`
# to the URI, resulting in a HTTP 500 error response.
location ~ \.php(?:$|/) {
# Required for legacy support
rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri;

fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;

try_files $fastcgi_script_name =404;

include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $path_info;
# fastcgi_param HTTPS on;

fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice
fastcgi_param front_controller_active true; # Enable pretty urls
fastcgi_pass php-handler;

fastcgi_intercept_errors on;
fastcgi_request_buffering off;

fastcgi_max_temp_file_size 0;
}

location ~ \.(?:css|js|svg|gif|png|jpg|ico|wasm|tflite|map)$ {
try_files $uri /index.php$request_uri;
add_header Cache-Control "public, max-age=15778463, $asset_immutable";
access_log off; # Optional: Don't log access to assets

location ~ \.wasm$ {
default_type application/wasm;
}
}

location ~ \.woff2?$ {
try_files $uri /index.php$request_uri;
expires 7d; # Cache-Control policy borrowed from `.htaccess`
access_log off; # Optional: Don't log access to assets
}

# Rule borrowed from `.htaccess`
location /remote {
return 301 /remote.php$request_uri;
}

location / {
try_files $uri $uri/ /index.php$request_uri;
}
}

Enable Nextcloud

1
2
$ sudo ln -s /etc/nginx/sites-available/nextcloud.conf /etc/nginx/sites-enabled/nextcloud.conf
$ sudo systemctl restart nginx

Visit http://drive.YOUR_DOMAIN.com and following instructions.

Install Collabora

Check these tutorials

https://c-nergy.be/blog/?p=18055

https://www.linuxbabe.com/ubuntu/integrate-collabora-onlinenextcloud-without-docker

Following is the summary of my installation based on the above two links.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ echo 'deb https://www.collaboraoffice.com/repos/CollaboraOnline/CODE-ubuntu2204 ./' | sudo tee /etc/apt/sources.list.d/collabora.list
$ cd /usr/share/keyrings
$ sudo wget https://collaboraoffice.com/downloads/gpg/collaboraonline-release-keyring.gpg
$ sudo apt install apt-transport-https ca-certificates
$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 0C54D189F4BA284D
$ sudo apt update
$ sudo apt install coolwsd code-brand
$ sudo coolconfig set ssl.enable false
$ sudo coolconfig set ssl.termination true
$ sudo coolconfig set storage.wopi.host nextcloud.YOUR_DOMAIN.com
$ sudo coolconfig set-admin-password
$ sudo systemctl restart coolwsd
$ sudo systemctl status coolwsd

create nginx site on /etc/nginx/sites-enabled/collabora.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
server {
listen 80;
listen [::]:80;
server_name collabora.YOUR_DOMAIN.com;

error_log /var/log/nginx/collabora.error;

# static files
location ^~ /browser {
proxy_pass http://localhost:9980;
proxy_set_header Host $http_host;
}
location ^~ /loleaflet {
proxy_pass http://localhost:9980;
proxy_set_header Host $http_host;
}


# WOPI discovery URL
location ^~ /hosting/discovery {
proxy_pass http://localhost:9980;
proxy_set_header Host $http_host;
}

# Capabilities
location ^~ /hosting/capabilities {
proxy_pass http://localhost:9980;
proxy_set_header Host $http_host;
}

# main websocket
location ~ ^/cool/(.*)/ws$ {
proxy_pass http://localhost:9980;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $http_host;
proxy_read_timeout 36000s;
}

# download, presentation and image upload
location ~ ^/(c|l)ool {
proxy_pass http://localhost:9980;
proxy_set_header Host $http_host;
}

# Admin Console websocket
location ^~ /cool/adminws {
proxy_pass http://localhost:9980;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $http_host;
proxy_read_timeout 36000s;
}
}

Enable it

1
2
$ sudo ln -s /etc/nginx/sites-available/collabora.conf /etc/nginx/sites-enabled/collabora.conf
$ sudo systemctl restart nginx

Open Nextcloud Administration and set the url collabora.YOUR_DOMAIN.com

Installing gitea and woodpecker using binary packages

Create Users

1
2
3
$ sudo adduser --system --shell /bin/bash --gecos 'Git Version Control' --group --disabled-password --home /home/git git
$ sudo adduser --system --shell /bin/bash --gecos 'Woodpecker CI' --group --disabled-password --home /home/woodpecker woodpecker
$ sudo usermod -aG docker woodpecker

Binaries

1
2
3
4
5
6
7
$ wget https://dl.gitea.com/gitea/1.19.0/gitea-1.19.0-linux-amd64
$ sudo mv gitea-1.19.0-linux-amd64 /usr/local/bin/gitea
$ sudo chmod +x /usr/local/bin/gitea
$ wget https://github.com/woodpecker-ci/woodpecker/releases/download/v0.15.7/woodpecker-agent_0.15.7_amd64.deb
$ wget https://github.com/woodpecker-ci/woodpecker/releases/download/v0.15.7/woodpecker-cli_0.15.7_amd64.deb
$ wget https://github.com/woodpecker-ci/woodpecker/releases/download/v0.15.7/woodpecker-server_0.15.7_amd64.deb
$ sudo apt-get install ./woodpecker-*

Directories

1
2
3
4
5
6
7
8
9
10
11
$ sudo mkdir -p /var/lib/gitea/{custom,data,log}
$ sudo chown -R git:git /var/lib/gitea/
$ sudo chmod -R 750 /var/lib/gitea/
$ sudo mkdir /etc/gitea
$ sudo chown root:git /etc/gitea
$ sudo chmod 770 /etc/gitea
$ sudo mkdir -p /var/lib/woodpecker
$ sudo chown -R woodpecker:woodpecker /var/lib/woodpecker
$ sudo chmod -R 750 /var/lib/woodpecker/
$ sudo touch /etc/woodpecker.conf
$ sudo chmod 770 /etc/woodpecker.conf

Setup

Database

1
2
3
4
5
6
7
8
9
$ sudo apt-get install postgresql postgresql-contrib
$ sudo -u postgres psql
postgres=# create database git;
postgres=# create user git with encrypted password 'pass';
postgres=# grant all privileges on database git to git;
postgres=# create database woodpecker;
postgres=# create user woodpecker with encrypted password 'pass';
postgres=# grant all privileges on database woodpecker to woodpecker;
postgres=# exit

nginx

Save the following as sudo vim /etc/nginx/sites-available/gitea.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server {
listen 80;
server_name git.YOUR_DOMAIN.com;

location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
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-Proto $scheme;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_buffering off;
}
}

Save the following as sudo vim /etc/nginx/sites-available/woodpecker.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 80;
server_name woodpecker.YOUR_DOMAIN.com;

location / {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;

proxy_pass http://127.0.0.1:8000;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;

chunked_transfer_encoding off;
}
}

enable both of these configs

1
2
sudo ln -s /etc/nginx/sites-available/gitea.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/woodpecker.conf /etc/nginx/sites-enabled/

Restart nginx service

1
sudo systemctl restart nginx

System Services

1
$ sudo wget https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/systemd/gitea.service -P /etc/systemd/system/

Open /etc/systemd/system/gitea.service and uncoment postgresql lines.

Start the service

1
$ sudo systemctl start gitea

open http://IP_ADDRESS:3000 and configure the database settings.

Open /etc/gitea/app.ini and and change ROOT_URL to git.YOUR_DOMAIN.com.

1
$ sudo systemctl restart gitea

Now visit http://git.YOUR_DOMAIN.com/admin/applications.

Add Woodpecker application with Redirect URI http://woodpecker.YOUR_DOMAIN.com/authorize and copy the client id and the secret somewhere as we will need then in next steps.

Create file /etc/systemd/system/woodpecker.service and paste the following

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Unit]
Description=Woodpecker
Documentation=https://woodpecker-ci.org/docs/intro
Requires=network-online.target
After=network-online.target

[Service]
User=woodpecker
Group=woodpecker
EnvironmentFile=/etc/woodpecker.conf
ExecStart=/usr/local/bin/woodpecker-server
RestartSec=5
Restart=on-failure
SyslogIdentifier=woodpecker-server
WorkingDirectory=/var/lib/woodpecker

[Install]
WantedBy=multi-user.target

Write the following configuration in /etc/woodpecker.conf

1
2
3
4
5
6
7
8
9
10
WOODPECKER_OPEN=true
WOODPECKER_HOST=http://woodpecker.YOUR_DOMAIN.com
WOODPECKER_GITEA=true
WOODPECKER_GITEA_URL=http://git.YOUR_DOMAIN.com
WOODPECKER_GITEA_CLIENT=COPIED_CLIENT_ID
WOODPECKER_GITEA_SECRET=COPIED_SECRET
WOODPECKER_GITEA_SKIP_VERIFY=true
WOODPECKER_DATABASE_DRIVER=postgres
WOODPECKER_DATABASE_DATASOURCE=postgres://woodpecker:password@127.0.0.1:5432/woodpecker?sslmode=disable
GODEBUG=netdns=go

The last environment variable is added because there is a bug and that seting that seems to be an workaround. Start woodpecker service.

1
$ sudo systemctl start woodpecker

Now visit http://woodpecker.YOUR_DOMAIN.com. it should redirect you to http://git.YOUR_DOMAIN.com for authentication. Ideally it should be done by now. But the problem is it will crash due to this issue. The workaround is to change WOODPECKER_GITEA_URL to http://woodpecker.YOUR_DOMAIN.com:3000. For some reason, woodpecker server crashes when gitea is behind nginx proxy.

If woodpecker crashes then do the following to get the backtrace

1
2
3
4
5
$ sudo -i
# set -o allexport
# source /etc/woodpecker.conf
# set +o allexport
# /usr/local/bin/woodpecker-server

Now if the woodpecker server is working then we can proceed further.
We need to change the gitea port because woodpecker-agent listens to 3000 which will already be in use by gitea.
So, open /etc/gitea/app.ini and change the HTTP_PORT to 4000.
Open the nginx configuration file /etc/nginx/sites-enabled/gitea.conf and change 3000 to 4000.
Now restart both services and check whether everything is working by opening git.YOUR_DOMAIN.com.

1
2
$ sudo systemctl restart gitea
$ sudo systemctl restart nginx

Install docker if you don’t have it installed already.

1
2
$ sudo apt-get install docker docker-compose
$ sudo docker login

Generate a secret using openssl rand -hex 32 and copy that generated secret into /etc/woodpecker.conf as shown below.

1
2
...
WOODPECKER_AGENT_SECRET=COPIED_SECRET

Then save the following file as /etc/systemd/system/woodpecker-agent.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Unit]
Description=Woodpecker
Documentation=https://woodpecker-ci.org/docs/intro
Requires=network-online.target
After=network-online.target

[Service]
User=woodpecker
Group=woodpecker
EnvironmentFile=/etc/woodpecker-agent.conf
ExecStart=/usr/local/bin/woodpecker-agent
RestartSec=5
Restart=on-failure
SyslogIdentifier=woodpecker-agent
WorkingDirectory=/var/lib/woodpecker

[Install]
WantedBy=multi-user.target

Following is the configuration file /etc/woodpecker-agent.conf

1
2
3
WOODPECKER_SERVER=localhost:9000
WOODPECKER_BACKEND=docker
WOODPECKER_AGENT_SECRET=COPIED_SECRET

Now start the woodpecker-agent service and check it’s status

1
2
$ sudo systemctl start woodpecker-agent
$ sudo systemctl status woodpecker-agent

For my case I got some errors

1
2
3
4
Mar 27 15:39:01 git systemd[1]: Started Woodpecker.
Mar 27 15:39:01 git woodpecker-agent[19259]: {"level":"error","error":"rpc error: code = Unknown desc = invalid agent token","time":"2023-03-27T15:39:01Z","message":"grpc error: done(): code: Unk>
Mar 27 15:39:01 git woodpecker-agent[19259]: {"level":"error","error":"rpc error: code = Unknown desc = invalid agent token","time":"2023-03-27T15:39:01Z","message":"pipeline done with error"}
Mar 27 15:39:01 git systemd[1]: woodpecker-agent.service: Deactivated successfully.

Which implies that the woodpecker-server needs to be restarted. So restart that and restart woodpecker-agent too. Now it should be running.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ sudo systemctl restart woodpecker
$ sudo systemctl restart woodpecker-agent
$ sudo systemctl status woodpecker-agent
● woodpecker-agent.service - Woodpecker
Loaded: loaded (/etc/systemd/system/woodpecker-agent.service; disabled; vendor preset: enabled)
Active: active (running) since Mon 2023-03-27 15:41:01 UTC; 2s ago
Docs: https://woodpecker-ci.org/docs/intro
Main PID: 19344 (woodpecker-agen)
Tasks: 8 (limit: 4660)
Memory: 3.6M
CPU: 21ms
CGroup: /system.slice/woodpecker-agent.service
└─19344 /usr/local/bin/woodpecker-agent

Mar 27 15:41:01 git systemd[1]: Started Woodpecker.

The Woodpecker CI should be up and running. So, now enable the services so that they start automatically when the machine is restarted.

1
2
3
4
5
6
$ sudo systemctl enable docker
$ sudo systemctl enable postgresql
$ sudo systemctl enable gitea
$ sudo systemctl enable woodpecker
$ sudo systemctl enable woodpecker-agent
$ sudo systemctl enable nginx

Install certbot add https

1
2
3
$ sudo apt-get install certbot python3-certbot-nginx
$ sudo certbot --nginx
$ sudo systemctl restart nginx

Open /etc/gitea/app.ini and update ROOT_URL=https://git.YOUR_DOMAIN.com/ then restart gitea service. Open /etc/woodpecker.conf and update WOODPECKER_HOST and WOODPECKER_GITEA_URL with https urls. Restart woodpecker service. Visit http://git.YOUR_DOMAIN.com/admin/applications again and update the Redirect URL with https.

Listing Terms and Acronyms in Text or LaTeX using bash

I often need to check for inconsistent capitalization in my tex files. So listing all the consecutive capitalized words and characters helps me to decide which one is intentional capitalization and which one is not. The following bash script has two functions can lists all terms (Capitalized Phrase) and acronyms used throughout the input file.

1
2
3
4
5
6
7
8
9
10
$ terms filename.tex 
19 Cloud Station
9 Sensor Gateway
7 Sensor Cloud Infrastructure
...
$ acronyms filename.tex
34 VM
13 IaaS
13 CPU
...
Read more

Rumal a header only HTML/CSS/Javascript Generator C++ library

Rumal is a C++ library that can generate HTML/CSS/Javascript code from significantly identical C++ syntax.
Currently it uses std::string which is supposed to be replaced with compile time strings. Injecting placeholders, is also planned but not yet implemented.
This will make it usable as a template engine.

1
2
3
4
5
std::cout << 
div(_id(42) / _class("test"),
span(_id(43) / _class("test"), "Hello"),
span("World")
);
Read more

Tash (ট্যাঁশ) an Open Source C++ library for ArangoDB and AQL

I am working on Tash which is an Open Source C++ library for ArangoDB Database which includes APIs for HTTP based document access and a query builder for AQL (Arango Query Language). These are a few example usages.

1
2
3
4
5
6
7
8
std::string name = "Hijibijbij";
tash::shell shell("school");
shell << select("s").in("students")
/ filter((clause("s.name") == name) && (clause("s.fathers_name") == name))
/ sort().asc("s._key")
/ yield("s");
nlohmann::json result;
shell >> result;

The above generates an equivalent AQL and execute it over the arangodb connection

tashgoru

Read more

Mathematica++ a C++ library that speaks Mathematica

1
2
3
4
5
6
7
8
symbol x("x");
value res;
std::string method = "Newton";

shell << Values(FindRoot(ArcTan(1000 * Cos(x)), List(x, 1, 2), Rule("Method") = method));
shell >> res;
std::vector<double> results = cast<std::vector<double>>(res);
std::cout << results[0] << std::endl; // Prints 10.9956

Mathematica++ A C++ library that speaks Mathematica.

Read more