diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4231d43 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +* +!extra/ +!scripts/ +!src/ +!go.mod +!go.sum +!LICENSE +!Makefile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 38f186e..54e7fb5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,6 +22,8 @@ jobs: go-version: 1.16 - name: Test run: make test + # Keep in mind that tags should only be in one of the following formats + # vVERSION or vVERSION-RELEASE e.g. v0.8.5 or v0.8.5-1 - name: Set environment variables run: echo "CURRENT_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV # run: |- diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a3829e0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.16.2-buster + +RUN apt-get update &&\ + apt-get install -y python3 python3-yaml &&\ + apt-get clean &&\ + rm -rf /var/lib/apt/lists/* + +WORKDIR /go/src/app + +COPY ["go.mod", "go.sum", "./"] +RUN go mod download + +COPY . . + +ARG CURRENT_TAG +ENV CURRENT_TAG=${CURRENT_TAG} +ARG COMMIT_DATE +ENV COMMIT_DATE=${COMMIT_DATE} + +RUN python3 scripts/build.py diff --git a/Makefile b/Makefile index a3f3595..9864565 100644 --- a/Makefile +++ b/Makefile @@ -12,9 +12,9 @@ default: set -e ;\ os_release_id=$$(grep -E '^ID=' /etc/os-release | sed 's/ID=//' || true) ;\ if [ "$$os_release_id" = "arch" ]; then \ - make --no-print-directory release-dynamic ;\ + make --no-print-directory release type=dynamic ;\ else \ - make --no-print-directory release-static ;\ + make --no-print-directory release type=static ;\ fi ;\ } @@ -22,7 +22,7 @@ start: go run $(GO_FILE) clean: - go clean + go clean || true rm -rf dist/* rm -f "$(OUT_FILE)" @@ -31,37 +31,52 @@ build: go build -v -o $(OUT_FILE) $(GO_FILE) @ls -lh "$(OUT_FILE)" +release: + @{\ + set -e ;\ + if [ "$(type)" != "static" ] && [ "$(type)" != "dynamic" ]; then \ + echo "The type parameter must be \"static\" or \"dynamic\"" ;\ + exit 1 ;\ + fi ;\ + echo "Building $(type) binary (GOARCH: $(GOARCH) GOARM: $(GOARM))..." ;\ + if [ -z "$${skip_clean}" ]; then make --no-print-directory clean; fi ;\ + export VERSION_FLAGS="-X main.Version=$$(make --no-print-directory version) -X main.CommitDate=$$(make --no-print-directory commit-date)" ;\ + case "$(type)" in \ + static) \ + make --no-print-directory release-static ;\ + ;;\ + dynamic) \ + make --no-print-directory release-dynamic ;\ + ;;\ + esac ;\ + } + @make --no-print-directory postbuild + # Build statically linked production binary release-static: - @echo "Building static binary (GOARCH: $(GOARCH) GOARM: $(GOARM))..." @{\ set -e ;\ - if [ -z "$${skip_clean}" ]; then make --no-print-directory clean; fi ;\ - export GOFLAGS="-a -trimpath -ldflags=-w -ldflags=-s" ;\ + args=(-a -trimpath -ldflags "-w -s $${VERSION_FLAGS}") ;\ if [ "$${GOARCH}" != "arm" ]; then \ - export GOFLAGS="$${GOFLAGS} -buildmode=pie" ;\ + args+=("-buildmode=pie") ;\ fi ;\ - CGO_ENABLED=0 go build -o "$(OUT_FILE)" $(GO_FILE) ;\ + CGO_ENABLED=0 go build "$${args[@]}" -o "$(OUT_FILE)" $(GO_FILE) ;\ } - @make --no-print-directory postbuild # Build dinamically linked production binary release-dynamic: - @echo "Building dynamic binary (GOARCH: $(GOARCH) GOARM: $(GOARM))..." @{\ set -e ;\ - if [ -z "$${skip_clean}" ]; then make --no-print-directory clean; fi ;\ export CGO_CPPFLAGS="$${CPPFLAGS}" ;\ export CGO_CFLAGS="$${CFLAGS}" ;\ export CGO_CXXFLAGS="$${CXXFLAGS}" ;\ export CGO_LDFLAGS="$${LDFLAGS}" ;\ - export GOFLAGS="-a -trimpath -ldflags=-linkmode=external -ldflags=-w -ldflags=-s" ;\ + args=(-a -trimpath -ldflags "-linkmode external -w -s $${VERSION_FLAGS}") ;\ if [ "$${GOARCH}" != "arm" ]; then \ - export GOFLAGS="$${GOFLAGS} -buildmode=pie" ;\ + args+=("-buildmode=pie") ;\ fi ;\ - go build -o "$(OUT_FILE)" $(GO_FILE) ;\ + go build "$${args[@]}" -o "$(OUT_FILE)" $(GO_FILE) ;\ } - @make --no-print-directory postbuild postbuild: @{\ @@ -87,6 +102,46 @@ postbuild: } @ls -lh "$(OUT_FILE)"* +docker: + @{\ + set -e ;\ + image_name=$(MODULE)_$$(openssl rand -hex 8) ;\ + container_name=$(MODULE)_$$(openssl rand -hex 8) ;\ + docker build \ + --build-arg "CURRENT_TAG=$${CURRENT_TAG}" \ + --build-arg "COMMIT_DATE=$$(make --no-print-directory commit-date)" \ + --tag $${image_name} . ;\ + docker run -d --rm --name $${container_name} --entrypoint tail $${image_name} -f /dev/null ;\ + make --no-print-directory clean ;\ + docker cp $${container_name}:/go/src/app/dist . ;\ + docker stop -t 0 $${container_name} ;\ + docker rmi --no-prune $${image_name} ;\ + } + +# Print the value of the VERSION variable if available, otherwise get version +# based on the latest git tag +version: + @{\ + set -e ;\ + if [ ! -z "$$VERSION" ]; then \ + echo "$$VERSION" ;\ + exit 0 ;\ + fi ;\ + git describe --tags | sed -r 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/' ;\ + } + +# Print the value of the COMMIT_DATE variable if available, otherwise get commit +# date from the last git commit +commit-date: + @{\ + set -e ;\ + if [ ! -z "$$COMMIT_DATE" ]; then \ + echo "$$COMMIT_DATE" ;\ + exit 0 ;\ + fi ;\ + git log -1 --no-merges --format=%cI ;\ + } + # Run unit tests on all packages test: go test -v ./src/... diff --git a/README.md b/README.md index 85ba0e1..37d131b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Automatically setup swap on zram ✨ -## Reasons to swap on zram +## Why swap on zram? * Significantly improves system responsiveness, especially when swap is under pressure. * More secure, user data leaks into swap are on volatile media. @@ -11,13 +11,6 @@ Automatically setup swap on zram ✨ See also https://fedoraproject.org/wiki/Changes/SwapOnZRAM#Benefit_to_Fedora -## Compiling - -* Install `go`, this depends on the distribution you are using e.g. for Ubuntu the command should be `sudo apt-get install golang`. -* Run `make release` to make a x86_64 build, to make an ARM build (i.e. for the Raspberry Pi) run `GOOS=linux GOARCH=arm GOARM=7 make release` -* A new executable called `zramd.bin` will be created under the `dist/` directory, now you can uninstall `go` if you like. -* Optionally on distributions using systemd, you can install `zramd` by just running `make install`, see below for additional installation methods. - ## Installation ### Install on Arch Linux from the AUR @@ -28,20 +21,17 @@ See also https://fedoraproject.org/wiki/Changes/SwapOnZRAM#Benefit_to_Fedora sudo systemctl enable --now zramd ``` -### Manual installation on any distribution with systemd +### Install on Ubuntu / Debian / Raspberry Pi OS -* Copy the `zramd` binary to `/usr/local/bin`. -* Copy the `extra/zramd.service` file to `/etc/systemd/system`. -* Reload and start the service: - ```bash - sudo systemctl daemon-reload - sudo systemctl enable --now zramd - ``` +* Head to the releases section and download the `.deb` file corresponding to your system architecture +* Install using `sudo dpkg -i DEB_FILE` ### Manual installation on any distribution without systemd +* Head to the releases section and download the `.tar.gz` file corresponding to your system architecture +* Extract the downloaded file e.g. `tar xf TAR_FILE` * Copy the `zramd` binary to `/usr/local/bin`. -* Depending on your init system there are various ways to set it up to autostart, if you are using Raspberry Pi OS, you can simply add a line to `/etc/rc.local` e.g. +* There are various ways to setup autostart depending on your init system, for example you can add a line to `/etc/rc.local` e.g. ```bash /usr/local/bin/zramd start ``` @@ -50,9 +40,10 @@ See also https://fedoraproject.org/wiki/Changes/SwapOnZRAM#Benefit_to_Fedora * zramd --help ``` - Usage: zramd [] + Usage: zramd [--version] [] Options: + --version print program version --help, -h display this help and exit Commands: @@ -78,22 +69,37 @@ See also https://fedoraproject.org/wiki/Changes/SwapOnZRAM#Benefit_to_Fedora --help, -h display this help and exit ``` +## Compilation + +### With Docker + +* Choose a valid git tag and run the `make docker` command e.g. + ```bash + CURRENT_TAG=v0.8.5 make docker + ``` + +### Manual Compilation + +* Install `go`, this depends on the distribution you are using e.g. for Ubuntu the command should be `sudo apt-get install golang`. +* Run `make release` to make a x86_64 build, to make an ARM build (i.e. for the Raspberry Pi) run `GOOS=linux GOARCH=arm GOARM=7 make release` +* A new executable called `zramd.bin` will be created under the `dist/` directory, now you can uninstall `go` if you like. + ## Configuration ### With systemd -The default configuration file is located at `/etc/default/zramd`, just edit the variables as you like and restart the `zramd` service i.e. `sudo systemctl restart zramd`. +* The default configuration file is located at `/etc/default/zramd`, just edit the variables as you like and restart the `zramd` service i.e. `sudo systemctl restart zramd` ### Without systemd -Just change the arguments as you like, e.g. `zramd start --max-size 1024` or `zramd start --percent 0.5 --priority 0`. +* Just change the arguments as you like, e.g. `zramd start --max-size 1024` or `zramd start --percent 0.5 --priority 0` ## Troubleshooting * **modprobe: FATAL: Module zram not found in directory /lib/modules/...** It can happen if you try to start the `zramd` service after a kernel upgrade, you just need to restart your computer. * **error: swapon: /dev/zramX: swapon failed: Operation not permitted** - First make sure that you are running as root (or at least that you have the required capabilities), also keep in mind that Linux only supports up to 32 swap devices (although it can start throwing the error from above when using a high value like 24 🤷‍♂). + First make sure that you are running as root (or at least that you have the required capabilities), also keep in mind that Linux only supports up to 32 swap devices (although it can start throwing the error from above when using a high value like 24). ## Notes diff --git a/scripts/build.py b/scripts/build.py index c753b13..5f13cb5 100755 --- a/scripts/build.py +++ b/scripts/build.py @@ -45,13 +45,15 @@ def clean() -> int: return subprocess.run(['make', 'clean'], env=os.environ).returncode def main() -> int: - if (ret := clean()) != 0: + ret = clean() + if ret != 0: return ret # Build all targets sequentially, building in parallel will have minimal or no # benefit and would make logging messy for target in TARGETS: - if (ret := build(*target)) != 0: + ret = build(*target) + if ret != 0: return ret # Finally write the used architectures so we can use them at later steps diff --git a/scripts/mkdeb.py b/scripts/mkdeb.py index c6b1b6a..735a47b 100755 --- a/scripts/mkdeb.py +++ b/scripts/mkdeb.py @@ -16,7 +16,7 @@ def print_error(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) -def read_config(file: str) -> str: +def read_config(file: str) -> dict: with open(file, 'r') as f: return yaml.safe_load(f) @@ -30,7 +30,8 @@ def dir_size(path: str) -> int: def parse_env(text: str, env: dict) -> str: result = text for expr, name in ENV_RE.findall(text): - if (env_val := env.get(name)) is not None: + env_val = env.get(name) + if env_val is not None: result = result.replace(expr, env_val) return result @@ -79,10 +80,12 @@ def make_deb(prefix: str, args: List[str]) -> int: return subprocess.run(final_args).returncode def main() -> int: - if not (config_file := os.environ.get('CONFIG_FILE')): + config_file = os.environ.get('CONFIG_FILE') + if not config_file: print_error('the CONFIG_FILE variable is not set') return 1 - if not (prefix := os.environ.get('PREFIX')): + prefix = os.environ.get('PREFIX') + if not prefix: print_error('the PREFIX variable is not set') return 1 @@ -91,17 +94,15 @@ def main() -> int: exist_ok=True ) - config: dict = read_config(config_file) + config = read_config(config_file) - install_cmd = ( - cmd - if (cmd := config.get('build', {}).get('install', {}).get('cmd')) - else ['make', 'install'] - ) + cmd = config.get('build', {}).get('install', {}).get('cmd') + install_cmd = cmd if cmd else ['make', 'install'] install_env = config.get('build', {}).get('install', {}).get('env', {}) for key, val in install_env.items(): install_env[key] = parse_env(val, os.environ) - if (ret := subprocess.run(install_cmd, env=install_env).returncode) != 0: + ret = subprocess.run(install_cmd, env=install_env).returncode + if ret != 0: return ret env = { @@ -119,10 +120,12 @@ def main() -> int: write_md5sums(prefix) args = config.get('build', {}).get('args', []) - if (ret := make_deb(prefix, args)) != 0: + ret = make_deb(prefix, args) + if ret != 0: return ret - if (target_name := config.get('build', {}).get('rename')): + target_name = config.get('build', {}).get('rename') + if target_name: dir_name = os.path.dirname(prefix) final_name = parse_env(target_name, env) os.rename(f"{prefix}.deb", os.path.join(dir_name, final_name)) diff --git a/src/zramd.go b/src/zramd.go index ba1cc42..3a31a59 100644 --- a/src/zramd.go +++ b/src/zramd.go @@ -12,6 +12,11 @@ import ( "github.com/alexflint/go-arg" ) +var ( + Version string = "0.0.0" + CommitDate string = "?" +) + // startCmd contains the arguments used by the start subcommand, Fraction will // be the same size as the physical memory by default, see also // https://fedoraproject.org/wiki/Changes/Scale_ZRAM_to_full_memory_size. @@ -27,8 +32,9 @@ type stopCmd struct { } type args struct { - Start *startCmd `arg:"subcommand:start" help:"load zram module and setup swap devices"` - Stop *stopCmd `arg:"subcommand:stop" help:"stop swap devices and unload zram module"` + Start *startCmd `arg:"subcommand:start" help:"load zram module and setup swap devices"` + Stop *stopCmd `arg:"subcommand:stop" help:"stop swap devices and unload zram module"` + Version bool `placeholder:"" help:"print program version"` } func errorf(format string, a ...interface{}) { @@ -141,6 +147,11 @@ func canRun() bool { } func run() int { + if len(os.Args) > 1 && os.Args[1] == "--version" { + fmt.Printf("zramd %s %s %s\n", Version, CommitDate, runtime.GOARCH) + return 0 + } + if !kernelversion.SupportsZram() { errorf("zram is not supported on kernels < 3.14") return 1