Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
1ed542c2
Commit
1ed542c2
authored
Mar 17, 2026
by
johnnyjoygh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add GitHub release installer and release workflow
parent
12e2205c
Changes
4
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
612 additions
and
184 deletions
+612
-184
build-stable-image.yml
.github/workflows/build-stable-image.yml
+0
-184
release.yml
.github/workflows/release.yml
+352
-0
README.md
README.md
+12
-0
install.sh
scripts/install.sh
+248
-0
No files found.
.github/workflows/build-stable-image.yml
deleted
100644 → 0
View file @
12e2205c
name
:
Build Stable Image
on
:
push
:
branches
:
-
"
release/**"
tags
:
-
"
v*.*.*"
jobs
:
prepare
:
runs-on
:
ubuntu-latest
outputs
:
version
:
${{ steps.version.outputs.version }}
steps
:
-
name
:
Extract version
id
:
version
run
:
|
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
else
echo "version=${GITHUB_REF_NAME#release/}" >> $GITHUB_OUTPUT
fi
build-frontend
:
runs-on
:
ubuntu-latest
steps
:
-
uses
:
actions/checkout@v6
-
uses
:
pnpm/action-setup@v4.2.0
with
:
version
:
10
-
uses
:
actions/setup-node@v6
with
:
node-version
:
"
24"
cache
:
pnpm
cache-dependency-path
:
"
web/pnpm-lock.yaml"
-
name
:
Get pnpm store directory
id
:
pnpm-cache
shell
:
bash
run
:
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
-
name
:
Setup pnpm cache
uses
:
actions/cache@v5
with
:
path
:
${{ steps.pnpm-cache.outputs.STORE_PATH }}
key
:
${{ runner.os }}-pnpm-store-${{ hashFiles('web/pnpm-lock.yaml') }}
restore-keys
:
${{ runner.os }}-pnpm-store-
-
run
:
pnpm install --frozen-lockfile
working-directory
:
web
-
name
:
Run frontend build
run
:
pnpm release
working-directory
:
web
-
name
:
Upload frontend artifacts
uses
:
actions/upload-artifact@v6
with
:
name
:
frontend-dist
path
:
server/router/frontend/dist
retention-days
:
1
build-push
:
needs
:
[
prepare
,
build-frontend
]
runs-on
:
ubuntu-latest
permissions
:
contents
:
read
packages
:
write
strategy
:
fail-fast
:
false
matrix
:
platform
:
-
linux/amd64
-
linux/arm/v7
-
linux/arm64
steps
:
-
uses
:
actions/checkout@v6
-
name
:
Download frontend artifacts
uses
:
actions/download-artifact@v7
with
:
name
:
frontend-dist
path
:
server/router/frontend/dist
-
name
:
Set up QEMU
uses
:
docker/setup-qemu-action@v3
-
name
:
Set up Docker Buildx
uses
:
docker/setup-buildx-action@v3
-
name
:
Login to Docker Hub
uses
:
docker/login-action@v3
with
:
username
:
${{ secrets.DOCKER_HUB_USERNAME }}
password
:
${{ secrets.DOCKER_HUB_TOKEN }}
-
name
:
Login to GitHub Container Registry
uses
:
docker/login-action@v3
with
:
registry
:
ghcr.io
username
:
${{ github.actor }}
password
:
${{ github.token }}
-
name
:
Build and push by digest
id
:
build
uses
:
docker/build-push-action@v6
with
:
context
:
.
file
:
./scripts/Dockerfile
platforms
:
${{ matrix.platform }}
cache-from
:
type=gha,scope=build-${{ matrix.platform }}
cache-to
:
type=gha,mode=max,scope=build-${{ matrix.platform }}
outputs
:
type=image,name=neosmemo/memos,push-by-digest=true,name-canonical=true,push=true
-
name
:
Export digest
run
:
|
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
-
name
:
Upload digest
uses
:
actions/upload-artifact@v6
with
:
name
:
digests-${{ strategy.job-index }}
path
:
/tmp/digests/*
if-no-files-found
:
error
retention-days
:
1
merge
:
needs
:
[
prepare
,
build-push
]
runs-on
:
ubuntu-latest
permissions
:
contents
:
read
packages
:
write
steps
:
-
name
:
Download digests
uses
:
actions/download-artifact@v7
with
:
pattern
:
digests-*
merge-multiple
:
true
path
:
/tmp/digests
-
name
:
Set up Docker Buildx
uses
:
docker/setup-buildx-action@v3
-
name
:
Docker meta
id
:
meta
uses
:
docker/metadata-action@v5
with
:
images
:
|
neosmemo/memos
ghcr.io/usememos/memos
tags
:
|
type=semver,pattern={{version}},value=${{ needs.prepare.outputs.version }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.prepare.outputs.version }}
type=raw,value=stable
flavor
:
|
latest=false
labels
:
|
org.opencontainers.image.version=${{ needs.prepare.outputs.version }}
-
name
:
Login to Docker Hub
uses
:
docker/login-action@v3
with
:
username
:
${{ secrets.DOCKER_HUB_USERNAME }}
password
:
${{ secrets.DOCKER_HUB_TOKEN }}
-
name
:
Login to GitHub Container Registry
uses
:
docker/login-action@v3
with
:
registry
:
ghcr.io
username
:
${{ github.actor }}
password
:
${{ github.token }}
-
name
:
Create manifest list and push
working-directory
:
/tmp/digests
run
:
|
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'neosmemo/memos@sha256:%s ' *)
env
:
DOCKER_METADATA_OUTPUT_JSON
:
${{ steps.meta.outputs.json }}
-
name
:
Inspect images
run
:
|
docker buildx imagetools inspect neosmemo/memos:stable
docker buildx imagetools inspect ghcr.io/usememos/memos:stable
.github/workflows/
build-binaries
.yml
→
.github/workflows/
release
.yml
View file @
1ed542c2
This diff is collapsed.
Click to expand it.
README.md
View file @
1ed542c2
...
@@ -70,6 +70,18 @@ docker run -d \
...
@@ -70,6 +70,18 @@ docker run -d \
Open
`http://localhost:5230`
and start writing!
Open
`http://localhost:5230`
and start writing!
### Native Binary
```
bash
curl
-fsSL
https://raw.githubusercontent.com/usememos/memos/main/scripts/install.sh | sh
```
To install a specific version:
```
bash
curl
-fsSL
https://raw.githubusercontent.com/usememos/memos/main/scripts/install.sh | sh
-s
--
--version
<version>
```
### Try the Live Demo
### Try the Live Demo
Don't want to install yet? Try our
[
live demo
](
https://demo.usememos.com/
)
first!
Don't want to install yet? Try our
[
live demo
](
https://demo.usememos.com/
)
first!
...
...
scripts/install.sh
0 → 100755
View file @
1ed542c2
#!/bin/sh
set
-eu
REPO
=
"
${
REPO
:-
usememos
/memos
}
"
BIN_NAME
=
"memos"
VERSION
=
"
${
MEMOS_VERSION
:-}
"
INSTALL_DIR
=
"
${
MEMOS_INSTALL_DIR
:-}
"
SKIP_CHECKSUM
=
"
${
MEMOS_SKIP_CHECKSUM
:-
0
}
"
usage
()
{
cat
<<
'
EOF
'
Install Memos from GitHub Releases.
Usage:
install.sh [--version <version>] [--install-dir <dir>] [--skip-checksum]
Environment:
MEMOS_VERSION Version to install without the leading "v". Defaults to latest release.
MEMOS_INSTALL_DIR Directory to install the binary into.
MEMOS_SKIP_CHECKSUM Set to 1 to skip checksum verification.
REPO GitHub repository in owner/name form. Defaults to usememos/memos.
Examples:
curl -fsSL https://raw.githubusercontent.com/usememos/memos/main/scripts/install.sh | sh
curl -fsSL https://raw.githubusercontent.com/usememos/memos/main/scripts/install.sh | sh -s -- --version 0.28.1
EOF
}
log
()
{
printf
'%s\n'
"
$*
"
}
fail
()
{
printf
'Error: %s\n'
"
$*
"
>
&2
exit
1
}
need_cmd
()
{
command
-v
"
$1
"
>
/dev/null 2>&1
||
fail
"required command not found:
$1
"
}
resolve_latest_version
()
{
latest_url
=
"
$(
curl
-fsSL
-o
/dev/null
-w
'%{url_effective}'
"https://github.com/
${
REPO
}
/releases/latest"
)
"
latest_tag
=
"
${
latest_url
##*/
}
"
[
-n
"
$latest_tag
"
]
||
fail
"failed to resolve latest release tag"
printf
'%s\n'
"
${
latest_tag
#v
}
"
}
detect_os
()
{
os
=
"
$(
uname
-s
|
tr
'[:upper:]'
'[:lower:]'
)
"
case
"
$os
"
in
linux
)
printf
'linux\n'
;;
darwin
)
printf
'darwin\n'
;;
*
)
fail
"unsupported operating system:
$os
"
;;
esac
}
detect_arch
()
{
arch
=
"
$(
uname
-m
)
"
case
"
$arch
"
in
x86_64|amd64
)
printf
'amd64\n'
;;
arm64|aarch64
)
printf
'arm64\n'
;;
armv7l|armv7
)
printf
'armv7\n'
;;
*
)
fail
"unsupported architecture:
$arch
"
;;
esac
}
resolve_install_dir
()
{
if
[
-n
"
$INSTALL_DIR
"
]
;
then
printf
'%s\n'
"
$INSTALL_DIR
"
return
fi
if
[
-w
"/usr/local/bin"
]
;
then
printf
'/usr/local/bin\n'
return
fi
if
command
-v
sudo
>
/dev/null 2>&1
;
then
printf
'/usr/local/bin\n'
return
fi
printf
'%s/.local/bin\n'
"
$HOME
"
}
download
()
{
src
=
"
$1
"
dest
=
"
$2
"
curl
-fsSL
"
$src
"
-o
"
$dest
"
}
verify_checksum
()
{
archive_path
=
"
$1
"
checksum_path
=
"
$2
"
if
[
"
$SKIP_CHECKSUM
"
=
"1"
]
;
then
log
"Skipping checksum verification"
return
fi
archive_name
=
"
$(
basename
"
$archive_path
"
)
"
expected_line
=
"
$(
grep
"
${
archive_name
}
\$
"
"
$checksum_path
"
||
true
)
"
[
-n
"
$expected_line
"
]
||
fail
"checksum entry not found for
${
archive_name
}
"
if
command
-v
sha256sum
>
/dev/null 2>&1
;
then
(
cd
"
$(
dirname
"
$archive_path
"
)
"
printf
'%s\n'
"
$expected_line
"
|
sha256sum
-c
-
)
return
fi
if
command
-v
shasum
>
/dev/null 2>&1
;
then
expected_sum
=
"
$(
printf
'%s'
"
$expected_line
"
|
awk
'{print $1}'
)
"
actual_sum
=
"
$(
shasum
-a
256
"
$archive_path
"
|
awk
'{print $1}'
)
"
[
"
$expected_sum
"
=
"
$actual_sum
"
]
||
fail
"checksum verification failed for
${
archive_name
}
"
return
fi
log
"Warning: sha256sum/shasum not found; skipping checksum verification"
}
extract_archive
()
{
archive_path
=
"
$1
"
dest_dir
=
"
$2
"
tar
-xzf
"
$archive_path
"
-C
"
$dest_dir
"
}
install_binary
()
{
src
=
"
$1
"
dest_dir
=
"
$2
"
mkdir
-p
"
$dest_dir
"
if
[
-w
"
$dest_dir
"
]
;
then
install
-m
755
"
$src
"
"
${
dest_dir
}
/
${
BIN_NAME
}
"
return
fi
if
command
-v
sudo
>
/dev/null 2>&1
;
then
sudo mkdir
-p
"
$dest_dir
"
sudo install
-m
755
"
$src
"
"
${
dest_dir
}
/
${
BIN_NAME
}
"
return
fi
fail
"install directory is not writable:
$dest_dir
"
}
parse_args
()
{
while
[
"$#"
-gt
0
]
;
do
case
"
$1
"
in
--version
)
[
"$#"
-ge
2
]
||
fail
"missing value for --version"
VERSION
=
"
$2
"
shift
2
;;
--install-dir
)
[
"$#"
-ge
2
]
||
fail
"missing value for --install-dir"
INSTALL_DIR
=
"
$2
"
shift
2
;;
--skip-checksum
)
SKIP_CHECKSUM
=
"1"
shift
;;
-h
|
--help
)
usage
exit
0
;;
*
)
fail
"unknown argument:
$1
"
;;
esac
done
}
main
()
{
parse_args
"
$@
"
need_cmd curl
need_cmd
tar
need_cmd
install
need_cmd
uname
need_cmd
grep
need_cmd
awk
os
=
"
$(
detect_os
)
"
arch
=
"
$(
detect_arch
)
"
if
[
-z
"
$VERSION
"
]
;
then
VERSION
=
"
$(
resolve_latest_version
)
"
fi
install_dir
=
"
$(
resolve_install_dir
)
"
tag
=
"v
${
VERSION
}
"
asset_suffix
=
"
${
arch
}
"
if
[
"
$arch
"
=
"armv7"
]
;
then
asset_suffix
=
"armv7"
fi
asset_name
=
"
${
BIN_NAME
}
_
${
VERSION
}
_
${
os
}
_
${
asset_suffix
}
.tar.gz"
checksums_name
=
"checksums.txt"
base_url
=
"https://github.com/
${
REPO
}
/releases/download/
${
tag
}
"
tmpdir
=
"
$(
mktemp
-d
)
"
trap
'rm -rf "$tmpdir"'
EXIT INT TERM
archive_path
=
"
${
tmpdir
}
/
${
asset_name
}
"
checksums_path
=
"
${
tmpdir
}
/
${
checksums_name
}
"
extract_dir
=
"
${
tmpdir
}
/extract"
mkdir
-p
"
$extract_dir
"
log
"Installing
${
BIN_NAME
}
${
VERSION
}
for
${
os
}
/
${
arch
}
"
download
"
${
base_url
}
/
${
asset_name
}
"
"
$archive_path
"
download
"
${
base_url
}
/
${
checksums_name
}
"
"
$checksums_path
"
verify_checksum
"
$archive_path
"
"
$checksums_path
"
extract_archive
"
$archive_path
"
"
$extract_dir
"
[
-f
"
${
extract_dir
}
/
${
BIN_NAME
}
"
]
||
fail
"archive did not contain
${
BIN_NAME
}
"
install_binary
"
${
extract_dir
}
/
${
BIN_NAME
}
"
"
$install_dir
"
log
"Installed
${
BIN_NAME
}
to
${
install_dir
}
/
${
BIN_NAME
}
"
if
!
command
-v
"
${
install_dir
}
/
${
BIN_NAME
}
"
>
/dev/null 2>&1
&&
!
printf
'%s'
":
$PATH
:"
|
grep
-q
":
${
install_dir
}
:"
;
then
log
"Add
${
install_dir
}
to your PATH to run
${
BIN_NAME
}
directly"
fi
}
main
"
$@
"
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment