{"id":16367868,"url":"https://github.com/nkonev/blog","last_synced_at":"2025-03-23T02:33:19.515Z","repository":{"id":54260402,"uuid":"91855710","full_name":"nkonev/blog","owner":"nkonev","description":"SPA blog application with Spring Boot and Vue.js","archived":false,"fork":false,"pushed_at":"2021-11-07T02:00:06.000Z","size":50116,"stargazers_count":14,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-11T02:51:04.677Z","etag":null,"topics":["docker","docker-swarm","elasticsearch","facebook","flywaydb","java13","junit5","oauth2","postgres","rabbitmq","redis","rendertron","selenide","selenium","spring-boot","stylus","testng","vkontakte","vuejs2"],"latest_commit_sha":null,"homepage":"https://nkonev.name","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nkonev.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-20T00:49:09.000Z","updated_at":"2024-03-27T17:15:12.000Z","dependencies_parsed_at":"2022-08-13T10:20:20.622Z","dependency_job_id":null,"html_url":"https://github.com/nkonev/blog","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkonev%2Fblog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkonev%2Fblog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkonev%2Fblog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nkonev%2Fblog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nkonev","download_url":"https://codeload.github.com/nkonev/blog/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221842776,"owners_count":16890202,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","docker-swarm","elasticsearch","facebook","flywaydb","java13","junit5","oauth2","postgres","rabbitmq","redis","rendertron","selenide","selenium","spring-boot","stylus","testng","vkontakte","vuejs2"],"created_at":"2024-10-11T02:51:03.319Z","updated_at":"2024-10-28T14:46:37.071Z","avatar_url":"https://github.com/nkonev.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://github.com/nkonev/blog/workflows/CI%20jobs/badge.svg)](https://github.com/nkonev/blog/actions)\n[![codecov](https://codecov.io/gh/nkonev/blog/branch/master/graph/badge.svg)](https://codecov.io/gh/nkonev/blog)\n[![Docker Image Version (latest semver)](https://img.shields.io/docker/v/nkonev/blog)](https://hub.docker.com/r/nkonev/blog/tags)\n\n# Features\n* Zero-downtime update deployment\n* Fast page loading due client-side rendering\n* Fulltext search by posts\n* Updating posts through web STOMP on main page\n* Draft posts that visible only for author and administrator\n* User locking\n* User deletion (with migrating posts to special `deleted` user)\n* Pages prerendering for crawlers with [rendertron](https://github.com/nkonev/rendertron-docker)\n* Dynamically setting header, subheader and background image without server restart\n* Auto cleaning \"orphanned\" images from PostgreSQL, and \"orphaned\" posts from Elasticsearch\n* Cluster out from the box - simple scale it with `docker service scale BLOGSTACK_blog=4`\n* Login through Facebook, Vkontakte OAuth2 providers\n* Binding several OAuth2 account to same blog account\n* Simply installation with docker swarm\n* Applications like Vkontakte/Facebook apps. Example [storage application](https://github.com/nkonev/blog-storage) on Go\n* Self-sufficient frontend asset. No CDN used\n* Simply [backup](https://github.com/nkonev/blog/blob/master/dev.md#take-dump) of everything to one .sql file\n\n# Requirements\n\n## Run\n* Docker 18.09.0+\n\n## Development\n* JDK 13\n* docker-compose 1.24.1 +\n* Google Chrome (as [default](https://github.com/nkonev/blog/blob/master/webdriver-test/src/test/resources/config/application.yml#L99) browser for webdriver-test). Just `dnf install chromium` in latest Fedora.\n* disable SELinux\n\n# FAQ\n\nQ: Can I run it without docker ?\n\nA: Yes, you can achieve it by manually install PostgreSQL, RabbitMQ, Redis, Elasticsearch and configure it's connections in config or through commandline. See Spring Boot documentation https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/html/boot-features-external-config.html.\n\nQ: How to build frontend if I am backend developer ?\n\nA: \n```bash\n./mvnw -P frontend generate-resources\n```\n\nQ: How to build full jar (with static) ?\n\nA: \n```bash\n./mvnw -P frontend clean package\n```\nIt will download java dependencies and nodejs with frontend dependencies.\n\n\nQ: Why does blog wait for PostgreSQL, Elasticsearch, Redis, RabbltMQ port availability on boot?\n\nA: Primarily for deploy tests run inside Github CI. When there isn' t these waits, I had spuriously tests fails due to unpredictable time of Elasticsearch boot.\n\n\n## Embedded API documentation\n\nEmbedded documentation are available at `http://127.0.0.1:8080/docs/index.html`\n\n\n## Request version info \n\nThis will available after full package, e. g. after resource filtering of `git.template.json` and renaming result in `target/classes/static` dir to `git.json`\n\n```\ncurl -i http://127.0.0.1:8080/git.json\n```\n\n# Running on Windows without docker\n\nFirst you should install Redis, PostgreSQL, Rabbit MQ, Elasticsearch\nand manually setup them (create database, schema, user for PostgreSQL, install web stomp plugin and create user for RabbitMQ).\n\nRedis Windows x86 which works on my PC (Windows 7 x86)\nhttp://bitsandpieces.it/redis-x86-32bit-builds-for-windows\n\n2.8.2104 http://fratuz610.s3.amazonaws.com/upload/public/redis-builds/x86/redis-windows-x86-2.8.2104.zip - requires enabled swapfile.\n\nrun\n```\nredis-server.exe --maxheap 8Mb\n```\n\n\nNext you should use localhost IP addresses and disable asciidoctor:\n```\nmvnw -P local -Dasciidoctor.skip=true clean test\n```\n\n\n# Demo Run / Installation\n\n```bash\ncd docker\n./swarm-init.sh\n```\n\nI strongly recommend copy and rename `docker-compose.template.yml` to `docker-compose.stack.yml`.\nNext I'll use renamed file.\n\n\n\n## Copy files on your server:\n```bash\nscp -r /path/to/blog/docker/* user@blog.test:/path/to/blog/\nchmod 600 traefik/acme.json\n```\n\n\n## Manual changes\n\nLet' s assume `cd docker`.\n\na) `./swarm-init.sh`\n\nb) In `docker-compose.template.yml` or `docker-compose.stack.yml`:.\n\nChange tag in service blog `image: nkonev/blog:current-test` -\u003e `image: nkonev/blog:latest`\n\nAlso you can remove demo profile\n\nc) Change next properties:\n\n```\n      - SPRING_MAIL_HOST=smtp.yandex.ru\n      - CUSTOM_EMAIL_FROM=username@yandex.ru\n      - SPRING_MAIL_USERNAME=username\n      - SPRING_MAIL_PASSWORD=password\n      - CUSTOM_BASE-URL=http://blog.test\n \n```\nAnd remove explicit ports definition where it's don't need - postgres, redis, rabbit, because of docker publishes ports by add it to iptables chain.\nIf you very want, you can skip setting these properties, but you'll have non-working email, wrong links in emails and so on.\n\nd) Generating monitoring grafana \u0026 prometheus password\n```bash\nsudo yum install -y httpd-tools\n# generate login and hash with replaced $ with $$ sign for able to copy-paste to docker-compose.stack.yml\nhtpasswd -nb admin admin | sed -e 's/\\$/\\$\\$/g'\n```\n\ne) Set `journald` logging with appropriate tag for all services\n\n```yaml\n    logging:\n      driver: \"journald\"\n      options:\n        tag: blog\n```\n\n\nf) Uncomment \u0026 change SSL setting in `./traefik/traefik.toml`\n\ng) Configure notifications in `./alertmanager/alert.yml`\n\ni) For able to http(s) request your domain registrar name with curl from container\nensure that \n```bash\ncat /proc/sys/net/ipv4/ip_forward\n```\nreturns non-zero\n\nnext \n\nOption a)\n```bash\nfirewall-cmd --permanent --zone=public --add-port=80/tcp\nfirewall-cmd --permanent --zone=public --add-port=443/tcp\nfirewall-cmd --reload\n```\n\nCheck\n```bash\nfirewall-cmd --list-all-zones\niptables -t nat --line-numbers --numeric --list\n```\n\nOption b) insert iptables rule\n```bash\niptables -I INPUT -i docker_gwbridge -p tcp -m multiport --dports 80,443 -j ACCEPT\n```\n\nIf all ok, you should do it persistent by\n```bash\nchmod +x /etc/rc.local\nvim /etc/rc.local\n```\n\n```bash\niptables -I INPUT -i docker_gwbridge -p tcp -m multiport --dports 80,443 -j ACCEPT\necho \"Successful inserted docker_gwbridge rule\"\n```\n\n\n## Starting with docker swarm\n\nNext you can \n```bash\ndocker stack deploy --compose-file docker-compose.stack.yml BLOGSTACK\ndocker service scale BLOGSTACK_blog=4\ndocker service ls\n```\n\nSee postgres volume\n```bash\ndocker volume inspect BLOGSTACK_postgresql_blog_dev_data_dir\n```\n\nSee logs of jars\nvia journalctl (see applied tags in `docker-compose.stack.yml`):\n```bash\njournalctl -f CONTAINER_TAG=blog\njournalctl -f CONTAINER_TAG=blog -o verbose\njournalctl -f CONTAINER_TAG=blog CONTAINER_TAG=postgresql CONTAINER_TAG=redis CONTAINER_TAG=rabbitmq\n```\n\nor via docker\n```bash\ndocker service logs -f BLOGSTACK_blog\n```\n\nRemove\n```bash\ndocker stack rm BLOGSTACK\n```\n\nRemove exited containers\n```bash\ndocker rm $(docker ps -aq -f name=BLOGSTACK_blog -f status=exited)\n```\n\n\n\n# Test on local machine\n\n## curl\n```bash\ncurl -H \"Host: blog.test\" http://127.0.0.1:8088\ncurl -H \"Host: grafana.blog.test\" -u \"admin:admin\" http://127.0.0.1:8088\ncurl -H \"Host: prometheus.blog.test\" -u \"admin:admin\" http://127.0.0.1:8088\ncurl -H \"Host: alertmanager.blog.test\" -u \"admin:admin\" http://127.0.0.1:8088\n```\n\n## Browser\n\nWe add domains to /etc/hosts for browser sends correct Host header\n```bash\nsudo tee --append /etc/hosts \u003c\u003c'EOF'\n127.0.0.1 blog.test\n127.0.0.1 grafana.blog.test\n127.0.0.1 prometheus.blog.test\n127.0.0.1 alertmanager.blog.test\nEOF\n```\n\n\n\n# Maintenance\n\n```bash\ndocker ps -aq | xargs docker rm\ndocker volume ls -q | xargs docker volume rm\ndocker images -q -a | xargs  docker rmi\n```\n\n\n## Open PostgreSQL\n```bash\ndocker exec -it $(docker ps --filter label=com.docker.swarm.service.name=BLOGSTACK_postgresql -q) psql -U blog\ndocker exec -it $(docker ps --filter label=com.docker.swarm.service.name=TESTBLOGSTACK_postgresql -q) psql -U blog\n```\n\n## Open blog\n```bash\ndocker exec -it $(docker ps --filter label=com.docker.swarm.service.name=BLOGSTACK_blog -q | head -n 1) bash\n```\n\n\n# SEO\nFirst configure `custom.rendertron.serviceUrl` - setup correct url of Rendertron installation. See also dockerized [build](https://hub.docker.com/r/nkonev/rendertron-docker/).\n\n## How to add SEO metrics scripts\n\nJust prepend `file:` location which contains index.html, and copy modified index.html to there folder.\n\n```yml\nspring.resources.static-locations: file:/var/www/, file:backend/src/main/resources/static/, classpath:/static/\n```\n\nSo firstly Spring Mvc will looking in `/var/www`, next in `$PWD/backend/src/main/resources/static/`...\n\nIf your search(Yandex Metrics for example) checks for existence script - request will passed through rendertron, which wipes `\u003cscript\u003e` tags.\n \nIn order to solve it, use `custom.seo.script=file:/var/www/seo.html` - Rendertron filter will inject content of \nthis file before closing `\u003c/head\u003e`. \n\n# Grafana\n\n## Fix disk usage in https://grafana.com/dashboards/1860\n\n\nSet query\n```100 - ((node_filesystem_avail_bytes{mountpoint=\"/rootfs\"} * 100) / node_filesystem_size_bytes{mountpoint=\"/rootfs\"})```\n\nSet Instant\n\n\n# TODO\n* re-implement buttons css\n* sitemap for SEO\n* edit metainfo for SEO by user\n* change post owner by admin\n* change comment owner by admin\n* LDAP\n* Google OAuth2 login\n* search by comments\n\n\n# Generate configs\n```\n./mvnw -pl configs-generator generate-sources\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnkonev%2Fblog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnkonev%2Fblog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnkonev%2Fblog/lists"}