mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
Merge branch 'sictiru' of github.com:samuelclay/NewsBlur into sictiru
This commit is contained in:
commit
57296704b2
316 changed files with 7188 additions and 5001 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -19,6 +19,7 @@ certbot.conf
|
|||
task_env.py
|
||||
app_env.py
|
||||
data/
|
||||
api/ip_addresses.txt
|
||||
.prom_cache
|
||||
config/certificates
|
||||
**/*.xcuserstate
|
||||
|
@ -53,10 +54,12 @@ docker/haproxy/haproxy.consul.cfg
|
|||
docker/nginx/nginx.consul.conf
|
||||
docker/prometheus/prometheus.yml
|
||||
docker/redis/redis_replica.conf
|
||||
docker/redis/redis_*_replica.conf
|
||||
docker/postgres/postgres.conf
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
/originals
|
||||
/node/originals
|
||||
media/safari/NewsBlur.safariextz
|
||||
|
||||
# IDE files
|
||||
|
@ -66,3 +69,4 @@ media/safari/NewsBlur.safariextz
|
|||
*.tfstate*
|
||||
.terraform*
|
||||
grafana.ini
|
||||
apps/api/ip_addresses.txt
|
||||
|
|
29
.vscode/settings.json
vendored
29
.vscode/settings.json
vendored
|
@ -1,12 +1,16 @@
|
|||
{
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.pylintEnabled": false,
|
||||
"python.linting.flake8Enabled": true,
|
||||
"python.linting.pylamaEnabled": false,
|
||||
"python.linting.flake8Args": [
|
||||
"--ignore=E501,W293,W503,W504,E302,E722,E226,E221,E402,E401"
|
||||
"isort.args": [
|
||||
"--profile",
|
||||
"black"
|
||||
],
|
||||
"python.pythonPath": "~/.virtualenvs/newsblur3/bin/python",
|
||||
// "python.linting.enabled": true,
|
||||
// "python.linting.pylintEnabled": false,
|
||||
// "python.linting.flake8Enabled": true,
|
||||
// "python.linting.pylamaEnabled": false,
|
||||
// "python.linting.flake8Args": [
|
||||
// "--ignore=E501,W293,W503,W504,E302,E722,E226,E221,E402,E401"
|
||||
// ],
|
||||
// "python.pythonPath": "~/.virtualenvs/newsblur/bin/python",
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
"editor.guides.bracketPairs": "active",
|
||||
"git.ignoreLimitWarning": true,
|
||||
|
@ -27,15 +31,12 @@
|
|||
"docker/volumes": true,
|
||||
"requirements.txt": true, // It's just a symlink to config/requirements.txt, which has git history
|
||||
},
|
||||
"python.formatting.blackArgs": [
|
||||
"--line-length=110",
|
||||
"--skip-string-normalization"
|
||||
],
|
||||
// "python.formatting.blackArgs": [
|
||||
// "--line-length=110",
|
||||
// "--skip-string-normalization"
|
||||
// ],
|
||||
"files.associations": {
|
||||
"*.yml": "ansible"
|
||||
},
|
||||
"nrf-connect.toolchain.path": "${nrf-connect.toolchain:1.9.1}",
|
||||
"C_Cpp.default.configurationProvider": "nrf-connect",
|
||||
"editor.formatOnSave": false,
|
||||
"ansible.python.interpreterPath": "/opt/homebrew/bin/python3",
|
||||
}
|
||||
|
|
|
@ -176,3 +176,17 @@ Provision a new redis server, replicate the data, take newsblur down for mainten
|
|||
aps -l db-redis-story1,db-redis-story2 -t consul
|
||||
make maintenance_off
|
||||
make task
|
||||
|
||||
### Switching to a new postgres server
|
||||
|
||||
# Old
|
||||
docker exec -it -u postgres postgres psql -c "SELECT pg_start_backup('label', true)"
|
||||
# New
|
||||
## Install `openssh-client` and `rsync`
|
||||
docker stop postgres
|
||||
rsync -Pav --stats --progress db-postgres.service.consul:/srv/newsblur/docker/volumes/postgres/data /srv/newsblur/docker/volumes/postgres/ --exclude postmaster.pid
|
||||
docker start postgres
|
||||
# New
|
||||
docker exec -it -u postgres postgres /usr/lib/postgresql/13/bin/pg_ctl -D /var/lib/postgresql/data promote
|
||||
# Old
|
||||
docker exec -it -u postgres postgres psql -c "SELECT pg_stop_backup()"
|
||||
|
|
5
Makefile
5
Makefile
|
@ -115,7 +115,8 @@ inventory:
|
|||
./ansible/utils/generate_inventory.py
|
||||
oldinventory:
|
||||
OLD=1 ./ansible/utils/generate_inventory.py
|
||||
|
||||
hinventory:
|
||||
./ansible/utils/generate_hetzner_inventory.py
|
||||
# Docker
|
||||
pull:
|
||||
docker pull newsblur/newsblur_python3
|
||||
|
@ -184,7 +185,7 @@ maintenance_off:
|
|||
|
||||
# Provision
|
||||
firewall:
|
||||
ansible-playbook ansible/all.yml -l db --tags firewall
|
||||
ansible-playbook ansible/all.yml -l db --tags ufw
|
||||
oldfirewall:
|
||||
ANSIBLE_CONFIG=/srv/newsblur/ansible.old.cfg ansible-playbook ansible/all.yml -l db --tags firewall
|
||||
repairmongo:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
host_key_checking = False
|
||||
remote_user = nb
|
||||
retry_files_enabled = False
|
||||
inventory = /srv/newsblur/ansible/inventories/digital_ocean.ini, /srv/newsblur/ansible/inventories/digital_ocean.yml
|
||||
inventory = /srv/newsblur/ansible/inventories/digital_ocean.ini, /srv/newsblur/ansible/inventories/digital_ocean.yml, /srv/newsblur/ansible/inventories/hetzner.ini, /srv/newsblur/ansible/inventories/hetzner.yml
|
||||
private_key_file = /srv/secrets-newsblur/keys/docker.key
|
||||
remote_tmp = ~/.ansible/tmp
|
||||
forks = 20
|
||||
|
|
2
ansible/inventories/.gitignore
vendored
2
ansible/inventories/.gitignore
vendored
|
@ -1 +1 @@
|
|||
*.ini
|
||||
digital_ocean*.ini
|
||||
|
|
|
@ -32,7 +32,9 @@ groups:
|
|||
search: inventory_hostname.startswith('db-elasticsearch')
|
||||
elasticsearch: inventory_hostname.startswith('db-elasticsearch')
|
||||
redis: inventory_hostname.startswith('db-redis')
|
||||
redis_story: inventory_hostname.startswith('db-redis-story')
|
||||
redis_story: inventory_hostname.startswith('db-redis-story') or inventory_hostname.startswith('hdb-redis-story')
|
||||
redis_session: inventory_hostname.startswith('db-redis-session') or inventory_hostname.startswith('hdb-redis-session')
|
||||
redis_user: inventory_hostname.startswith('db-redis-user') or inventory_hostname.startswith('hdb-redis-user')
|
||||
postgres: inventory_hostname.startswith('db-postgres')
|
||||
mongo: inventory_hostname.startswith('db-mongo') and not inventory_hostname.startswith('db-mongo-analytics')
|
||||
mongo_analytics: inventory_hostname.startswith('db-mongo-analytics')
|
||||
|
|
1
ansible/inventories/hetzner.ini
Symbolic link
1
ansible/inventories/hetzner.ini
Symbolic link
|
@ -0,0 +1 @@
|
|||
/srv/secrets-newsblur/configs/hetzner.ini
|
60
ansible/inventories/hetzner.yml
Normal file
60
ansible/inventories/hetzner.yml
Normal file
|
@ -0,0 +1,60 @@
|
|||
plugin: constructed
|
||||
strict: False
|
||||
|
||||
groups:
|
||||
|
||||
hall: inventory_hostname.startswith('h')
|
||||
|
||||
haproxy: inventory_hostname.startswith('hwww')
|
||||
|
||||
app: inventory_hostname.startswith('happ')
|
||||
happ: inventory_hostname.startswith('happ')
|
||||
web: inventory_hostname.startswith('happ')
|
||||
hweb: inventory_hostname.startswith('happ')
|
||||
django: inventory_hostname.startswith('happ-web')
|
||||
hdjango: inventory_hostname.startswith('happ-web')
|
||||
refresh: inventory_hostname.startswith('happ-refresh')
|
||||
hrefresh: inventory_hostname.startswith('happ-refresh')
|
||||
count: inventory_hostname.startswith('happ-count')
|
||||
hcount: inventory_hostname.startswith('happ-count')
|
||||
push: inventory_hostname.startswith('happ-push')
|
||||
hpush: inventory_hostname.startswith('happ-push')
|
||||
blogs: inventory_hostname.startswith('blog')
|
||||
|
||||
node: inventory_hostname.startswith('hnode')
|
||||
hnode: inventory_hostname.startswith('hnode')
|
||||
node_socket: inventory_hostname.startswith('hnode-socket')
|
||||
hnode_socket: inventory_hostname.startswith('hnode-socket')
|
||||
node_images: inventory_hostname.startswith('hnode-images')
|
||||
hnode_images: inventory_hostname.startswith('hnode-images')
|
||||
node_text: inventory_hostname.startswith('hnode-text')
|
||||
hnode_text: inventory_hostname.startswith('hnode-text')
|
||||
node_page: inventory_hostname.startswith('hnode-page')
|
||||
hnode_page: inventory_hostname.startswith('hnode-page')
|
||||
node_favicons: inventory_hostname.startswith('hnode-favicons')
|
||||
hnode_favicons: inventory_hostname.startswith('hnode-favicons')
|
||||
|
||||
# debugs: inventory_hostname.startswith('hdebug')
|
||||
|
||||
htask: inventory_hostname.startswith('htask')
|
||||
task: inventory_hostname.startswith('htask')
|
||||
celery: inventory_hostname.startswith('htask-celery')
|
||||
work: inventory_hostname.startswith('htask-work')
|
||||
|
||||
staging: inventory_hostname.startswith('hstaging')
|
||||
|
||||
hdb: inventory_hostname.startswith('hdb')
|
||||
db: inventory_hostname.startswith('hdb')
|
||||
search: inventory_hostname.startswith('hdb-elasticsearch')
|
||||
elasticsearch: inventory_hostname.startswith('hdb-elasticsearch')
|
||||
redis: inventory_hostname.startswith('hdb-redis')
|
||||
redis_story: inventory_hostname.startswith('hdb-redis-story')
|
||||
redis_session: inventory_hostname.startswith('hdb-redis-session')
|
||||
redis_user: inventory_hostname.startswith('hdb-redis-user')
|
||||
postgres: inventory_hostname.startswith('hdb-postgres')
|
||||
mongo: inventory_hostname.startswith('hdb-mongo') and not inventory_hostname.startswith('hdb-mongo-analytics')
|
||||
mongo_analytics: inventory_hostname.startswith('hdb-mongo-analytics')
|
||||
consul: inventory_hostname.startswith('hdb-consul')
|
||||
hconsul: inventory_hostname.startswith('hdb-consul')
|
||||
metrics: inventory_hostname.startswith('hdb-metrics')
|
||||
sentry: inventory_hostname.startswith('hdb-sentry')
|
|
@ -127,6 +127,9 @@
|
|||
- name: Reload gunicorn
|
||||
command: "kill -HUP {{ psaux.stdout }}"
|
||||
when: not pulled.changed
|
||||
rescue:
|
||||
- name: Restart Docker Container
|
||||
command: "docker restart newsblur_web"
|
||||
tags:
|
||||
- static
|
||||
|
||||
|
|
|
@ -6,16 +6,23 @@
|
|||
- ../env_vars/base.yml
|
||||
|
||||
tasks:
|
||||
- name: Extract part of hostname to determine container name
|
||||
set_fact:
|
||||
redis_role: "{{ inventory_hostname.split('-')[2] }}"
|
||||
tags:
|
||||
- never
|
||||
- replicaofnoone
|
||||
|
||||
- name: Turning off secondary for redis by deleting redis_replica.conf
|
||||
copy:
|
||||
dest: /srv/newsblur/docker/redis/redis_replica.conf
|
||||
dest: "/srv/newsblur/docker/redis/redis_{{ redis_role }}_replica.conf"
|
||||
content: ""
|
||||
tags:
|
||||
- never
|
||||
- replicaofnoone
|
||||
|
||||
- name: Setting Redis REPLICAOF NO ONE
|
||||
shell: docker exec redis redis-cli REPLICAOF NO ONE
|
||||
shell: docker exec redis-{{ redis_role }} redis-cli REPLICAOF NO ONE
|
||||
tags:
|
||||
- never
|
||||
- replicaofnoone
|
||||
|
|
6
ansible/playbooks/restart_server.yml
Normal file
6
ansible/playbooks/restart_server.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
- hosts: all
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Restart the server
|
||||
ansible.builtin.reboot:
|
|
@ -8,16 +8,16 @@
|
|||
- motd_role: db
|
||||
|
||||
roles:
|
||||
- {role: 'base', tags: 'base'}
|
||||
- {role: 'ufw', tags: 'ufw'}
|
||||
- {role: 'docker', tags: 'docker'}
|
||||
- {role: 'repo', tags: ['repo', 'pull']}
|
||||
- {role: 'dnsmasq', tags: 'dnsmasq'}
|
||||
- {role: 'consul', tags: 'consul'}
|
||||
- {role: 'consul-client', tags: 'consul'}
|
||||
- {role: 'mongo-exporter', tags: 'mongo-exporter'}
|
||||
- {role: 'postgres-exporter', tags: 'postgres-exporter'}
|
||||
- {role: 'redis-exporter', tags: 'redis-exporter'}
|
||||
- {role: 'node-exporter', tags: ['node-exporter', 'metrics']}
|
||||
- {role: 'prometheus', tags: ['prometheus', 'metrics']}
|
||||
- {role: 'grafana', tags: ['grafana', 'metrics']}
|
||||
# - {role: 'base', tags: 'base'}
|
||||
# - {role: 'ufw', tags: 'ufw'}
|
||||
# - {role: 'docker', tags: 'docker'}
|
||||
# - {role: 'repo', tags: ['repo', 'pull']}
|
||||
# - {role: 'dnsmasq', tags: 'dnsmasq'}
|
||||
# - {role: 'consul', tags: 'consul'}
|
||||
# - {role: 'consul-client', tags: 'consul'}
|
||||
# - {role: 'mongo-exporter', tags: 'mongo-exporter'}
|
||||
- { role: "postgres-exporter", tags: "postgres-exporter" }
|
||||
- { role: "redis-exporter", tags: "redis-exporter" }
|
||||
- { role: "node-exporter", tags: ["node-exporter", "metrics"] }
|
||||
- { role: "prometheus", tags: ["prometheus", "metrics"] }
|
||||
- { role: "grafana", tags: ["grafana", "metrics"] }
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
- name: SETUP -> node containers
|
||||
hosts: node
|
||||
become: true
|
||||
vars_files:
|
||||
- ../env_vars/base.yml
|
||||
vars:
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
- {role: 'docker', tags: 'docker'}
|
||||
- {role: 'repo', tags: ['repo', 'pull']}
|
||||
- {role: 'dnsmasq', tags: 'dnsmasq'}
|
||||
- {role: 'consul', tags: 'consul'}
|
||||
- {role: 'consul-client', tags: 'consul'}
|
||||
# - {role: 'consul', tags: 'consul'}
|
||||
# - {role: 'consul-client', tags: 'consul'}
|
||||
- {role: 'node-exporter', tags: ['node-exporter', 'metrics']}
|
||||
- {role: 'redis', tags: 'redis'}
|
||||
- {role: 'flask_metrics', tags: ['flask-metrics', 'metrics', 'flask_metrics']}
|
||||
|
|
|
@ -23,5 +23,5 @@
|
|||
- {role: 'nginx', tags: 'nginx'}
|
||||
- {role: 'node', tags: 'node'}
|
||||
- {role: 'node-exporter', tags: ['node-exporter', 'metrics']}
|
||||
- {role: 'prometheus', tags: ['prometheus', 'metrics']}
|
||||
- {role: 'grafana', tags: ['grafana', 'metrics']}
|
||||
# - {role: 'prometheus', tags: ['prometheus', 'metrics']}
|
||||
# - {role: 'grafana', tags: ['grafana', 'metrics']}
|
||||
|
|
|
@ -8,13 +8,12 @@
|
|||
- motd_role: task
|
||||
roles:
|
||||
- {role: 'base', tags: 'base'}
|
||||
- {role: 'ufw', tags: 'ufw'}
|
||||
- {role: 'docker', tags: 'docker'}
|
||||
- {role: 'repo', tags: ['repo', 'pull']}
|
||||
- {role: 'dnsmasq', tags: 'dnsmasq'}
|
||||
- {role: 'consul', tags: 'consul'}
|
||||
- {role: 'consul-client', tags: 'consul'}
|
||||
- {role: 'apns', tags: 'apns'}
|
||||
# - {role: 'netdata', tags: 'netdata'}
|
||||
- {role: 'node-exporter', tags: ['node-exporter', 'metrics']}
|
||||
- {role: 'celery_task', tags: 'celery'}
|
||||
- {role: 'ufw', tags: 'ufw'}
|
||||
|
|
|
@ -103,14 +103,15 @@
|
|||
become: yes
|
||||
command:
|
||||
docker run --rm --name=pg_basebackup --network=host -e POSTGRES_PASSWORD=newsblur -v /srv/newsblur/docker/volumes/postgres/data:/var/lib/postgresql/data postgres:13 pg_basebackup -h db-postgres.service.nyc1.consul -p 5432 -U newsblur -D /var/lib/postgresql/data -Fp -R -Xs -P -c fast
|
||||
|
||||
- name: Create Postgres docker volumes with correct permissions
|
||||
become: yes
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
recurse: yes
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
owner: 999
|
||||
group: 999
|
||||
with_items:
|
||||
- /srv/newsblur/docker/volumes/postgres/archive
|
||||
- /srv/newsblur/docker/volumes/postgres/backups
|
||||
|
@ -129,7 +130,7 @@
|
|||
- name: pg_ctl promote
|
||||
become: yes
|
||||
command:
|
||||
docker exec -it postgres su - postgres -c "/usr/lib/postgresql/13/bin/pg_ctl -D /var/lib/postgresql/data promote"
|
||||
docker exec postgres su - postgres -c "/usr/lib/postgresql/13/bin/pg_ctl -D /var/lib/postgresql/data promote"
|
||||
# when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-postgres-secondary']
|
||||
tags:
|
||||
- never
|
||||
|
|
|
@ -6,6 +6,17 @@
|
|||
tags: packages
|
||||
# ignore_errors: yes
|
||||
|
||||
- name: whoami
|
||||
debug:
|
||||
var: ansible_user_id
|
||||
tags: whoami
|
||||
|
||||
- name: Set timezone
|
||||
become: yes
|
||||
ansible.builtin.timezone:
|
||||
name: 'America/New_York'
|
||||
tags: timezone
|
||||
|
||||
- name: Copy zshrc
|
||||
template:
|
||||
src: zshrc.txt.j2
|
||||
|
@ -20,10 +31,20 @@
|
|||
become: yes
|
||||
|
||||
- name: Cloning oh-my-zsh
|
||||
git:
|
||||
repo: https://github.com/robbyrussell/oh-my-zsh
|
||||
dest: /home/nb/.oh-my-zsh
|
||||
force: yes
|
||||
block:
|
||||
- name: Cloning oh-my-zsh
|
||||
git:
|
||||
repo: https://github.com/robbyrussell/oh-my-zsh
|
||||
dest: /home/nb/.oh-my-zsh
|
||||
force: yes
|
||||
rescue:
|
||||
- name: chown oh-my-zsh
|
||||
become: yes
|
||||
file:
|
||||
path: /home/nb/.oh-my-zsh
|
||||
owner: nb
|
||||
group: nb
|
||||
recurse: yes
|
||||
|
||||
- name: Copy toprc
|
||||
copy: src=toprc.txt dest=~/.toprc
|
||||
|
|
|
@ -43,7 +43,7 @@ alias cd..='cd ..'
|
|||
|
||||
alias smtp='python -m smtpd -n -c DebuggingServer 127.0.0.1:1025'
|
||||
alias tlnb='echo "----------------\n"; tail -f /srv/newsblur/logs/newsblur.log'
|
||||
alias sp='sudo docker exec -it {% if 'task' in inventory_hostname %}{{ inventory_hostname|regex_replace('\d+', '') }}{% else %}newsblur_web{% endif %} python manage.py shell_plus'
|
||||
alias sp='sudo docker exec -it {% if 'task' in inventory_hostname %}{{ inventory_hostname|regex_replace('\-?\d+', '')|regex_replace('htask', 'task')|regex_replace('happ', 'app') }}{% else %}newsblur_web{% endif %} python manage.py shell_plus'
|
||||
alias dps='sudo docker ps -a'
|
||||
alias cdnb='cd /srv/newsblur'
|
||||
alias sshdo=/srv/newsblur/utils/ssh.sh
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
max-size: 100m
|
||||
healthcheck:
|
||||
# test: celery inspect ping -A newsblur_web -d celery@$HOSTNAME
|
||||
test: bash -c "(($(date +%s) - $(stat /srv/newsblur/logs/newsblur.log -c %Y) < 120)) && exit 0 || exit 1"
|
||||
test: bash -c "(($(date +%s) - $(stat /srv/newsblur/logs/newsblur.log -c %Y) < 600)) && exit 0 || exit 1"
|
||||
interval: 60s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"service": {
|
||||
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}",
|
||||
"name": "{{ inventory_hostname|regex_replace('\-?\d+', '')|regex_replace('htask', 'task') }}",
|
||||
"id": "{{ inventory_hostname }}",
|
||||
"tags": [
|
||||
"celery_task"
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
{
|
||||
{% if inventory_hostname.startswith("h") %}
|
||||
"datacenter": "nyc1",
|
||||
{% else %}
|
||||
"datacenter": "nyc1",
|
||||
{% endif %}
|
||||
"data_dir": "/opt/consul",
|
||||
"log_level": "INFO",
|
||||
"log_file": "/var/log/consul/consul.log",
|
||||
"enable_syslog": true,
|
||||
"retry_join": [{{ consul_manager_ip.stdout|trim }}],
|
||||
{% if inventory_hostname.startswith("hdb") and inventory_hostname|regex_replace("\-?\d+", "") not in ["hdb-mongo-analytics", "hdb-sentry"] %}
|
||||
"advertise_addr": "{% raw %}{{ GetAllInterfaces | include \"name\" \"^enp\" | include \"flags\" \"forwardable|up\" | attr \"address\" }}{% endraw %}",
|
||||
{% else %}
|
||||
"advertise_addr": "{% raw %}{{ GetAllInterfaces | include \"name\" \"^eth\" | include \"flags\" \"forwardable|up\" | attr \"address\" }}{% endraw %}",
|
||||
{% endif %}
|
||||
"bind_addr": "0.0.0.0",
|
||||
"ui_config": {"enabled": true},
|
||||
"dns_config": {
|
||||
|
|
|
@ -1,12 +1,52 @@
|
|||
#!/usr/bin/env python
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import digitalocean
|
||||
|
||||
TOKEN_FILE = "/srv/secrets-newsblur/keys/digital_ocean.token"
|
||||
|
||||
OLD = False
|
||||
# Uncomment below to allow existing servers to find the consul-manager
|
||||
OLD = True
|
||||
def get_host_ips_from_group(group_name):
|
||||
"""
|
||||
Fetches IP addresses of hosts from a specified group using ansible-inventory command across combined inventory.
|
||||
|
||||
:param group_name: The name of the group to fetch host IPs from.
|
||||
:param inventory_base_path: Base path to the inventory directories. Defaults to the path in ansible.cfg.
|
||||
:return: A list of IP addresses belonging to the specified group.
|
||||
"""
|
||||
cmd = ['ansible-inventory', '-i', '/srv/newsblur/ansible/inventories/hetzner.ini', '-i', '/srv/newsblur/ansible/inventories/hetzner.yml', '--list']
|
||||
|
||||
try:
|
||||
# Execute the ansible-inventory command
|
||||
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
|
||||
|
||||
# Parse the JSON output from ansible-inventory
|
||||
inventory_data = json.loads(result.stdout)
|
||||
|
||||
host_ips = []
|
||||
# Check if the group exists
|
||||
if group_name in inventory_data:
|
||||
# Get the list of hosts in the specified group
|
||||
if 'hosts' in inventory_data[group_name]:
|
||||
for host in inventory_data[group_name]['hosts']:
|
||||
# Fetch the host details, specifically looking for the ansible_host variable for the IP
|
||||
host_vars = inventory_data['_meta']['hostvars'][host]
|
||||
ip_address = host_vars.get('ansible_host', None)
|
||||
if ip_address:
|
||||
host_ips.append(ip_address)
|
||||
else:
|
||||
# If ansible_host is not defined, fallback to using the host's name
|
||||
host_ips.append(host)
|
||||
return host_ips
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Failed to execute ansible-inventory: {e.stderr}")
|
||||
return []
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Failed to parse JSON output: {e}")
|
||||
return []
|
||||
|
||||
|
||||
TOKEN_FILE = "/srv/secrets-newsblur/keys/digital_ocean.token"
|
||||
|
||||
with open(TOKEN_FILE) as f:
|
||||
token = f.read().strip()
|
||||
|
@ -14,11 +54,12 @@ with open(TOKEN_FILE) as f:
|
|||
|
||||
manager = digitalocean.Manager(token=token)
|
||||
my_droplets = manager.get_all_droplets()
|
||||
consul_manager_droplets = [d for d in my_droplets if d.name.startswith("db-consul")]
|
||||
if OLD:
|
||||
consul_manager_ip_address = ','.join([f"\"{droplet.ip_address}\"" for droplet in consul_manager_droplets])
|
||||
else:
|
||||
consul_manager_ip_address = ','.join([f"\"{droplet.private_ip_address}\"" for droplet in consul_manager_droplets])
|
||||
consul_manager_droplets = [d for d in my_droplets if "db-consul" in d.name]
|
||||
|
||||
# Use ansible-inventory to get the consul-manager ip
|
||||
group_name = 'hconsul'
|
||||
hetzner_hosts = get_host_ips_from_group(group_name)
|
||||
consul_manager_ip_address = ','.join([f"\"{droplet.ip_address}\"" for droplet in consul_manager_droplets] + [f"\"{host}\"" for host in hetzner_hosts])
|
||||
|
||||
print(consul_manager_ip_address)
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
---
|
||||
- name: Add the HashiCorp GPG key
|
||||
become: yes
|
||||
# Only warn if fail
|
||||
ignore_errors: yes
|
||||
apt_key:
|
||||
url: https://apt.releases.hashicorp.com/gpg
|
||||
state: present
|
||||
|
@ -10,11 +12,21 @@
|
|||
apt_repository:
|
||||
repo: "deb [arch=amd64] https://apt.releases.hashicorp.com {{ ansible_distribution_release }} main"
|
||||
|
||||
- name: Add the official HashiCorp Linux repository.
|
||||
become: yes
|
||||
apt_repository:
|
||||
repo: "deb [arch=arm64] https://apt.releases.hashicorp.com {{ ansible_distribution_release }} main"
|
||||
|
||||
- name: Update apt
|
||||
become: yes
|
||||
apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: Installing Consul
|
||||
become: yes
|
||||
apt:
|
||||
allow_downgrades: yes
|
||||
pkg: consul=1.10.4
|
||||
pkg: consul=1.10.12-1
|
||||
state: present
|
||||
|
||||
- name: Register Manager IP
|
||||
|
|
|
@ -19,7 +19,17 @@
|
|||
|
||||
- name: Bind dnsmasq to localhost
|
||||
become: yes
|
||||
lineinfile: dest=/etc/dnsmasq.conf state=present line='listen-address=127.0.0.1'
|
||||
lineinfile:
|
||||
dest: /etc/dnsmasq.conf
|
||||
state: present
|
||||
line: 'listen-address=127.0.0.1'
|
||||
|
||||
- name: Gather network facts
|
||||
become: yes
|
||||
setup:
|
||||
gather_subset:
|
||||
- '!all'
|
||||
- network
|
||||
|
||||
- name: Check if resolv.conf exists
|
||||
become: yes
|
||||
|
@ -27,18 +37,23 @@
|
|||
path: /etc/resolv.conf
|
||||
register: resolvconf
|
||||
|
||||
# - debug: msg="{{resolvconf}}"
|
||||
|
||||
- name: Add localhost to resolv.conf
|
||||
become: yes
|
||||
lineinfile: dest=/etc/resolv.conf state=present line='nameserver 127.0.0.1' insertbefore=BOF
|
||||
when: resolvconf.stat.readable
|
||||
lineinfile:
|
||||
dest: /etc/resolv.conf
|
||||
state: present
|
||||
line: 'nameserver 127.0.0.1'
|
||||
insertbefore: BOF
|
||||
when: resolvconf.stat.exists
|
||||
|
||||
- name: user=root dnsmasq
|
||||
- name: Set dnsmasq user to root
|
||||
become: yes
|
||||
lineinfile: dest=/etc/dnsmasq.conf state=present line='user=root'
|
||||
lineinfile:
|
||||
dest: /etc/dnsmasq.conf
|
||||
state: present
|
||||
line: 'user=root'
|
||||
|
||||
- name: Stop systemd-resolved
|
||||
- name: Stop and disable systemd-resolved
|
||||
become: yes
|
||||
systemd:
|
||||
name: systemd-resolved
|
||||
|
@ -54,8 +69,31 @@
|
|||
owner: root
|
||||
group: root
|
||||
mode: 0644
|
||||
vars:
|
||||
network_interfaces: "{{ ansible_interfaces }}"
|
||||
notify: restart dnsmasq
|
||||
|
||||
- name: Create systemd override directory for dnsmasq
|
||||
become: yes
|
||||
file:
|
||||
path: /etc/systemd/system/dnsmasq.service.d
|
||||
state: directory
|
||||
mode: 0755
|
||||
|
||||
# - name: Add override for dnsmasq service
|
||||
# become: yes
|
||||
# copy:
|
||||
# dest: /etc/systemd/system/dnsmasq.service.d/override.conf
|
||||
# content: |
|
||||
# [Unit]
|
||||
# After=docker.service
|
||||
|
||||
- name: Remove override for dnsmasq service
|
||||
become: yes
|
||||
file:
|
||||
path: /etc/systemd/system/dnsmasq.service.d/override.conf
|
||||
state: absent
|
||||
|
||||
- name: Launch dnsmasq
|
||||
become: yes
|
||||
service:
|
||||
|
|
|
@ -8,11 +8,13 @@ server=/consul/127.0.0.1#8600
|
|||
{# dnsmasq should not needlessly read /etc/resolv.conf #}
|
||||
no-resolv
|
||||
|
||||
interface=lo
|
||||
interface=eth0
|
||||
interface=eth1
|
||||
{% for interface in network_interfaces %}
|
||||
{% if not interface.startswith('veth') and not interface.startswith('docker') and not interface.startswith('br') %}
|
||||
# interface={{ interface }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
bind-interfaces
|
||||
# bind-interfaces # This will bind only to the interfaces that are up in interface= above
|
||||
# log-dhcp
|
||||
# log-queries
|
||||
# log-facility=/var/log/dnsmasq.log
|
||||
|
|
|
@ -15,25 +15,37 @@
|
|||
state: present
|
||||
with_items: "{{ docker_prerequisite_packages_Ubuntu }}"
|
||||
|
||||
- name: Install prerequisite packages (for Ubuntu 14.04 only)
|
||||
apt:
|
||||
name: "{{ item.package }}"
|
||||
state: present
|
||||
with_items: "{{ docker_prerequisite_packages_Ubuntu_1404 }}"
|
||||
when: ansible_distribution_version == '14.04'
|
||||
|
||||
- name: Import Docker CE repository gpg key
|
||||
- name: Download Docker GPG key
|
||||
become: yes
|
||||
apt_key:
|
||||
url: https://download.docker.com/linux/ubuntu/gpg
|
||||
state: present
|
||||
id: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88
|
||||
|
||||
- name: Add Docker CE repository
|
||||
shell:
|
||||
cmd: "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg"
|
||||
creates: /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
- name: Set up the Docker repository with the correct GPG key
|
||||
become: yes
|
||||
apt_repository:
|
||||
repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
|
||||
ansible.builtin.apt_repository:
|
||||
repo: "deb [arch={{ ansible_architecture }} signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Set up the Docker repository with the correct GPG key
|
||||
become: yes
|
||||
ansible.builtin.apt_repository:
|
||||
repo: "deb [arch=arm64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Set up the Docker repository with the correct GPG key
|
||||
become: yes
|
||||
ansible.builtin.apt_repository:
|
||||
repo: "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Update APT cache
|
||||
become: yes
|
||||
ansible.builtin.apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: Install Docker CE
|
||||
become: yes
|
||||
|
|
|
@ -1,4 +1,18 @@
|
|||
---
|
||||
- name: Set facts for secondary elasticsearch servers
|
||||
set_fact:
|
||||
elasticsearch_secondary: no
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Set facts for secondary elasticsearch servers
|
||||
set_fact:
|
||||
elasticsearch_secondary: yes
|
||||
# when: inventory_hostname not in ["db-elasticsearch"]
|
||||
when: inventory_hostname not in ["hdb-elasticsearch-1"]
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Permissions for elasticsearch
|
||||
become: yes
|
||||
file:
|
||||
|
@ -40,6 +54,7 @@
|
|||
- name: newsblurnet
|
||||
aliases:
|
||||
- elasticsearch
|
||||
user: "{{ ansible_effective_user_id|int }}:{{ ansible_effective_group_id|int }}"
|
||||
volumes:
|
||||
- /srv/newsblur/docker/volumes/elasticsearch:/usr/share/elasticsearch/data
|
||||
- /var/log/elasticsearch/:/var/log/elasticsearch/
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"service": {
|
||||
{% if not elasticsearch_secondary %}
|
||||
"name": "db-elasticsearch",
|
||||
{% else %}
|
||||
"name": "db-elasticsearch-staging",
|
||||
{% endif %}
|
||||
"tags": [
|
||||
"db"
|
||||
],
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
job: >-
|
||||
[ $(date +\%d) -le 07 ] &&
|
||||
docker pull certbot/dns-{{ dns_plugin }}:{{ certbot_version }};
|
||||
docker run --rm -it
|
||||
docker run --rm
|
||||
-v /etc/letsencrypt:/etc/letsencrypt
|
||||
-v /var/lib/letsencrypt:/var/lib/letsencrypt
|
||||
-v /var/log/letsencrypt:/var/log/letsencrypt
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
- name: Start mongo-exporter container
|
||||
become: yes
|
||||
docker_container:
|
||||
|
@ -12,9 +11,9 @@
|
|||
- name: newsblurnet
|
||||
env:
|
||||
# MONGODB_URI: 'mongodb://{{ inventory_hostname }}.node.nyc1.consul:27017/admin?'
|
||||
MONGODB_URI: 'mongodb://{{ mongodb_username }}:{{ mongodb_password }}@{{ inventory_hostname }}.node.nyc1.consul:27017/admin?authSource=admin'
|
||||
MONGODB_URI: "mongodb://{{ mongodb_username }}:{{ mongodb_password }}@{{ inventory_hostname }}.node.nyc1.consul:27017/admin?authSource=admin"
|
||||
ports:
|
||||
- '9216:9216'
|
||||
- "9216:9216"
|
||||
|
||||
- name: Register mongo-exporter in consul
|
||||
tags: consul
|
||||
|
@ -24,5 +23,5 @@
|
|||
dest: /etc/consul.d/mongo-exporter.json
|
||||
notify:
|
||||
- reload consul
|
||||
- name: Command to register mongo-exporter
|
||||
command: "consul services register /etc/consul.d/mongo-exporter.json"
|
||||
# - name: Command to register mongo-exporter
|
||||
# command: "consul services register /etc/consul.d/mongo-exporter.json"
|
||||
|
|
|
@ -1,4 +1,20 @@
|
|||
---
|
||||
- name: Set mongo_analytics_secondary
|
||||
set_fact:
|
||||
mongo_analytics_secondary: no
|
||||
tags:
|
||||
- consul
|
||||
- always
|
||||
|
||||
- name: Set mongo_analytics_secondary
|
||||
set_fact:
|
||||
mongo_analytics_secondary: yes
|
||||
# when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) not in ['db-mongo-analytics']
|
||||
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) not in ['hdb-mongo-analytics']
|
||||
tags:
|
||||
- consul
|
||||
- always
|
||||
|
||||
- name: Permissions for mongo
|
||||
become: yes
|
||||
file:
|
||||
|
@ -8,6 +24,17 @@
|
|||
group: "{{ ansible_effective_group_id|int }}"
|
||||
path: /var/log/mongodb
|
||||
|
||||
- name: Copy MongoDB keyfile
|
||||
become: yes
|
||||
copy:
|
||||
content: "{{ mongodb_keyfile }}"
|
||||
dest: /srv/newsblur/config/mongodb_keyfile.key
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
mode: 0400
|
||||
tags:
|
||||
- keyfile
|
||||
|
||||
- name: Block for mongo volume
|
||||
block:
|
||||
- name: Get the volume name
|
||||
|
@ -37,46 +64,47 @@
|
|||
opts: defaults,discard
|
||||
state: mounted
|
||||
|
||||
|
||||
- name: Set permissions on mongo volume
|
||||
# become: yes
|
||||
file:
|
||||
path: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}"
|
||||
state: directory
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
recurse: yes
|
||||
|
||||
- name: Make backup directory
|
||||
# become: yes
|
||||
file:
|
||||
path: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup/"
|
||||
state: directory
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
mode: 0755
|
||||
|
||||
- name: Create symlink to mounted volume for backups to live
|
||||
file:
|
||||
state: link
|
||||
src: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup"
|
||||
path: /srv/newsblur/backup
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
force: yes
|
||||
when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-mongo-secondary', 'db-mongo-analytics']
|
||||
|
||||
- name: Copy MongoDB keyfile
|
||||
become: yes
|
||||
copy:
|
||||
content: "{{ mongodb_keyfile }}"
|
||||
dest: /srv/newsblur/config/mongodb_keyfile.key
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
mode: 0400
|
||||
tags:
|
||||
- keyfile
|
||||
|
||||
- name: Set permissions on mongo volume
|
||||
become: yes
|
||||
file:
|
||||
path: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}"
|
||||
state: directory
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
recurse: yes
|
||||
- name: Block for mongo volume on hetzner
|
||||
block:
|
||||
- name: Create backup directory
|
||||
file:
|
||||
path: "/srv/newsblur/docker/volumes/mongo/backup"
|
||||
state: directory
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
mode: 0755
|
||||
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['hdb-mongo-secondary', 'hdb-mongo-analytics']
|
||||
|
||||
- name: Make backup directory
|
||||
become: yes
|
||||
file:
|
||||
path: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup/"
|
||||
state: directory
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
mode: 0755
|
||||
|
||||
- name: Create symlink to mounted volume for backups to live
|
||||
file:
|
||||
state: link
|
||||
src: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup"
|
||||
path: /srv/newsblur/backup
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
force: yes
|
||||
|
||||
- name: Start db-mongo docker container
|
||||
become: yes
|
||||
docker_container:
|
||||
|
@ -103,7 +131,46 @@
|
|||
- /srv/newsblur/config/mongodb_keyfile.key:/srv/newsblur/config/mongodb_keyfile.key
|
||||
- /var/log/mongodb/:/var/log/mongodb/
|
||||
- /mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup/:/backup/
|
||||
when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-mongo', 'db-mongo-primary', 'db-mongo-secondary']
|
||||
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['db-mongo', 'db-mongo-primary', 'db-mongo-secondary']
|
||||
|
||||
- name: Start mongo and set permissions
|
||||
block:
|
||||
- name: Start db-mongo docker container on hetzner
|
||||
docker_container:
|
||||
name: mongo
|
||||
image: mongo:4.0
|
||||
state: started
|
||||
container_default_behavior: no_defaults
|
||||
hostname: "{{ inventory_hostname }}"
|
||||
restart_policy: unless-stopped
|
||||
networks_cli_compatible: yes
|
||||
network_mode: host
|
||||
# network_mode: default
|
||||
# networks:
|
||||
# - name: newsblurnet
|
||||
# aliases:
|
||||
# - mongo
|
||||
# ports:
|
||||
# - "27017:27017"
|
||||
command: --config /etc/mongod.conf
|
||||
# user: 1000:1001
|
||||
user: "{{ ansible_effective_user_id|int }}:{{ ansible_effective_group_id|int }}"
|
||||
volumes:
|
||||
- /srv/newsblur/docker/volumes/mongo:/data/db
|
||||
- /srv/newsblur/ansible/roles/mongo/templates/mongo.conf:/etc/mongod.conf
|
||||
- /srv/newsblur/config/mongodb_keyfile.key:/srv/newsblur/config/mongodb_keyfile.key
|
||||
- /var/log/mongodb/:/var/log/mongodb/
|
||||
- /srv/newsblur/docker/volumes/mongo/backup/:/backup/
|
||||
|
||||
- name: Set permissions on mongo volume
|
||||
become: yes
|
||||
file:
|
||||
path: "/srv/newsblur/docker/volumes/"
|
||||
state: directory
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
recurse: yes
|
||||
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['hdb-mongo-primary', 'hdb-mongo-secondary', 'hdb-mongo-analytics']
|
||||
|
||||
- name: Start db-mongo-analytics docker container
|
||||
become: yes
|
||||
|
@ -131,7 +198,35 @@
|
|||
- /srv/newsblur/config/mongodb_keyfile.key:/srv/newsblur/config/mongodb_keyfile.key
|
||||
- /var/log/mongodb/:/var/log/mongodb/
|
||||
- /mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}/backup/:/backup/
|
||||
when: (inventory_hostname | regex_replace('[0-9]+', '')) == 'db-mongo-analytics'
|
||||
when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-mongo-analytics']
|
||||
|
||||
- name: Start db-mongo-analytics docker container on hetzner
|
||||
become: yes
|
||||
docker_container:
|
||||
name: mongo
|
||||
image: mongo:4.0
|
||||
state: started
|
||||
container_default_behavior: no_defaults
|
||||
hostname: "{{ inventory_hostname }}"
|
||||
restart_policy: unless-stopped
|
||||
networks_cli_compatible: yes
|
||||
# network_mode: host
|
||||
network_mode: default
|
||||
networks:
|
||||
- name: newsblurnet
|
||||
aliases:
|
||||
- mongo
|
||||
ports:
|
||||
- "27017:27017"
|
||||
command: --config /etc/mongod.conf
|
||||
user: 1000:1001
|
||||
volumes:
|
||||
- /srv/newsblur/docker/volumes/mongo:/data/db
|
||||
- /srv/newsblur/ansible/roles/mongo/templates/mongo.analytics.conf:/etc/mongod.conf
|
||||
- /srv/newsblur/config/mongodb_keyfile.key:/srv/newsblur/config/mongodb_keyfile.key
|
||||
- /var/log/mongodb/:/var/log/mongodb/
|
||||
- /srv/newsblur/docker/volumes/mongo/backup/:/backup/
|
||||
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['hdb-mongo-analytics']
|
||||
|
||||
- name: Create mongo database user
|
||||
shell:
|
||||
|
@ -148,7 +243,6 @@
|
|||
]
|
||||
}
|
||||
)' admin
|
||||
when: (inventory_hostname | regex_replace('[0-9]+', '')) == 'db-mongo-analytics'
|
||||
register: auth_result
|
||||
changed_when:
|
||||
- auth_result.rc == 0
|
||||
|
@ -170,7 +264,7 @@
|
|||
template:
|
||||
src: consul_service.json
|
||||
dest: /etc/consul.d/mongo.json
|
||||
when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-mongo-primary', 'db-mongo-secondary']
|
||||
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['db-mongo-primary', 'db-mongo-secondary', 'hdb-mongo-primary', 'hdb-mongo-secondary']
|
||||
notify:
|
||||
- reload consul
|
||||
|
||||
|
@ -180,7 +274,7 @@
|
|||
template:
|
||||
src: consul_service.analytics.json
|
||||
dest: /etc/consul.d/mongo.json
|
||||
when: (inventory_hostname | regex_replace('[0-9]+', '')) == 'db-mongo-analytics'
|
||||
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['db-mongo-analytics', 'hdb-mongo-analytics']
|
||||
notify:
|
||||
- reload consul
|
||||
|
||||
|
@ -214,7 +308,6 @@
|
|||
register: app_changed
|
||||
|
||||
- name: Add mongo backup log
|
||||
become: yes
|
||||
file:
|
||||
path: /var/log/mongo_backup.log
|
||||
state: touch
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"service": {
|
||||
"name": "db-mongo-analytics",
|
||||
{% if not mongo_analytics_secondary %}
|
||||
"name": "db-mongo-analytics",
|
||||
{% else %}
|
||||
"name": "db-mongo-analytics-secondary",
|
||||
{% endif %}
|
||||
"id": "{{ inventory_hostname }}",
|
||||
"tags": [
|
||||
"db"
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
---
|
||||
- name: Ensure 'nb' user owns /srv/ recursively
|
||||
become: yes
|
||||
file:
|
||||
path: /srv
|
||||
owner: nb
|
||||
group: nb
|
||||
recurse: yes
|
||||
state: directory
|
||||
|
||||
- name: Copy node secrets
|
||||
copy:
|
||||
src: /srv/secrets-newsblur/settings/node_settings.env
|
||||
|
@ -34,22 +43,22 @@
|
|||
- name: Get the volume name
|
||||
shell: ls /dev/disk/by-id/ | grep -v part
|
||||
register: volume_name_raw
|
||||
when: '"node-page" in inventory_hostname'
|
||||
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
|
||||
|
||||
- set_fact:
|
||||
volume_name: "{{ volume_name_raw.stdout }}"
|
||||
when: '"node-page" in inventory_hostname'
|
||||
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
|
||||
|
||||
- debug:
|
||||
msg: "{{ volume_name }}"
|
||||
when: '"node-page" in inventory_hostname'
|
||||
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
|
||||
|
||||
- name: Create the mount point
|
||||
become: yes
|
||||
file:
|
||||
path: "/mnt/{{ inventory_hostname | regex_replace('-', '') }}"
|
||||
state: directory
|
||||
when: '"node-page" in inventory_hostname'
|
||||
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
|
||||
|
||||
- name: Mount volume read-write
|
||||
become: yes
|
||||
|
@ -59,7 +68,7 @@
|
|||
fstype: xfs
|
||||
opts: defaults,discard
|
||||
state: mounted
|
||||
when: '"node-page" in inventory_hostname'
|
||||
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
|
||||
|
||||
- name: Symlink node-page volume from /srv/originals
|
||||
become: yes
|
||||
|
@ -67,10 +76,9 @@
|
|||
dest: /srv/originals
|
||||
src: "/mnt/{{ inventory_hostname | regex_replace('-', '') }}"
|
||||
state: link
|
||||
when: '"node-page" in inventory_hostname'
|
||||
when: '"node-page" in inventory_hostname and not inventory_hostname.startswith("hnode")'
|
||||
|
||||
- name: Start node docker containers
|
||||
become: yes
|
||||
docker_container:
|
||||
name: node
|
||||
image: newsblur/newsblur_node
|
||||
|
@ -104,7 +112,6 @@
|
|||
when: item in inventory_hostname
|
||||
|
||||
- name: Start non-newsblur node docker containers
|
||||
become: yes
|
||||
docker_container:
|
||||
name: "{{ item.container_name }}"
|
||||
image: "{{ item.image }}"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"service": {
|
||||
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}",
|
||||
"name": "{{ inventory_hostname|regex_replace('\-?\d+', '')|regex_replace('hnode', 'node') }}",
|
||||
"id": "{{ inventory_hostname }}",
|
||||
"tags": [
|
||||
"web",
|
||||
|
|
|
@ -10,5 +10,5 @@
|
|||
|
||||
- name: reload postgres config
|
||||
become: yes
|
||||
command: docker exec -u postgres postgres pg_ctl reload
|
||||
command: docker exec postgres pg_ctl reload
|
||||
listen: reload postgres
|
||||
|
|
|
@ -1,4 +1,17 @@
|
|||
---
|
||||
- name: Set facts for all postgres servers
|
||||
set_fact:
|
||||
postgres_secondary: no
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Set facts for secondary postgres servers
|
||||
set_fact:
|
||||
postgres_secondary: yes
|
||||
# when: inventory_hostname not in ["db-postgres2"]
|
||||
when: inventory_hostname not in ["hdb-postgres-1"]
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Template postgresql-13.conf file
|
||||
template:
|
||||
|
@ -6,24 +19,67 @@
|
|||
dest: /srv/newsblur/docker/postgres/postgres.conf
|
||||
notify: reload postgres
|
||||
register: updated_config
|
||||
|
||||
|
||||
- name: Create Postgres docker volumes with correct permissions
|
||||
become: yes
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
recurse: yes
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
owner: 999
|
||||
group: 999
|
||||
with_items:
|
||||
- /srv/newsblur/docker/volumes/postgres/archive
|
||||
- /srv/newsblur/docker/volumes/postgres/backups
|
||||
- /srv/newsblur/docker/volumes/postgres/data
|
||||
|
||||
- name: Template postgres secondaries with empty standby.signal file
|
||||
become: yes
|
||||
copy:
|
||||
dest: /srv/newsblur/docker/volumes/postgres/data/standby.signal
|
||||
content: ""
|
||||
owner: 999
|
||||
group: 999
|
||||
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['db-postgres-secondary', 'hdb-postgres']
|
||||
|
||||
- name: Copy SSH private key
|
||||
become: yes
|
||||
copy:
|
||||
src: /srv/secrets-newsblur/keys/postgres.key
|
||||
dest: /home/nb/.ssh/id_rsa
|
||||
owner: 999
|
||||
group: 999
|
||||
mode: "0600"
|
||||
|
||||
- name: Copy SSH public key
|
||||
become: yes
|
||||
copy:
|
||||
src: /srv/secrets-newsblur/keys/postgres.key.pub
|
||||
dest: /home/nb/.ssh/id_rsa.pub
|
||||
owner: 999
|
||||
group: 999
|
||||
mode: "0600"
|
||||
|
||||
- name: Add SSH public key to authorized keys
|
||||
authorized_key:
|
||||
user: "nb"
|
||||
state: present
|
||||
key: "{{ lookup('file', '/srv/secrets-newsblur/keys/postgres.key.pub') }}"
|
||||
|
||||
- name: Build the custom postgres docker image
|
||||
docker_image:
|
||||
name: newsblur/postgres:13
|
||||
source: build
|
||||
build:
|
||||
path: /srv/newsblur/docker/postgres/
|
||||
force_tag: yes
|
||||
state: present
|
||||
|
||||
- name: Start postgres docker containers
|
||||
become: yes
|
||||
docker_container:
|
||||
name: postgres
|
||||
image: postgres:13
|
||||
image: newsblur/postgres:13
|
||||
state: started
|
||||
container_default_behavior: no_defaults
|
||||
command: postgres -c config_file=/etc/postgresql/postgresql.conf
|
||||
|
@ -35,11 +91,11 @@
|
|||
network_mode: default
|
||||
networks:
|
||||
- name: newsblurnet
|
||||
aliases:
|
||||
aliases:
|
||||
- postgres
|
||||
user: "999:999"
|
||||
ports:
|
||||
- 5432:5432
|
||||
user: 1000:1001
|
||||
volumes:
|
||||
- /srv/newsblur/docker/volumes/postgres/data:/var/lib/postgresql/data
|
||||
- /srv/newsblur/docker/volumes/postgres/archive:/var/lib/postgresql/archive
|
||||
|
@ -47,15 +103,16 @@
|
|||
- /srv/newsblur/docker/postgres/postgres.conf:/etc/postgresql/postgresql.conf
|
||||
- /srv/newsblur/docker/postgres/postgres_hba-13.conf:/etc/postgresql/pg_hba.conf
|
||||
- /srv/newsblur/docker/postgres/postgres_ident-13.conf:/etc/postgresql/pg_ident.conf
|
||||
- /home/nb/.ssh/id_rsa:/var/lib/postgresql/.ssh/id_rsa
|
||||
restart_policy: unless-stopped
|
||||
when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-postgres-primary', 'db-postgres']
|
||||
when: (inventory_hostname | regex_replace('\-?[0-9]+', '')) in ['db-postgres-primary', 'db-postgres', 'hdb-postgres', 'hdb-postgres-secondary']
|
||||
|
||||
# - name: Change ownership in postgres docker container
|
||||
# become: yes
|
||||
# command: >
|
||||
# docker exec postgres chown -fR postgres.postgres /var/lib/postgresql
|
||||
# ignore_errors: yes
|
||||
|
||||
- name: Change ownership in postgres docker container
|
||||
become: yes
|
||||
command: >
|
||||
docker exec postgres chown -fR postgres.postgres /var/lib/postgresql
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Ensure newsblur role in postgres
|
||||
become: yes
|
||||
shell: >
|
||||
|
@ -63,9 +120,10 @@
|
|||
docker exec postgres createuser -s newsblur -U postgres;
|
||||
docker exec postgres createdb newsblur -U newsblur;
|
||||
register: ensure_role
|
||||
ignore_errors: yes
|
||||
changed_when:
|
||||
- "ensure_role.rc == 0"
|
||||
failed_when:
|
||||
- "ensure_role.rc == 0"
|
||||
failed_when:
|
||||
- "'already exists' not in ensure_role.stderr"
|
||||
- "ensure_role.rc != 0"
|
||||
|
||||
|
@ -89,7 +147,9 @@
|
|||
name: disk_usage_sanity_checker
|
||||
minute: "0"
|
||||
job: >-
|
||||
OUTPUT=$(df / | head -n 2 | tail -1) docker run --rm -it -v /srv/newsblur:/srv/newsblur --network=newsblurnet --hostname {{ ansible_hostname }} newsblur/newsblur_python3 /srv/newsblur/utils/monitor_disk_usage.py $OUTPUT
|
||||
OUTPUT=$(df / | head -n 2 | tail -1) docker run --rm -it -v /srv/newsblur:/srv/newsblur \
|
||||
--network=newsblurnet --hostname {{ ansible_hostname }} newsblur/newsblur_python3 \
|
||||
/srv/newsblur/utils/monitor_disk_usage.py $OUTPUT
|
||||
tags: cron
|
||||
|
||||
- name: Add postgresql archive cleaner cronjob
|
||||
|
@ -115,8 +175,8 @@
|
|||
path: /var/log/postgres_backup.log
|
||||
state: touch
|
||||
mode: 0777
|
||||
owner: 1000
|
||||
group: 1001
|
||||
owner: 999
|
||||
group: 999
|
||||
|
||||
- name: Add postgres backup
|
||||
cron:
|
||||
|
@ -125,4 +185,3 @@
|
|||
hour: "4"
|
||||
job: /srv/newsblur/docker/postgres/backup_postgres.sh >> /var/log/postgres_backup.log 2>&1
|
||||
tags: cron
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"service": {
|
||||
{% if inventory_hostname.startswith('db-postgres2') %}
|
||||
{% if not postgres_secondary %}
|
||||
"name": "db-postgres",
|
||||
{% else %}
|
||||
"name": "db-postgres-secondary",
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
restart_policy: unless-stopped
|
||||
container_default_behavior: no_defaults
|
||||
env:
|
||||
REDIS_ADDR: "db-{{item.redis_target}}.service.nyc1.consul:6379"
|
||||
REDIS_ADDR: "db-{{item.redis_target}}.service.nyc1.consul:{{ item.redis_port }}"
|
||||
networks_cli_compatible: yes
|
||||
network_mode: default
|
||||
networks:
|
||||
|
@ -16,12 +16,16 @@
|
|||
with_items:
|
||||
- port: 9121
|
||||
redis_target: "redis-user"
|
||||
redis_port: 6381
|
||||
- port: 9122
|
||||
redis_target: "redis-sessions"
|
||||
redis_target: "redis-session"
|
||||
redis_port: 6382
|
||||
- port: 9123
|
||||
redis_target: "redis-story"
|
||||
redis_port: 6380
|
||||
- port: 9124
|
||||
redis_target: "redis-pubsub"
|
||||
redis_port: 6383
|
||||
|
||||
- name: Register redis-exporters in consul
|
||||
tags: consul
|
||||
|
@ -35,7 +39,7 @@
|
|||
- port: 9121
|
||||
redis_target: "redis-user"
|
||||
- port: 9122
|
||||
redis_target: "redis-sessions"
|
||||
redis_target: "redis-session"
|
||||
- port: 9123
|
||||
redis_target: "redis-story"
|
||||
- port: 9124
|
||||
|
|
|
@ -8,7 +8,19 @@
|
|||
state: reloaded
|
||||
listen: reload consul
|
||||
|
||||
- name: restart redis
|
||||
- name: restart redis user
|
||||
become: yes
|
||||
command: docker restart redis
|
||||
listen: restart redis
|
||||
command: docker restart redis-user
|
||||
listen: restart redis_user
|
||||
- name: restart redis story
|
||||
become: yes
|
||||
command: docker restart redis-story
|
||||
listen: restart redis_story
|
||||
- name: restart redis session
|
||||
become: yes
|
||||
command: docker restart redis-session
|
||||
listen: restart redis_session
|
||||
- name: restart redis pubsub
|
||||
become: yes
|
||||
command: docker restart redis-pubsub
|
||||
listen: restart redis_pubsub
|
||||
|
|
|
@ -1,4 +1,31 @@
|
|||
---
|
||||
- name: Extract part of hostname to determine container name
|
||||
set_fact:
|
||||
redis_role: "{{ inventory_hostname.split('-')[2] }}"
|
||||
redis_secondary: no
|
||||
# redis_port: 6379
|
||||
redis_ports:
|
||||
story: 6380
|
||||
user: 6381
|
||||
session: 6382
|
||||
pubsub: 6383
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Set redis_port for redis servers
|
||||
set_fact:
|
||||
redis_port: "{{ redis_ports[redis_role] }}"
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Set redis_secondary for secondary redis servers
|
||||
set_fact:
|
||||
redis_secondary: yes
|
||||
# when: inventory_hostname not in ["db-redis-user", "db-redis-story1", "db-redis-session", "db-redis-pubsub"]
|
||||
when: inventory_hostname not in ["hdb-redis-user-1", "hdb-redis-story-1", "hdb-redis-session-1", "hdb-redis-pubsub"]
|
||||
tags:
|
||||
- always
|
||||
|
||||
- name: Install sysfsutils for disabling transparent huge pages
|
||||
become: yes
|
||||
package:
|
||||
|
@ -25,29 +52,42 @@
|
|||
copy:
|
||||
src: /srv/newsblur/docker/redis/redis.conf
|
||||
dest: /srv/newsblur/docker/redis/redis.conf
|
||||
notify: restart redis
|
||||
notify: "restart redis_{{ redis_role }}"
|
||||
register: updated_config
|
||||
|
||||
- name: Template redis_replica.conf file
|
||||
template:
|
||||
src: /srv/newsblur/docker/redis/redis_replica.conf.j2
|
||||
dest: /srv/newsblur/docker/redis/redis_replica.conf
|
||||
notify: restart redis
|
||||
dest: "/srv/newsblur/docker/redis/redis_{{ redis_role }}_replica.conf"
|
||||
notify: "restart redis_{{ redis_role }}"
|
||||
register: updated_config
|
||||
when: "'db-redis-story1' in inventory_hostname"
|
||||
when: redis_secondary
|
||||
|
||||
- name: Remove redis_replica.conf file if not secondary
|
||||
copy:
|
||||
dest: "/srv/newsblur/docker/redis/redis_{{ redis_role }}_replica.conf"
|
||||
content: ""
|
||||
when: not redis_secondary
|
||||
|
||||
- name: Create Redis docker volume directory
|
||||
file:
|
||||
path: /srv/newsblur/docker/volumes
|
||||
state: directory
|
||||
recurse: yes
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
|
||||
- name: Create Redis docker volume with correct permissions
|
||||
file:
|
||||
path: /srv/newsblur/docker/volumes/redis
|
||||
path: "/srv/newsblur/docker/volumes/redis_{{ redis_role }}"
|
||||
state: directory
|
||||
recurse: yes
|
||||
owner: "{{ ansible_effective_user_id|int }}"
|
||||
group: "{{ ansible_effective_group_id|int }}"
|
||||
|
||||
- name: Start redis docker containers
|
||||
become: yes
|
||||
docker_container:
|
||||
name: redis
|
||||
name: "redis-{{ redis_role }}"
|
||||
image: redis:7
|
||||
pull: true
|
||||
state: started
|
||||
|
@ -62,19 +102,19 @@
|
|||
aliases:
|
||||
- redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
- "{{ redis_port }}:6379"
|
||||
restart_policy: unless-stopped
|
||||
user: 1000:1001
|
||||
user: "{{ ansible_effective_user_id|int }}:{{ ansible_effective_group_id|int }}"
|
||||
volumes:
|
||||
- /srv/newsblur/docker/volumes/redis:/data
|
||||
- /srv/newsblur/docker/redis/redis.conf:/usr/local/etc/redis/redis_server.conf
|
||||
- /srv/newsblur/docker/redis/redis_replica.conf:/usr/local/etc/redis/redis_replica.conf
|
||||
- "/srv/newsblur/docker/volumes/redis_{{ redis_role }}:/data"
|
||||
- "/srv/newsblur/docker/redis/redis.conf:/usr/local/etc/redis/redis_server.conf"
|
||||
- "/srv/newsblur/docker/redis/redis_{{ redis_role }}_replica.conf:/usr/local/etc/redis/redis_replica.conf"
|
||||
|
||||
- name: Register redis in consul
|
||||
become: yes
|
||||
template:
|
||||
src: consul_service.json
|
||||
dest: /etc/consul.d/redis.json
|
||||
dest: "/etc/consul.d/redis_{{ redis_role }}.json"
|
||||
notify:
|
||||
- reload consul
|
||||
tags: consul
|
||||
|
@ -123,7 +163,7 @@
|
|||
job: >
|
||||
docker run --rm
|
||||
-v /srv/newsblur:/srv/newsblur
|
||||
-v /srv/newsblur/docker/volumes/redis/dump.rdb:/data/dump.rdb
|
||||
-v /srv/newsblur/docker/volumes/redis_{{ redis_role }}/dump.rdb:/data/dump.rdb
|
||||
--network=newsblurnet
|
||||
--hostname={{ ansible_hostname }}
|
||||
newsblur/newsblur_python3 python /srv/newsblur/utils/backups/backup_redis.py
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
{
|
||||
"service": {
|
||||
{% if inventory_hostname in ["db-redis-user", "db-redis-story2", "db-redis-session", "db-redis-pubsub"] %}
|
||||
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}",
|
||||
{% if not redis_secondary %}
|
||||
"name": "{{ inventory_hostname|regex_replace('\-?\d+', '')|regex_replace("hdb", "db") }}",
|
||||
{% else %}
|
||||
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}-staging",
|
||||
"name": "db-redis-{{ redis_role }}-staging",
|
||||
{% endif %}
|
||||
"id": "{{ inventory_hostname }}",
|
||||
"tags": [
|
||||
"redis"
|
||||
],
|
||||
"port": 6379,
|
||||
"port": {{ redis_port }},
|
||||
"checks": [{
|
||||
"id": "{{inventory_hostname}}-ping",
|
||||
{% if inventory_hostname.startswith('db-redis-story') %}
|
||||
"http": "http://{{ ansible_host }}:5579/db_check/redis_story?consul=1",
|
||||
{% elif inventory_hostname.startswith('db-redis-user') %}
|
||||
"http": "http://{{ ansible_host }}:5579/db_check/redis_user?consul=1",
|
||||
{% elif inventory_hostname.startswith('db-redis-pubsub') %}
|
||||
"http": "http://{{ ansible_host }}:5579/db_check/redis_pubsub?consul=1",
|
||||
{% elif inventory_hostname.startswith('db-redis-sessions') %}
|
||||
"http": "http://{{ ansible_host }}:5579/db_check/redis_sessions?consul=1",
|
||||
{% if 'db-redis-story' in inventory_hostname %}
|
||||
"http": "http://{{ ansible_host }}:5579/db_check/redis_story?consul=1&port={{ redis_port }}",
|
||||
{% elif 'db-redis-user' in inventory_hostname %}
|
||||
"http": "http://{{ ansible_host }}:5579/db_check/redis_user?consul=1&port={{ redis_port }}",
|
||||
{% elif 'db-redis-pubsub' in inventory_hostname %}
|
||||
"http": "http://{{ ansible_host }}:5579/db_check/redis_pubsub?consul=1&port={{ redis_port }}",
|
||||
{% elif 'db-redis-session' in inventory_hostname %}
|
||||
"http": "http://{{ ansible_host }}:5579/db_check/redis_sessions?consul=1&port={{ redis_port }}",
|
||||
{% else %}
|
||||
"http": "http://{{ ansible_host }}:5000/db_check/redis?consul=1",
|
||||
{% endif %}
|
||||
|
|
|
@ -1,13 +1,26 @@
|
|||
---
|
||||
- name: Stop ufw and delete all rules
|
||||
become: yes
|
||||
ufw: state=reset
|
||||
tags: ufw
|
||||
|
||||
- name: Set firewall default policy
|
||||
- name: Set hosts
|
||||
set_fact:
|
||||
hetzner_hosts: "{{ groups['hall'] | map('extract', hostvars, ['ansible_host']) }}"
|
||||
do_hosts: "{{ groups['NewsBlur_Docker'] | map('extract', hostvars, ['ansible_host']) }}"
|
||||
|
||||
- name: Generate UFW batch script
|
||||
become: yes
|
||||
ufw: state=disabled policy=reject
|
||||
tags: ufw
|
||||
template:
|
||||
src: ufw_rules.sh.j2
|
||||
dest: /tmp/ufw_rules.sh
|
||||
mode: '0755'
|
||||
|
||||
# - name: Stop ufw and delete all rules
|
||||
# become: yes
|
||||
# ufw: state=reset
|
||||
# tags: ufw
|
||||
|
||||
# - name: Set firewall default policy
|
||||
# become: yes
|
||||
# ufw: state=disabled policy=reject
|
||||
# tags: ufw
|
||||
#
|
||||
# - name: Set ufw policy to deny all incoming connections
|
||||
# ufw: policy=deny direction=incoming
|
||||
|
@ -16,58 +29,87 @@
|
|||
# - name: Set ufw policy to allow all ougoing connections
|
||||
# ufw: policy=allow direction=outgoing
|
||||
# tags: ufw
|
||||
|
||||
- name: Execute UFW batch script
|
||||
become: yes
|
||||
command: /tmp/ufw_rules.sh
|
||||
|
||||
# - name: Allow ssh
|
||||
# become: yes
|
||||
# ufw: rule=allow port=22
|
||||
# tags: ufw
|
||||
|
||||
# - name: Allow all access from RFC1918 networks to this host
|
||||
# become: yes
|
||||
# ufw:
|
||||
# rule: allow
|
||||
# src: '{{ item }}'
|
||||
# with_items:
|
||||
# - 10.0.0.0/8
|
||||
# - 172.18.0.0/16
|
||||
# - 172.17.0.0/16
|
||||
# tags:
|
||||
# - firewall
|
||||
# - ufw
|
||||
|
||||
- name: Allow ssh
|
||||
become: yes
|
||||
ufw: rule=allow port=22
|
||||
tags: ufw
|
||||
# - name: Allow all access from Hetzner inventory hosts
|
||||
# become: yes
|
||||
# ufw:
|
||||
# rule: allow
|
||||
# src: '{{ item }}'
|
||||
# with_items: "{{ groups['hall'] | map('extract', hostvars, ['ansible_host']) }}"
|
||||
# tags:
|
||||
# - firewall
|
||||
# - ufw
|
||||
# - hetzner_firewall
|
||||
# - hfirewall
|
||||
|
||||
- name: Allow all access from RFC1918 networks to this host
|
||||
become: yes
|
||||
ufw:
|
||||
rule: allow
|
||||
src: '{{ item }}'
|
||||
with_items:
|
||||
- 10.0.0.0/8
|
||||
- 172.18.0.0/16
|
||||
- 172.17.0.0/16
|
||||
tags:
|
||||
- firewall
|
||||
- ufw
|
||||
|
||||
- name: Allow all access from inventory hosts old + new
|
||||
become: yes
|
||||
ufw:
|
||||
rule: allow
|
||||
src: '{{ item }}'
|
||||
with_items: "{{ groups['oldandnew'] | map('extract', hostvars, ['ansible_host']) }}"
|
||||
when: "'oldandnew' in groups"
|
||||
tags:
|
||||
- firewall
|
||||
- ufw
|
||||
# - name: Allow all access from Hetzner inventory hosts with docker
|
||||
# become: yes
|
||||
# ufw:
|
||||
# rule: allow
|
||||
# route: yes
|
||||
# src: '{{ item }}'
|
||||
# with_items: "{{ groups['hall'] | map('extract', hostvars, ['ansible_host']) }}"
|
||||
# tags:
|
||||
# - firewall
|
||||
# - ufw
|
||||
# - hetzner_firewall
|
||||
# - hfirewall
|
||||
|
||||
- name: Allow all access from inventory hosts
|
||||
become: yes
|
||||
ufw:
|
||||
rule: allow
|
||||
src: '{{ item }}'
|
||||
with_items: "{{ groups['NewsBlur_Docker'] | map('extract', hostvars, ['ansible_host']) }}"
|
||||
when: "'oldandnew' not in groups"
|
||||
tags:
|
||||
- firewall
|
||||
- ufw
|
||||
# - name: Allow all access from inventory hosts old + new
|
||||
# become: yes
|
||||
# ufw:
|
||||
# rule: allow
|
||||
# src: '{{ item }}'
|
||||
# with_items: "{{ groups['oldandnew'] | map('extract', hostvars, ['ansible_host']) }}"
|
||||
# when: "'oldandnew' in groups"
|
||||
# tags:
|
||||
# - firewall
|
||||
# - ufw
|
||||
|
||||
- name: Allow all access from inventory hosts with docker
|
||||
become: yes
|
||||
ufw:
|
||||
rule: allow
|
||||
route: yes
|
||||
src: '{{ item }}'
|
||||
with_items: "{{ groups['NewsBlur_Docker'] | map('extract', hostvars, ['ansible_host']) }}"
|
||||
when: "'oldandnew' not in groups"
|
||||
tags:
|
||||
- firewall
|
||||
- ufw
|
||||
# - name: Allow all access from inventory hosts
|
||||
# become: yes
|
||||
# ufw:
|
||||
# rule: allow
|
||||
# src: '{{ item }}'
|
||||
# with_items: "{{ groups['NewsBlur_Docker'] | map('extract', hostvars, ['ansible_host']) }}"
|
||||
# when: "'oldandnew' not in groups"
|
||||
# tags:
|
||||
# - firewall
|
||||
# - ufw
|
||||
|
||||
# - name: Allow all access from inventory hosts with docker
|
||||
# become: yes
|
||||
# ufw:
|
||||
# rule: allow
|
||||
# route: yes
|
||||
# src: '{{ item }}'
|
||||
# with_items: "{{ groups['NewsBlur_Docker'] | map('extract', hostvars, ['ansible_host']) }}"
|
||||
# when: "'oldandnew' not in groups"
|
||||
# tags:
|
||||
# - firewall
|
||||
# - ufw
|
||||
|
||||
# Code from https://stackoverflow.com/a/51741599/8717: "What is the best practice of docker + ufw under Ubuntu"
|
||||
- name: Solving UFW and Docker issues by adding ufw after.rules
|
||||
|
|
43
ansible/roles/ufw/templates/ufw_rules.sh.j2
Normal file
43
ansible/roles/ufw/templates/ufw_rules.sh.j2
Normal file
|
@ -0,0 +1,43 @@
|
|||
#!/bin/bash
|
||||
# Script to apply UFW rules incrementally with regex matching for `ufw status verbose` output
|
||||
|
||||
# Fetch the current UFW status once and store it
|
||||
CURRENT_UFW_STATUS=$(ufw status verbose)
|
||||
|
||||
# Function to check and apply a rule with regex for forward rules
|
||||
apply_rule() {
|
||||
local rule="$1"
|
||||
local rule_type="$2" # "IN" for incoming, "FWD" for forwarded (route) rules
|
||||
local ip_address="$3"
|
||||
|
||||
# Construct the regex pattern based on rule type
|
||||
local regex_pattern
|
||||
if [ "$rule_type" == "FWD" ]; then
|
||||
regex_pattern="ALLOW FWD\\s+$ip_address"
|
||||
else
|
||||
regex_pattern="ALLOW IN\\s+$ip_address"
|
||||
fi
|
||||
|
||||
# Use grep with -P for Perl-compatible regex, and -q to quietly check for a match
|
||||
if echo "$CURRENT_UFW_STATUS" | grep -Pq -- "$regex_pattern"; then
|
||||
echo "Rule already exists: $regex_pattern"
|
||||
else
|
||||
echo "Applying rule: $rule"
|
||||
ufw $rule
|
||||
fi
|
||||
}
|
||||
|
||||
# Apply rules
|
||||
# Example for direct allow rules
|
||||
apply_rule "allow 22" "IN" "22" # IP address parameter is not used for this rule
|
||||
|
||||
# Example for forwarded (route) rules
|
||||
{% for host in hetzner_hosts %}
|
||||
apply_rule "allow from {{ host }}" "IN" "{{ host }}"
|
||||
apply_rule "route allow from {{ host }}" "FWD" "{{ host }}"
|
||||
{% endfor %}
|
||||
|
||||
{% for host in do_hosts %}
|
||||
apply_rule "allow from {{ host }}" "IN" "{{ host }}"
|
||||
apply_rule "route allow from {{ host }}" "FWD" "{{ host }}"
|
||||
{% endfor %}
|
|
@ -29,6 +29,14 @@
|
|||
tags:
|
||||
- static
|
||||
|
||||
- name: "Write out ips from ansible inventory of all servers with app/node/www/task in name into addresses.txt file"
|
||||
template:
|
||||
src: ip_addresses.txt.j2
|
||||
dest: /srv/newsblur/apps/api/ip_addresses.txt
|
||||
tags:
|
||||
- inventory
|
||||
|
||||
|
||||
- name: Prune docker
|
||||
become: yes
|
||||
community.docker.docker_prune:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"service": {
|
||||
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}",
|
||||
"name": "{{ inventory_hostname|regex_replace('\-?\d+', '')|regex_replace('happ', 'app') }}",
|
||||
"id": "{{ inventory_hostname }}-web",
|
||||
"tags": [
|
||||
"web"
|
||||
|
|
5
ansible/roles/web/templates/ip_addresses.txt.j2
Normal file
5
ansible/roles/web/templates/ip_addresses.txt.j2
Normal file
|
@ -0,0 +1,5 @@
|
|||
{%- for item in hostvars %}
|
||||
{% if "happ" in item or "hnode" in item or "hwww" in item or "htask" in item %}
|
||||
{{ hostvars[item].ansible_host }}
|
||||
{% endif %}
|
||||
{%- endfor %}
|
|
@ -26,4 +26,4 @@
|
|||
- import_playbook: playbooks/setup_metrics.yml
|
||||
when: "'metrics' in inventory_hostname"
|
||||
- import_playbook: playbooks/setup_sentry.yml
|
||||
when: "'sentry' in inventory_hostname"
|
||||
when: "'sentry' in inventory_hostname or 'metrics' in inventory_hostname"
|
||||
|
|
34
ansible/utils/generate_hetzner_inventory.py
Executable file
34
ansible/utils/generate_hetzner_inventory.py
Executable file
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from hetzner.robot import Robot
|
||||
|
||||
TOKEN_FILE = "/srv/secrets-newsblur/keys/hetzner.yaml"
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
# Load credentials from a YAML file
|
||||
with open(TOKEN_FILE, "r") as file:
|
||||
credentials = yaml.safe_load(file)
|
||||
|
||||
user = credentials["hetzner_robot"]["username"]
|
||||
password = credentials["hetzner_robot"]["password"]
|
||||
outfile = f"/srv/newsblur/ansible/inventories/hetzner.ini"
|
||||
print(user, password)
|
||||
robot = Robot(user, password)
|
||||
|
||||
# Check if the request was successful
|
||||
if robot.servers:
|
||||
with open(outfile, "w") as inventory_file:
|
||||
inventory_file.write("[hetzner_servers]\n")
|
||||
for server in robot.servers:
|
||||
# Assuming the server IP is under 'server_ip' key
|
||||
inventory_file.write(f"{server.ip}\n")
|
||||
print(f"Inventory file 'hetzner_inventory.ini' created with {len(servers)} servers")
|
||||
else:
|
||||
print(f"Failed to fetch server data")
|
|
@ -1,25 +1,28 @@
|
|||
import os
|
||||
import base64
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import os
|
||||
import urllib.parse
|
||||
|
||||
import lxml.html
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth import login as login_user
|
||||
from django.contrib.auth import logout as logout_user
|
||||
from apps.reader.forms import SignupForm, LoginForm
|
||||
from django.core.mail import mail_admins
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
|
||||
from apps.profile.models import Profile
|
||||
from apps.social.models import MSocialProfile, MSharedStory, MSocialSubscription
|
||||
from apps.rss_feeds.models import Feed, MStarredStoryCounts, MStarredStory
|
||||
from apps.reader.forms import LoginForm, SignupForm
|
||||
from apps.reader.models import RUserStory, UserSubscription, UserSubscriptionFolders
|
||||
from apps.rss_feeds.models import Feed, MStarredStory, MStarredStoryCounts
|
||||
from apps.rss_feeds.text_importer import TextImporter
|
||||
from apps.reader.models import UserSubscription, UserSubscriptionFolders, RUserStory
|
||||
from apps.social.models import MSharedStory, MSocialProfile, MSocialSubscription
|
||||
from utils import json_functions as json
|
||||
from utils import log as logging
|
||||
from utils.feed_functions import relative_timesince
|
||||
from utils.user_functions import ajax_login_required, get_user
|
||||
from utils.view_functions import required_params
|
||||
from utils.user_functions import get_user, ajax_login_required
|
||||
|
||||
|
||||
@json.json_view
|
||||
|
@ -511,8 +514,10 @@ def save_story(request, token=None):
|
|||
return response
|
||||
|
||||
def ip_addresses(request):
|
||||
import digitalocean
|
||||
doapi = digitalocean.Manager(token=settings.DO_TOKEN_API_IPADDRESSES)
|
||||
droplets = doapi.get_all_droplets()
|
||||
addresses = '\n'.join([d.ip_address for d in droplets if any(name in d.name for name in ['task', 'staging', 'app', 'node'])])
|
||||
# Read local file /srv/newsblur/apps/api/ip_addresses.txt and return that
|
||||
with open('/srv/newsblur/apps/api/ip_addresses.txt', 'r') as f:
|
||||
addresses = f.read()
|
||||
|
||||
mail_admins(f"IP Addresses accessed from {request.META['REMOTE_ADDR']} by {request.user}", addresses)
|
||||
|
||||
return HttpResponse(addresses, content_type='text/plain')
|
||||
|
|
|
@ -24,7 +24,7 @@ from utils.view_functions import is_true
|
|||
from utils.story_functions import truncate_chars
|
||||
from utils import log as logging
|
||||
from utils import mongoengine_fields
|
||||
from apns2.errors import BadDeviceToken, Unregistered
|
||||
from apns2.errors import BadDeviceToken, Unregistered, DeviceTokenNotForTopic
|
||||
from apns2.client import APNsClient
|
||||
from apns2.payload import Payload
|
||||
from bs4 import BeautifulSoup
|
||||
|
@ -306,14 +306,15 @@ class MUserFeedNotification(mongo.Document):
|
|||
|
||||
tokens = MUserNotificationTokens.get_tokens_for_user(self.user_id)
|
||||
# To update APNS:
|
||||
# 0. Upgrade to latest openssl: brew install openssl
|
||||
# 1. Create certificate signing request in Keychain Access
|
||||
# 2. Upload to https://developer.apple.com/account/resources/certificates/list
|
||||
# 3. Download to secrets/certificates/ios/aps.cer
|
||||
# 4. Open in Keychain Access:
|
||||
# 4. Open in Keychain Access, Under "My Certificates":
|
||||
# - export "Apple Push Service: com.newsblur.NewsBlur" as aps.p12 (or just use aps.cer in #5)
|
||||
# - export private key as aps_key.p12 WITH A PASSPHRASE (removed later)
|
||||
# 5. openssl x509 -in aps.cer -inform DER -out aps.pem -outform PEM
|
||||
# 6. openssl pkcs12 -nocerts -out aps_key.pem -in aps_key.p12
|
||||
# 6. openssl pkcs12 -in aps_key.p12 -out aps_key.pem -nodes -legacy
|
||||
# 7. openssl rsa -out aps_key.noenc.pem -in aps_key.pem
|
||||
# 7. cat aps.pem aps_key.noenc.pem > aps.p12.pem
|
||||
# 8. Verify: openssl s_client -connect gateway.push.apple.com:2195 -cert aps.p12.pem
|
||||
|
@ -348,7 +349,7 @@ class MUserFeedNotification(mongo.Document):
|
|||
)
|
||||
try:
|
||||
apns.send_notification(token, payload, topic="com.newsblur.NewsBlur")
|
||||
except (BadDeviceToken, Unregistered):
|
||||
except (BadDeviceToken, Unregistered, DeviceTokenNotForTopic):
|
||||
logging.user(user, '~BMiOS token expired: ~FR~SB%s' % (token[:50]))
|
||||
else:
|
||||
confirmed_ios_tokens.append(token)
|
||||
|
|
|
@ -1598,7 +1598,7 @@ class Profile(models.Model):
|
|||
self.save()
|
||||
|
||||
delta = datetime.datetime.now() - self.last_seen_on
|
||||
months_ago = delta.days / 30
|
||||
months_ago = round(delta.days / 30)
|
||||
user = self.user
|
||||
data = dict(user=user, months_ago=months_ago)
|
||||
text = render_to_string('mail/email_premium_expire_grace.txt', data)
|
||||
|
@ -1627,7 +1627,7 @@ class Profile(models.Model):
|
|||
return
|
||||
|
||||
delta = datetime.datetime.now() - self.last_seen_on
|
||||
months_ago = delta.days / 30
|
||||
months_ago = round(delta.days / 30)
|
||||
user = self.user
|
||||
data = dict(user=user, months_ago=months_ago)
|
||||
text = render_to_string('mail/email_premium_expire.txt', data)
|
||||
|
|
|
@ -1,81 +1,125 @@
|
|||
import base64
|
||||
import concurrent
|
||||
import datetime
|
||||
import random
|
||||
import re
|
||||
import socket
|
||||
import ssl
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import zlib
|
||||
|
||||
import redis
|
||||
import requests
|
||||
import random
|
||||
import zlib
|
||||
import concurrent
|
||||
import re
|
||||
import ssl
|
||||
import socket
|
||||
import base64
|
||||
import urllib.request, urllib.error, urllib.parse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.template.loader import render_to_string
|
||||
from django.db import IntegrityError
|
||||
from django.db.utils import DatabaseError
|
||||
from django.db.models import Q
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import login as login_user
|
||||
from django.contrib.auth import logout as logout_user
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404, UnreadablePostError
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.core.validators import validate_email
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.db.utils import DatabaseError
|
||||
from django.http import (
|
||||
Http404,
|
||||
HttpResponse,
|
||||
HttpResponseForbidden,
|
||||
HttpResponseRedirect,
|
||||
UnreadablePostError,
|
||||
)
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.utils import feedgenerator
|
||||
from django.utils.encoding import smart_str
|
||||
from mongoengine.queryset import OperationError
|
||||
from mongoengine.queryset import NotUniqueError
|
||||
from apps.recommendations.models import RecommendedFeed
|
||||
from apps.analyzer.models import MClassifierTitle, MClassifierAuthor, MClassifierFeed, MClassifierTag
|
||||
from apps.analyzer.models import apply_classifier_titles, apply_classifier_feeds
|
||||
from apps.analyzer.models import apply_classifier_authors, apply_classifier_tags
|
||||
from apps.analyzer.models import get_classifiers_for_user, sort_classifiers_by_feed
|
||||
from apps.profile.models import Profile, MCustomStyling, MDashboardRiver
|
||||
from apps.reader.models import UserSubscription, UserSubscriptionFolders, RUserStory, RUserUnreadStory, Feature
|
||||
from apps.reader.forms import SignupForm, LoginForm, FeatureForm
|
||||
from apps.rss_feeds.models import MFeedIcon, MStarredStoryCounts, MSavedSearch
|
||||
from django.views.decorators.cache import never_cache
|
||||
from mongoengine.queryset import NotUniqueError, OperationError
|
||||
|
||||
from apps.analyzer.models import (
|
||||
MClassifierAuthor,
|
||||
MClassifierFeed,
|
||||
MClassifierTag,
|
||||
MClassifierTitle,
|
||||
apply_classifier_authors,
|
||||
apply_classifier_feeds,
|
||||
apply_classifier_tags,
|
||||
apply_classifier_titles,
|
||||
get_classifiers_for_user,
|
||||
sort_classifiers_by_feed,
|
||||
)
|
||||
from apps.notifications.models import MUserFeedNotification
|
||||
from apps.profile.models import MCustomStyling, MDashboardRiver, Profile
|
||||
from apps.reader.forms import FeatureForm, LoginForm, SignupForm
|
||||
from apps.reader.models import (
|
||||
Feature,
|
||||
RUserStory,
|
||||
RUserUnreadStory,
|
||||
UserSubscription,
|
||||
UserSubscriptionFolders,
|
||||
)
|
||||
from apps.recommendations.models import RecommendedFeed
|
||||
from apps.rss_feeds.models import MFeedIcon, MSavedSearch, MStarredStoryCounts
|
||||
from apps.search.models import MUserSearch
|
||||
from apps.statistics.models import MStatistics, MAnalyticsLoader
|
||||
from apps.statistics.models import MAnalyticsLoader, MStatistics
|
||||
from apps.statistics.rstats import RStats
|
||||
|
||||
# from apps.search.models import SearchStarredStory
|
||||
try:
|
||||
from apps.rss_feeds.models import Feed, MFeedPage, DuplicateFeed, MStory, MStarredStory
|
||||
from apps.rss_feeds.models import (
|
||||
DuplicateFeed,
|
||||
Feed,
|
||||
MFeedPage,
|
||||
MStarredStory,
|
||||
MStory,
|
||||
)
|
||||
except:
|
||||
pass
|
||||
from apps.social.models import MSharedStory, MSocialProfile, MSocialServices
|
||||
from apps.social.models import MSocialSubscription, MActivity, MInteraction
|
||||
from apps.categories.models import MCategory
|
||||
from apps.social.views import load_social_page
|
||||
from apps.rss_feeds.tasks import ScheduleImmediateFetches
|
||||
from utils import json_functions as json
|
||||
from utils.user_functions import get_user, ajax_login_required
|
||||
from utils.user_functions import extract_user_agent
|
||||
from utils.feed_functions import relative_timesince
|
||||
from utils.story_functions import format_story_link_date__short
|
||||
from utils.story_functions import format_story_link_date__long
|
||||
from utils.story_functions import strip_tags
|
||||
from utils import log as logging
|
||||
from utils.view_functions import get_argument_or_404, render_to, is_true
|
||||
from utils.view_functions import required_params
|
||||
from utils.ratelimit import ratelimit
|
||||
from vendor.timezones.utilities import localtime_for_timezone
|
||||
import tweepy
|
||||
|
||||
from apps.categories.models import MCategory
|
||||
from apps.rss_feeds.tasks import ScheduleImmediateFetches
|
||||
from apps.social.models import (
|
||||
MActivity,
|
||||
MInteraction,
|
||||
MSharedStory,
|
||||
MSocialProfile,
|
||||
MSocialServices,
|
||||
MSocialSubscription,
|
||||
)
|
||||
from apps.social.views import load_social_page
|
||||
from utils import json_functions as json
|
||||
from utils import log as logging
|
||||
from utils.feed_functions import relative_timesince
|
||||
from utils.ratelimit import ratelimit
|
||||
from utils.story_functions import (
|
||||
format_story_link_date__long,
|
||||
format_story_link_date__short,
|
||||
strip_tags,
|
||||
)
|
||||
from utils.user_functions import ajax_login_required, extract_user_agent, get_user
|
||||
from utils.view_functions import (
|
||||
get_argument_or_404,
|
||||
is_true,
|
||||
render_to,
|
||||
required_params,
|
||||
)
|
||||
from vendor.timezones.utilities import localtime_for_timezone
|
||||
|
||||
BANNED_URLS = [
|
||||
"brentozar.com",
|
||||
]
|
||||
ALLOWED_SUBDOMAINS = [
|
||||
'dev',
|
||||
'www',
|
||||
'hwww',
|
||||
'dwww',
|
||||
# 'beta', # Comment to redirect beta -> www, uncomment to allow beta -> staging (+ dns changes)
|
||||
'staging',
|
||||
'hstaging',
|
||||
'discovery',
|
||||
'debug',
|
||||
'debug3',
|
||||
|
@ -671,10 +715,14 @@ def load_single_feed(request, feed_id):
|
|||
if feed.is_newsletter and not usersub:
|
||||
# User must be subscribed to a newsletter in order to read it
|
||||
raise Http404
|
||||
|
||||
if feed.num_subscribers == 1 and not usersub and not user.is_staff:
|
||||
# This feed could be private so user must be subscribed in order to read it
|
||||
raise Http404
|
||||
|
||||
if page > 400:
|
||||
logging.user(request, "~BR~FK~SBOver page 400 on single feed: %s" % page)
|
||||
assert False
|
||||
raise Http404
|
||||
|
||||
if query:
|
||||
if user.profile.is_premium:
|
||||
|
@ -874,7 +922,6 @@ def load_feed_page(request, feed_id):
|
|||
raise Http404
|
||||
|
||||
feed = Feed.get_by_id(feed_id)
|
||||
|
||||
if feed and feed.has_page and not feed.has_page_exception:
|
||||
if settings.BACKED_BY_AWS.get('pages_on_node'):
|
||||
domain = Site.objects.get_current().domain
|
||||
|
@ -883,8 +930,9 @@ def load_feed_page(request, feed_id):
|
|||
feed.pk,
|
||||
)
|
||||
try:
|
||||
page_response = requests.get(url)
|
||||
except requests.ConnectionError:
|
||||
page_response = requests.get(url, verify=not settings.DEBUG)
|
||||
except requests.ConnectionError as e:
|
||||
logging.user(request, f"~FR~SBError loading original page: {url} {e}")
|
||||
page_response = None
|
||||
if page_response and page_response.status_code == 200:
|
||||
response = HttpResponse(page_response.content, content_type="text/html; charset=utf-8")
|
||||
|
|
|
@ -1,33 +1,36 @@
|
|||
import urllib.request
|
||||
import base64
|
||||
import datetime
|
||||
import gzip
|
||||
import http.client
|
||||
import operator
|
||||
import struct
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from io import BytesIO
|
||||
from socket import error as SocketError
|
||||
|
||||
import boto3
|
||||
import lxml.html
|
||||
import numpy
|
||||
import scipy
|
||||
import scipy.misc
|
||||
import scipy.cluster
|
||||
import struct
|
||||
import operator
|
||||
import gzip
|
||||
import datetime
|
||||
import requests
|
||||
import base64
|
||||
import http.client
|
||||
from PIL import BmpImagePlugin, PngImagePlugin, Image
|
||||
from socket import error as SocketError
|
||||
import boto3
|
||||
from io import BytesIO
|
||||
import scipy
|
||||
import scipy.cluster
|
||||
import scipy.misc
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from django.contrib.sites.models import Site
|
||||
from apps.rss_feeds.models import MFeedPage, MFeedIcon
|
||||
from utils.facebook_fetcher import FacebookFetcher
|
||||
from utils import log as logging
|
||||
from utils.feed_functions import timelimit, TimeoutError
|
||||
from OpenSSL.SSL import Error as OpenSSLError, SESS_CACHE_NO_INTERNAL_STORE
|
||||
from django.http import HttpResponse
|
||||
from OpenSSL.SSL import SESS_CACHE_NO_INTERNAL_STORE
|
||||
from OpenSSL.SSL import Error as OpenSSLError
|
||||
from PIL import BmpImagePlugin, Image, PngImagePlugin
|
||||
from pyasn1.error import PyAsn1Error
|
||||
from requests.packages.urllib3.exceptions import LocationParseError
|
||||
|
||||
from apps.rss_feeds.models import MFeedIcon, MFeedPage
|
||||
from utils import log as logging
|
||||
from utils.facebook_fetcher import FacebookFetcher
|
||||
from utils.feed_functions import TimeoutError, timelimit
|
||||
|
||||
|
||||
class IconImporter(object):
|
||||
|
||||
|
@ -206,8 +209,10 @@ class IconImporter(object):
|
|||
if self.page_data:
|
||||
content = self.page_data
|
||||
elif settings.BACKED_BY_AWS.get('pages_on_node'):
|
||||
domain = Site.objects.get_current().domain
|
||||
url = "https://%s/original_page/%s" % (
|
||||
domain = "node-page.service.consul:8008"
|
||||
if settings.DOCKERBUILD:
|
||||
domain = "node:8008"
|
||||
url = "http://%s/original_page/%s" % (
|
||||
domain,
|
||||
self.feed.pk,
|
||||
)
|
||||
|
|
|
@ -462,6 +462,10 @@ class Feed(models.Model):
|
|||
username = re.search('youtube.com/user/(\w+)', url).group(1)
|
||||
url = "http://gdata.youtube.com/feeds/base/users/%s/uploads" % username
|
||||
without_rss = True
|
||||
if url and 'youtube.com/@' in url:
|
||||
username = url.split('youtube.com/@')[1]
|
||||
url = "http://gdata.youtube.com/feeds/base/users/%s/uploads" % username
|
||||
without_rss = True
|
||||
if url and 'youtube.com/channel/' in url:
|
||||
channel_id = re.search('youtube.com/channel/([-_\w]+)', url).group(1)
|
||||
url = "https://www.youtube.com/feeds/videos.xml?channel_id=%s" % channel_id
|
||||
|
|
|
@ -1,23 +1,28 @@
|
|||
import requests
|
||||
import re
|
||||
import traceback
|
||||
import feedparser
|
||||
import time
|
||||
import urllib.request, urllib.error, urllib.parse
|
||||
import http.client
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import zlib
|
||||
from socket import error as SocketError
|
||||
|
||||
import feedparser
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.utils.encoding import smart_bytes
|
||||
from mongoengine.queryset import NotUniqueError
|
||||
from socket import error as SocketError
|
||||
from django.conf import settings
|
||||
from django.utils.text import compress_string as compress_string_with_gzip
|
||||
from utils import log as logging
|
||||
from apps.rss_feeds.models import MFeedPage
|
||||
from utils.feed_functions import timelimit, TimeoutError
|
||||
from mongoengine.queryset import NotUniqueError
|
||||
from OpenSSL.SSL import Error as OpenSSLError
|
||||
from pyasn1.error import PyAsn1Error
|
||||
from sentry_sdk import capture_exception, flush
|
||||
|
||||
from apps.rss_feeds.models import MFeedPage
|
||||
from utils import log as logging
|
||||
from utils.feed_functions import TimeoutError, timelimit
|
||||
|
||||
# from utils.feed_functions import mail_feed_error_to_admin
|
||||
|
||||
BROKEN_PAGES = [
|
||||
|
@ -127,6 +132,7 @@ class PageImporter(object):
|
|||
return
|
||||
except (ValueError, urllib.error.URLError, http.client.BadStatusLine, http.client.InvalidURL,
|
||||
requests.exceptions.ConnectionError) as e:
|
||||
logging.debug(' ***> [%-30s] Page fetch failed: %s' % (self.feed.log_title[:30], e))
|
||||
self.feed.save_page_history(401, "Bad URL", e)
|
||||
try:
|
||||
fp = feedparser.parse(self.feed.feed_address)
|
||||
|
@ -134,10 +140,8 @@ class PageImporter(object):
|
|||
return html
|
||||
feed_link = fp.feed.get('link', "")
|
||||
self.feed.save()
|
||||
logging.debug(' ***> [%-30s] Page fetch failed: %s' % (self.feed.log_title[:30], e))
|
||||
except (urllib.error.HTTPError) as e:
|
||||
self.feed.save_page_history(e.code, e.msg, e.fp.read())
|
||||
except (http.client.IncompleteRead) as e:
|
||||
logging.debug(' ***> [%-30s] Page fetch failed: %s' % (self.feed.log_title[:30], e))
|
||||
self.feed.save_page_history(500, "IncompleteRead", e)
|
||||
except (requests.exceptions.RequestException,
|
||||
requests.packages.urllib3.exceptions.HTTPError) as e:
|
||||
|
@ -305,8 +309,10 @@ class PageImporter(object):
|
|||
return feed_page
|
||||
|
||||
def save_page_node(self, html):
|
||||
domain = Site.objects.get_current().domain
|
||||
url = "https://%s/original_page/%s" % (
|
||||
domain = "node-page.service.consul:8008"
|
||||
if settings.DOCKERBUILD:
|
||||
domain = "node:8008"
|
||||
url = "http://%s/original_page/%s" % (
|
||||
domain,
|
||||
self.feed.pk,
|
||||
)
|
||||
|
|
|
@ -2,13 +2,16 @@ import datetime
|
|||
import os
|
||||
import shutil
|
||||
import time
|
||||
|
||||
import redis
|
||||
from newsblur_web.celeryapp import app
|
||||
from celery.exceptions import SoftTimeLimitExceeded
|
||||
from utils import log as logging
|
||||
from django.conf import settings
|
||||
|
||||
from apps.profile.middleware import DBProfilerMiddleware
|
||||
from newsblur_web.celeryapp import app
|
||||
from utils import log as logging
|
||||
from utils.redis_raw_log_middleware import RedisDumpMiddleware
|
||||
|
||||
FEED_TASKING_MAX = 10000
|
||||
|
||||
@app.task(name='task-feeds')
|
||||
|
@ -45,6 +48,7 @@ def TaskFeeds():
|
|||
else:
|
||||
logging.debug(" ---> ~SN~FBToo many tasked feeds. ~SB%s~SN tasked." % tasked_feeds_size)
|
||||
active_count = 0
|
||||
feeds = []
|
||||
|
||||
logging.debug(" ---> ~SN~FBTasking %s feeds took ~SB%s~SN seconds (~SB%s~SN/~FG%s~FB~SN/%s tasked/queued/scheduled)" % (
|
||||
active_count,
|
||||
|
|
75
archive/hetzner migration.md
Normal file
75
archive/hetzner migration.md
Normal file
|
@ -0,0 +1,75 @@
|
|||
Migration from Digital Ocean to Hetzner, covering about 120 servers (20 db, 80 task, 20 app)
|
||||
|
||||
## Tweet
|
||||
|
||||
> "Time for a big server transition. I’m moving the servers from Digital Ocean to Hetzner. Every database server (postgresql, mongo, redis x 4, elasticsearch, prometheus, consul, and sentry) is making the move. I’m going to do it all at once, which means about an hour of downtime."
|
||||
|
||||
> "Afterwards, you shouldn't notice anything different, although these are bare metal servers, so theoretically they should be faster and more reliable."
|
||||
|
||||
```
|
||||
make maintenance_on
|
||||
make celery_stop
|
||||
```
|
||||
|
||||
## Postgres
|
||||
|
||||
> Edit postgres/consul_service.json: db-postgres2 -> hdb-postgres-1
|
||||
|
||||
```
|
||||
aps -l db-postgres2,hdb-postgres-1,hdb-postgres-2 -t consul
|
||||
aps -l hdb-postgres-1 -t pg_promote
|
||||
```
|
||||
|
||||
## Mongo
|
||||
|
||||
```
|
||||
sshdo db-mongo-primary1
|
||||
sudo docker exec -it mongo mongo
|
||||
rs.config()
|
||||
rs.reconfig()
|
||||
```
|
||||
|
||||
## Mongo analytics
|
||||
|
||||
> Edit mongo/tasks/main.yml: mongo_analytics_secondary
|
||||
|
||||
```
|
||||
aps -l db-mongo-analytics2,hdb-mongo-analytics-1 -t consul
|
||||
```
|
||||
|
||||
## Redis
|
||||
|
||||
> Edit redis/tasks/main.yml: redis_secondary
|
||||
|
||||
```
|
||||
aps -l hdb-redis-user-1,hdb-redis-user-2,db-redis-user -t consul
|
||||
aps -l hdb-redis-session-1,hdb-redis-session-2,db-redis-sessions -t consul
|
||||
aps -l hdb-redis-story-1,hdb-redis-story-2,db-redis-story1 -t consul
|
||||
aps -l hdb-redis-pubsub,db-redis-pubsub -t consul
|
||||
apd -l hdb-redis-user-1,hdb-redis-session-1,hdb-redis-story-1,hdb-redis-pubsub -t replicaofnoone
|
||||
```
|
||||
|
||||
## Elasticsearch
|
||||
|
||||
> Edit elasticsearch/tasks/main.yml: elasticsearch_secondary
|
||||
```
|
||||
aps -l db-elasticsearch1,hdb-elasticsearch-1 -t consul
|
||||
```
|
||||
> Eventually `MUserSearch.remove_all()`
|
||||
|
||||
## Test hwww.newsblur.com
|
||||
```
|
||||
ansible-playbook ansible/deploy.yml -l happ-web-01 --tags maintenance_off
|
||||
```
|
||||
|
||||
## Looks good? Launch
|
||||
|
||||
> Haproxy on DO to redirect to Hetzner
|
||||
```
|
||||
aps -l www -t haproxy
|
||||
```
|
||||
> Change DNS to point to Hetzner
|
||||
```
|
||||
make maintenance_off
|
||||
```
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
addressable (2.8.5)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
colorator (1.1.0)
|
||||
concurrent-ruby (1.1.9)
|
||||
em-websocket (0.5.2)
|
||||
concurrent-ruby (1.2.2)
|
||||
em-websocket (0.5.3)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
http_parser.rb (~> 0)
|
||||
eventmachine (1.2.7)
|
||||
ffi (1.15.3)
|
||||
ffi (1.16.3)
|
||||
forwardable-extended (2.6.0)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (1.8.10)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (4.2.0)
|
||||
jekyll (4.2.2)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
|
@ -29,23 +29,23 @@ GEM
|
|||
rouge (~> 3.0)
|
||||
safe_yaml (~> 1.0)
|
||||
terminal-table (~> 2.0)
|
||||
jekyll-feed (0.15.1)
|
||||
jekyll-feed (0.17.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-paginate (1.1.0)
|
||||
jekyll-redirect-from (0.16.0)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-sass-converter (2.1.0)
|
||||
jekyll-sass-converter (2.2.0)
|
||||
sassc (> 2.0.1, < 3.0)
|
||||
jekyll-seo-tag (2.7.1)
|
||||
jekyll-seo-tag (2.8.0)
|
||||
jekyll (>= 3.8, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
kramdown (2.3.1)
|
||||
kramdown (2.4.0)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.3)
|
||||
listen (3.5.1)
|
||||
liquid (4.0.4)
|
||||
listen (3.8.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.4.0)
|
||||
|
@ -55,23 +55,22 @@ GEM
|
|||
jekyll-seo-tag (~> 2.1)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (4.0.6)
|
||||
rb-fsevent (0.11.0)
|
||||
public_suffix (5.0.4)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.2.5)
|
||||
rouge (3.26.0)
|
||||
rexml (3.2.6)
|
||||
rouge (3.30.0)
|
||||
safe_yaml (1.0.5)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
terminal-table (2.0.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
unicode-display_width (1.7.0)
|
||||
webrick (1.7.0)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.8.1)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
universal-darwin-20
|
||||
arm64-darwin-23
|
||||
|
||||
DEPENDENCIES
|
||||
jekyll (~> 4.2.0)
|
||||
|
@ -85,4 +84,4 @@ DEPENDENCIES
|
|||
webrick (~> 1.7)
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.20
|
||||
2.4.22
|
||||
|
|
|
@ -27,6 +27,7 @@ description: > # this means to ignore newlines until "baseurl:"
|
|||
A new sound of an old instrument.
|
||||
baseurl: "" # the subpath of your site, e.g. /blog
|
||||
url: "https://blog.newsblur.com" # the base hostname & protocol for your site, e.g. http://example.com
|
||||
site.url: "https://blog.newsblur.com" # the base hostname & protocol for your site, e.g. http://example.com
|
||||
permalink: pretty
|
||||
twitter_username: newsblur
|
||||
github_username: samuelclay
|
||||
|
|
20
blog/_posts/2023-12-06-ios-grid-view.md
Normal file
20
blog/_posts/2023-12-06-ios-grid-view.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
layout: post
|
||||
title: Introducing the Grid view on iOS
|
||||
tags: ['ios']
|
||||
---
|
||||
|
||||
The Grid view is now on iOS. Read stories with large thumbnails in a magazine-like format, where you can see a customizable number of story previews at once. Works beautifully for both iPhone and iPad.
|
||||
<img src="/assets/ipad-grid-1.png" style="width: 100%;border: 1px solid #A0A0A0;margin: 24px auto;display: block;">
|
||||
|
||||
Just like on the web, you can customize how many stories you see and how large each story is, giving you the freedom to read stories with large thumbnails or small image previews.
|
||||
|
||||
<img src="/assets/ipad-grid-2.png" style="width: 100%;border: 1px solid #A0A0A0;margin: 24px auto;display: block;">
|
||||
|
||||
It even works on iPhone!
|
||||
|
||||
<img src="/assets/iphone-grid.png" style="width: 100%;border: 1px solid #A0A0A0;margin: 24px auto;display: block;">
|
||||
|
||||
If you have any other ideas you'd like to see on iPad and iPhone, feel free to post an idea on the <a href="https://forum.newsblur.com">NewsBlur Forum</a>.
|
||||
|
||||
This is a huge release and has been a year in the making. Coming up soon: a new Mac app and intelligent feed discovery.
|
|
@ -37,7 +37,7 @@ h1, h2, h3, h4, h5, h6,
|
|||
p, blockquote, pre,
|
||||
ul, ol, dl, figure,
|
||||
%vertical-rhythm {
|
||||
margin-bottom: $spacing-unit / 2;
|
||||
margin-bottom: calc($spacing-unit / 2);
|
||||
}
|
||||
|
||||
|
||||
|
@ -138,7 +138,7 @@ a {
|
|||
blockquote {
|
||||
color: $grey-color;
|
||||
border-left: 4px solid $grey-color-light;
|
||||
padding-left: $spacing-unit / 2;
|
||||
padding-left: calc($spacing-unit / 2);
|
||||
@include relative-font-size(1.125);
|
||||
letter-spacing: -1px;
|
||||
font-style: italic;
|
||||
|
@ -195,8 +195,8 @@ pre {
|
|||
@include media-query($on-laptop) {
|
||||
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit}));
|
||||
max-width: calc(#{$content-width} - (#{$spacing-unit}));
|
||||
padding-right: $spacing-unit / 2;
|
||||
padding-left: $spacing-unit / 2;
|
||||
padding-right: calc($spacing-unit / 2);
|
||||
padding-left: calc($spacing-unit / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,7 +250,7 @@ table {
|
|||
}
|
||||
}
|
||||
th, td {
|
||||
padding: ($spacing-unit / 3) ($spacing-unit / 2);
|
||||
padding: calc(($spacing-unit / 3)) calc($spacing-unit / 2);
|
||||
}
|
||||
th {
|
||||
background-color: lighten($grey-color-light, 3%);
|
||||
|
|
|
@ -94,8 +94,8 @@
|
|||
|
||||
@include media-query($on-palm) {
|
||||
position: absolute;
|
||||
top: $spacing-unit / 2;
|
||||
right: $spacing-unit / 2;
|
||||
top: calc($spacing-unit / 2);
|
||||
right: calc($spacing-unit / 2);
|
||||
background-color: $background-color;
|
||||
border: 1px solid $grey-color-light;
|
||||
border-radius: 5px;
|
||||
|
@ -158,7 +158,7 @@
|
|||
|
||||
.footer-heading {
|
||||
@include relative-font-size(1.125);
|
||||
margin-bottom: $spacing-unit / 2;
|
||||
margin-bottom: calc($spacing-unit / 2);
|
||||
}
|
||||
|
||||
.contact-list,
|
||||
|
@ -170,14 +170,14 @@
|
|||
.footer-col-wrapper {
|
||||
@include relative-font-size(0.9375);
|
||||
color: $grey-color;
|
||||
margin-left: -$spacing-unit / 2;
|
||||
margin-left: calc($spacing-unit / -2);
|
||||
@extend %clearfix;
|
||||
}
|
||||
|
||||
.footer-col {
|
||||
float: left;
|
||||
margin-bottom: $spacing-unit / 2;
|
||||
padding-left: $spacing-unit / 2;
|
||||
margin-bottom: calc($spacing-unit / 2);
|
||||
padding-left: calc($spacing-unit / 2);
|
||||
}
|
||||
|
||||
.footer-col-1 {
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>A New Logo for a New Blog | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="A New Logo for a New Blog" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="We’ve come a long way, readers. What started as a fun project to scratch an itch has become a fun project that pays for its ever-increasing self. This week I’m going to show how motivated I am about turning NewsBlur into a serious blog reader. And it starts with a collection of a circles:" />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="A New Logo for a New Blog" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"A New Logo for a New Blog","dateModified":"2011-03-15T08:39:00-04:00","datePublished":"2011-03-15T08:39:00-04:00","url":"https://blog.newsblur.com/2011/03/15/a-new-logo-for-a-new-blog/","@type":"BlogPosting","description":"We’ve come a long way, readers. What started as a fun project to scratch an itch has become a fun project that pays for its ever-increasing self. This week I’m going to show how motivated I am about turning NewsBlur into a serious blog reader. And it starts with a collection of a circles:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/03/15/a-new-logo-for-a-new-blog/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-03-15T08:39:00-04:00","datePublished":"2011-03-15T08:39:00-04:00","description":"We’ve come a long way, readers. What started as a fun project to scratch an itch has become a fun project that pays for its ever-increasing self. This week I’m going to show how motivated I am about turning NewsBlur into a serious blog reader. And it starts with a collection of a circles:","headline":"A New Logo for a New Blog","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/03/15/a-new-logo-for-a-new-blog/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/03/15/a-new-logo-for-a-new-blog/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Explaining Intelligence | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Explaining Intelligence" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="If you’re not using intelligence classifiers, you’re only getting half the value out of NewsBlur. " />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Explaining Intelligence" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Explaining Intelligence","dateModified":"2011-04-01T11:11:33-04:00","datePublished":"2011-04-01T11:11:33-04:00","url":"https://blog.newsblur.com/2011/04/01/explaining-intelligence/","@type":"BlogPosting","description":"If you’re not using intelligence classifiers, you’re only getting half the value out of NewsBlur. ","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/01/explaining-intelligence/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-04-01T11:11:33-04:00","datePublished":"2011-04-01T11:11:33-04:00","description":"If you’re not using intelligence classifiers, you’re only getting half the value out of NewsBlur. ","headline":"Explaining Intelligence","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/01/explaining-intelligence/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/04/01/explaining-intelligence/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Where We Are in April | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Where We Are in April" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Hi readers, I want to take a moment to share what I’m working on for the month of April:" />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Where We Are in April" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Where We Are in April","dateModified":"2011-04-23T14:57:02-04:00","datePublished":"2011-04-23T14:57:02-04:00","url":"https://blog.newsblur.com/2011/04/23/where-we-are-in-april/","@type":"BlogPosting","description":"Hi readers, I want to take a moment to share what I’m working on for the month of April:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/23/where-we-are-in-april/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-04-23T14:57:02-04:00","datePublished":"2011-04-23T14:57:02-04:00","description":"Hi readers, I want to take a moment to share what I’m working on for the month of April:","headline":"Where We Are in April","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/23/where-we-are-in-april/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/04/23/where-we-are-in-april/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Make your own feed reader with NewsBlur’s new API | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Make your own feed reader with NewsBlur’s new API" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Please vote for this blog post on Hacker News: http://news.ycombinator.com/item?id=2485377." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Make your own feed reader with NewsBlur’s new API" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Make your own feed reader with NewsBlur’s new API","dateModified":"2011-04-26T06:41:00-04:00","datePublished":"2011-04-26T06:41:00-04:00","url":"https://blog.newsblur.com/2011/04/26/make-your-own-feed-reader-with-newsblurs-new-api/","@type":"BlogPosting","description":"Please vote for this blog post on Hacker News: http://news.ycombinator.com/item?id=2485377.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/26/make-your-own-feed-reader-with-newsblurs-new-api/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-04-26T06:41:00-04:00","datePublished":"2011-04-26T06:41:00-04:00","description":"Please vote for this blog post on Hacker News: http://news.ycombinator.com/item?id=2485377.","headline":"Make your own feed reader with NewsBlur’s new API","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/04/26/make-your-own-feed-reader-with-newsblurs-new-api/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/04/26/make-your-own-feed-reader-with-newsblurs-new-api/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Blar: A new Android app for NewsBlur | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Blar: A new Android app for NewsBlur" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="This Summer is shaping up to be the season for mobile apps. Blar, a new Android client for NewsBlur, has just been released. It’s available on the Android Market here: https://market.android.com/details?id=bitwrit.Blar. It is created by Harris Munir, who you can contact through his site." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Blar: A new Android app for NewsBlur" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Blar: A new Android app for NewsBlur","dateModified":"2011-08-09T09:44:00-04:00","datePublished":"2011-08-09T09:44:00-04:00","url":"https://blog.newsblur.com/2011/08/09/blar-a-new-android-app-for-newsblur/","@type":"BlogPosting","description":"This Summer is shaping up to be the season for mobile apps. Blar, a new Android client for NewsBlur, has just been released. It’s available on the Android Market here: https://market.android.com/details?id=bitwrit.Blar. It is created by Harris Munir, who you can contact through his site.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/08/09/blar-a-new-android-app-for-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-08-09T09:44:00-04:00","datePublished":"2011-08-09T09:44:00-04:00","description":"This Summer is shaping up to be the season for mobile apps. Blar, a new Android client for NewsBlur, has just been released. It’s available on the Android Market here: https://market.android.com/details?id=bitwrit.Blar. It is created by Harris Munir, who you can contact through his site.","headline":"Blar: A new Android app for NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/08/09/blar-a-new-android-app-for-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/08/09/blar-a-new-android-app-for-newsblur/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Customizing the reader, step 1: story titles | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Customizing the reader, step 1: story titles" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="The iPhone app is now only a few days away from launching. But it took 3 weeks of sitting around in the App Store approval queue before getting here. During that time, I started working on the new customizations that folks have been asking for." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Customizing the reader, step 1: story titles" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Customizing the reader, step 1: story titles","dateModified":"2011-09-30T09:40:30-04:00","datePublished":"2011-09-30T09:40:30-04:00","url":"https://blog.newsblur.com/2011/09/30/customizing-the-reader-step-1-story-titles/","@type":"BlogPosting","description":"The iPhone app is now only a few days away from launching. But it took 3 weeks of sitting around in the App Store approval queue before getting here. During that time, I started working on the new customizations that folks have been asking for.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/09/30/customizing-the-reader-step-1-story-titles/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-09-30T09:40:30-04:00","datePublished":"2011-09-30T09:40:30-04:00","description":"The iPhone app is now only a few days away from launching. But it took 3 weeks of sitting around in the App Store approval queue before getting here. During that time, I started working on the new customizations that folks have been asking for.","headline":"Customizing the reader, step 1: story titles","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/09/30/customizing-the-reader-step-1-story-titles/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/09/30/customizing-the-reader-step-1-story-titles/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>A Social Feed Reader | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="A Social Feed Reader" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="NewsBlur was released exactly one year ago. You can read the initial reaction on Hacker News: http://news.ycombinator.com/item?id=1834305. Since then, so much has changed and all for the better. Usage is up—way, way up. Premium users are helping the site run. Load times are approaching the goal of less than 100 ms (0.10 sec) per page. In short, things couldn’t be better." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="A Social Feed Reader" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"A Social Feed Reader","dateModified":"2011-10-26T11:41:23-04:00","datePublished":"2011-10-26T11:41:23-04:00","url":"https://blog.newsblur.com/2011/10/26/a-social-feed-reader/","@type":"BlogPosting","description":"NewsBlur was released exactly one year ago. You can read the initial reaction on Hacker News: http://news.ycombinator.com/item?id=1834305. Since then, so much has changed and all for the better. Usage is up—way, way up. Premium users are helping the site run. Load times are approaching the goal of less than 100 ms (0.10 sec) per page. In short, things couldn’t be better.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/10/26/a-social-feed-reader/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2011-10-26T11:41:23-04:00","datePublished":"2011-10-26T11:41:23-04:00","description":"NewsBlur was released exactly one year ago. You can read the initial reaction on Hacker News: http://news.ycombinator.com/item?id=1834305. Since then, so much has changed and all for the better. Usage is up—way, way up. Premium users are helping the site run. Load times are approaching the goal of less than 100 ms (0.10 sec) per page. In short, things couldn’t be better.","headline":"A Social Feed Reader","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2011/10/26/a-social-feed-reader/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2011/10/26/a-social-feed-reader/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>2011: Year in Review | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="2011: Year in Review" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Twelve months can be a quick flyby if you don’t stop to write everything down. Luckily, a habit I’ve kept since July 2009, when I started recording monthly goals for my project, is still going strong." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="2011: Year in Review" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"2011: Year in Review","dateModified":"2012-01-16T20:47:00-05:00","datePublished":"2012-01-16T20:47:00-05:00","url":"https://blog.newsblur.com/2012/01/16/2011-year-in-review/","@type":"BlogPosting","description":"Twelve months can be a quick flyby if you don’t stop to write everything down. Luckily, a habit I’ve kept since July 2009, when I started recording monthly goals for my project, is still going strong.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/01/16/2011-year-in-review/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-01-16T20:47:00-05:00","datePublished":"2012-01-16T20:47:00-05:00","description":"Twelve months can be a quick flyby if you don’t stop to write everything down. Luckily, a habit I’ve kept since July 2009, when I started recording monthly goals for my project, is still going strong.","headline":"2011: Year in Review","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/01/16/2011-year-in-review/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/01/16/2011-year-in-review/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>SSL & Stripe.js | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="SSL & Stripe.js" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Two big announcements today:" />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="SSL & Stripe.js" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"SSL & Stripe.js","dateModified":"2012-02-29T15:45:00-05:00","datePublished":"2012-02-29T15:45:00-05:00","url":"https://blog.newsblur.com/2012/02/29/ssl-stripejs/","@type":"BlogPosting","description":"Two big announcements today:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/02/29/ssl-stripejs/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-02-29T15:45:00-05:00","datePublished":"2012-02-29T15:45:00-05:00","description":"Two big announcements today:","headline":"SSL & Stripe.js","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/02/29/ssl-stripejs/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/02/29/ssl-stripejs/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>From project to profession: going indie on NewsBlur | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="From project to profession: going indie on NewsBlur" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Exactly four months ago, Jason Kottke found my project, NewsBlur, and tweeted:" />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="From project to profession: going indie on NewsBlur" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"From project to profession: going indie on NewsBlur","dateModified":"2012-03-01T11:48:00-05:00","datePublished":"2012-03-01T11:48:00-05:00","url":"https://blog.newsblur.com/2012/03/01/going-full-time/","@type":"BlogPosting","description":"Exactly four months ago, Jason Kottke found my project, NewsBlur, and tweeted:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/01/going-full-time/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-03-01T11:48:00-05:00","datePublished":"2012-03-01T11:48:00-05:00","description":"Exactly four months ago, Jason Kottke found my project, NewsBlur, and tweeted:","headline":"From project to profession: going indie on NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/01/going-full-time/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/03/01/going-full-time/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>New mobile app for NewsBlur: Web Feeds for Nokia MeeGo | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="New mobile app for NewsBlur: Web Feeds for Nokia MeeGo" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="And what a gorgeous mobile app it is. App developer Róbert Márki just released Web Feeds, the first NewsBlur app for Nokia MeeGo. Take a look at these screenshots:" />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="New mobile app for NewsBlur: Web Feeds for Nokia MeeGo" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"New mobile app for NewsBlur: Web Feeds for Nokia MeeGo","dateModified":"2012-03-14T13:14:00-04:00","datePublished":"2012-03-14T13:14:00-04:00","url":"https://blog.newsblur.com/2012/03/14/mobile-app-web-feeds-nokia-meego/","@type":"BlogPosting","description":"And what a gorgeous mobile app it is. App developer Róbert Márki just released Web Feeds, the first NewsBlur app for Nokia MeeGo. Take a look at these screenshots:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/14/mobile-app-web-feeds-nokia-meego/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-03-14T13:14:00-04:00","datePublished":"2012-03-14T13:14:00-04:00","description":"And what a gorgeous mobile app it is. App developer Róbert Márki just released Web Feeds, the first NewsBlur app for Nokia MeeGo. Take a look at these screenshots:","headline":"New mobile app for NewsBlur: Web Feeds for Nokia MeeGo","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/14/mobile-app-web-feeds-nokia-meego/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/03/14/mobile-app-web-feeds-nokia-meego/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Knight News Challenge: NewsBlur | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Knight News Challenge: NewsBlur" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Knight News Challenge: NewsBlur" />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Knight News Challenge: NewsBlur" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Knight News Challenge: NewsBlur","dateModified":"2012-03-16T11:29:00-04:00","datePublished":"2012-03-16T11:29:00-04:00","url":"https://blog.newsblur.com/2012/03/16/knight-news-challenge/","@type":"BlogPosting","description":"Knight News Challenge: NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/16/knight-news-challenge/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-03-16T11:29:00-04:00","datePublished":"2012-03-16T11:29:00-04:00","description":"Knight News Challenge: NewsBlur","headline":"Knight News Challenge: NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/03/16/knight-news-challenge/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/03/16/knight-news-challenge/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Building real-time feed updates for NewsBlur with Redis and WebSockets | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Building real-time feed updates for NewsBlur with Redis and WebSockets" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Today, NewsBlur is going real-time. Blogs using the PubSubHubbub protocol (PuSH), which includes all Blogger, Tumblr, and many Wordpress blogs, will instantaneously show new updates to subscribers on NewsBlur. Making this happen, while not for the faint of heart, was straight-forward enough that I’m sharing the recipe I used to get everything hooked up and running smoothly." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Building real-time feed updates for NewsBlur with Redis and WebSockets" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Building real-time feed updates for NewsBlur with Redis and WebSockets","dateModified":"2012-04-02T17:52:00-04:00","datePublished":"2012-04-02T17:52:00-04:00","url":"https://blog.newsblur.com/2012/04/02/building-real-time-feed-updates-for-newsblur/","@type":"BlogPosting","description":"Today, NewsBlur is going real-time. Blogs using the PubSubHubbub protocol (PuSH), which includes all Blogger, Tumblr, and many Wordpress blogs, will instantaneously show new updates to subscribers on NewsBlur. Making this happen, while not for the faint of heart, was straight-forward enough that I’m sharing the recipe I used to get everything hooked up and running smoothly.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/04/02/building-real-time-feed-updates-for-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-04-02T17:52:00-04:00","datePublished":"2012-04-02T17:52:00-04:00","description":"Today, NewsBlur is going real-time. Blogs using the PubSubHubbub protocol (PuSH), which includes all Blogger, Tumblr, and many Wordpress blogs, will instantaneously show new updates to subscribers on NewsBlur. Making this happen, while not for the faint of heart, was straight-forward enough that I’m sharing the recipe I used to get everything hooked up and running smoothly.","headline":"Building real-time feed updates for NewsBlur with Redis and WebSockets","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/04/02/building-real-time-feed-updates-for-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/04/02/building-real-time-feed-updates-for-newsblur/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Introducing Blurblogs, Roy, and Y Combinator | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Introducing Blurblogs, Roy, and Y Combinator" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="What a difference a few months make. NewsBlur was a side-project of mine for two years. In March of this year, I committed myself full-time and went from developing NewsBlur almost entirely on the NYC subway to writing code every waking minute of the day. And now there are three big announcements to make. # 1. NewsBlur is now a *social* news reader The big news of the day is that you can now share stories on NewsBlur. When you share a story, your comments and the original story are posted to your blurblog. Your blurblog is a simple and customizable website. People can comment and reply directly on your blurblog, and you can follow your friends to read the news stories and blog posts that they care about.Since you’re good at picking your friends, and your friends are good at picking their friends, you will see friends of friends show up, expanding your network with shared stories that you will enjoy. It’s a new way of sharing the news. And because NewsBlur is already an easy to use news reader, it’s simple to find and share stories that your friends will care about. Every NewsBlur user has their own blurblog. All you have to do is signup for an account on www.newsblur.com and share interesting stories. # 2. Y Combinator For those of you who work with computer science, you may know that a Y-combinator generalizes recursion, abstracting its implementation, and thereby separating it from the actual work of the function in question.[^1] I’m pleased as punch to announce an investment in NewsBlur by Y Combinator, the investment firm. Over the past two months, we’ve been humbled by the roster of experienced partners giving us candid advice. It’s their tough love that is the catalyst for the next few months of transitioning NewsBlur from side project to world-class news reader. Expect NewsBlur to become simpler and more refined. # 3. Introducing Roy Yang When Y Combinator accepted me as a solo founder, their first piece of advice was to find a co-founder. Looking at every successful startup, a common pattern emerges. Every great startup has multiple people carrying the load when the company takes off. There is one person on this planet that I would trust as a co-founder. His name is Roy Yang and we have been friends since we met in New York four years ago. We worked together for nearly two years at Daylife, another news startup. I attended his wedding last year in Mexico, and he was the only person I called when I knew I needed somebody talented, focused, and able to complement me on a project that demands enormous time and effort.Roy is now responsible for both iOS apps and is instrumental in challenging me when I think I’m right and am clearly not. He’s got the patience of a monk and the determination of a true New Yorker. Follow Roy’s blurblog to keep up with him. # A glimpse into the future of NewsBlur This summer marks the beginning of NewsBlur as a full-time startup. Look forward to new mobile apps, new designs, and new features. Here’s a quick idea of what we’re working on for the next few weeks:" />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Introducing Blurblogs, Roy, and Y Combinator" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Introducing Blurblogs, Roy, and Y Combinator","dateModified":"2012-07-30T11:22:00-04:00","datePublished":"2012-07-30T11:22:00-04:00","url":"https://blog.newsblur.com/2012/07/30/introducing-blurblogs-roy-and-y-combinator/","@type":"BlogPosting","description":"What a difference a few months make. NewsBlur was a side-project of mine for two years. In March of this year, I committed myself full-time and went from developing NewsBlur almost entirely on the NYC subway to writing code every waking minute of the day. And now there are three big announcements to make. # 1. NewsBlur is now a *social* news reader The big news of the day is that you can now share stories on NewsBlur. When you share a story, your comments and the original story are posted to your blurblog. Your blurblog is a simple and customizable website. People can comment and reply directly on your blurblog, and you can follow your friends to read the news stories and blog posts that they care about.Since you’re good at picking your friends, and your friends are good at picking their friends, you will see friends of friends show up, expanding your network with shared stories that you will enjoy. It’s a new way of sharing the news. And because NewsBlur is already an easy to use news reader, it’s simple to find and share stories that your friends will care about. Every NewsBlur user has their own blurblog. All you have to do is signup for an account on www.newsblur.com and share interesting stories. # 2. Y Combinator For those of you who work with computer science, you may know that a Y-combinator generalizes recursion, abstracting its implementation, and thereby separating it from the actual work of the function in question.[^1] I’m pleased as punch to announce an investment in NewsBlur by Y Combinator, the investment firm. Over the past two months, we’ve been humbled by the roster of experienced partners giving us candid advice. It’s their tough love that is the catalyst for the next few months of transitioning NewsBlur from side project to world-class news reader. Expect NewsBlur to become simpler and more refined. # 3. Introducing Roy Yang When Y Combinator accepted me as a solo founder, their first piece of advice was to find a co-founder. Looking at every successful startup, a common pattern emerges. Every great startup has multiple people carrying the load when the company takes off. There is one person on this planet that I would trust as a co-founder. His name is Roy Yang and we have been friends since we met in New York four years ago. We worked together for nearly two years at Daylife, another news startup. I attended his wedding last year in Mexico, and he was the only person I called when I knew I needed somebody talented, focused, and able to complement me on a project that demands enormous time and effort.Roy is now responsible for both iOS apps and is instrumental in challenging me when I think I’m right and am clearly not. He’s got the patience of a monk and the determination of a true New Yorker. Follow Roy’s blurblog to keep up with him. # A glimpse into the future of NewsBlur This summer marks the beginning of NewsBlur as a full-time startup. Look forward to new mobile apps, new designs, and new features. Here’s a quick idea of what we’re working on for the next few weeks:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/07/30/introducing-blurblogs-roy-and-y-combinator/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-07-30T11:22:00-04:00","datePublished":"2012-07-30T11:22:00-04:00","description":"What a difference a few months make. NewsBlur was a side-project of mine for two years. In March of this year, I committed myself full-time and went from developing NewsBlur almost entirely on the NYC subway to writing code every waking minute of the day. And now there are three big announcements to make. # 1. NewsBlur is now a *social* news reader The big news of the day is that you can now share stories on NewsBlur. When you share a story, your comments and the original story are posted to your blurblog. Your blurblog is a simple and customizable website. People can comment and reply directly on your blurblog, and you can follow your friends to read the news stories and blog posts that they care about.Since you’re good at picking your friends, and your friends are good at picking their friends, you will see friends of friends show up, expanding your network with shared stories that you will enjoy. It’s a new way of sharing the news. And because NewsBlur is already an easy to use news reader, it’s simple to find and share stories that your friends will care about. Every NewsBlur user has their own blurblog. All you have to do is signup for an account on www.newsblur.com and share interesting stories. # 2. Y Combinator For those of you who work with computer science, you may know that a Y-combinator generalizes recursion, abstracting its implementation, and thereby separating it from the actual work of the function in question.[^1] I’m pleased as punch to announce an investment in NewsBlur by Y Combinator, the investment firm. Over the past two months, we’ve been humbled by the roster of experienced partners giving us candid advice. It’s their tough love that is the catalyst for the next few months of transitioning NewsBlur from side project to world-class news reader. Expect NewsBlur to become simpler and more refined. # 3. Introducing Roy Yang When Y Combinator accepted me as a solo founder, their first piece of advice was to find a co-founder. Looking at every successful startup, a common pattern emerges. Every great startup has multiple people carrying the load when the company takes off. There is one person on this planet that I would trust as a co-founder. His name is Roy Yang and we have been friends since we met in New York four years ago. We worked together for nearly two years at Daylife, another news startup. I attended his wedding last year in Mexico, and he was the only person I called when I knew I needed somebody talented, focused, and able to complement me on a project that demands enormous time and effort.Roy is now responsible for both iOS apps and is instrumental in challenging me when I think I’m right and am clearly not. He’s got the patience of a monk and the determination of a true New Yorker. Follow Roy’s blurblog to keep up with him. # A glimpse into the future of NewsBlur This summer marks the beginning of NewsBlur as a full-time startup. Look forward to new mobile apps, new designs, and new features. Here’s a quick idea of what we’re working on for the next few weeks:","headline":"Introducing Blurblogs, Roy, and Y Combinator","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/07/30/introducing-blurblogs-roy-and-y-combinator/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/07/30/introducing-blurblogs-roy-and-y-combinator/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Take it to the couch with the NewsBlur iPad app | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Take it to the couch with the NewsBlur iPad app" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="There’s no wrong way to hold an iPad loaded with the brand new NewsBlur iPad app, provided it’s facing you and turned on." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Take it to the couch with the NewsBlur iPad app" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Take it to the couch with the NewsBlur iPad app","dateModified":"2012-09-05T08:21:00-04:00","datePublished":"2012-09-05T08:21:00-04:00","url":"https://blog.newsblur.com/2012/09/05/newsblur-ipad-app/","@type":"BlogPosting","description":"There’s no wrong way to hold an iPad loaded with the brand new NewsBlur iPad app, provided it’s facing you and turned on.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/09/05/newsblur-ipad-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-09-05T08:21:00-04:00","datePublished":"2012-09-05T08:21:00-04:00","description":"There’s no wrong way to hold an iPad loaded with the brand new NewsBlur iPad app, provided it’s facing you and turned on.","headline":"Take it to the couch with the NewsBlur iPad app","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/09/05/newsblur-ipad-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/09/05/newsblur-ipad-app/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Giving Life to “The People Have Spoken” | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Giving Life to “The People Have Spoken”" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="For a long time, we’ve maintained The People Have Spoken, the blog of what’s popular on NewsBlur, with a simple algorithm that measured how often something was shared. While that’s a great way to see the stuff our users really like (Randall Munroe would probably win the NewsBlur equivalent of the Oscar), it makes it harder for everyone to find new stuff that they might not have seen or heard of before. So we’ve decided to throw a human into the mix. I’ll let Allie introduce herself in her own words:" />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Giving Life to “The People Have Spoken”" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Giving Life to “The People Have Spoken”","dateModified":"2012-10-01T17:52:00-04:00","datePublished":"2012-10-01T17:52:00-04:00","url":"https://blog.newsblur.com/2012/10/01/giving-life-to-popular/","@type":"BlogPosting","description":"For a long time, we’ve maintained The People Have Spoken, the blog of what’s popular on NewsBlur, with a simple algorithm that measured how often something was shared. While that’s a great way to see the stuff our users really like (Randall Munroe would probably win the NewsBlur equivalent of the Oscar), it makes it harder for everyone to find new stuff that they might not have seen or heard of before. So we’ve decided to throw a human into the mix. I’ll let Allie introduce herself in her own words:","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/01/giving-life-to-popular/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-10-01T17:52:00-04:00","datePublished":"2012-10-01T17:52:00-04:00","description":"For a long time, we’ve maintained The People Have Spoken, the blog of what’s popular on NewsBlur, with a simple algorithm that measured how often something was shared. While that’s a great way to see the stuff our users really like (Randall Munroe would probably win the NewsBlur equivalent of the Oscar), it makes it harder for everyone to find new stuff that they might not have seen or heard of before. So we’ve decided to throw a human into the mix. I’ll let Allie introduce herself in her own words:","headline":"Giving Life to “The People Have Spoken”","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/01/giving-life-to-popular/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/10/01/giving-life-to-popular/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Do the robot: the official NewsBlur Android app is here | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Do the robot: the official NewsBlur Android app is here" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="You’ve been bugging us for two years about it, and now it’s finally here: NewsBlur’s expansion to mobile is complete, with our first-ever official Android app ready and waiting for your device. Thanks to the gifts of money and time from Y Combinator and the programming prowess of Papermill creator Ryan Bateman (otherwise known as @secretsquirrel), you can now join your iOS brethren on the couch with your daily dose of RSS goodness. Level of accompanying smugness is up to you." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Do the robot: the official NewsBlur Android app is here" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Do the robot: the official NewsBlur Android app is here","dateModified":"2012-10-18T11:47:00-04:00","datePublished":"2012-10-18T11:47:00-04:00","url":"https://blog.newsblur.com/2012/10/18/do-the-robot-the-official-newsblur-android-app-is/","@type":"BlogPosting","description":"You’ve been bugging us for two years about it, and now it’s finally here: NewsBlur’s expansion to mobile is complete, with our first-ever official Android app ready and waiting for your device. Thanks to the gifts of money and time from Y Combinator and the programming prowess of Papermill creator Ryan Bateman (otherwise known as @secretsquirrel), you can now join your iOS brethren on the couch with your daily dose of RSS goodness. Level of accompanying smugness is up to you.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/18/do-the-robot-the-official-newsblur-android-app-is/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-10-18T11:47:00-04:00","datePublished":"2012-10-18T11:47:00-04:00","description":"You’ve been bugging us for two years about it, and now it’s finally here: NewsBlur’s expansion to mobile is complete, with our first-ever official Android app ready and waiting for your device. Thanks to the gifts of money and time from Y Combinator and the programming prowess of Papermill creator Ryan Bateman (otherwise known as @secretsquirrel), you can now join your iOS brethren on the couch with your daily dose of RSS goodness. Level of accompanying smugness is up to you.","headline":"Do the robot: the official NewsBlur Android app is here","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/18/do-the-robot-the-official-newsblur-android-app-is/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/10/18/do-the-robot-the-official-newsblur-android-app-is/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Extreme makeover: NewsBlur iOS app edition | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Extreme makeover: NewsBlur iOS app edition" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Now that NewsBlur has joined the wonderful world of Android, we’re turning our attention back to the iOS app, with a full-scale feature parity push for the new iPhone 5 and iOS 6. It’s perfect for catching up on your reading when you realize that Apple Maps has sent you to the wrong address. Again." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Extreme makeover: NewsBlur iOS app edition" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Extreme makeover: NewsBlur iOS app edition","dateModified":"2012-10-26T14:37:00-04:00","datePublished":"2012-10-26T14:37:00-04:00","url":"https://blog.newsblur.com/2012/10/26/ios-update-1-6/","@type":"BlogPosting","description":"Now that NewsBlur has joined the wonderful world of Android, we’re turning our attention back to the iOS app, with a full-scale feature parity push for the new iPhone 5 and iOS 6. It’s perfect for catching up on your reading when you realize that Apple Maps has sent you to the wrong address. Again.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/26/ios-update-1-6/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-10-26T14:37:00-04:00","datePublished":"2012-10-26T14:37:00-04:00","description":"Now that NewsBlur has joined the wonderful world of Android, we’re turning our attention back to the iOS app, with a full-scale feature parity push for the new iPhone 5 and iOS 6. It’s perfect for catching up on your reading when you realize that Apple Maps has sent you to the wrong address. Again.","headline":"Extreme makeover: NewsBlur iOS app edition","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/26/ios-update-1-6/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/10/26/ios-update-1-6/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Time for some free NewsBlur swag! | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Time for some free NewsBlur swag!" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="That’s right, t-shirts, stickers, buttons, and magnets. I’ve got a whole lot of good stuff to send out, so give me some critical info and I’ll get you hooked up with the latest in startup love." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Time for some free NewsBlur swag!" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Time for some free NewsBlur swag!","dateModified":"2012-10-29T15:38:00-04:00","datePublished":"2012-10-29T15:38:00-04:00","url":"https://blog.newsblur.com/2012/10/29/free-newsblur-swag-time/","@type":"BlogPosting","description":"That’s right, t-shirts, stickers, buttons, and magnets. I’ve got a whole lot of good stuff to send out, so give me some critical info and I’ll get you hooked up with the latest in startup love.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/29/free-newsblur-swag-time/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-10-29T15:38:00-04:00","datePublished":"2012-10-29T15:38:00-04:00","description":"That’s right, t-shirts, stickers, buttons, and magnets. I’ve got a whole lot of good stuff to send out, so give me some critical info and I’ll get you hooked up with the latest in startup love.","headline":"Time for some free NewsBlur swag!","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/10/29/free-newsblur-swag-time/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/10/29/free-newsblur-swag-time/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>The sharing bookmarklet: bringing your online explorations to NewsBlur | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="The sharing bookmarklet: bringing your online explorations to NewsBlur" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="There are lots of reasons not to post a cool article you’ve seen to your blurblog. Maybe you already follow too many blogs, and don’t have room to add any more to your feed (in which case, may we humbly recommend a Premium account?) Maybe you don’t want everyone to know just how crazy you’ve gotten about jai-alai or aerotrekking or My Little Pony: Friendship Is Magic. Or maybe you found a cool article on Facebook or Twitter or through an e-mail from a friend, and don’t want to go through the hassle of adding the site’s whole feed to your NewsBlur dashboard just to post one piece." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="The sharing bookmarklet: bringing your online explorations to NewsBlur" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"The sharing bookmarklet: bringing your online explorations to NewsBlur","dateModified":"2012-12-17T12:04:00-05:00","datePublished":"2012-12-17T12:04:00-05:00","url":"https://blog.newsblur.com/2012/12/17/sharing-bookmarklet/","@type":"BlogPosting","description":"There are lots of reasons not to post a cool article you’ve seen to your blurblog. Maybe you already follow too many blogs, and don’t have room to add any more to your feed (in which case, may we humbly recommend a Premium account?) Maybe you don’t want everyone to know just how crazy you’ve gotten about jai-alai or aerotrekking or My Little Pony: Friendship Is Magic. Or maybe you found a cool article on Facebook or Twitter or through an e-mail from a friend, and don’t want to go through the hassle of adding the site’s whole feed to your NewsBlur dashboard just to post one piece.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/12/17/sharing-bookmarklet/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2012-12-17T12:04:00-05:00","datePublished":"2012-12-17T12:04:00-05:00","description":"There are lots of reasons not to post a cool article you’ve seen to your blurblog. Maybe you already follow too many blogs, and don’t have room to add any more to your feed (in which case, may we humbly recommend a Premium account?) Maybe you don’t want everyone to know just how crazy you’ve gotten about jai-alai or aerotrekking or My Little Pony: Friendship Is Magic. Or maybe you found a cool article on Facebook or Twitter or through an e-mail from a friend, and don’t want to go through the hassle of adding the site’s whole feed to your NewsBlur dashboard just to post one piece.","headline":"The sharing bookmarklet: bringing your online explorations to NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2012/12/17/sharing-bookmarklet/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2012/12/17/sharing-bookmarklet/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>A blurblog of one’s own: new privacy controls | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="A blurblog of one’s own: new privacy controls" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Here at NewsBlur HQ, we love greeting each new day by seeing what everyone posts on their blurblogs, but we understand that not everyone might want to have their reading preferences broadcast to the public (or have the public broadcast its opinions on said preferences). So we’re introducing a special new service for premium account holders that allows you to protect your posts from prying eyes." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="A blurblog of one’s own: new privacy controls" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"A blurblog of one’s own: new privacy controls","dateModified":"2013-01-03T00:00:00-05:00","datePublished":"2013-01-03T00:00:00-05:00","url":"https://blog.newsblur.com/2013/01/03/privacy-controls/","@type":"BlogPosting","description":"Here at NewsBlur HQ, we love greeting each new day by seeing what everyone posts on their blurblogs, but we understand that not everyone might want to have their reading preferences broadcast to the public (or have the public broadcast its opinions on said preferences). So we’re introducing a special new service for premium account holders that allows you to protect your posts from prying eyes.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/01/03/privacy-controls/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-01-03T00:00:00-05:00","datePublished":"2013-01-03T00:00:00-05:00","description":"Here at NewsBlur HQ, we love greeting each new day by seeing what everyone posts on their blurblogs, but we understand that not everyone might want to have their reading preferences broadcast to the public (or have the public broadcast its opinions on said preferences). So we’re introducing a special new service for premium account holders that allows you to protect your posts from prying eyes.","headline":"A blurblog of one’s own: new privacy controls","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/01/03/privacy-controls/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/01/03/privacy-controls/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Three Months to Scale NewsBlur | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Three Months to Scale NewsBlur" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="At 4:16pm last Wednesday I got a short and to-the-point email from Nilay Patel at The Verge with only a link that started with the host “googlereader.blogspot.com”. The sudden spike in NewsBlur’s visitors immediately confirmed — Google was shutting down Reader." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Three Months to Scale NewsBlur" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Three Months to Scale NewsBlur","dateModified":"2013-03-17T17:24:00-04:00","datePublished":"2013-03-17T17:24:00-04:00","url":"https://blog.newsblur.com/2013/03/17/three-months-to-scale-newsblur/","@type":"BlogPosting","description":"At 4:16pm last Wednesday I got a short and to-the-point email from Nilay Patel at The Verge with only a link that started with the host “googlereader.blogspot.com”. The sudden spike in NewsBlur’s visitors immediately confirmed — Google was shutting down Reader.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/03/17/three-months-to-scale-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-03-17T17:24:00-04:00","datePublished":"2013-03-17T17:24:00-04:00","description":"At 4:16pm last Wednesday I got a short and to-the-point email from Nilay Patel at The Verge with only a link that started with the host “googlereader.blogspot.com”. The sudden spike in NewsBlur’s visitors immediately confirmed — Google was shutting down Reader.","headline":"Three Months to Scale NewsBlur","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/03/17/three-months-to-scale-newsblur/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/03/17/three-months-to-scale-newsblur/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>The NewsBlur Redesign | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="The NewsBlur Redesign" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Not to say that NewsBlur was ugly before today, but it certainly didn’t have the loving embrace of a talented designer. So without waiting another moment (or month) I proudly present the NewsBlur redesign." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="The NewsBlur Redesign" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"The NewsBlur Redesign","dateModified":"2013-05-20T22:47:00-04:00","datePublished":"2013-05-20T22:47:00-04:00","url":"https://blog.newsblur.com/2013/05/20/the-newsblur-redesign/","@type":"BlogPosting","description":"Not to say that NewsBlur was ugly before today, but it certainly didn’t have the loving embrace of a talented designer. So without waiting another moment (or month) I proudly present the NewsBlur redesign.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/20/the-newsblur-redesign/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-05-20T22:47:00-04:00","datePublished":"2013-05-20T22:47:00-04:00","description":"Not to say that NewsBlur was ugly before today, but it certainly didn’t have the loving embrace of a talented designer. So without waiting another moment (or month) I proudly present the NewsBlur redesign.","headline":"The NewsBlur Redesign","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/20/the-newsblur-redesign/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/05/20/the-newsblur-redesign/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Keyboard Shortcuts Manager | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Keyboard Shortcuts Manager" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Hot on the heels of the redesign launch, I’m already putting out new features. There are a number of post-redesign priorities on my list, but one of the most requested features is to customize the keyboard shortcuts." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Keyboard Shortcuts Manager" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Keyboard Shortcuts Manager","dateModified":"2013-05-23T09:01:00-04:00","datePublished":"2013-05-23T09:01:00-04:00","url":"https://blog.newsblur.com/2013/05/23/keyboard-shortcuts-manager/","@type":"BlogPosting","description":"Hot on the heels of the redesign launch, I’m already putting out new features. There are a number of post-redesign priorities on my list, but one of the most requested features is to customize the keyboard shortcuts.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/23/keyboard-shortcuts-manager/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-05-23T09:01:00-04:00","datePublished":"2013-05-23T09:01:00-04:00","description":"Hot on the heels of the redesign launch, I’m already putting out new features. There are a number of post-redesign priorities on my list, but one of the most requested features is to customize the keyboard shortcuts.","headline":"Keyboard Shortcuts Manager","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/23/keyboard-shortcuts-manager/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/05/23/keyboard-shortcuts-manager/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Read NewsBlur on your Mac with the new ReadKit | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Read NewsBlur on your Mac with the new ReadKit" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="ReadKit, a native Mac app for reading Instapaper, Pocket, and NewsBlur on your desktop, completes the RSS reading trifecta. NewsBlur has a web app, native iOS app, and now a native Mac app." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Read NewsBlur on your Mac with the new ReadKit" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Read NewsBlur on your Mac with the new ReadKit","dateModified":"2013-05-30T12:12:16-04:00","datePublished":"2013-05-30T12:12:16-04:00","url":"https://blog.newsblur.com/2013/05/30/read-newsblur-on-your-mac-with-the-new-readkit/","@type":"BlogPosting","description":"ReadKit, a native Mac app for reading Instapaper, Pocket, and NewsBlur on your desktop, completes the RSS reading trifecta. NewsBlur has a web app, native iOS app, and now a native Mac app.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/30/read-newsblur-on-your-mac-with-the-new-readkit/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-05-30T12:12:16-04:00","datePublished":"2013-05-30T12:12:16-04:00","description":"ReadKit, a native Mac app for reading Instapaper, Pocket, and NewsBlur on your desktop, completes the RSS reading trifecta. NewsBlur has a web app, native iOS app, and now a native Mac app.","headline":"Read NewsBlur on your Mac with the new ReadKit","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/05/30/read-newsblur-on-your-mac-with-the-new-readkit/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/05/30/read-newsblur-on-your-mac-with-the-new-readkit/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Text view comes to the NewsBlur iOS app | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Text view comes to the NewsBlur iOS app" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="The iOS apps are finding themselves host to a whole slew of additions and enhancements. Today I get to tell you about the iOS app’s newest feature: the Text view." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Text view comes to the NewsBlur iOS app" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Text view comes to the NewsBlur iOS app","dateModified":"2013-06-04T08:31:18-04:00","datePublished":"2013-06-04T08:31:18-04:00","url":"https://blog.newsblur.com/2013/06/04/text-view-comes-to-the-newsblur-ios-app/","@type":"BlogPosting","description":"The iOS apps are finding themselves host to a whole slew of additions and enhancements. Today I get to tell you about the iOS app’s newest feature: the Text view.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/06/04/text-view-comes-to-the-newsblur-ios-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-06-04T08:31:18-04:00","datePublished":"2013-06-04T08:31:18-04:00","description":"The iOS apps are finding themselves host to a whole slew of additions and enhancements. Today I get to tell you about the iOS app’s newest feature: the Text view.","headline":"Text view comes to the NewsBlur iOS app","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/06/04/text-view-comes-to-the-newsblur-ios-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/06/04/text-view-comes-to-the-newsblur-ios-app/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>NewsBlur Puzzle T-shirt 2013 | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="NewsBlur Puzzle T-shirt 2013" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Last year I was proud to be able to send a free t-shirt and handwritten note to every single user who requested one. It took a few days of writing, stuffing, and mailing to send out a couple hundred t-shirts." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="NewsBlur Puzzle T-shirt 2013" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"NewsBlur Puzzle T-shirt 2013","dateModified":"2013-07-24T15:49:00-04:00","datePublished":"2013-07-24T15:49:00-04:00","url":"https://blog.newsblur.com/2013/07/24/newsblur-puzzle-t-shirt-2013/","@type":"BlogPosting","description":"Last year I was proud to be able to send a free t-shirt and handwritten note to every single user who requested one. It took a few days of writing, stuffing, and mailing to send out a couple hundred t-shirts.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/07/24/newsblur-puzzle-t-shirt-2013/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-07-24T15:49:00-04:00","datePublished":"2013-07-24T15:49:00-04:00","description":"Last year I was proud to be able to send a free t-shirt and handwritten note to every single user who requested one. It took a few days of writing, stuffing, and mailing to send out a couple hundred t-shirts.","headline":"NewsBlur Puzzle T-shirt 2013","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/07/24/newsblur-puzzle-t-shirt-2013/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/07/24/newsblur-puzzle-t-shirt-2013/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Simple Search for Feeds, Saved Stories, and Blurblogs | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Simple Search for Feeds, Saved Stories, and Blurblogs" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Search, which can easily be considered one of the most important features of a world-class news reader, is also one of the most difficult features to build." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Simple Search for Feeds, Saved Stories, and Blurblogs" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Simple Search for Feeds, Saved Stories, and Blurblogs","dateModified":"2013-07-30T12:38:23-04:00","datePublished":"2013-07-30T12:38:23-04:00","url":"https://blog.newsblur.com/2013/07/30/simple-search-for-feeds-saved-stories-and-blurblogs/","@type":"BlogPosting","description":"Search, which can easily be considered one of the most important features of a world-class news reader, is also one of the most difficult features to build.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/07/30/simple-search-for-feeds-saved-stories-and-blurblogs/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-07-30T12:38:23-04:00","datePublished":"2013-07-30T12:38:23-04:00","description":"Search, which can easily be considered one of the most important features of a world-class news reader, is also one of the most difficult features to build.","headline":"Simple Search for Feeds, Saved Stories, and Blurblogs","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/07/30/simple-search-for-feeds-saved-stories-and-blurblogs/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/07/30/simple-search-for-feeds-saved-stories-and-blurblogs/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Mark as read by number of days and other improvements | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Mark as read by number of days and other improvements" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Here’s a few big improvements for the NewsBlur website." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Mark as read by number of days and other improvements" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Mark as read by number of days and other improvements","dateModified":"2013-09-13T17:05:55-04:00","datePublished":"2013-09-13T17:05:55-04:00","url":"https://blog.newsblur.com/2013/09/13/mark-as-read-by-number-of-days-and-other/","@type":"BlogPosting","description":"Here’s a few big improvements for the NewsBlur website.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/13/mark-as-read-by-number-of-days-and-other/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-09-13T17:05:55-04:00","datePublished":"2013-09-13T17:05:55-04:00","description":"Here’s a few big improvements for the NewsBlur website.","headline":"Mark as read by number of days and other improvements","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/13/mark-as-read-by-number-of-days-and-other/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/09/13/mark-as-read-by-number-of-days-and-other/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Upping unread stories to 30 days for premium accounts | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Upping unread stories to 30 days for premium accounts" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="While I love shipping new features and fixing bugs, the single largest user request was neither a feature nor a bug. NewsBlur allows for two weeks of unread stories. Once a story is more than 14 days old, it would no longer show up as unread. The justification for this was simple: you have a week to read a story, and have a second week as a grace period." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Upping unread stories to 30 days for premium accounts" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Upping unread stories to 30 days for premium accounts","dateModified":"2013-09-16T17:09:14-04:00","datePublished":"2013-09-16T17:09:14-04:00","url":"https://blog.newsblur.com/2013/09/16/upping-unread-stories-to-30-days-for-premium/","@type":"BlogPosting","description":"While I love shipping new features and fixing bugs, the single largest user request was neither a feature nor a bug. NewsBlur allows for two weeks of unread stories. Once a story is more than 14 days old, it would no longer show up as unread. The justification for this was simple: you have a week to read a story, and have a second week as a grace period.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/16/upping-unread-stories-to-30-days-for-premium/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-09-16T17:09:14-04:00","datePublished":"2013-09-16T17:09:14-04:00","description":"While I love shipping new features and fixing bugs, the single largest user request was neither a feature nor a bug. NewsBlur allows for two weeks of unread stories. Once a story is more than 14 days old, it would no longer show up as unread. The justification for this was simple: you have a week to read a story, and have a second week as a grace period.","headline":"Upping unread stories to 30 days for premium accounts","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/16/upping-unread-stories-to-30-days-for-premium/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/09/16/upping-unread-stories-to-30-days-for-premium/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Offline reading with the NewsBlur iOS app | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Offline reading with the NewsBlur iOS app" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Today I’m launching version 3.0 of the iPhone and iPad app for NewsBlur. This major update brings loads of big features that combine to make the world’s best iOS news reader with the fastest sync in town." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Offline reading with the NewsBlur iOS app" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Offline reading with the NewsBlur iOS app","dateModified":"2013-09-17T12:46:00-04:00","datePublished":"2013-09-17T12:46:00-04:00","url":"https://blog.newsblur.com/2013/09/17/offline-reading-with-the-newsblur-ios-app/","@type":"BlogPosting","description":"Today I’m launching version 3.0 of the iPhone and iPad app for NewsBlur. This major update brings loads of big features that combine to make the world’s best iOS news reader with the fastest sync in town.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/17/offline-reading-with-the-newsblur-ios-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-09-17T12:46:00-04:00","datePublished":"2013-09-17T12:46:00-04:00","description":"Today I’m launching version 3.0 of the iPhone and iPad app for NewsBlur. This major update brings loads of big features that combine to make the world’s best iOS news reader with the fastest sync in town.","headline":"Offline reading with the NewsBlur iOS app","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/09/17/offline-reading-with-the-newsblur-ios-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/09/17/offline-reading-with-the-newsblur-ios-app/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>The NewsBlur iPhone and iPad app meets iOS 7 | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="The NewsBlur iPhone and iPad app meets iOS 7" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Apple’s latest operating system for iOS is a departure from their old aesthetic. So I’ve decided to give the NewsBlur iOS app a slightly new look. But even more than how the app looks is how the app works. Tons of new features made it into this mega-release." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="The NewsBlur iPhone and iPad app meets iOS 7" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"The NewsBlur iPhone and iPad app meets iOS 7","dateModified":"2013-10-28T09:53:00-04:00","datePublished":"2013-10-28T09:53:00-04:00","url":"https://blog.newsblur.com/2013/10/28/the-newsblur-iphone-and-ipad-app-meets-ios-7/","@type":"BlogPosting","description":"Apple’s latest operating system for iOS is a departure from their old aesthetic. So I’ve decided to give the NewsBlur iOS app a slightly new look. But even more than how the app looks is how the app works. Tons of new features made it into this mega-release.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/10/28/the-newsblur-iphone-and-ipad-app-meets-ios-7/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-10-28T09:53:00-04:00","datePublished":"2013-10-28T09:53:00-04:00","description":"Apple’s latest operating system for iOS is a departure from their old aesthetic. So I’ve decided to give the NewsBlur iOS app a slightly new look. But even more than how the app looks is how the app works. Tons of new features made it into this mega-release.","headline":"The NewsBlur iPhone and iPad app meets iOS 7","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/10/28/the-newsblur-iphone-and-ipad-app-meets-ios-7/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/10/28/the-newsblur-iphone-and-ipad-app-meets-ios-7/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Version 3.0 of the NewsBlur Android App | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Version 3.0 of the NewsBlur Android App" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="Hot on the heels of version 3.0 of the NewsBlur iOS app comes the next version of the Android app. A bunch of new features have made it into this release, including the new story navigation pane and the text view." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Version 3.0 of the NewsBlur Android App" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Version 3.0 of the NewsBlur Android App","dateModified":"2013-11-18T16:51:00-05:00","datePublished":"2013-11-18T16:51:00-05:00","url":"https://blog.newsblur.com/2013/11/18/version-3-0-of-the-newsblur-android-app/","@type":"BlogPosting","description":"Hot on the heels of version 3.0 of the NewsBlur iOS app comes the next version of the Android app. A bunch of new features have made it into this release, including the new story navigation pane and the text view.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/11/18/version-3-0-of-the-newsblur-android-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-11-18T16:51:00-05:00","datePublished":"2013-11-18T16:51:00-05:00","description":"Hot on the heels of version 3.0 of the NewsBlur iOS app comes the next version of the Android app. A bunch of new features have made it into this release, including the new story navigation pane and the text view.","headline":"Version 3.0 of the NewsBlur Android App","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/11/18/version-3-0-of-the-newsblur-android-app/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/11/18/version-3-0-of-the-newsblur-android-app/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Background updates and dynamic font sizing on the NewsBlur iOS app | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Background updates and dynamic font sizing on the NewsBlur iOS app" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="This week brings us a minor, but major, update for the NewsBlur iOS app. Several new features, some due to new APIs in iOS 7, have made it into the app." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Background updates and dynamic font sizing on the NewsBlur iOS app" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Background updates and dynamic font sizing on the NewsBlur iOS app","dateModified":"2013-12-19T10:38:00-05:00","datePublished":"2013-12-19T10:38:00-05:00","url":"https://blog.newsblur.com/2013/12/19/background-updates-and-dynamic-font-sizing-on-the/","@type":"BlogPosting","description":"This week brings us a minor, but major, update for the NewsBlur iOS app. Several new features, some due to new APIs in iOS 7, have made it into the app.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/12/19/background-updates-and-dynamic-font-sizing-on-the/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2013-12-19T10:38:00-05:00","datePublished":"2013-12-19T10:38:00-05:00","description":"This week brings us a minor, but major, update for the NewsBlur iOS app. Several new features, some due to new APIs in iOS 7, have made it into the app.","headline":"Background updates and dynamic font sizing on the NewsBlur iOS app","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2013/12/19/background-updates-and-dynamic-font-sizing-on-the/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2013/12/19/background-updates-and-dynamic-font-sizing-on-the/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Faster parallel network requests for version 3.5 of the NewsBlur Android app | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Faster parallel network requests for version 3.5 of the NewsBlur Android app" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="The single biggest criticism I’ve heard of the Android app is that it can be slow when loading feeds and then loading stories. That changes today with the release of version 3.5 of the NewsBlur Android app." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Faster parallel network requests for version 3.5 of the NewsBlur Android app" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Faster parallel network requests for version 3.5 of the NewsBlur Android app","dateModified":"2014-01-03T11:35:00-05:00","datePublished":"2014-01-03T11:35:00-05:00","url":"https://blog.newsblur.com/2014/01/03/faster-parallel-network-requests-for-version-35/","@type":"BlogPosting","description":"The single biggest criticism I’ve heard of the Android app is that it can be slow when loading feeds and then loading stories. That changes today with the release of version 3.5 of the NewsBlur Android app.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2014/01/03/faster-parallel-network-requests-for-version-35/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2014-01-03T11:35:00-05:00","datePublished":"2014-01-03T11:35:00-05:00","description":"The single biggest criticism I’ve heard of the Android app is that it can be slow when loading feeds and then loading stories. That changes today with the release of version 3.5 of the NewsBlur Android app.","headline":"Faster parallel network requests for version 3.5 of the NewsBlur Android app","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2014/01/03/faster-parallel-network-requests-for-version-35/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2014/01/03/faster-parallel-network-requests-for-version-35/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<link rel="icon" href="https://newsblur.com/media/img/favicon_64.png" sizes="64x64"/>
|
||||
<link rel="alternate" type="application/rss+xml"
|
||||
title="The NewsBlur Blog RSS feed"
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.7.1 -->
|
||||
href="/feed.xml" /><!-- Begin Jekyll SEO tag v2.8.0 -->
|
||||
<title>Three new features for the web: syntax highlighting for source code, adjustable video widths, and footnotes | The NewsBlur Blog</title>
|
||||
<meta name="generator" content="Jekyll v4.2.0" />
|
||||
<meta name="generator" content="Jekyll v4.2.2" />
|
||||
<meta property="og:title" content="Three new features for the web: syntax highlighting for source code, adjustable video widths, and footnotes" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta name="description" content="A few small new features to get your first full week of the new year started off right." />
|
||||
|
@ -23,7 +23,7 @@
|
|||
<meta name="twitter:card" content="summary" />
|
||||
<meta property="twitter:title" content="Three new features for the web: syntax highlighting for source code, adjustable video widths, and footnotes" />
|
||||
<script type="application/ld+json">
|
||||
{"headline":"Three new features for the web: syntax highlighting for source code, adjustable video widths, and footnotes","dateModified":"2014-01-06T12:46:00-05:00","datePublished":"2014-01-06T12:46:00-05:00","url":"https://blog.newsblur.com/2014/01/06/three-new-features-for-the-web-syntax/","@type":"BlogPosting","description":"A few small new features to get your first full week of the new year started off right.","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2014/01/06/three-new-features-for-the-web-syntax/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"@context":"https://schema.org"}</script>
|
||||
{"@context":"https://schema.org","@type":"BlogPosting","dateModified":"2014-01-06T12:46:00-05:00","datePublished":"2014-01-06T12:46:00-05:00","description":"A few small new features to get your first full week of the new year started off right.","headline":"Three new features for the web: syntax highlighting for source code, adjustable video widths, and footnotes","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.newsblur.com/2014/01/06/three-new-features-for-the-web-syntax/"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://blog.newsblur.com/assets/newsblur_logo_512.png"}},"url":"https://blog.newsblur.com/2014/01/06/three-new-features-for-the-web-syntax/"}</script>
|
||||
<!-- End Jekyll SEO tag -->
|
||||
<link rel="stylesheet" href="/assets/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cloud.typography.com/6565292/711824/css/fonts.css" />
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue