2 Commits

Author SHA1 Message Date
c3cb3db680 add post, fix profile err 2025-08-09 11:51:58 +09:00
55745ff051 rm log 2025-08-09 11:51:58 +09:00
13 changed files with 1900 additions and 42 deletions

View File

@@ -0,0 +1,181 @@
---
title: "archlinuxのinstall"
slug: "arch"
date: "2025-08-08"
tags: ["arch"]
draft: false
---
## 最小構成
```sh
# cgdisk /dev/sda
```
```sh
$ mkfs.vfat /dev/sda1
$ mkfs.ext4 /dev/sda2
$ mount /dev/sda2 /mnt
$ mount --mkdir /dev/sda1 /mnt/boot
$ pacstrap /mnt base base-devel linux linux-firmware linux-headers
$ arch-chroot /mnt
$ pacman -S dhcpcd grub os-prober efibootmgr
$ grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=grub
$ grub-mkconfig -o /boot/grub/grub.cfg
```
これだけで`exit;reboot`すると起動できます。
## networkの設定
```sh
$ pacman -S git vim tmux zsh openssh
```
次にnetworkです。ここでは`systemd-networkd`を使用します。`dhcpcd`を使ったほうが簡単ではあります。もし安定しないようなら`dhcpcd`を使用。
```sh
# systemctl enable dhcpcd
```
```sh
$ systemctl enable systemd-networkd
```
network deviceをeth0にします。
```sh
$ ip link
$ ln -s /dev/null /etc/udev/rules.d/80-net-setup-link.rules
```
```sh:/etc/systemd/network/eth.network
[Match]
Name=eth0
[Network]
Address=192.168.1.2/24
Gateway=192.168.1.1
DNS=192.168.1.1
```
```sh
$ systemctl enable systemd-resolved
```
## auto-login
次にauto-loginを設定していきます。ここでは`getty`を使用。`${USER}`のところを自分のusernameにしてください。
```sh
$ mkdir -p /etc/systemd/system/getty@tty1.service.d/
```
```sh:/etc/systemd/system/getty@tty1.service.d/override.conf
[Service]
ExecStart=
ExecStart=-/usr/bin/agetty --autologin ${USER} --noclear %I $TERM
```
```sh
$ systemctl daemon-reload
$ systemctl restart getty@tty1
```
## window-manager
`xorg`でdesktop(window-manager)を作ります。`i3`を使うことにしましょう。`xorg`は`wayland`に切り替えたほうがいいかも。
```sh
$ pacman -S xorg xorg-xinit i3 xterm
# 確認
$ startx
$ i3
```
```sh:~/.xinitrc
exec i3
```
```sh:~/.bash_profile
if [[ ! $DISPLAY && $XDG_VTNR -eq 1 ]]; then
exec startx
fi
```
## sshの使い方
```sh
$ systemctl enable sshd
$ cat /etc/ssh/sshd_config
Port 22119
PasswordAuthentication no
$ systemctl restart sshd
```
基本的にlanで使う場合はdefaultで問題ありませんが、wanで使う場合は変更します。とはいえ、lanでもport, passwordは変えておいたほうがいいでしょう。
次に接続側でkeyを作ってserverに登録します。
```sh
$ ssh-keygen -f ~/.ssh/archlinux
$ ssh-copy-id -i ~/.ssh/archlinux ${USER}@192.168.1.2 -p 22119
```
`ssh-copy-id`がない場合は以下のようにしましょう。
```sh
$ cat ~/.ssh/archlinux.pub | ssh -p 22119 ${USER}@192.168.1.2 'cat >> ~/.ssh/authorized_keys'
```
この設定で`ssh archlinux`コマンドで接続できます。
```sh:~/.ssh/config
Host archlinux
User syui
Hostname 192.168.1.2
Port 22119
IdentityFile ~/.ssh/archlinux
```
おそらく、これがarchlinuxを普通に使っていくうえでの最小構成かと思います。
serverだけならxorgなどは必要ありません。
## zshの使い方
```sh
$ sudo pacman -S git-zsh-completion powerline zsh-autocomplete zsh-autosuggestions zsh-completions zsh-history-substring-search zsh-syntax-highlighting
```
例えば、`ls -`と入力すると補完され、`C-n`, `C-p`で選択。
```sh:~/.zshrc
alias u="sudo pacman -Syu --noconfirm"
alias zs="vim ~/.zshrc"
alias zr="exec $SHELL && source ~/.zshrc"
source /usr/share/zsh/plugins/zsh-autocomplete/zsh-autocomplete.plugin.zsh
source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
source /usr/share/zsh/plugins/zsh-history-substring-search/zsh-history-substring-search.zsh
# source /usr/share/powerline/bindings/zsh/powerline.zsh
autoload -Uz compinit
compinit
fpath=(/usr/share/zsh/site-functions $fpath)
HISTSIZE=10000
SAVEHIST=10000
HISTFILE=~/.zsh_history
setopt SHARE_HISTORY
setopt HIST_IGNORE_DUPS
bindkey '^[[A' history-substring-search-up
bindkey '^[[B' history-substring-search-down
```
`powerline`は重いのでコメントしています。

File diff suppressed because it is too large Load Diff

View File

@@ -83,11 +83,14 @@ export const atproto = {
return await request(`${apiEndpoint}/xrpc/${ENDPOINTS.getProfile}?actor=${actor}`) return await request(`${apiEndpoint}/xrpc/${ENDPOINTS.getProfile}?actor=${actor}`)
}, },
async getRecords(pds, repo, collection, limit = 10, cursor = null) { async getRecords(pds, repo, collection, limit = 10, cursor = null, reverse = false) {
let url = `${pds}/xrpc/${ENDPOINTS.listRecords}?repo=${repo}&collection=${collection}&limit=${limit}` let url = `${pds}/xrpc/${ENDPOINTS.listRecords}?repo=${repo}&collection=${collection}&limit=${limit}`
if (cursor) { if (cursor) {
url += `&cursor=${cursor}` url += `&cursor=${cursor}`
} }
if (reverse) {
url += `&reverse=true`
}
const res = await request(url) const res = await request(url)
return { return {
records: res.records || [], records: res.records || [],
@@ -151,7 +154,7 @@ export const collections = {
const cached = dataCache.get(cacheKey) const cached = dataCache.get(cacheKey)
if (cached) return cached if (cached) return cached
const data = await atproto.getRecords(pds, repo, `${collection}.chat.comment`, limit) const data = await atproto.getRecords(pds, repo, `${collection}.chat.comment`, limit, null, true) // reverse=true for chronological order
// Extract records array for backward compatibility // Extract records array for backward compatibility
const records = data.records || data const records = data.records || data
dataCache.set(cacheKey, records) dataCache.set(cacheKey, records)
@@ -161,7 +164,7 @@ export const collections = {
async getChat(pds, repo, collection, limit = 10, cursor = null) { async getChat(pds, repo, collection, limit = 10, cursor = null) {
// Don't use cache for pagination requests // Don't use cache for pagination requests
if (cursor) { if (cursor) {
const result = await atproto.getRecords(pds, repo, `${collection}.chat`, limit, cursor) const result = await atproto.getRecords(pds, repo, `${collection}.chat`, limit, cursor, true) // reverse=true for chronological order
return result return result
} }
@@ -172,7 +175,7 @@ export const collections = {
return Array.isArray(cached) ? { records: cached, cursor: null } : cached return Array.isArray(cached) ? { records: cached, cursor: null } : cached
} }
const data = await atproto.getRecords(pds, repo, `${collection}.chat`, limit) const data = await atproto.getRecords(pds, repo, `${collection}.chat`, limit, null, true) // reverse=true for chronological order
// Cache only the records array for backward compatibility // Cache only the records array for backward compatibility
dataCache.set(cacheKey, data.records || data) dataCache.set(cacheKey, data.records || data)
return data return data

View File

@@ -1,4 +1,5 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { logger } from '../utils/logger.js'
export default function AuthButton({ user, onLogin, onLogout, loading }) { export default function AuthButton({ user, onLogin, onLogout, loading }) {
const [handleInput, setHandleInput] = useState('') const [handleInput, setHandleInput] = useState('')
@@ -12,7 +13,7 @@ export default function AuthButton({ user, onLogin, onLogout, loading }) {
try { try {
await onLogin(handleInput.trim()) await onLogin(handleInput.trim())
} catch (error) { } catch (error) {
console.error('Login failed:', error) logger.error('Login failed:', error)
alert('ログインに失敗しました: ' + error.message) alert('ログインに失敗しました: ' + error.message)
} finally { } finally {
setIsLoading(false) setIsLoading(false)

View File

@@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import Avatar, { AvatarWithCard, AvatarList } from './Avatar.jsx' import Avatar, { AvatarWithCard, AvatarList } from './Avatar.jsx'
import { getAvatar, batchFetchAvatars, prefetchAvatar } from '../utils/avatar.js' import { getAvatar, batchFetchAvatars, prefetchAvatar } from '../utils/avatar.js'
import { logger } from '../utils/logger.js'
/** /**
* Test component to demonstrate avatar functionality * Test component to demonstrate avatar functionality
@@ -63,7 +64,7 @@ export default function AvatarTest() {
setTestResults(results) setTestResults(results)
} catch (error) { } catch (error) {
console.error('Test failed:', error) logger.error('Test failed:', error)
} finally { } finally {
setLoading(false) setLoading(false)
} }
@@ -78,7 +79,7 @@ export default function AvatarTest() {
batchResults: Object.fromEntries(avatarMap) batchResults: Object.fromEntries(avatarMap)
})) }))
} catch (error) { } catch (error) {
console.error('Batch test failed:', error) logger.error('Batch test failed:', error)
} finally { } finally {
setLoading(false) setLoading(false)
} }
@@ -94,7 +95,7 @@ export default function AvatarTest() {
prefetchResult: cachedAvatar prefetchResult: cachedAvatar
})) }))
} catch (error) { } catch (error) {
console.error('Prefetch test failed:', error) logger.error('Prefetch test failed:', error)
} finally { } finally {
setLoading(false) setLoading(false)
} }

View File

@@ -1,6 +1,7 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { atproto, collections } from '../api/atproto.js' import { atproto, collections } from '../api/atproto.js'
import { env } from '../config/env.js' import { env } from '../config/env.js'
import { logger } from '../utils/logger.js'
const ProfileForm = ({ user, agent, apiConfig, onProfilePosted }) => { const ProfileForm = ({ user, agent, apiConfig, onProfilePosted }) => {
const [text, setText] = useState('') const [text, setText] = useState('')
@@ -79,7 +80,7 @@ const ProfileForm = ({ user, agent, apiConfig, onProfilePosted }) => {
} }
} catch (err) { } catch (err) {
console.error('Failed to create profile:', err) logger.error('Failed to create profile:', err)
setError(err.message || 'プロフィールの作成に失敗しました') setError(err.message || 'プロフィールの作成に失敗しました')
} finally { } finally {
setPosting(false) setPosting(false)

View File

@@ -1,6 +1,7 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { atproto } from '../api/atproto.js' import { atproto } from '../api/atproto.js'
import { getPdsFromHandle, getApiConfig } from '../utils/pds.js' import { getPdsFromHandle, getApiConfig } from '../utils/pds.js'
import { logger } from '../utils/logger.js'
export default function UserLookup() { export default function UserLookup() {
const [handleInput, setHandleInput] = useState('') const [handleInput, setHandleInput] = useState('')
@@ -26,7 +27,7 @@ export default function UserLookup() {
config: apiConfig config: apiConfig
}) })
} catch (error) { } catch (error) {
console.error('User lookup failed:', error) logger.error('User lookup failed:', error)
setUserInfo({ error: error.message }) setUserInfo({ error: error.message })
} finally { } finally {
setLoading(false) setLoading(false)

View File

@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { OAuthService } from '../services/oauth.js' import { OAuthService } from '../services/oauth.js'
import { logger } from '../utils/logger.js'
const oauthService = new OAuthService() const oauthService = new OAuthService()
@@ -21,7 +22,7 @@ export function useAuth() {
// If we're on callback page and authentication succeeded, notify parent // If we're on callback page and authentication succeeded, notify parent
if (window.location.pathname === '/oauth/callback') { if (window.location.pathname === '/oauth/callback') {
console.log('OAuth callback completed, notifying parent window') logger.log('OAuth callback completed, notifying parent window')
// Get referrer or use stored return URL // Get referrer or use stored return URL
const returnUrl = sessionStorage.getItem('oauth_return_url') || const returnUrl = sessionStorage.getItem('oauth_return_url') ||
@@ -48,7 +49,7 @@ export function useAuth() {
} }
} }
} catch (error) { } catch (error) {
console.error('Auth initialization failed:', error) logger.error('Auth initialization failed:', error)
} finally { } finally {
setLoading(false) setLoading(false)
} }

View File

@@ -2,6 +2,7 @@ import { BrowserOAuthClient } from '@atproto/oauth-client-browser'
import { Agent } from '@atproto/api' import { Agent } from '@atproto/api'
import { env } from '../config/env.js' import { env } from '../config/env.js'
import { isSyuIsHandle } from '../utils/pds.js' import { isSyuIsHandle } from '../utils/pds.js'
import { logger } from '../utils/logger.js'
export class OAuthService { export class OAuthService {
constructor() { constructor() {
@@ -44,7 +45,7 @@ export class OAuthService {
// Try to restore session // Try to restore session
return await this.restoreSession() return await this.restoreSession()
} catch (error) { } catch (error) {
console.error('OAuth initialization failed:', error) logger.error('OAuth initialization failed:', error)
this.initPromise = null this.initPromise = null
throw error throw error
} }
@@ -89,18 +90,18 @@ export class OAuthService {
displayName = profile.data.displayName || null displayName = profile.data.displayName || null
avatar = profile.data.avatar || null avatar = profile.data.avatar || null
console.log('Profile fetched from session:', { logger.log('Profile fetched from session:', {
did, did,
handle, handle,
displayName, displayName,
avatar: avatar ? 'present' : 'none' avatar: avatar ? 'present' : 'none'
}) })
} catch (error) { } catch (error) {
console.log('Failed to get profile from session:', error) logger.log('Failed to get profile from session:', error)
// Keep the basic info we have // Keep the basic info we have
} }
} else if (did && did.includes('test-')) { } else if (did && did.includes('test-')) {
console.log('Skipping profile fetch for test DID:', did) logger.log('Skipping profile fetch for test DID:', did)
} }
this.sessionInfo = { this.sessionInfo = {
@@ -140,7 +141,7 @@ export class OAuthService {
} }
return null return null
} catch (error) { } catch (error) {
console.error('Auth check failed:', error) logger.error('Auth check failed:', error)
return null return null
} }
} }
@@ -168,7 +169,7 @@ export class OAuthService {
// Reload page // Reload page
window.location.reload() window.location.reload()
} catch (error) { } catch (error) {
console.error('Logout failed:', error) logger.error('Logout failed:', error)
} }
} }

View File

@@ -21,21 +21,14 @@ export default function AtUriViewer({ uri, onAtUriClick }) {
setError(null) setError(null)
try { try {
console.log('Loading AT URI:', uri)
const atUri = parseAtUri(uri) const atUri = parseAtUri(uri)
if (!atUri) { if (!atUri) {
throw new Error('Invalid AT URI') throw new Error('Invalid AT URI')
} }
console.log('Parsed AT URI:', {
hostname: atUri.hostname,
collection: atUri.collection,
rkey: atUri.rkey
})
const result = await getRecord(atUri.hostname, atUri.collection, atUri.rkey) const result = await getRecord(atUri.hostname, atUri.collection, atUri.rkey)
console.log('getRecord result:', result)
if (!result.success) { if (!result.success) {
throw new Error(result.error) throw new Error(result.error)
@@ -43,7 +36,6 @@ export default function AtUriViewer({ uri, onAtUriClick }) {
setRecord(result.data) setRecord(result.data)
} catch (err) { } catch (err) {
console.error('AtUriViewer error:', err)
setError(err.message) setError(err.message)
} finally { } finally {
setLoading(false) setLoading(false)

View File

@@ -39,7 +39,6 @@ export const resolveIdentity = async (identifier) => {
did = response.data.did did = response.data.did
resolved = true resolved = true
} catch (error) { } catch (error) {
console.log('Failed to resolve from syu.is:', error)
} }
if (!resolved) { if (!resolved) {
@@ -64,7 +63,6 @@ export const resolveIdentity = async (identifier) => {
didDoc = await plcResponse.json() didDoc = await plcResponse.json()
} }
} catch (error) { } catch (error) {
console.log('Failed to resolve from plc.syu.is:', error)
} }
// If plc.syu.is fails, try plc.directory // If plc.syu.is fails, try plc.directory
@@ -75,7 +73,6 @@ export const resolveIdentity = async (identifier) => {
didDoc = await plcResponse.json() didDoc = await plcResponse.json()
} }
} catch (error) { } catch (error) {
console.log('Failed to resolve from plc.directory:', error)
} }
} }
@@ -114,17 +111,13 @@ export const resolveIdentity = async (identifier) => {
// Get record from AT Protocol // Get record from AT Protocol
export const getRecord = async (did, collection, rkey) => { export const getRecord = async (did, collection, rkey) => {
try { try {
console.log('getRecord called with:', { did, collection, rkey })
const identityResult = await resolveIdentity(did) const identityResult = await resolveIdentity(did)
console.log('resolveIdentity result:', identityResult)
if (!identityResult.success) { if (!identityResult.success) {
return { success: false, error: identityResult.error } return { success: false, error: identityResult.error }
} }
const pdsUrl = identityResult.pdsUrl const pdsUrl = identityResult.pdsUrl
console.log('Using PDS URL:', pdsUrl)
const client = createAtpClient(pdsUrl) const client = createAtpClient(pdsUrl)
@@ -134,15 +127,12 @@ export const getRecord = async (did, collection, rkey) => {
rkey rkey
}) })
console.log('getRecord response:', response)
return { return {
success: true, success: true,
data: response.data, data: response.data,
pdsUrl pdsUrl
} }
} catch (error) { } catch (error) {
console.error('getRecord error:', error)
return { return {
success: false, success: false,
error: error.message error: error.message

View File

@@ -45,7 +45,10 @@ pub struct ProfileFetcher {
impl ProfileFetcher { impl ProfileFetcher {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
client: reqwest::Client::new(), client: reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build()
.unwrap_or_else(|_| reqwest::Client::new()),
} }
} }
@@ -84,11 +87,15 @@ impl ProfileFetcher {
let response = self.client let response = self.client
.get(&url) .get(&url)
.query(&[("repo", handle)]) .query(&[("repo", handle)])
.timeout(std::time::Duration::from_secs(10))
.send() .send()
.await?; .await
.map_err(|e| anyhow::anyhow!("Request failed: {}", e))?;
if !response.status().is_success() { if !response.status().is_success() {
return Err(anyhow::anyhow!("Failed to describe repo: {}", response.status())); let status = response.status();
let error_text = response.text().await.unwrap_or_default();
return Err(anyhow::anyhow!("Failed to describe repo: {} - {}", status, error_text));
} }
let repo_desc: RepoDescription = response.json().await?; let repo_desc: RepoDescription = response.json().await?;
@@ -117,11 +124,15 @@ impl ProfileFetcher {
let response = self.client let response = self.client
.get(&url) .get(&url)
.query(&[("actor", did)]) .query(&[("actor", did)])
.timeout(std::time::Duration::from_secs(10))
.send() .send()
.await?; .await
.map_err(|e| anyhow::anyhow!("Request failed: {}", e))?;
if !response.status().is_success() { if !response.status().is_success() {
return Err(anyhow::anyhow!("Failed to get profile: {}", response.status())); let status = response.status();
let error_text = response.text().await.unwrap_or_default();
return Err(anyhow::anyhow!("Failed to get profile: {} - {}", status, error_text));
} }
let profile_data: Value = response.json().await?; let profile_data: Value = response.json().await?;

View File

@@ -1875,7 +1875,7 @@ async fn check_and_process_new_posts(
async fn get_existing_records(config: &AuthConfig, collection: &str) -> Result<Vec<serde_json::Value>> { async fn get_existing_records(config: &AuthConfig, collection: &str) -> Result<Vec<serde_json::Value>> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let url = format!("{}/xrpc/com.atproto.repo.listRecords?repo={}&collection={}&limit=100", let url = format!("{}/xrpc/com.atproto.repo.listRecords?repo={}&collection={}&limit=100&reverse=true",
config.admin.pds, config.admin.pds,
urlencoding::encode(&config.admin.did), urlencoding::encode(&config.admin.did),
urlencoding::encode(collection)); urlencoding::encode(collection));