diff --git a/.github/workflows/build-and-push-docker-image.yml b/.github/workflows/build-and-push-dev-image.yml similarity index 100% rename from .github/workflows/build-and-push-docker-image.yml rename to .github/workflows/build-and-push-dev-image.yml diff --git a/.github/workflows/build-and-push-release-image.yml b/.github/workflows/build-and-push-release-image.yml new file mode 100644 index 00000000..4cf8b99d --- /dev/null +++ b/.github/workflows/build-and-push-release-image.yml @@ -0,0 +1,38 @@ +name: build-and-push-docker-image + +on: + push: + branches: + # Run on pushing branches like `release/1.0.0` + - "release/v*.*.*" + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Extract build args + # Extract version from branch name + # Example: branch name `release/v1.0.0` sets up env.VERSION=1.0.0 + run: | + echo "VERSION=${GITHUB_REF_NAME#release/v}" >> $GITHUB_ENV + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_NEOSMEMO_USERNAME }} + password: ${{ secrets.DOCKER_NEOSMEMO_TOKEN }} + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + + - name: Build and Push + id: docker_build + uses: docker/build-push-action@v2 + with: + context: ./ + file: ./Dockerfile + push: true + tags: ${{ secrets.DOCKER_NEOSMEMO_USERNAME }}/memos:${{ env.VERSION }} diff --git a/bin/server/cmd/root.go b/bin/server/cmd/root.go index 551d6aa4..d8c06a90 100644 --- a/bin/server/cmd/root.go +++ b/bin/server/cmd/root.go @@ -4,40 +4,28 @@ import ( "fmt" "os" + "memos/common" "memos/server" "memos/store" ) type Main struct { - profile *Profile + profile *common.Profile server *server.Server db *store.DB } -func Execute() { - m := Main{} - profile := GetProfile() - m.profile = &profile - - err := m.Run() - if err != nil { - fmt.Printf("%+v\n", err) - os.Exit(1) - } -} - func (m *Main) Run() error { - db := store.NewDB(m.profile.dsn, m.profile.mode) - + db := store.NewDB(m.profile) if err := db.Open(); err != nil { return fmt.Errorf("cannot open db: %w", err) } m.db = db - s := server.NewServer(m.profile.port, m.profile.mode) + s := server.NewServer(m.profile) s.ShortcutService = store.NewShortcutService(db) s.MemoService = store.NewMemoService(db) @@ -53,3 +41,16 @@ func (m *Main) Run() error { return nil } + +func Execute() { + profile := common.GetProfile() + m := Main{ + profile: &profile, + } + + err := m.Run() + if err != nil { + fmt.Printf("%+v\n", err) + os.Exit(1) + } +} diff --git a/bin/server/cmd/profile.go b/common/profile.go similarity index 81% rename from bin/server/cmd/profile.go rename to common/profile.go index 5b3dd122..c0a5c4f1 100644 --- a/bin/server/cmd/profile.go +++ b/common/profile.go @@ -1,4 +1,4 @@ -package cmd +package common import ( "fmt" @@ -9,12 +9,12 @@ import ( ) type Profile struct { - // mode can be "release" or "dev" - mode string - // port is the binding port for server. - port int - // dsn points to where Memos stores its own data - dsn string + // Mode can be "release" or "dev" + Mode string `json:"mode"` + // Port is the binding port for server. + Port int `json:"port"` + // DSN points to where Memos stores its own data + DSN string `json:"-"` } func checkDSN(dataDir string) (string, error) { @@ -61,8 +61,8 @@ func GetProfile() Profile { dsn := fmt.Sprintf("file:%s/memos_%s.db", dataDir, mode) return Profile{ - mode: mode, - port: port, - dsn: dsn, + Mode: mode, + Port: port, + DSN: dsn, } } diff --git a/server/server.go b/server/server.go index 43731b74..ccc020db 100644 --- a/server/server.go +++ b/server/server.go @@ -3,6 +3,7 @@ package server import ( "fmt" "memos/api" + "memos/common" "time" "github.com/gorilla/securecookie" @@ -15,15 +16,15 @@ import ( type Server struct { e *echo.Echo + Profile *common.Profile + UserService api.UserService MemoService api.MemoService ShortcutService api.ShortcutService ResourceService api.ResourceService - - port int } -func NewServer(port int, mode string) *Server { +func NewServer(profile *common.Profile) *Server { e := echo.New() e.Debug = true e.HideBanner = true @@ -46,17 +47,19 @@ func NewServer(port int, mode string) *Server { HTML5: true, })) + // In dev mode, set the const secret key to make login session persistence. secret := []byte("justmemos") - if mode != "dev" { + if profile.Mode == "release" { secret = securecookie.GenerateRandomKey(16) } e.Use(session.Middleware(sessions.NewCookieStore(secret))) s := &Server{ - e: e, - port: port, + e: e, + Profile: profile, } + // Webhooks api skips auth checker. webhookGroup := e.Group("/h") s.registerWebhookRoutes(webhookGroup) @@ -64,6 +67,7 @@ func NewServer(port int, mode string) *Server { apiGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc { return BasicAuthMiddleware(s.UserService, next) }) + s.registerSystemRoutes(apiGroup) s.registerAuthRoutes(apiGroup) s.registerUserRoutes(apiGroup) s.registerMemoRoutes(apiGroup) @@ -74,5 +78,5 @@ func NewServer(port int, mode string) *Server { } func (server *Server) Run() error { - return server.e.Start(fmt.Sprintf(":%d", server.port)) + return server.e.Start(fmt.Sprintf(":%d", server.Profile.Port)) } diff --git a/server/system.go b/server/system.go new file mode 100644 index 00000000..92afc219 --- /dev/null +++ b/server/system.go @@ -0,0 +1,19 @@ +package server + +import ( + "encoding/json" + "net/http" + + "github.com/labstack/echo/v4" +) + +func (s *Server) registerSystemRoutes(g *echo.Group) { + g.GET("/ping", func(c echo.Context) error { + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) + if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(s.Profile)); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose system profile").SetInternal(err) + } + + return nil + }) +} diff --git a/store/migration/10001__schema.sql b/store/migration/10001__schema.sql index 0234b942..a9b182ea 100644 --- a/store/migration/10001__schema.sql +++ b/store/migration/10001__schema.sql @@ -36,7 +36,7 @@ CREATE TABLE memo ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), - -- allowed row status are 'NORMAL', 'HIDDEN'. + -- allowed row status are 'NORMAL', 'PINNED', 'HIDDEN'. row_status TEXT NOT NULL DEFAULT 'NORMAL', content TEXT NOT NULL DEFAULT '', creator_id INTEGER NOT NULL, @@ -69,7 +69,7 @@ CREATE TABLE shortcut ( title TEXT NOT NULL DEFAULT '', payload TEXT NOT NULL DEFAULT '', creator_id INTEGER NOT NULL, - -- allowed row status are 'NORMAL', 'ARCHIVED'. + -- allowed row status are 'NORMAL', 'PINNED'. row_status TEXT NOT NULL DEFAULT 'NORMAL', FOREIGN KEY(creator_id) REFERENCES users(id) ); diff --git a/store/seed/10002__memo.sql b/store/seed/10002__memo.sql index 7ef4f3d5..1800fa31 100644 --- a/store/seed/10002__memo.sql +++ b/store/seed/10002__memo.sql @@ -1,4 +1,10 @@ -INSERT INTO memo - (`content`, `creator_id`) +INSERT INTO + memo ( + `content`, + `creator_id` + ) VALUES - ('👋 Welcome to memos', 101); + ( + '#memos 👋 Welcome to memos', + 101 + ); diff --git a/store/sqlite.go b/store/sqlite.go index 4b01077a..70a4db43 100644 --- a/store/sqlite.go +++ b/store/sqlite.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io/fs" + "memos/common" "sort" _ "github.com/mattn/go-sqlite3" @@ -19,18 +20,17 @@ var seedFS embed.FS type DB struct { Db *sql.DB - // datasource name DSN string - // mode: release or dev + // mode should be release or dev mode string } // NewDB returns a new instance of DB associated with the given datasource name. -func NewDB(dsn string, mode string) *DB { +func NewDB(profile *common.Profile) *DB { db := &DB{ - DSN: dsn, - mode: mode, + DSN: profile.DSN, + mode: profile.Mode, } return db }