Skip to main content

GitHub Actions

· One min read

Open in Notion

Context in Workflow

More contexts at https://docs.github.com/en/actions/learn-github-actions/contexts

  • Url for repo, for example: https://github.com/bndynet/bndynet ${{ github.server_url }}/${{ github.repository }}
  • Url for pull request ${{ github.server_url }}/${{ github.repository }}/pull/${{ github.event.pull_request.number }}
  • Url for issue ${{ github.server_url }}/${{ github.repository }}/issues/${{ github.event.issue.number }}

About CRON

# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6)
# │ │ │ │ │
# │ │ │ │ │
# │ │ │ │ │
# * * * * * <command to execute>

Examples

name: Test Build

on:
push:
pull_request:
schedule:
- cron: '00 1 * * 1' # At 01:00 on Mondays.
name: Sync Notion pages to posts

on:
schedule:
# Runs "At 20:00 on every day-of-week"
- cron: '0 20 * * *'

jobs:
# Build job
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Generate posts
uses: bndynet/github-action-notion@v1
with:
notion-token: ${{ secrets.NOTION_TOKEN}}
root-page-id: ${{ secrets.NOTION_ROOT_PAGE_ID }}

- name: Commit posts
uses: EndBug/add-and-commit@v9 # https://github.com/marketplace/actions/add-commit
with:
add: '_posts'
message: Sync Notion pages to posts by GitHub Actions
committer_name: Bendy Zhang
committer_email: email@your.com

Marble testing for rxjs

· 2 min read

Open in Notion

Examples

'-' or '------': Equivalent to [NEVER](<https://rxjs.dev/api/index/const/NEVER>), or an observable that never emits or errors or completes.

|: Equivalent to [EMPTY](<https://rxjs.dev/api/index/const/EMPTY>), or an observable that never emits and completes immediately.

#: Equivalent to [throwError](<https://rxjs.dev/api/index/function/throwError>), or an observable that never emits and errors immediately.

'--a--': An observable that waits 2 "frames", emits value a on frame 2 and then never completes.

'--a--b--|': On frame 2 emit a, on frame 5 emit b, and on frame 8, complete.

'--a--b--#': On frame 2 emit a, on frame 5 emit b, and on frame 8, error.

'-a-^-b--|': In a hot observable, on frame -2 emit a, then on frame 2 emit b, and on frame 5, complete.

'--(abc)-|': on frame 2 emit ab, and c, then on frame 8, complete.

'-----(a|)': on frame 5 emit a and complete.

'a 9ms b 9s c|': on frame 0 emit a, on frame 10 emit b, on frame 9,011 emit c, then on frame 9,012 complete.

'--a 2.5m b': on frame 2 emit a, on frame 150,003 emit b and never complete.

import { TestScheduler } from 'rxjs/testing';
import { throttleTime } from 'rxjs';

const testScheduler = new TestScheduler((actual, expected) => {
// asserting the two objects are equal - required
// for TestScheduler assertions to work via your test framework
// e.g. using chai.
expect(actual).deep.equal(expected);
});

// This test runs synchronously.
it('generates the stream correctly', () => {
testScheduler.run((helpers) => {
const { cold, time, expectObservable, expectSubscriptions } = helpers;
const e1 = cold(' -a--b--c---|');
const e1subs = ' ^----------!';
const t = time(' ---| '); // t = 3
const expected = '-a-----c---|';

expectObservable(e1.pipe(throttleTime(t))).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

testScheduler.run((helpers) => {
const { time, cold } = helpers;
const source = cold('---a--b--|');
const t = time(' --| ');
// --|
const expected = ' -----a--b|';
const result = source.pipe(delay(t));
expectObservable(result).toBe(expected);
});

testScheduler.run((helpers) => {
const { animate, cold } = helpers;
animate(' ---x---x---x---x');
const requests = cold('-r-------r------');
/* ... */
const expected = ' ---a-------b----';
});
});

Git

· 10 min read

Open in Notion

Download and Installation

Quick start (most-used)

git status
git add .
git commit -m "message"
git pull --rebase
git push

Config

Identity

git config --global user.name "Bendy Zhang"
git config --global user.email "81795705@qq.com"
git config --global --list

Helpful defaults

git config --global init.defaultBranch main
git config --global pull.rebase true
git config --global rebase.autoStash true
git config --global fetch.prune true
git config --global rerere.enabled true

Ignore files globally

git config --global core.excludesfile ~/.gitignore_global
# edit ~/.gitignore_global

Workspace (working tree / index / HEAD)

  • Working tree (workspace): Files you see and edit in your filesystem.
  • Index / Staging area: What you get after git add; ready to be committed.
  • HEAD: The currently checked-out commit (usually the tip of the current branch).

Check workspace status

git status
git diff # workspace vs index
git diff --staged # index vs HEAD

Move changes between workspace and staging

git add <file>
git add -p
git restore --staged <file> # unstage

Discard workspace changes (dangerous)

git restore <file>
git restore .

Clean untracked files in workspace

git clean -n -d # dry run
git clean -fd # really delete

Temporarily save workspace changes (stash)

git stash
git stash pop

Repo basics

Init / clone

git init
git clone <repo>
git clone --depth 1 <repo> # shallow clone
git clone --filter=blob:none <repo> # partial clone (faster for big repos)

Remotes

git remote -v
git remote add origin <url>
git remote add upstream <url>
git remote set-url origin <new-url>
git remote rename origin old-origin
git remote remove upstream

Fetch / pull

git fetch --all --prune
git pull
git pull --rebase
git pull --rebase --autostash

Commit

Commit with empty message

git config --global alias.nccommit "commit -a --allow-empty-message -m ''"
git nccommit

Amend / change commit info

git commit --amend --no-edit
git commit --amend --author="Bendy Zhang <zbatbndy.net>" --no-edit

git commit --amend --date="Sun Aug 7 13:05 2022 +0800" --no-edit
GIT_COMMITTER_DATE="Sun Aug 7 14:10 2022 +0800" git commit --amend --no-edit

Fix the last commit without changing message (add forgotten files)

git add .
git commit --amend --no-edit

Create commits without committing staged changes (split work)

git add -p # stage interactively
git commit -m "..."

Branch

List / switch / create

git branch
git branch -a
git switch <branch>
git switch -c <new-branch>
# old style:
git checkout <branch>
git checkout -b <new-branch>

New branch from remote

git checkout -b localBranch origin/remote_branch

Push changes to new branch

git checkout -b newBranch
git push -u origin newBranch # -u is short for `-set-upstream`

Bind local branch with remote branch

git branch --set-upstream-to=origin/remote_branch your_branch

Rename branch

git branch -m oldName newName
git branch -m newName # rename current branch
git push origin :oldName newName
git push -u origin newName

Delete branches

git branch -d <branch> # safe delete (only if merged)
git branch -D <branch> # force delete

git push origin --delete <branch>

Remove multiple branches

git branch -d `git branch --list '3.2.*'`

Reset local branch to remote branch

git fetch origin && git reset --hard origin/master

Merge & rebase

git merge master
git merge master --squash # merge master branch to current branch and keep changes without commits
git rebase master
git rebase --interactive --root # squash all of your commits down to the root commit

Cherry-pick

git cherry-pick <commitid>
# if conflicts, resolve then:
git add . && git cherry-pick --continue

git cherry-pick A..B # The commit A should be older than B which will not include A. If requires A, please use `A^~B`

Diff

git diff # working tree vs index
git diff --staged # index vs HEAD
git diff HEAD # working tree + index vs HEAD
git diff <a>..<b>
git diff <a>...<b> # changes on branch b since it diverged from a

Undoing things / recovery

Unstage / discard

git reset # unstage all
git reset HEAD <file>
git restore --staged <file>
git checkout -- <file> # discard changes in working directory (legacy)
git restore <file> # discard changes (recommended)

Reset commits

git reset --soft HEAD~1 # undo commit, keep changes staged
git reset --mixed HEAD~1 # undo commit, keep changes unstaged (default)
git reset --hard HEAD~1 # undo commit and discard changes

Revert (safe for shared branches)

git revert <commit>
git revert <old>..<new> # revert a range (creates multiple commits)

Recover lost work

git reflog
git fsck --lost-found

Remove untracked files & directories

git clean -n -d # dry run
git clean -fd # remove untracked files + dirs
git clean -fX # remove ignored files
git clean -fx # remove ignored and non-ignored files

Stash usage

git stash // stash current changes
git stash pop // pop last stash and remove from history
git stash save 'message' // stash current changes with message
git stash list // show all stashes
git stash apply <id> // apply <id> stash
git stash drop <id> // delete one stash
git stash clear // delete all stashes

Log

$ git log --follow --pretty=oneline things/text.txt view log of the renamed file

Common log views

git log --oneline --decorate --graph --all
git show <commit>
git show <commit>:<path/to/file>
git blame <file>

Search history

git log -S "searchText" # pickaxe
git log -G "regex" # regex search in diffs
git log -- path/to/file # file history
git log --since="2024-01-01" --until="2024-02-01"
git log --author="zb@bndy.net" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -

Output:

added lines: 545140, removed lines: 1152979, total lines: -607839
git log --pretty=format:%h,%an,%ae,%ad,%s --name-only

Output:

acb533d,Bendy Zhang,zb@bndy.net,Sun Jul 12 16:17:14 2020 +0800,commit message
.gitignore
README.md
  • -since=<date>, --after=<date> Show commits more recent than a specific date.
  • -until=<date>, --before=<date> Show commits older than a specific date.
  • --author=<pattern>, --committer=<pattern> Limit the commits output to ones with author/committer header lines that match the specified pattern (regular expression). With more than one -author=<pattern>, commits whose author matches any of the given patterns are chosen (similarly for multiple -committer=<pattern>).

Pretty formats

Tag

# create tag
git tag -a <tagname> -m '<tagcomment>'

# show tags
git tag
git tag -n

# show specified tag
git show <tagname>

# push to remote
git push origin <tagname>

# push all tags to remote
git push origin --tags

# checkout tag
git checkout <tagname>

# delete tag
git tag -d <tagname>

# delete remote tag
git push origin <tagname>
git push --delete origin tagname

Clone subfolder (sparse-checkout)

By following ways, you will get the folder fastest that you want.

git clone -n --depth=1 --filter=blob:none git@github.com:bndynet/bndynet.git
cd bndynet
git sparse-checkout set "/_data/*" --no-cone
git checkout

Or

git init
git sparse-checkout init --no-cone
git sparse-checkout set "/_data/*" --no-cone
git remote add origin git@github.com:bndynet/bndynet.git
git config core.sparsecheckout true
echo "partialclonefilter = blob:none" >> .git/config
git pull --depth=1 origin master

Subtree

# with squash, this repo history commits will not be merged into parent repo
git subtree add --prefix=subfolder https://github.com/subrepo.git master --squash
git subtree push --prefix=dist https://github.com/subrepo.git gh-pages

Submodule

git submodule add submodule-repo path
git submodule update --init --recursive
git submodule status
git submodule sync --recursive
git rm submodule-name
git rm submodule-name --cached
# update submodule to master
cd submodule_folder
git checkout master
git pull
cd ../
git add .
git commit -m ''

Workflow (fork / PR)

with Git: Fork, Branching, Commits, and Pull Request

  1. Fork a repo.

  2. Clone the forked project to your local machine:

    git clone git@github.com:USERNAME/<forked_repo>

  3. Configure remotes:

    git remote add upstream git://github.com/<origin_repo>

  4. Create a branch for new feature:

    git checkout -b my-new-branch

  5. Develop on my-new-branch branch only, but Do not merge my-new-branch branch to the your master (as it should stay equal to upstream master)!!

  6. Commit changes to my-new-branch branch:

    git add .
    git commit -m "commit message"
  7. Push branch to GitHub, to allow your mentor to review your code:

    $ git push origin my-new-branch

  8. Repeat steps 5-7 till development is complete.

  9. Fetch upstream changes that were done by other contributors:

    $ git fetch upstream

  10. Update local master:

git checkout master
git pull upstream master

ATTENTION: any time you lost of track of your code – launch “gitk —all” in source folder, UI application come up that will show all branches and history in pretty view, explanation.

  1. Rebase my-new-branch branch on top of the upstream master:
git checkout my-new-branch
git rebase master
  1. In the process of the rebase, it may discover conflicts. In that case it will stop and allow you to fix the conflicts. After fixing conflicts, use git add . to update the index with those contents, and then just run:
git rebase --continue
  1. Push branch to GitHub (with all your final changes and actual code):

We forcing changes to your issue branch(our sand box) is not common branch, and rebasing means recreation of commits so no way to push without force. NEVER force to common branch.

git push origin my-new-branch --force
  1. Created build for testing and send it to any mentor for testing.
  2. Only after all testing is done – Send a Pull Request.

Attention: Please recheck that in your pull request you send only your changes, and no other changes!! Check it by command: git diff my-new-branch upstream/master

QA / troubleshooting

.gitignore can not ignore a file

Need run following command to remove all files from the repository and add them back.

git rm -rf --cached .
git add .

Fix line endings issues (CRLF/LF)

git config --global core.autocrlf input # mac/linux
git config --global core.autocrlf true # windows (typical)
git config --global core.safecrlf warn

Remove file from history (BFG / filter-repo)

# Prefer git-filter-repo (recommended modern tool)
# After rewriting history, force-push is required.
git filter-repo --path <path> --invert-paths
git push --force --all
git push --force --tags

Common alias

git config --global alias.s "status"
git config --global alias.a "\!git add . && git status"
git config --global alias.au "\!git add -u . && git status"
git config --global alias.aa "\!git add . && git add -u . && git status"
git config --global alias.b "branch"
git config --global alias.c "commit"
git config --global alias.cm "commit -m"
git config --global alias.ca "commit --amend"
git config --global alias.ac "\!git add . && git commit"
git config --global alias.acm "\!git add . && git commit -m"
git config --global alias.l "log --graph --all --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset'"
git config --global alias.ll "log --stat --abbrev-commit"
git config --global alias.lg "log --color --graph --pretty=format:'%C(bold white)%h%Creset -%C(bold green)%d%Creset %s %C(bold green)(%cr)%Creset %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative"
git config --global alias.llg "log --color --graph --pretty=format:'%C(bold white)%H %d%Creset%n%s%n%+b%C(bold blue)%an <%ae>%Creset %C(bold green)%cr (%ci)' --abbrev-commit"
git config --global alias.d "diff"
git config --global alias.master "checkout master"
git config --global alias.main "checkout main"
git config --global alias.alias "\!git config --list | grep 'alias\.' | sed 's/alias\.\([^=]*\)=\(.*\)/\1\ => \2/' | sort"
git config --global alias.hi "\!echo 'Hello World!'"
git config --global --unset alias.hi

Global Error Handling

· One min read

Open in Notion

global-error-handler.ts

import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHandler, Injectable, NgZone } from '@angular/core';

@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor() {}

public handleError(error: any): void {
// Check if it's an error from an HTTP response
const isServerError = error instanceof HttpErrorResponse;

if (!isServerError) {
// TODO
}

console.error('Error from Global Error Handler', error);
}
}

app.module.ts

@NgModule({
declarations: [AppComponent],
imports: [
CommonModule,
HttpClientModule,
],
providers: [
{ provide: ErrorHandler, useClass: GlobalErrorHandler },
],
bootstrap: [AppComponent],
})
export class AppModule {}

Setup

· One min read

Open in Notion

Mirrors

Add mirror in conf/setting.xml file in Maven root folder:

<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>

Ruby

· One min read

Open in Notion

Installing

bookmark

gem sources -l
gem sources --remove https://ruby.taobao.org/
gem sources --add https://gems.ruby-china.com/

Installing on M1 Apple for macOS 13 Ventura

brew install chruby ruby-install

Next, if you’re on an Apple Silicon Mac (M1 or M2), you need to check which version of the Apple Command Line Tools (CLT) or Xcode you have:

brew config

Look for the lines at the bottom that start with CLT:  and Xcode: . If either one of them starts with 14 , then you’ll need to install Ruby like this:

ruby-install ruby -- --enable-shared

Otherwise, don’t use any extra options:

ruby-install ruby

Nginx

· 5 min read

Open in Notion

Run in Docker

sudo docker pull nginx
sudo docker run --name my-nginx -p 8080:80 -v /some/content:/usr/share/nginx/html:ro -d nginx
sudo chmod -R 755 /some/content

Install Nginx on CentOS 8

1. Update the System:

First, update your system to ensure all packages are up to date.

sudo dnf update -y

2. Add the Nginx Repository

CentOS 8 doesn't include the latest Nginx packages in its default repositories. To get the latest version, add the official Nginx repository.

Create a repository file for Nginx:

sudo nano /etc/yum.repos.d/nginx.repo

Add the following content to the file:

[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key

The nginx-stable repository is enabled by default, while the nginx-mainline repository is not. You can enable the nginx-mainline repository by setting enabled=1 if you prefer to use the mainline version of Nginx.

3. Install Nginx:

Install Nginx using the dnf package manager.

sudo dnf install nginx -y

4. Start and Enable Nginx:

Once the installation is complete, start the Nginx service and enable it to start on boot.

sudo systemctl start nginx
sudo systemctl enable nginx

5. Verify Nginx Installation:

You can verify that Nginx is running by checking its status.

sudo systemctl status nginx

6. Adjust Firewall Settings:

If you have a firewall enabled, you'll need to allow traffic on HTTP (port 80) and HTTPS (port 443) ports.

sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload

Configuration for static website based on framework

server {
listen 80;
server_name yourdomain.com;

root /var/www/angular-app;
index index.html;

location / {
try_files $uri /index.html;
}

error_page 404 /index.html;

gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}

Logs

sudo tail -f /var/log/nginx/error.log

Distribute Domains to Ports

server {
listen 80;
server_name domain1.com;

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

server {
listen 80;
server_name domain2.com;

location / {
proxy_pass http://localhost:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

Use map:

http {
map $host $backend {
default localhost:80;
domain1.com localhost:8080;
domain2.com localhost:8081;
}

server {
listen 80;

location / {
proxy_pass http://$backend;
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;
}
}
}

Certificate in Nginx

1. Obtain an SSL/TLS Certificate

You can obtain a certificate from a Certificate Authority (CA) like Let's Encrypt, which provides free SSL/TLS certificates. Using Certbot, you can automate the process of obtaining and renewing certificates

Install Certbot:

sudo dnf install certbot python3-certbot-nginx -y

Obtain a Certificate: Replace your_domain.com with your actual domain name.

sudo certbot --nginx -d your_domain.com

Follow the prompts to complete the certificate issuance process. Certbot will automatically configure Nginx for SSL.

2. Manually Configuring Nginx with an SSL/TLS Certificate

If you have an existing certificate or need to configure it manually, follow these steps:

Create a Configuration File for Your Site:

sudo nano /etc/nginx/conf.d/your_domain.conf

Add SSL Configuration: Replace your_domain.com with your actual domain name and specify the paths to your certificate and key files.

server {
listen 80;
server_name your_domain.com;
return 301 https://$host$request_uri; # Redirect HTTP to HTTPS
}

server {
listen 443 ssl;
server_name your_domain.com;

ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

location / {
proxy_pass http://backend_server; # Change to your backend application address
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;
}
}

3. Configure Firewall

Ensure your firewall allows HTTPS traffic:

sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload

4. Test and Reload Nginx

Test Nginx Configuration:

sudo nginx -t

Reload Nginx to Apply Changes:

sudo systemctl reload nginx

Q&A

Error about socket 80

nginx: [emerg] socket() [::]:80 failed (97: Unknown error)

Nginx try to listen the IPV6 [::]:80. Comment below line from the configuration /etc/nginx/sites-available/default.

# listen [::]:80 default_server;

Permission denied

Let www-data user have the permission to read the folder.

sudo chown -R www-data:www-data /your/site
sudo chmod 755 /your/site

If you still have same error, maybe your path under some user home folder. You should change the home folder permission.

sudo chown -R username:www-data /home/username
sudo chmod -R 750 /home/username
sudo chmod -R 755 /home/username/public_html

Service Operations on CentOS

· One min read

Open in Notion

$ systemctl disable httpd
rm '/etc/systemd/system/multi-user.target.wants/httpd.service'

$ systemctl status httpd
httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service;
disabled
)
$ systemctl enable httpd
ln -s '/usr/lib/systemd/system/httpd.service' '/etc/systemd/system/multi-user.target.wants/httpd.service'

$ systemctl status httpd
httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service;
enabled
)

Docker Q&A

· One min read

Open in Notion

IPv4 forwarding is disabled. Networking will not work

Solution 1: This won’t work every time

// run on host server
sysctl -w net.ipv4.ip_forward=1
sudo systemctl restart network

Solution 2: Append net.ipv4.ip_forward=1 to /etc/sysctl.conf file.

Solution 3: Append parameter —net=host to docker run, that will use host ports and ignore the -p parameters for example:

docker run -it --network host –name mynginx nginx

/var/jenkins_home/copy_reference_file.log Permission denied

$ docker run -ti -p 8080:8080 -p 50000:50000 -v /opt/jenkins:/var/jenkins_home jenkins touch: cannot touch ‘/var/jenkins_home/copy_reference_file.log’: Permission denied Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?

If you meet the above error, please figure out your volume mapping permissions.

sudo chown -R 1000:1000 /opt/jenkins