aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig3
-rw-r--r--deno/deno.json1
-rw-r--r--deno/deno.lock5
-rw-r--r--deno/tools/service.ts17
-rw-r--r--dictionary.txt1
-rw-r--r--services/docker/debian-dev/Dockerfile24
-rwxr-xr-xservices/docker/debian-dev/bootstrap/extra/setup-cmake.bash9
-rwxr-xr-xservices/docker/debian-dev/bootstrap/extra/setup-dotnet.bash10
-rwxr-xr-xservices/docker/debian-dev/bootstrap/extra/setup-llvm.bash26
-rw-r--r--services/docker/debian-dev/bootstrap/home/.bashrc117
-rw-r--r--services/docker/debian-dev/bootstrap/official.sources23
-rwxr-xr-xservices/docker/debian-dev/bootstrap/setup-apt.bash41
-rwxr-xr-xservices/docker/debian-dev/bootstrap/setup.bash56
-rw-r--r--services/docker/nginx/configs/templates/code.conf.template6
-rw-r--r--services/docker/nginx/configs/templates/root.conf.template5
-rw-r--r--services/templates/disabled/docker-compose.yaml21
-rw-r--r--services/templates/disabled/nginx/code.conf.template20
-rw-r--r--services/templates/docker-compose.yaml.template15
-rw-r--r--services/templates/envs/2fauth.env.template15
-rw-r--r--store/debian-dev/Dockerfile22
-rwxr-xr-xstore/debian-dev/setup/apt.bash33
-rw-r--r--store/debian-dev/setup/bashrc3
-rwxr-xr-xstore/debian-dev/setup/cmake.bash9
-rwxr-xr-xstore/debian-dev/setup/code-server.bash17
-rwxr-xr-xstore/debian-dev/setup/for-container.bash14
-rwxr-xr-xstore/debian-dev/setup/llvm.bash11
-rwxr-xr-xstore/debian-dev/setup/package.bash10
-rw-r--r--store/debian-dev/setup/quiltrc-dpkg (renamed from services/docker/debian-dev/bootstrap/home/.quiltrc-dpkg)0
-rwxr-xr-xstore/debian-dev/setup/user.bash11
-rw-r--r--www/content/notes/cheat-sheet.md9
30 files changed, 154 insertions, 400 deletions
diff --git a/.editorconfig b/.editorconfig
index 69ce65e..92ff780 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -18,6 +18,9 @@ max_line_length = 80
[*.{sh,bash}]
indent_size = 2
+[store/debian-dev/**/*.{sh,bash}]
+indent_size = 4
+
[*.{html,css,js,ts}]
indent_size = 2
diff --git a/deno/deno.json b/deno/deno.json
index 53cdf7a..d4beef0 100644
--- a/deno/deno.json
+++ b/deno/deno.json
@@ -11,7 +11,6 @@
"@std/io": "jsr:@std/io@^0.225.2",
"@std/path": "jsr:@std/path@^1.1.0",
"@std/testing": "jsr:@std/testing@^1.0.13",
- "@std/dotenv": "jsr:@std/dotenv@^0.225.5",
"@std/fs": "jsr:@std/fs@^1.0.18",
"yargs": "npm:yargs@^18.0.0",
"@types/yargs": "npm:@types/yargs@^17.0.33"
diff --git a/deno/deno.lock b/deno/deno.lock
index 871a9ae..9037ebe 100644
--- a/deno/deno.lock
+++ b/deno/deno.lock
@@ -10,7 +10,6 @@
"jsr:@std/collections@^1.1.1": "1.1.1",
"jsr:@std/csv@^1.0.6": "1.0.6",
"jsr:@std/data-structures@^1.0.8": "1.0.8",
- "jsr:@std/dotenv@~0.225.5": "0.225.5",
"jsr:@std/encoding@1": "1.0.10",
"jsr:@std/encoding@^1.0.10": "1.0.10",
"jsr:@std/expect@^1.0.16": "1.0.16",
@@ -88,9 +87,6 @@
"@std/data-structures@1.0.8": {
"integrity": "2fb7219247e044c8fcd51341788547575653c82ae2c759ff209e0263ba7d9b66"
},
- "@std/dotenv@0.225.5": {
- "integrity": "9ce6f9d0ec3311f74a32535aa1b8c62ed88b1ab91b7f0815797d77a6f60c922f"
- },
"@std/encoding@1.0.10": {
"integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
},
@@ -1300,7 +1296,6 @@
"dependencies": [
"jsr:@std/collections@^1.1.1",
"jsr:@std/csv@^1.0.6",
- "jsr:@std/dotenv@~0.225.5",
"jsr:@std/encoding@^1.0.10",
"jsr:@std/expect@^1.0.16",
"jsr:@std/fs@^1.0.18",
diff --git a/deno/tools/service.ts b/deno/tools/service.ts
index 1172473..bd4d22c 100644
--- a/deno/tools/service.ts
+++ b/deno/tools/service.ts
@@ -1,6 +1,5 @@
import { dirname, join, relative } from "@std/path";
import { copySync, existsSync, walkSync } from "@std/fs";
-import { parse } from "@std/dotenv";
import { distinct } from "@std/collections";
// @ts-types="npm:@types/mustache"
import Mustache from "mustache";
@@ -36,8 +35,20 @@ function loadTemplatedConfigFiles(
for (const file of files) {
console.log(` from file ${file}`);
const text = Deno.readTextFileSync(file);
- for (const [key, valueText] of Object.entries(parse(text))) {
- // TODO: dotenv silently override old values, so everything will be new for now.
+ let lineNumber = 0;
+ for (const rawLine of text.split("\n")) {
+ lineNumber++;
+ const line = rawLine.trim();
+ if (line.length === 0) continue;
+ if (line.startsWith("#")) continue;
+ const equalSymbolIndex = line.indexOf("=");
+ if (equalSymbolIndex === -1) {
+ throw new Error(`Line ${lineNumber} of ${file} is invalid.`);
+ }
+ const [key, valueText] = [
+ line.slice(0, equalSymbolIndex).trim(),
+ line.slice(equalSymbolIndex + 1).trim(),
+ ];
console.log(` (${key in config ? "override" : "new"}) ${key}`);
getVariableKeysOfTemplate(valueText).forEach((name) => {
if (!(name in config)) {
diff --git a/dictionary.txt b/dictionary.txt
index dee097e..8df3201 100644
--- a/dictionary.txt
+++ b/dictionary.txt
@@ -9,7 +9,6 @@ aarch64
esmtp
healthcheck
-2fauth
certbot
roundcube
roundcubemail
diff --git a/services/docker/debian-dev/Dockerfile b/services/docker/debian-dev/Dockerfile
deleted file mode 100644
index 8114c56..0000000
--- a/services/docker/debian-dev/Dockerfile
+++ /dev/null
@@ -1,24 +0,0 @@
-FROM debian:latest
-
-ARG USER=crupest
-ARG IN_CHINA=
-
-ENV CRUPEST_DEBIAN_DEV_USER=${USER}
-ENV CRUPEST_DEBIAN_DEV_IN_CHINA=${IN_CHINA}
-
-ADD bootstrap /bootstrap
-RUN /bootstrap/setup.bash
-
-ENV LANG=en_US.utf8
-USER ${USER}
-WORKDIR /home/${USER}
-
-RUN --mount=type=secret,id=code-server-password,required=true,env=CRUPEST_CODE_SERVER_PASSWORD \
- mkdir -p ${HOME}/.config/code-server && \
- echo -e "auth: password\nhashed-password: " >> ${HOME}/.config/code-server/config.yaml && \
- echo -n "$CRUPEST_CODE_SERVER_PASSWORD" | argon2 $(shuf -i 10000000-99999999 -n 1 --random-source /dev/urandom) -e >> ${HOME}/.config/code-server/config.yaml
-
-EXPOSE 4567
-VOLUME [ "/home/${USER}" ]
-
-CMD [ "tini", "--", "/usr/bin/code-server", "--bind-addr", "0.0.0.0:4567" ]
diff --git a/services/docker/debian-dev/bootstrap/extra/setup-cmake.bash b/services/docker/debian-dev/bootstrap/extra/setup-cmake.bash
deleted file mode 100755
index 76c1ae4..0000000
--- a/services/docker/debian-dev/bootstrap/extra/setup-cmake.bash
+++ /dev/null
@@ -1,9 +0,0 @@
-#! /usr/bin/env bash
-
-set -e
-
-CMAKE_VERSION=$(curl -s https://api.github.com/repos/Kitware/CMake/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
-wget -O cmake-installer.sh https://github.com/Kitware/CMake/releases/download/v"$CMAKE_VERSION"/cmake-"$CMAKE_VERSION"-linux-x86_64.sh
-chmod +x cmake-installer.sh
-./cmake-installer.sh --skip-license --prefix=/usr
-rm cmake-installer.sh
diff --git a/services/docker/debian-dev/bootstrap/extra/setup-dotnet.bash b/services/docker/debian-dev/bootstrap/extra/setup-dotnet.bash
deleted file mode 100755
index 0ef7743..0000000
--- a/services/docker/debian-dev/bootstrap/extra/setup-dotnet.bash
+++ /dev/null
@@ -1,10 +0,0 @@
-#! /usr/bin/env bash
-
-set -e
-
-wget https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
-dpkg -i packages-microsoft-prod.deb
-rm packages-microsoft-prod.deb
-
-apt-get update
-apt-get install -y dotnet-sdk-7.0
diff --git a/services/docker/debian-dev/bootstrap/extra/setup-llvm.bash b/services/docker/debian-dev/bootstrap/extra/setup-llvm.bash
deleted file mode 100755
index 48dde86..0000000
--- a/services/docker/debian-dev/bootstrap/extra/setup-llvm.bash
+++ /dev/null
@@ -1,26 +0,0 @@
-#! /usr/bin/env bash
-
-set -e
-
-LLVM_VERSION=18
-
-. /bootstrap/func.bash
-
-if is_true "$CRUPEST_DEBIAN_DEV_IN_CHINA"; then
- base_url=https://mirrors.tuna.tsinghua.edu.cn/llvm-apt
-else
- base_url=https://apt.llvm.org
-fi
-
-wget "$base_url/llvm.sh"
-chmod +x llvm.sh
-./llvm.sh $LLVM_VERSION all -m "$base_url"
-rm llvm.sh
-
-update-alternatives --install /usr/bin/clang clang /usr/bin/clang-$LLVM_VERSION 100 \
- --slave /usr/bin/clang++ clang++ /usr/bin/clang++-$LLVM_VERSION \
- --slave /usr/bin/clangd clangd /usr/bin/clangd-$LLVM_VERSION \
- --slave /usr/bin/clang-format clang-format /usr/bin/clang-format-$LLVM_VERSION \
- --slave /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-$LLVM_VERSION \
- --slave /usr/bin/lldb lldb /usr/bin/lldb-$LLVM_VERSION \
- --slave /usr/bin/lld lld /usr/bin/lld-$LLVM_VERSION
diff --git a/services/docker/debian-dev/bootstrap/home/.bashrc b/services/docker/debian-dev/bootstrap/home/.bashrc
deleted file mode 100644
index 3646ee2..0000000
--- a/services/docker/debian-dev/bootstrap/home/.bashrc
+++ /dev/null
@@ -1,117 +0,0 @@
-# ~/.bashrc: executed by bash(1) for non-login shells.
-# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
-# for examples
-
-# If not running interactively, don't do anything
-case $- in
- *i*) ;;
- *) return;;
-esac
-
-# don't put duplicate lines or lines starting with space in the history.
-# See bash(1) for more options
-HISTCONTROL=ignoreboth
-
-# append to the history file, don't overwrite it
-shopt -s histappend
-
-# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
-HISTSIZE=1000
-HISTFILESIZE=2000
-
-# check the window size after each command and, if necessary,
-# update the values of LINES and COLUMNS.
-shopt -s checkwinsize
-
-# If set, the pattern "**" used in a pathname expansion context will
-# match all files and zero or more directories and subdirectories.
-#shopt -s globstar
-
-# make less more friendly for non-text input files, see lesspipe(1)
-#[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
-
-# set variable identifying the chroot you work in (used in the prompt below)
-if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
- debian_chroot=$(cat /etc/debian_chroot)
-fi
-
-# set a fancy prompt (non-color, unless we know we "want" color)
-case "$TERM" in
- xterm-color|*-256color) color_prompt=yes;;
-esac
-
-# uncomment for a colored prompt, if the terminal has the capability; turned
-# off by default to not distract the user: the focus in a terminal window
-# should be on the output of commands, not on the prompt
-#force_color_prompt=yes
-
-if [ -n "$force_color_prompt" ]; then
- if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
- # We have color support; assume it's compliant with Ecma-48
- # (ISO/IEC-6429). (Lack of such support is extremely rare, and such
- # a case would tend to support setf rather than setaf.)
- color_prompt=yes
- else
- color_prompt=
- fi
-fi
-
-if [ "$color_prompt" = yes ]; then
- PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
-else
- PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
-fi
-unset color_prompt force_color_prompt
-
-# If this is an xterm set the title to user@host:dir
-case "$TERM" in
-xterm*|rxvt*)
- PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
- ;;
-*)
- ;;
-esac
-
-# enable color support of ls and also add handy aliases
-if [ -x /usr/bin/dircolors ]; then
- test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
- alias ls='ls --color=auto'
- #alias dir='dir --color=auto'
- #alias vdir='vdir --color=auto'
-
- #alias grep='grep --color=auto'
- #alias fgrep='fgrep --color=auto'
- #alias egrep='egrep --color=auto'
-fi
-
-# colored GCC warnings and errors
-#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
-
-# some more ls aliases
-#alias ll='ls -l'
-#alias la='ls -A'
-#alias l='ls -CF'
-
-# Alias definitions.
-# You may want to put all your additions into a separate file like
-# ~/.bash_aliases, instead of adding them here directly.
-# See /usr/share/doc/bash-doc/examples in the bash-doc package.
-
-if [ -f ~/.bash_aliases ]; then
- . ~/.bash_aliases
-fi
-
-# enable programmable completion features (you don't need to enable
-# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
-# sources /etc/bash.bashrc).
-if ! shopt -oq posix; then
- if [ -f /usr/share/bash-completion/bash_completion ]; then
- . /usr/share/bash-completion/bash_completion
- elif [ -f /etc/bash_completion ]; then
- . /etc/bash_completion
- fi
-fi
-
-alias dquilt="quilt --quiltrc=${HOME}/.quiltrc-dpkg"
-. /usr/share/bash-completion/completions/quilt
-complete -F _quilt_completion $_quilt_complete_opt dquilt
diff --git a/services/docker/debian-dev/bootstrap/official.sources b/services/docker/debian-dev/bootstrap/official.sources
deleted file mode 100644
index c9aa9a0..0000000
--- a/services/docker/debian-dev/bootstrap/official.sources
+++ /dev/null
@@ -1,23 +0,0 @@
-Types: deb
-URIs: http://deb.debian.org/debian
-Suites: bookworm bookworm-updates bookworm-backports
-Components: main contrib non-free non-free-firmware
-Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
-
-Types: deb-src
-URIs: http://deb.debian.org/debian
-Suites: bookworm bookworm-updates bookworm-backports
-Components: main contrib non-free non-free-firmware
-Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
-
-Types: deb
-URIs: http://deb.debian.org/debian-security
-Suites: bookworm-security
-Components: main contrib non-free non-free-firmware
-Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
-
-Types: deb-src
-URIs: http://deb.debian.org/debian-security
-Suites: bookworm-security
-Components: main contrib non-free non-free-firmware
-Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
diff --git a/services/docker/debian-dev/bootstrap/setup-apt.bash b/services/docker/debian-dev/bootstrap/setup-apt.bash
deleted file mode 100755
index 38cba05..0000000
--- a/services/docker/debian-dev/bootstrap/setup-apt.bash
+++ /dev/null
@@ -1,41 +0,0 @@
-#! /usr/bin/env bash
-# shellcheck disable=1090,1091
-
-set -e
-
-if [[ $EUID -ne 0 ]]; then
- die "This script must be run as root."
-fi
-
-script_dir=$(dirname "$0")
-
-old_one="/etc/apt/sources.list"
-new_one="/etc/apt/sources.list.d/debian.sources"
-
-echo "Setup apt sources ..."
-
-echo "Backup old ones to .bak ..."
-if [[ -f "$old_one" ]]; then
- mv "$old_one" "$old_one.bak"
-fi
-
-if [[ -f "$new_one" ]]; then
- mv "$new_one" "$new_one.bak"
-fi
-
-echo "Copy the new one ..."
-cp "$script_dir/official.sources" "$new_one"
-
-if [[ -n "$CRUPEST_DEBIAN_DEV_IN_CHINA" ]]; then
- echo "Replace with China mirror ..."
- china_mirror="mirrors.ustc.edu.cn"
- sed -i "s|deb.debian.org|${china_mirror}|" "$new_one"
-fi
-
-echo "Try to use https ..."
-apt-get update
-apt-get install -y apt-transport-https ca-certificates
-
-sed -i 's|http://|https://|' "$new_one"
-
-echo "APT source setup done!"
diff --git a/services/docker/debian-dev/bootstrap/setup.bash b/services/docker/debian-dev/bootstrap/setup.bash
deleted file mode 100755
index 65aabbb..0000000
--- a/services/docker/debian-dev/bootstrap/setup.bash
+++ /dev/null
@@ -1,56 +0,0 @@
-#! /usr/bin/env bash
-# shellcheck disable=1090,1091
-
-set -e -o pipefail
-
-die() {
- echo "$@" >&2
- exit 1
-}
-
-if [[ $EUID -ne 0 ]]; then
- die "This script must be run as root."
-fi
-
-script_dir=$(dirname "$0")
-
-os_release_file="/etc/os-release"
-if [[ -f "$os_release_file" ]]; then
- debian_version=$(. "$os_release_file"; echo "$VERSION_CODENAME")
- if [[ "$debian_version" != "bookworm" ]]; then
- die "This script can only be run on Debian Bookworm. But it is $debian_version"
- fi
-else
- die "$os_release_file not found. Failed to get debian version."
-fi
-
-script_dir=$(dirname "$0")
-
-export DEBIAN_FRONTEND=noninteractive
-
-echo "Begin to setup debian..."
-
-bash "$script_dir/setup-apt.bash"
-
-echo "Installing packages..."
-apt-get update
-apt-get install -y \
- tini locales procps sudo vim less man bash-completion curl wget \
- build-essential git devscripts debhelper quilt argon2
-
-echo "Setting up locale..."
-localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
-
-echo "Setting up sudo..."
-sed -i.bak 's|%sudo[[:space:]]\+ALL=(ALL:ALL)[[:space:]]\+ALL|%sudo ALL=(ALL:ALL) NOPASSWD: ALL|' /etc/sudoers
-
-echo "Creating user $CRUPEST_DEBIAN_DEV_USER ..."
-useradd -m -G sudo -s /usr/bin/bash "$CRUPEST_DEBIAN_DEV_USER"
-
-echo "Setting up code-server..."
-curl -fsSL https://code-server.dev/install.sh | sh
-
-echo "Cleaning up apt source index..."
-rm -rf /var/lib/apt/lists/*
-
-echo "Setup debian done."
diff --git a/services/docker/nginx/configs/templates/code.conf.template b/services/docker/nginx/configs/templates/code.conf.template
deleted file mode 100644
index aa70ebc..0000000
--- a/services/docker/nginx/configs/templates/code.conf.template
+++ /dev/null
@@ -1,6 +0,0 @@
-server {
- server_name code.${CRUPEST_DOMAIN};
- include common/http-listen;
-
- include common/acme-challenge;
-}
diff --git a/services/docker/nginx/configs/templates/root.conf.template b/services/docker/nginx/configs/templates/root.conf.template
index e3e93ad..4cc9a51 100644
--- a/services/docker/nginx/configs/templates/root.conf.template
+++ b/services/docker/nginx/configs/templates/root.conf.template
@@ -6,11 +6,6 @@ server {
root /srv/www;
}
- location /2fa/ {
- include common/proxy-common;
- proxy_pass http://2fauth:8000/;
- }
-
location /git/ {
include common/proxy-common;
client_max_body_size 5G;
diff --git a/services/templates/disabled/docker-compose.yaml b/services/templates/disabled/docker-compose.yaml
index 565ca49..0cd2256 100644
--- a/services/templates/disabled/docker-compose.yaml
+++ b/services/templates/disabled/docker-compose.yaml
@@ -1,22 +1,4 @@
services:
- debian-dev:
- pull_policy: build
- build:
- context: ./docker/debian-dev
- dockerfile: Dockerfile
- pull: true
- args:
- - USER=crupest
- tags:
- - "crupest/debian-dev:latest"
- container_name: debian-dev
- init: true
- command: [ "/bootstrap/start/code-server.bash" ]
- volumes:
- - ./data/debian-dev:/data
- - debian-dev-home:/home/crupest
- restart: on-failure:3
-
timeline:
image: crupest/timeline:latest
pull_policy: always
@@ -27,6 +9,3 @@ services:
- TIMELINE_DisableAutoBackup=true
volumes:
- ./data/timeline:/root/timeline
-
-volumes:
- debian-dev-home:
diff --git a/services/templates/disabled/nginx/code.conf.template b/services/templates/disabled/nginx/code.conf.template
deleted file mode 100644
index 0abe042..0000000
--- a/services/templates/disabled/nginx/code.conf.template
+++ /dev/null
@@ -1,20 +0,0 @@
-server {
- server_name code.@@CRUPEST_DOMAIN@@;
- include common/https-listen;
-
- location / {
- include common/proxy-common;
- proxy_pass http://debian-dev:8080/;
- }
-
- client_max_body_size 5G;
-}
-
-
-server {
- server_name code.@@CRUPEST_DOMAIN@@;
- include common/http-listen;
-
- include common/https-redirect;
- include common/acme-challenge;
-}
diff --git a/services/templates/docker-compose.yaml.template b/services/templates/docker-compose.yaml.template
index 6194c78..14d8ed4 100644
--- a/services/templates/docker-compose.yaml.template
+++ b/services/templates/docker-compose.yaml.template
@@ -90,19 +90,6 @@ services:
ipv4_address: "172.21.5.5"
restart: on-failure:3
- 2fauth:
- image: "2fauth/2fauth"
- pull_policy: always
- hostname: 2fauth
- env_file:
- - "./@@CRUPEST_GENERATED_DIR@@/envs/2fauth.env"
- volumes:
- - "./data/2fauth:/2fauth"
- networks:
- default:
- ipv4_address: "172.21.5.6"
- restart: "on-failure:3"
-
v2ray:
pull_policy: build
build:
@@ -115,7 +102,7 @@ services:
- "./@@CRUPEST_GENERATED_DIR@@/envs/v2ray.env"
networks:
default:
- ipv4_address: "172.21.5.7"
+ ipv4_address: "172.21.5.6"
restart: "on-failure:3"
auto-backup:
diff --git a/services/templates/envs/2fauth.env.template b/services/templates/envs/2fauth.env.template
deleted file mode 100644
index de2ad3a..0000000
--- a/services/templates/envs/2fauth.env.template
+++ /dev/null
@@ -1,15 +0,0 @@
-APP_NAME=2FAuth-crupest
-APP_TIMEZONE=UTC
-SITE_OWNER=@@CRUPEST_EMAIL@@
-APP_KEY=@@CRUPEST_2FAUTH_APP_KEY@@
-APP_URL=@@CRUPEST_ROOT_URL@@/2fa
-APP_SUBDIRECTORY=2fa
-MAIL_MAILER=smtp
-MAIL_HOST=@@CRUPEST_MAIL_SERVER_DOMAIN@@
-MAIL_PORT=465
-MAIL_USERNAME=@@CRUPEST_2FAUTH_MAIL_USERNAME@@
-MAIL_PASSWORD=@@CRUPEST_2FAUTH_MAIL_PASSWORD@@
-MAIL_ENCRYPTION=ssl
-MAIL_FROM_NAME=2FAuth-crupest
-MAIL_FROM_ADDRESS=@@CRUPEST_2FAUTH_MAIL_USERNAME@@
-TRUSTED_PROXIES=*
diff --git a/store/debian-dev/Dockerfile b/store/debian-dev/Dockerfile
new file mode 100644
index 0000000..d5e25ba
--- /dev/null
+++ b/store/debian-dev/Dockerfile
@@ -0,0 +1,22 @@
+ARG VERSION=latest
+FROM debian:${VERSION}
+
+ARG USER=
+ARG CHINA=
+
+ENV CRUPEST_DEBIAN_DEV_USER=${USER}
+ENV CRUPEST_DEBIAN_DEV_CHINA=${CHINA}
+
+ADD setup /setup
+RUN export DEBIAN_FRONTEND=noninteractive; \
+ /setup/apt.bash && /setup/package.bash && \
+ /setup/for-container.bash && \
+ rm -rf /var/lib/apt/lists/*
+
+
+ENV LANG=en_US.utf8
+USER ${USER}
+WORKDIR /home/${USER}
+RUN env DEBIAN_FRONTEND=noninteractive /setup/user.bash
+
+VOLUME [ "/home/${USER}" ]
diff --git a/store/debian-dev/setup/apt.bash b/store/debian-dev/setup/apt.bash
new file mode 100755
index 0000000..e841351
--- /dev/null
+++ b/store/debian-dev/setup/apt.bash
@@ -0,0 +1,33 @@
+#! /usr/bin/env bash
+
+set -e -o pipefail
+
+china_mirror="mirrors.ustc.edu.cn"
+try_files=("/etc/apt/sources.list" "/etc/apt/sources.list.d/debian.sources")
+files=()
+
+for try_file in "${try_files[@]}"; do
+ if [[ -f "$try_file" ]]; then
+ files+=("$try_file")
+ fi
+done
+
+for file in "${files[@]}"; do
+ echo "copy $file to $file.bak"
+ cp "$file" "$file.bak"
+done
+
+if [[ -n "$CRUPEST_DEBIAN_DEV_CHINA" ]]; then
+ echo "use China mirrors"
+ for file in "${files[@]}"; do
+ sed -i "s|deb.debian.org|${china_mirror}|g" "$file"
+ done
+fi
+
+echo "use https"
+apt-get update
+apt-get install -y apt-transport-https ca-certificates
+
+for file in "${files[@]}"; do
+ sed -i 's|http://|https://|g' "$file"
+done
diff --git a/store/debian-dev/setup/bashrc b/store/debian-dev/setup/bashrc
new file mode 100644
index 0000000..00c9d11
--- /dev/null
+++ b/store/debian-dev/setup/bashrc
@@ -0,0 +1,3 @@
+alias dquilt='quilt "--quiltrc=${HOME}/.quiltrc-dpkg"'
+. /usr/share/bash-completion/completions/quilt
+complete -F _quilt_completion $_quilt_complete_opt dquilt
diff --git a/store/debian-dev/setup/cmake.bash b/store/debian-dev/setup/cmake.bash
new file mode 100755
index 0000000..dd7307e
--- /dev/null
+++ b/store/debian-dev/setup/cmake.bash
@@ -0,0 +1,9 @@
+#! /usr/bin/env bash
+
+set -e -o pipefail
+
+CMAKE_VERSION=$(curl -s https://api.github.com/repos/Kitware/CMake/releases/latest | \
+ grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
+
+curl -fsSL "https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-linux-x86_64.sh" | \
+ sh -s -- --skip-license --prefix=/usr
diff --git a/store/debian-dev/setup/code-server.bash b/store/debian-dev/setup/code-server.bash
new file mode 100755
index 0000000..1151dc2
--- /dev/null
+++ b/store/debian-dev/setup/code-server.bash
@@ -0,0 +1,17 @@
+#! /usr/bin/env bash
+
+set -e -o pipefail
+
+if [[ $# != 1 ]]; then
+ echo "Require exactly one argument, the password of the code server." >&2
+ exit 1
+fi
+
+curl -fsSL https://code-server.dev/install.sh | sh
+
+apt update && apt install argon2
+mkdir -p "${HOME}/.config/code-server"
+echo -e "auth: password\nhashed-password: " >> "${HOME}/.config/code-server/config.yaml"
+echo -n "$1" | \
+ argon2 "$(shuf -i 10000000-99999999 -n 1 --random-source /dev/urandom)" -e \
+ >> "${HOME}/.config/code-server/config.yaml"
diff --git a/store/debian-dev/setup/for-container.bash b/store/debian-dev/setup/for-container.bash
new file mode 100755
index 0000000..0aa47b0
--- /dev/null
+++ b/store/debian-dev/setup/for-container.bash
@@ -0,0 +1,14 @@
+#! /usr/bin/env bash
+
+set -e -o pipefail
+
+echo "set up locale"
+localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
+
+echo "set up sudo"
+sed -i.bak 's|%sudo[[:space:]]\+ALL=(ALL:ALL)[[:space:]]\+ALL|%sudo ALL=(ALL:ALL) NOPASSWD: ALL|' /etc/sudoers
+
+if ! id "username" &>/dev/null; then
+ echo "create user $CRUPEST_DEBIAN_DEV_USER"
+ useradd -m -G sudo -s /usr/bin/bash "$CRUPEST_DEBIAN_DEV_USER"
+fi
diff --git a/store/debian-dev/setup/llvm.bash b/store/debian-dev/setup/llvm.bash
new file mode 100755
index 0000000..ca6d4bf
--- /dev/null
+++ b/store/debian-dev/setup/llvm.bash
@@ -0,0 +1,11 @@
+#! /usr/bin/env bash
+
+set -e -o pipefail
+
+if [[ -n "$CRUPEST_DEBIAN_DEV_CHINA" ]]; then
+ base_url=https://mirrors.tuna.tsinghua.edu.cn/llvm-apt
+else
+ base_url=https://apt.llvm.org
+fi
+
+curl -fsSL "$base_url/llvm.sh" | sh -s -- all -m "$base_url"
diff --git a/store/debian-dev/setup/package.bash b/store/debian-dev/setup/package.bash
new file mode 100755
index 0000000..5ad7b7a
--- /dev/null
+++ b/store/debian-dev/setup/package.bash
@@ -0,0 +1,10 @@
+#! /usr/bin/env bash
+
+set -e -o pipefail
+
+echo "install packages"
+apt-get update
+apt-get install -y \
+ locales lsb-release software-properties-common \
+ sudo procps bash-completion man less gnupg curl wget \
+ vim build-essential git devscripts debhelper quilt
diff --git a/services/docker/debian-dev/bootstrap/home/.quiltrc-dpkg b/store/debian-dev/setup/quiltrc-dpkg
index e8fc3c5..e8fc3c5 100644
--- a/services/docker/debian-dev/bootstrap/home/.quiltrc-dpkg
+++ b/store/debian-dev/setup/quiltrc-dpkg
diff --git a/store/debian-dev/setup/user.bash b/store/debian-dev/setup/user.bash
new file mode 100755
index 0000000..4e13804
--- /dev/null
+++ b/store/debian-dev/setup/user.bash
@@ -0,0 +1,11 @@
+#! /usr/bin/env bash
+
+set -e -o pipefail
+
+base_dir="$(dirname "$0")"
+dot_files=("bashrc" "quiltrc-dpkg")
+
+for file in "${dot_files[@]}"; do
+ echo "copy $base_dir/$file $HOME/.$file"
+ cp "$base_dir/$file" "$HOME/.$file"
+done
diff --git a/www/content/notes/cheat-sheet.md b/www/content/notes/cheat-sheet.md
index 2f30140..aba8e18 100644
--- a/www/content/notes/cheat-sheet.md
+++ b/www/content/notes/cheat-sheet.md
@@ -42,7 +42,7 @@ A complete command is `[prefix] [docker (based on challenge kind)] [command] [ch
| part | for | segment |
| :-: | :-: | --- |
-| prefix | * | `docker run -it --rm --name certbot -v "./data/certbot/certs:/etc/letsencrypt" -v "./data/certbot/data:/var/lib/letsencrypt"` |
+| prefix | * | `docker run -it --rm --name certbot -v "./data/certbot/certs:/etc/letsencrypt" -v "./data/certbot/data:/var/lib/letsencrypt" certbot/certbot` |
| docker | challenge standalone | `-p "0.0.0.0:80:80"` |
| docker | challenge nginx | `-v "./data/certbot/webroot:/var/www/certbot"` |
| command | create/expand/shrink | `certonly` |
@@ -59,11 +59,14 @@ For example, **test** create/expand/shrink with standalone server:
```sh
docker run -it --rm --name certbot \
- -v "./data/certbot/certs:/etc/letsencrypt" -v "./data/certbot/data:/var/lib/letsencrypt"` \
+ -v "./data/certbot/certs:/etc/letsencrypt" \
+ -v "./data/certbot/data:/var/lib/letsencrypt" \
-p "0.0.0.0:80:80" \
+ certbot/certbot \
certonly \
--standalone \
- -d crupest.life -d mail.crupest.life \
+ --cert-name crupest.life \
+ -d crupest.life -d mail.crupest.life -d timeline.crupest.life \
--test-cert --dry-run
```